가이의 다락방

AVR 에서의 UART 통신 본문

Study

AVR 에서의 UART 통신

gaiserne 2010. 9. 13. 19:47

AVR 에서의 UART 통신


※ 이곳에서 설명하는 모든 AVR관련 문서는 ATmega128 (16MHz)가 기준입니다.

AVR내에서 UART통신은 적어도 한국 내에서는 가장 많이 사용되는 Serial 통신이다. Serial 통신은 직렬 통신을 뜻하는데, 회로 상에서 보았을 때, 하나의 선을 통해 데이터들이 줄을 서서 일렬로 통과하기 때문에 붙여진 이름이 아닐까 생각한다. 이 부분은 인터넷을 검색하면 쉽게 나오는 사실 이기 때문에 자세한 언급은 하지 않겠다.


일단 일반적으로 책에 서술되어 있는 UART통신의 예제를 보도록 하자.(알기 쉽게 배우는 ATmega128, 신동욱/오창헌 참고)


표 1. USART0 에코 소스.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

// USART0 송수신 실험

#include <avr/io.h>

void Putch(char);

char Getch(void);

 

void main(void)

{

     char string[] = "This is USART0 control program";

     char *pStr;

 

     UCSR0A = 0x00;

     UCSR0B = 0x08;

     UCSR0C = 0x06;

     UBRR0H = 0;

     UBRR0L = 103;

 

     while(1){

          Putch(Getch());

     }

}

 

void Putch(char data)

{

     while(!(UCSR0A & 0x20));

     UDR0 = data;

}

 

void Getch(void)

{

     While(!(UCSR0A & 0x80));

      return UDR0;

}

위의 소스는 대략적으로 말하자면 USART0번 으로 부터 들어온 데이터를 읽어서 다시 USART0으로 보내준다. 즉, USART0에 대하여 1)에코 동작을 한다는 뜻이다.


이 코드에 무슨 문제가 있느냐? 라고 묻는 다면, 사실 별 문제없다면 없는 소스이다. 적어도 이 동작아래에서는 말이다. 그렇지만 복잡한 동작을 요구하게 되는 상황이 왔을 때 이 코드가 얼마나 매끄럽게 돌아가게 되는지는 의구심을 가져봐야 한다.

여기서 중요한 점은 데이터를 주고받는 Putch, Getch 부분인데, 24번 30번줄에서 while 구문의 의미가 중요하다. 이 코드의 의미는 데이터의 수신 또는 송신이 가능한지 알아보는 UCSR0A의 비트를 검사해서 가능할 때까지 대기 하는 것이다. 간단히 말하자면 while(조건); 이러한 구문에서 조건의 내용이 참이라면 while문은 계속 실행될 것이고, 거짓이라면 while문을 빠져나올 것이다. while(조건);while(조건){} 이것과 같은 구문이다. 따라서 비트를 검사해서 송수신이 가능하면 while 그 이후의 구문인 데이터를 보내거나 받는 작업이 시작되는 것인데, 여기서 설정된 BaudRate 9600은 꽤 커 보이지만 우리가 사용하는 AVR의 클럭은 16MHz! 초당 16,000,000번의 산술연산이 가능한데 그럼 연속적으로 데이터를 보낸다고 가정할 때 초당 9600번의 비트를 보내는 중간 중간 AVR은 while문안에서 잠자코 있으란 이야기가 된다. 단순 계산이지만 클럭으로 BaudRate를 나눠보면 1비트를 전송하는 동안 1667번의 클럭을 아무것도 안하고 놀게 된다는 계산이 나온다. 이것은 심각한 낭비가 아닌가?

더욱 심각한 문제는 데이터가 송신되는 것을 기다리는 수신 상태일 때, 상대방이 데이터를 늦게 보내주고, 나는 데이터를 보내야할 경우와 반대로 송신도중에 수신이 오는 경우 이다. 데이터의 송수신은 정해진 시간을 넘기게 되면 TimeOut이 되어 통신의 실패로 연결된다. 이러한 문제를 해결하기 위해서는 어차피 자체적으로 값을 가지고 있어서 나중에 보내도 되는 송신은 지체가 있더라도 수신은 항상 가능한 상태여야 하는 것이다.(사실 송신 또한 다음 데이터가 언제 들어오게 될지 모르므로 최대한 빠르게 처리 해주는 것이 좋다.)


상기 문제의 해결은 간단하다. 인터럽트를 이용하여 데이터를 주고받도록 하는 것이다. 무언가를 하다가 수신 완료 인터럽트가 발생하면 버퍼로부터 데이터를 읽어 오고, 송신 완료 인터럽트가 발생하면, 다음 비트를 보내면 되는 것이다. 그럼 표2.와 같이 코드를 수정하여 보자.


표 2. USART0 인트럽트로 데이터 송수신.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

// USART0 인트럽트 실험

#include <avr/interrupt.h>

 

char rx;

 

void main(void)

{

     unsigned char led = 0xFE;

 

     UCSR0A = 0x00;

     UCSR0B = 0xD8;

     UCSR0C = 0x06;

     UBRR0H = 0;

     UBRR0L = 103;

     SREG = 0x80;

 

     while(1)

     {

     }

}

 

 

SIGNAL(SIG_UART0_RECV)

{

     rx = UDR0;

     UDR0 = rx;  // 원하는 코드 기입.

}


표1의 소스의 에코 소스를 마찬가지로 에코기능을 하도록 구성하였다. 수신되는 즉시 송신하도록 되어 있지만, rx에 그 값이 남아 있으니 이제 언제든지 rx변수로부터 수신된 값을 확인할 수 있다. 그럼 이걸로 완성!?


사실 아직도 부족한 것이 있다. 일반적으로 수신되는 값이 하나 일리가 없다. 보통은 적어도 수십 바이트의 데이터가 연속적으로 들어오게 되는데, 이런 방식으로는 수신을 받더라도 수신된 사실을 알 길이 없고, 연속된 데이터의 처리를 26번째 줄에서 하게 되면 인터럽트 처리 구문 내이기 때문에 2)다른 인터럽트 처리가 불가능하다.


여기서 버퍼의 필요성이 나오게 된다.


1) 일반적으로 수신된 값을 그대로 돌려보내주는 것을 에코(Echo) 라고 한다. 보내는 입장에서는 보내는 값을 그대로 받아 볼 수 있으므로, 데이터가 제대로 송수신되고 있는지 확인하는 기본적인 방법(주로 디버깅)으로 쓰인다.

2) 통신의 경우 보통 바로 다음 데이터가 오게 된다.