여기에서는 이전 포스트에 이어서 포트를 이용한 LED 실습을 진행한다.

LED실험 2

 두 번째 LED실험도 앞의 실험과 비슷하다. 이번에는 다음과 같은 순서로 1→2→3→4→1→2→3→4→1→… 계속 시차를 두고 반복하는 실험이다.


[표 1] 두 번째 LED 실험

순서

LED상태

이진수

16진수

1

●●●○○●●●

0b00011000

0x18

2

●●○●●○●●

0b00100100

0x24

3

●○●●●●○●

0b01000010

0x42

4

○●●●●●●○

0b10000001

0x81


프로그램 예는 다음과 같다.


#define F_CPU 16000000

#include <avr/io.h>

#include <util/delay.h>

#include "Am8USBasp.h"

int main(void) {

   uchar ucA, ucaRelay[4] = {0x18, 0x24, 0x42, 0x81};

   InitAM8();

   while(1) {

       for (ucA=0; ucA<4; ucA++) {

           LED(ucaRelay[ucA]);

           _delay_ms(200);

       }

   }

}


이 예를 보면 typedef 명령어로 unsigned char형은 byte형으로 재정의하였으며 앞으로도 이것은 계속 사용할 것이다. 그리고 LED로 순차적으로 내보낼 데이터는 배열로 처리하였음을 눈여겨 보기 바란다.


LED실험 3

 다음 [표 2]에 설명한 바와 같이 1→2→ … →13→14→1→2→ … 의 순서로 14가지 패턴을 반복하면 마치 LED한개가 좌우로 왕복하는 듯이 보이는 예제이다. 이 세 번째 LED실험은 앞의 것들과 달리 조금 복잡해 보인다.


[표 2] 세 번째 LED 실험

순서

LED상태

이진수

16진수

1

●●●●●●●○

0b00000001

0x01

2

●●●●●●○●

0b00000010

0x02

3

●●●●●○●●

0b00000100

0x04

4

●●●●○●●●

0b00001000

0x08

5

●●●○●●●●

0b00010000

0x10

6

●●○●●●●●

0b00100000

0x20

7

●○●●●●●●

0b01000000

0x40

8

○●●●●●●●

0b10000000

0x80

9

●○●●●●●●

0b01000000

0x40

10

●●○●●●●●

0b00100000

0x20

11

●●●○●●●●

0b00010000

0x10

12

●●●●○●●●

0b00001000

0x08

13

●●●●●○●●

0b00000100

0x04

14

●●●●●●○●

0b00000010

0x02


 이번 프로그램을 이전 실험과 같이 14개의 데이터를 포트에 순차적으로 내보내는 식으로 프로그램을 작성할 수도 있으나 그렇게 하면 너무 비효율적인 프로그램이 된다. 그래서 비트이동 연산자와 반복문을 이용하면 좀 더 간단하게 프로그램이 가능하다. 프로그램 예는 다음과 같다.


#define F_CPU 16000000

#include <avr/io.h>

#include <util/delay.h>

#include "Am8USBasp.h"

typedef enum DIR {UP, DOWN} EDIR;

int main(void) {

   uchar ucLed = 1;

   EDIR eDir = UP;

   InitAM8();

   while(1) {

       if (eDir == UP) {

           ucLed <<= 1;

           if (ucLed == 0x80)

               eDir = DOWN;

       } else { // if (eDir == DOWN)

           ucLed >>= 1;

           if (ucLed == 0x01)

           eDir = UP;

       }

       LED(ucLed);

       _delay_ms(200);

   }

}


LED실험 4 : LED탑 쌓기

 마지막으로 아래와 같은 패턴을 보여주는 프로그램을 작성해보자. 벽돌로 하나씩 탑을 쌓는 것처럼 보여서 ‘LED 탑 쌓기’실험이라고 이름을 붙여 보았다.


[표 3] 네 번째 LED 실험

순서

LED상태

이진수

16진수

1

○●●●●●●●

0b01111111

0x7F

2

●○●●●●●●

0b10111111

0xBF

3

●●○●●●●●

0b11011111

0xDF

4

●●●○●●●●

0b11101111

0xEF

5

●●●●○●●●

0b11110111

0xF7

6

●●●●●○●●

0b11111101

