avr 보드와 또다른 디바이스간 통신방법중 가장 많이 사용되고 쉬운 것이 usart 통신을 이용하는 것이다.
avr128에는 usart통신을 위해 두개의 usart 통신 포트를 제공하고 있다.
위 그림은 avr128의 핀 번호를 나타내는 그림이다. avr128은 USART0와 USART1으로 두개의 usart 통신핀을 제공해 주고 있다. 위 그림에서 PE0핀이 RXD0, PE1 핀이 TXD0 으로 USART0 이고, PD2핀이 RXD1, PD3 핀이 TXD1 으로 USART1 이다. 이 네개의 핀을 이용해서 동시에 두개의 usart통신을 할수 있다. 여기선 USART0하나만 이용하여 usart통신을 해보도록 하자. 다음부터 나오는 모든 USART를 USART0로 통일한다. 보통 데이터 쉬트에서는 USART0와 USART1을 통합해서 USARTn 이라고 호칭한다.
usart통신은 하려면 우선 UDR0 레지스터에 대해 알아야 한다. 이 레지스터는 USART0의 송수신 버퍼 역할을 하는 레지스터이다. 이 레지스터에 값을 적으서 송신을 할수 있고 이 레지스터에서 수신된 값을 읽을 수 있는 것이다. 실질적으로 소스로 구현할때도 다음과 같이 간단하게 구현이 된다.
unsigned char ch;
ch = UDR0;
다음으로 알아볼 레지스터는 보오레이트를 설정하는 레지스터인 UBBR0 이다. 보오레이트란, 데이터 송수신 속도를 말하는 것이다. usart통신은 병렬통신과는 틀리게 하나의 핀으로 전송을 하는 것이기 때문에 전송 속도를 상호간에 약속을 해주어야만 올마른 통신이 가능하다. UBBR0 에는 UBRR0H와 UBRR0L 두개가 있는데 총 12비트로 구성이 된다.
위 그림은 UBBR0 레지스터의 데이터시트 그림이다. 그림에서 보시다 시피 UBBR0H와 UBBR0L 두개가 있는데 H가 상위 비트이고 L이 하위 비트를 가르킨다. 값을 넣을때는 항시 UBRR0H와 UBBR0L 두개모두에 값을 넣어 주어야 한다. 여기서 문제가 되는 것이 있다. 바로 UBRR0H와 UBBR0L에 어떤 값을 넣어야 자신이 원하는 통신속도를 설정할 수 있느냐 이다. 방법은 다음과 같다.
위 표는 현재 자신이 사용중인 AVR의 클럭을 이용해서 원하는 보오레이트를 구하는 공식이다. 현재 자신이 사용하는 avr의 클럭이 16Mhz 이고 비동기 일반모드로(Asynchronous Normal Mode) 보오레이트 값이 19200을 원한다면 다음과 같이 계산할 수 있을 것이다. 19200 = 16000000/16(UBRR +1) .
이 것을 계산하면 UBRR = 51.08333333333 값이 나온다. 즉 51 값을 넣어주면 된다는 것이다.
다음 소스는 16Mhz 클럭에 보오레이트 19200으로 통신하기 위해 레지스터를 설정하는 소스이다.
UBRR0H = 0x00;
UBRR0L = 0x33; ->51
자 이제 USART 의 제어및 상태 레지스터에 대해 알아 보자. 이 레지스터 이름은 UCSR0A, UCSR0B, UCSR0C 가 존재한다. 이 내용에 대해선 각자 데이터 쉬트를 참조하도록 하자. 전부 말하려면 몇페이지는 걸릴 것이다. 그리고 다 말할 필요도 없다. 데이터 쉬트에 아주 자세히 나와 있다.
이제 기본적인 USART에 대해 알아 보았으니 가장 중요한 데모 소스를 구현해 보도록 하자.
unsigned char recvData;
/* receive data */
interrupt [USART0_RXC] void usart0_rx_isr(void)
{
recvData = UDR0;
if(recvData == 'd')
PORTB.0 = 0x01;
if(recvData == 's')
PORTB.0 = 0x00;
}
void Putchar(unsigned char c)
{
while((UCSR0A & 0x20) == 0x00);
UDR0 = c;
#asm("sei");
}
int main(void)
{
UBRR0H = 0;
UBRR0L = 51; //if 16Mhz -> 19200 baud
UCSR0A = 0X00;
UCSR0B = 0X98;
UCSR0C = 0X06;
SREG = 0x80;
#asm("sei");
ch = 'a';
Putchar(ch);
while (1)
{
};
}
이 소스는 처음 시작 될때 usart로 문자 'a'를 송신한다. 그리고 'd'나 's' 문자를 수신하면 B0포트에 값을 넣었다 뺐다 하는 소스이다. 즉 B0포트에 LED가 달려 있다면 LED를 깜박이는 소스가 될 것이다.
위 소스를 차례대로 분석해 보기로 하자.
interrupt [USART0_RXC] void usart0_rx_isr(void)
{
recvData = UDR0;
if(recvData == 'd')
PORTB.0 = 0x01;
if(recvData == 's')
PORTB.0 = 0x00;
}
이 소스는 USART0 에서 데이터가 수신되었을때 발생되는 인터럽트 함수 이다. 이 함수는 우선 usart 송수신 버퍼인 UDR0에서 하나의 문자를 받아온다. 그런후 그 문자가 'd'이면 B0포트에 1을 출력하고 's'이면 0을 출력하도록 구성이 되어있다. 만약 B0포트에 LED가 연결되어 있다면 'd'나 's'에 따라 LED가 켜졌다 꺼졌다 할것이다.
void Putchar(unsigned char c)
{
while((UCSR0A & 0x20) == 0x00);
UDR0 = c;
#asm("sei");
}
이 소스는 데이터 송신할때 사용되는 함수이다. 위에서 본 인터럽트처럼 정해저 있는 것이 아니라 그냥 사용자 임의로 만들면 될 것이다...^^;; 안에 내용을 보면 whlie()함수내에서 UCSR0A 레지스터의 5번째 비트값인 UDRE0 값을 비교한다. UDRE0는 UDR0가 비어 있는지 비어있지 않는지를 나타 내는 레지스터로 UDR0가 비어 있다면 1로 셋되고 아니면 0으로 셋된다. 즉 while()문은 버퍼가 빌때까지 기다리는 루프가 될것이다.
그리고 루프에서 벗어 났다는 것은 버퍼가 비었다는 의미이므로 UDR0버퍼에 문자를 넣어 데이터를 송신할 수있게 된다. 그리고 다음에 #asm("sei"); 구문이 나오는데 이것은 global 인터럽트를 인에이블 시키는 것으로 인터럽트가 발생할수 있도록 설정하는 것이다.
UBRR0H = 0;
UBRR0L = 51; //if 16Mhz -> 19200 baud
UCSR0A = 0X00;
UCSR0B = 0X98;
UCSR0C = 0X06;
SREG = 0x80;
자 이제 가장 중요한 것이 나오게 된다. 우선 첫째줄과 두번째줄의 UBRR0H와 UBRR0L이 나온다 이것은 보오레이트를 설정하는 것이라고 위에서 말했다. 16Mhz에서 192000 보오레이트를 원한다면 51을 넣으면 된다. 계산하는 방법은 위에서 이미 언급하였다. 다음으로 UCSR0A, UCSR0B, UCSR0C 가 나온다. UCSR0A는 모두 0으로 셋팅 하였기에 언급하지 않고 UCSR0B와 UCSR0C만 보도록 하자. UCSR0B에 0X98을 넣는 다는 것은 비트로 계산해 보면 10011000 을 넣는 것이다.
다음은 UCSR0B의 레지스터 구조를 나타낸 그림이다. 여기에 0X98을 넣었다는 것은 RXCIE0, RXEN0, TXEN0를 1로 셋팅 하였다는 것이다. RXCIE0는 수신완료 인터럽트를 개별적으로 허용하는 비트이다. 이 것이 1로 셋팅된다면 데이터가 수신 되었을때 수신완료 인터럽트가 발생하게 된다. RXEN0는 USART0포트의 수신부가 동작하도록 허용하는 비트이다. 반드시 1로 셋팅해야 할 것이다. TXEN0는 USART0포트의 송신부가 동작하도록 허용하는 비트이다. 이것도 1로 셋팅해 줘야 할 것이다.
다음 UCSR0C에 0X06을 넣는데 데이터 시트 찾아 보면 알게 될 것이다. 단순히 비동기 일반 모드로 USART 통신을 하고 싶다면 위에 써 놓은것으로 하면 잘 될 것이다. 손이 아파서 더이상 적지 못하겠다...GG~
ㅡㅡ;;