0xFB

7

●●●●●●○●

0b11111110

0xFD

8

●●●●●●●○

0b11111110

0xFE

9

○●●●●●●○

0b01111111

10

●○●●●●●○

0b10111111

11

●●○●●●●○

0b11011111

12

●●●○●●●○

0b11101111

13

●●●●○●●○

0b11110111

14

●●●●●○●○

0b11111101

15

●●●●●●○○

0b11111110

16

○●●●●●○○

0b01111111

17

●○●●●●○○

0b10111111

18

●●○●●●○○

0b11011111

19

●●●○●●○○

0b11101111

20

●●●●○●○○

0b11110111

21

●●●●●○○○

0b11111101

22

○●●●●○○○

0b11111110


이 실험은 이제 포트에 데이터를 순차적으로 내보내는 식으로는 프로그램이 거의 불가능하다. 이번에도 역시 비트이동연산자와 반복문을 조합하여 프로그램을 작성할 수 있다.


#define F_CPU 16000000

#include <avr/io.h>

#include <util/delay.h>

#include "Am8USBasp.h"

int main(void) {

  uchar ucaLEDTower[8] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F };

   uchar ucled, uck, ucl;

   InitAM8();

   while (1) {

       for (uck=0;uck<8;uck++) {

           ucled = 0x80;

           for (ucl=0;ucl<8-uck;ucl++) {

               LED( ucled | ucaLEDTower[uck] );

               _delay_ms(200);

               ucled >>= 1;

           }

       }

   }

}


위 프로그램은 길이는 짧지만 한 줄씩 읽어가면서 분석해 볼 여지가 있다. 다중 반복문을 사용하고 있는데 내부 반복문에서는 LED가 위에서 아래로 한 칸씩 떨어지는 것을 비트쉬프트 연산자로 처리하고 있으며 이 때 PORTB로 내보내는 데이터를 배열 byaLEDTower[]과 OR연산을 하여 마치 벽돌이 쌓여 있는 듯한 효과를 내는 것이다. 배열 byaLEDTower[]은 벽돌이 쌓인 모양의 데이터를 가지고 있다. 바깥 반복문에서는 떨어지는 루틴을 8번 반복하게끔 하는데 떨어지는 곳의 높이를 점차로 높인다.


 이번 포스트에서는 LED를 이용하여 간단한 예제들을 살펴보았다. 독자가 C언어의 문법을 잘 숙지하고 있다면 어떻게 동작이 되는지 쉽게 분석해 볼 수 있을 것이다.




Posted by 살레시오

댓글을 달아 주세요

 본 포스트에서는 ATmega8A의 포트에 LED어레이를 연결하여 LED를 구동시키는 실습을 진행한다. 실습을 위해서 LED 8개를 PB4:0와 PC4:0에 연결하며 회로도는 다음 그림과 같다. LED는 4비트씩 나누어서 포트B와 포트C에 연결하였다.


[그림 1] LED실습을 위한 회로도


이 회로도를 살펴보면 PD5핀이 스위칭 트랜지스터의 베이스와 연결되어 있음을 알 수 있다. 트랜지스터를 온시키기 위해서는 베이스에 아주 작은 양의 전류를 흘려주어야 하는데 보통 이를 위해서 큰 저항(수십~백kΩ)을 베이스와 포트핀 사이에 연결해야 한다. 하지만 본 실험에서는 외부 저항을 생략하고 그 대신에 ATmega8(A)내부에 내장된 풀업 저항을 이용하여 트랜지스터를 on시키는 방법을 사용하였다. 이를 위해서 DDRD5비트를 입력으로 (‘0’) 설정하며, 트랜지스터를 on시키기 위해서는 PORTD5 비트를 온(‘1’)시켜서 내부 풀업을 연결한다. 그 이후에 PB4:0와 PC4:0에 표시하려는 데이터를 내보내면 LED에 그 데이터가 표시되게 되는 것이다. (PD5핀과 PD6핀을 입력으로 설정해야 함에 주의해야 한다. 이 핀들을 출력으로 설정해 버리면 트랜지스터를 on/off시킬 수 없다.)


 또한, 포트핀의 내부 저항이 25Ω 정도인 것을 고려하면 외부 저항의 용량은 저항은 300Ω 정도가 적당하지만, 본 실습 장치는 USB전원으로 구동되므로 전류를 줄이고 LED가 켜졌음을 확인하는데 문제가 없는 수준에서 포트핀으로 흐르는 전류 값을 최소화하기 위해서 1KΩ의 저항을 연결하였다.

 만약 PB0부터 PB4까지가 모두 동시에 세트(‘1’된다 하더라도 포트B로 흘러나가는 전류의 합은 다음 식과 같이 계산할 수 있다.



여기서 1.7V는 LED양단의 전압 강하 값이다. 이것은 포트B로 흐르는 전류의 합이 100mA 이하여야 된다는 데이터쉬트상의 스펙을 벗어나지 않게 된다. 포트C에 대해서도 같은 계산 결과가 적용될 수 있을 것이다.


 다음과 같이 LED에 관련된 매크로와 함수를 정의한다.


#define sbi(sfr,bit) (_SFR_BYTE(sfr)|=_BV(bit))
#define cbi(sfr,bit) (_SFR_BYTE(sfr)&=~_BV(bit))
typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned long ulong;

#define TurnLEDOn ({sbi(PORTD,5);cbi(PORTD,6);})
#define Turn7SegOn ({sbi(PORTD,6);cbi(PORTD,5);})
#define TurnOffAll ({cbi(PORTD,5);cbi(PORTD,6);})
void LED(uchar b) { // Display b into LED
   TurnLEDOn;
   PORTB &= 0xF0;
   PORTC &= 0xF0;
   PORTB |= (b&0x0F);
   PORTC |= (b>>4);
}


 먼저 sbi(sft, bit)와 cbi(sft, bit) 매크로는 sfr 레지스터의 bit번을 세트시키는 매크로이다. 예를 들어 PORTB레지스터의 2번 비트를 세트시키고 싶다면 sbi(PORTB,2) 라고 하면 되고 PORTD 레지스터의 5번 비트를 리셋시키고 싶다면 cbi(PORTD,5) 라고 하면 된다. 또한 unsigned char형을 uchar 로 재정의 했는데 이는 긴 데이터 형의 이름을 짧게 사용하기 위한 것이다. uint 와 ulong 도 같은 이유로 재정의했다.

 LED와 관련된 매크로는 TurnLEDOn, Turn7SegOn, TurnOffAll 세 개가 정의되어 있는데 각각 LED만 켜고, 7세그먼트만 켜고, 둘 다 끄는 동작을 수행하는 매크로이다. 그리고 LED(uchar b)함수가 정의되어 있는데 이 함수에는 LED에 내보낼 데이터를 넘겨주면 된다. LED와 7세그먼트가 포트B의 하위 4비트와 포트C의 하위 4비트에 걸쳐 있기 때문에 LED( )함수의 내부에는 넘겨진 데이터의 하위 4비트를 PORTB4:0에, 그리고 상위 4비트를 PORTC4:0에 복사하는 코드가 작성되어 있다. 이 때 PORTB, PORTC레지스터의 상위 4비트는 그 값을 그대로 유지하도록 하였다.


LED실험 (1)

 첫 번째 실험은 [표 1]과 같이 4개씩 LED를 번갈아서 켜는 실험이다.


[표 1] 첫 번째 LED 실험

순서

LED상태

이진수

16진수

1

○○○○●●●●

0b00001111

0x0F

2

●●●●○○○○

0b11110000

0xF0


위의 표에서 1-2-1-2- 순으로 일정한 시차를 두고 무한히 반복하는 실험이다. 프로그램 예는 다음과 같다.


#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>

typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned long ulong;

#define sbi(sfr,bit) (_SFR_BYTE(sfr)|=_BV(bit))
#define cbi(sfr,bit) (_SFR_BYTE(sfr)&=~_BV(bit))

#define TurnLEDOn ({sbi(PORTD,5);cbi(PORTD,6);})
#define Turn7SegOn ({sbi(PORTD,6);cbi(PORTD,5);})
#define TurnOffAll ({cbi(PORTD,5);cbi(PORTD,6);})
void LED(uchar b) { // Display b into LED
   TurnLEDOn;
   PORTB &= 0xF0;
   PORTC &= 0xF0;
   PORTB |= (b&0x0F);
   PORTC |= (b>>4);
}
int main(void) {
   InitAM8();
   uchar ucLED = 0x0F;
   while(1) {
       LED(ucLED);
       ucLED = ~ucLED;
       _delay_ms(500);
   }
}


이 프로그램을 보면 main()함수 안에서 while(1){ } 반복문에 의해서 무한루프에 빠지게 프로그램이 되어 있다. 일반적으로 PC상에서는 운영체제 (Operation System, OS로 줄여 표기하고 윈도우, 리눅스 등이 있다.)가 응용프로그램을 실행시키고 응용프로그램이 종료가 되면 다시 OS로 되돌아가지만 uC는 OS가 없기 때문에 응용프로그램 안에서 프로그램 실행이 계속 머물러야 하므로 무한 루프가 이용된다.


 전술한 바와 같이 LED를 켜는 코드와 7세그먼트를 켜는 코드를 매크로 TurnLEDOn, Turn7SegOn 으로 각각 정의해 놓고 있어서 LED를 구동시키고 싶을 때는 TurnLEDOn이라는 매크로를 사용하면 된다. 이렇게 매크로를 정의해 놓은 이유는 사용의 편의성 때문이기도 하지만 프로그램의 가독성 때문에도 그렇다. 예를 들어서 main()함수 중간에


sbi(PORTD,5);
cbi(PORTD,6);


라고 작성되어 있다고 가정해보자. 다른 사람이 이 프로그램을 읽는다면 한 눈에 이 두 줄이 무슨 역할을 하는지는 알기 힘들 것이다. 하지만


TurnLEDOn;


이라고 작성하면 이것은 이전의 것과 똑같은 코드를 생성하지만 (전처리기에 의해서 이것은 ({sbi(PORTD,5);cbi(PORTD,6);})로 컴파일하기 전에 치환된다.) 한 눈에 LED를 켜는 코드라고 알아볼 수 있을 것이다.



Posted by 살레시오

댓글을 달아 주세요

 포트(port)란 uC가 외부 기기와의 데이터를 주고받는 통로를 말한다. 입력되는 신호를 받고 출력 신호를 내보낼 수 있는 핀으로서 AVR에서는 8개씩 묶어서 포트A, 포트B, 포트C, … 와 같은 식으로 이름이 붙어 있는 것이 일반적이다. 예를 들어 포트D는 8개의 핀으로 구성되며 각 핀은 PD0, PD1, … PD7과 같이 이름이 붙어 있다. 특히 AVR의 경우는 입/출력전류 드라이브 능력이 40mA정도로 높은 편이며 내부에 풀업 저항 (20k~50k)을 내장하여 선택적으로 사용할 수 있으므로 스위치나 센서 등을 연결하는 경우 회로를 간략화할 수 있다.  포트핀의 구조도는 아래와 같다.


[그림 1] avr의 포트 핀 회로도


 풀업(pull-up) 저항이란 디지털 회로에서 논리적으로 H레벨을 유지하기 위해서 포트핀과 Vcc를 연결하는 저항을 말한다. 보통은 uC외부에서 연결해야 하지만 AVR은 모든 포트핀에 풀업저항을 내장하고 있어서 SFR을 적절히 설정하면 선택적으로 연결할 수 있다.


포트와 관련된 레지스터

 AVR은 각각의 포트에 3개의 관련 I/O레지스터를 가지는데 다음과 같다.

       ① DDRX (Data Direction Register) 레지스터

       ② PORTX (Data Register) 레지스터

       ③ PINX (Port Input Pin Address) 레지스터

모두 8비트 레지스터이고 ATmega8(A)의 경우는 X는 B, C, 혹은 D가 된다. 먼저 포트가 입력으로 쓰일지 출력으로 쓰일지 방향을 정해야 하는데

  • DDRX의 해당 비트가 '1' 이면 출력,

  • DDRX의 해당 비트가 '0'이면 입력

으로 지정된다. 그리고

  • 출력인 경우 PORTX 에 출력값을 쓰고,

  • 입력인 경우에는 PINX에서 값을 읽어들인다.

입력값을 읽는 레지스터와 출력값을 내보내는 레지스터가 다르다는 것에 유의해야 한다. 또한 DDRx의 설정과 무관하게 PINx레지스터를 이용하여 해당 핀의 전압 값을 읽어 들일 수 있다. 만약 포트핀이 출력으로 설정되었다면 PINXn 비트는 PORTXn비트의 값과 같아지고 입력으로 설정되었다면 PINXn비트는 핀에 인가되는 전압값에 의해서 그 값이 결정된다.


 또 한 가지 알아야 할 것은 포트핀이 입력으로 설정된 경우 PORTX레지스터는 풀업 저항을 연결할 것인지 아닌지를 결정한다는 것이다. 이를 아래 [표 1]에 도시하였다. PUD비트는 MCUCR의 b4이다.


[표 1] portx 레지스터의 설정에 따른 동작

DDRXn

PORTXn

PUD

I/O

풀업저항

비고

0

0

×

입력

연결 안 됨

3스테이크(하이임피던스)

0

1

0

입력

연결됨

내부에서 풀업

1

0

×

출력

연결 안 됨

'0' 출력 (sink)

1

1

×

출력

연결 안 됨

'1' 출력 (source)


PORTX 레지스터는 비트별로 그 값을 각각 지정해 줄 수 있다. 즉 같은 포트라도 어떤 핀은 입력으로 다른 핀은 출력으로 사용할 수 있다. 다음은 ATmega8(A)의 포트에 관련된 레지스터를 정리해 놓은 것이다.



예를 들어서 ATmega8(A)의 2번 핀은 PD0/RxD 두 가지 기능이 있다. 이 핀을 포트핀(즉 PD0)으로 사용하고 싶다면 DDRD0 비트를 설정하여 입력/출력 방향을 결정해야 한다. 그 다음 출력일 경우 PORTD0 비트를 이용하여 출력할 값을 내보내면 되고 입력일 경우 PIND0 비트값을 읽어들여서 핀에 인가되는 전압값을 읽어들이면 된다.


포트핀의 내부 저항값과 등가 회로

 AVR 포트핀이 출력으로 쓰일 때 데이터쉬트에서 발췌한 전압-전류 곡선이 [그림 1]에 도시되어 있다.

[그림 2] 포트 핀의 전압-전류 곡선 (Vcc=5V) : (a) 출력=1 (b) 출력 = 0


이 그림에서 직선 (엄밀히 말하면 곡선이지만 곡률이 거의 0이므로 직선이라고 가정한다)의 기울기가 바로 저항값이 되며 이 직선의 기울기가 실온일 경우 약 25 정도 된다는 것은 쉽게 알 수 있다. 이는 포트핀의 내부저항이 약 25Ω정도 된다는 것을 의미하며 이것을 근거로 포트핀의 테브낭 등가회로를 작성해 보면 [그림 2]와 같다.


[그림 3] (a) 출력 = 1 인 경우 (b) 출력 = 0 인 경우


포트핀의 내부저항이 약 25Ω이라는 사실을 아는 것은 매우 중요한데, 그 이유는 이 등가회로를 이용하면 주변 회로의 전압-전류를 정량적으로 계산할 수 있기 때문이다.


 예를 들면 포트핀에 센서를 연결했을 경우 센서의 데이터쉬트에서 필요한 정보를 얻은 후에 실제 전압-전류값을 정량적으로 계산할 수 있다.



Posted by 살레시오

댓글을 달아 주세요

 디지털 입출력 포트(digital I/O port, 혹은 그냥 포트)는 디지털 신호를 출력하거나 입력받을 수 있는 통로(물리적으로는 핀)이다. 디지털 신호는 0과 1 두 가지 상태만을 표현하므로 포트를 통해서 0 또는 1신호를 내보내거나 입력받을 수 있다. 아두이노 우노에는 13개의 디지털핀이 있는데 이것들이 포트에 해당된다.


 앞으로의 설명을 위해서 약간의 전기회로 지식이 필요하다. 먼저 전압(voltage)과 전류(current)의 개념을 설명하면 다음과 같다. 마트에서 흔히 살 수 있는 AA나 AAA사이즈 건전지 하나의 '전압'은 1.5V (V는 Volt 볼트, 전압의 단위) 라는 것은 알고 있을 것이다. 이 의미는 음극과 양극의 '전위차'가 1.5V라는 의미이며 일단 '전압은 전류를 흘릴 수 있는 힘' 정도로 이해하면 된다. 이 전위차가 있는 두 부분을 도선으로 연결하면 전자가 도선을 따라서 흐르게 되는데 이 전자의 흐름이 전류이다. 전자는 음극에서 양극으로 흐른다. 전압(또는 전위차)이 높을 수록 전자가 더 많이 흐르고 전류값도 높아진다. 전자의 흐름인 전류의 단위는 암페어(Ampere, A로 표시함)이다. 전류는 '양전하의 흐름'이다.


  • 전류(단위는 암페어, A) : 양전하의 흐름

  • 전압(단위는 볼트, V) : 전류를 흘릴 수 있는 힘



위 그림을 보면 건전지의 +극과 -극을 저항 $R$로 연결하였다. (왼쪽은 건전지와 저항의 모양을 그대로 그렸고 오른쪽은 이것을 기호로 표시한 것이다.) 이 경우 전류가 도선을 따라 흐르게 되는데 전지의 전압을 $v$, 저항을 $R$이라고 하면 전류의 크기는 $\frac{v}{R}$로 계산된다. 이것을 오옴의 법칙이라고 한다. 저항의 단위는 오옴(ohm)이다. '저항은 전류의 흐름을 제한하는 역할을 하는 소자'이다.


  • 저항(단위는 오옴 Ω) : 전류의 흐름을 제한하는 역할을 하는 소자

  • 오옴의 법칙 : $v = i R$


 디지털 시스템의 디지털 신호는 '전압'으로 표현된다. 신호 0(LOW)은 0V (GND, 그라운드라고 읽는다)가 사용되고 1신호는 주로 5V, 3.3V 혹은 1.8V이다. 아두이노의 경우 동작 전압이 대부분 5V이므로 신호 1 (HIGH) 은 전압으로 5V가 되는 것이다.


포트를 이용한 출력 내보내기


 포트를 출력으로 사용하는 경우는 스위치를 생각하면 간단히 이해할 수 있다. 건전지와 연결된 전구사이에 스위치가 있는 간단한 실험장치를 생각해 보면 된다. 스위치를 손가락으로 눌러서 연결(on 되었다고 한다)되면 전구에 전압이 걸려서 켜질 것이고 손가락을 떼면 (off되었다고 한다.) 전구가 꺼지게 된다.



포트는 이와 같이 핀에 연결된 회로에 전압을 인가하거나(1, HIGH 신호) 인가하지 않을 (0, LOW 신호) 수 있는 스위치의 역할을 하는데 아두이노의 경우 이 스위치를 프로그램을 통해서 on시키거나 off시킬 수 있다. 사용자가 원하는 타이밍, 주기, 속도를 가지고 스위치를 켰다 끌 수 있는 것이다. 따라서 손으로 스위치를 조작하는 것과는 비교할 수 없는 정밀도와 속도로 개폐를 제어할 수 있다.


포트를 이용한 입력신호 받기


 입력의 경우에는 출력과 반대로 이 물리적인 핀과 연결된 부분의 전압이 0V(LOW)이냐 혹은 5V이냐(HIGH)를 읽어들이는 기능을 한다. 보통 디지털 입력 실험을 할 때 처음으로 접하는 부품이 택스위치 회로인데 택스위치가 눌려졌는지 혹은 떼어졌는지를 포트의 입력 기능으로 판별할 수 있다.


 위 그림 (a)에서 화살표 표시된 곳의 전압은 5V인데 저항에 전류가 흐르지 않아 저항 양단에 전위차가 발생하지 않기 때문이다. 반면에 (b)를 보면 화살표 된 곳의 전압은 0V인데 GND와 직결되어 있기 때문이다.


 이제 (c) 그림을 보면 스위치를 안 눌렸을 때 (a)와 같고 스위치를 누르면 (b)그림과 같다는 것을 알 수 있다. 따라서 스위치를 눌렀을 때와 안 눌렸을 때의 전압값이 달라지므로 포트에서 이 전압값을 읽어 들여서 스위치의 상태를 검출할 수 있다. 여기에선 사용된 저항 $R$을 '풀업(pull-up) 저항'이라고 한다.


아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad006}

Posted by 살레시오

댓글을 달아 주세요