(본 글은 2017.12.02.에 필자의 다른 티스토리 http://avrlab.tistory.com에 적었던 것을 옮겨왔습니다.)
※ 원래는 atmega32의 serial 통신을 다룰 예정이었으나,
atmega128과 atmega32에서의 프로그래밍 내용이 크게 다른 점이 없으므로 atmega128에 대한 설명을 주로하고 도중에 atmega32에 관하여 언급하는 방향으로 진행하고자 합니다.
atmega32와 atmega128의 serial 통신이 다른 점은
1) atmega32는 USART 포트가 하나 밖에 없으므로 각 제어 레지스터와 제어 비트에서 숫자0을 제거하면 됩니다. (ex UDR0 --> UDR, UCSR0A -->UCSRA)
2) atmega32에서 UCSRC에 값을 기록할 때에 URSEL 비트를 설정한다.
3) atmega32에서는 STS 명령을 쓰지 않는다.(OUT 명령만 사용)
4) 수신 인터럽트 번지가 다르다.
이 외의 다른 중요한 루틴은 아무런 차이가 없습니다.
1)은 본문에서 특별히 다룰 필요는 없고, 2)와 3), 4)는 해당 내용 설명시 atmega32에 대하여 언급합니다.
※ 본 글에서는 대부분의 인터넷 상에 있는 소스들과 달리, 데이터 수신시에 인터럽트 서비스 루틴이 일단 큐에 저장하고 나중에 데이터를 가져다 쓰는 방식으로 프로그래밍하여, CPU가 바빠서 수신 테이터를 놓치는 일이 최소화되도록 하고 있습니다.
1. USART 초기화
atmega128은 두 개의 비동기 통신 포트를 가지고 있습니다. 각각의 통신 포트를 USART0, USART1으로 부르고 있습니다. USART0와 USART1을 제어하는 제어 레지스터들은 포트의 번호만 다를 뿐, 이름이나 구조가 동일합니다. USART0를 제어하는 레지스터가 UCSR0A, UCSR0B, UCSR0C 등과 같이 세 개 있습니다. USART1을 제어하는 레지스터는 UCSR1A, UCSR1B, UCSR1C입니다. 또한 각 각의 레지스터의 비트들이 담당하는 기능도 완전히 같습니다. 따라서 하나의 USART 포트를 제어하는 루틴을 만들면 숫자만 바꿔서 다른 제어루틴도 쉽게 만들 수 있습니다. 본 글에서는 USART0을 사용하는 루틴들만 다루겠습니다. 각 루틴들의 레지스터 이름이나 비트명의 0을 1로 바꾸면 USART1을 제어하는 루틴이됩니다.
USART1의 제어 루틴을 작성할 때에 USART1의 제어 레지스터들은 모두 입출력 포트 번지가 63보다 크기 있기 때문에 OUT 명령 대신에STS 명령을 사용해야 합니다. 데이터를 읽어 올 때에도 IN 명령이 아니라 LDS 명령을 사용해야 합니다. USART0의 경우에도 UCSR0C, UBRRH 레지스터는 63보다 크기 때문에 STS 명령과 LDS 명령을 사용해야 합니다.
atmega32는 모든 레지스터의 입출력에 OUT 명령과 IN 명령을 사용합니다.
serial 통신을 정확히 하려면 본 글에서 제시하는 것보다 훨씬 많은 부분을 손보아야 합니다. avr에는 통신 에러가 발생한 경우 에러가 발생한 원인을 확인할 수 있는 레지스터들을 갖추고 있기 때문에 정밀한 제어가 가능합니다. 그러나 그런 내용을 모두 다루는 것은 대부분의 경우 코딩 작업은 길어지지만 별로 실익은 없는 편입니다.
본 글에서는 통신 에러가 별로 발생하지 않는 통상적인 환경에서 사용하는 것을 가정하여 진행합니다. 실제로는 Frame Error나 Over Run 에러 등에 의해 오류가 발생하는 것을 거의 겅험하지 않아서, 혹은 발생했어도 심각한 경우가 아니었기 때문에, 세세히 그런 오류를 제어해야 할 필요를 느낀 바가 없으며 이에 따라 한 번도 구현해 보려는 시도도 해 본바가 없습니다. 양해 바랍니다. 대신에 꼭 필요한 내용만 다룰 예정입니다.
USART 포트로 serial 통신을 하려면 통신 대상과 통신 속도, 스톱비트 수, 패리티 등의 통신 프로토콜을 맞추어 주어야 합니다.
① 통신 속도
USART 통신 속도는 UBRR 레지스터에 적절한 값을 시록하여 결정합니다. UBRR레지스터는 16비트의 값을 같기때문에 UBRRH와 UBRRL 두 개의 레지스터로 관리합니다. 이름에서 알 수 있듯이 UBRRH가 상위 값이고, UBRRL이 하위 값입니다. atmega128에서 USART0의 UBRR 레지스터 실제 이름은 UBRR0H, UBRR0L입니다
통신 속도를 결정하기 위해서는 이 UBRR 레지스터에 적절한 값을 기록해야 합니다. UBRR레지스터에 기록해야할 값은 atmega128의 클럭 속도와 통신 속도, 그리고 U2X 기능의 사용 등에 따라 결정됩니다.
우선 U2X 기능에 대하여 살펴보도록 합니다. atmega128의 비동기 통신은 주클럭을 16으로 나누어 사용합니다. 만약 U2X비트를 1로 설정하면 주클럭을 8로 나누어 사용하기 때문에 USART의 동작 속도가 2배로 빨라집니다. 동작 속도가 빨라지면 전력 소비는 늘겠지만 통신 속도를 더 정밀하게 맞출 수 있는 장점도 있습니다. USART0의 U2X 비트 이름은 U2X0로 제어레지스터인 UCSR0A의 1번 비트입니다.
원하는 통신 속도로 설정하기 위해 UBRR레지스터에 기록해야 하는 값.이 atmega128의 매뉴얼 193쪽 Table 82에 CPU 클럭 속도 별로 제시되어 있습니다. 이 표에도 U2X가 0인 경우와 1인 경우로 나누어 값을 제시하고 있고, 각 각의 값을 기록했을 때에 속도 오차율도 제시하고 있습니다. U2X를 1로 설정했을 때와 0으로 두었들 때의 오차율을 비교하여 유의미한 차이가 있을 때에는, U2X를 1로 설정하고 그렇지 않을 때에는 0으로 두고 사용하는 것이 좋을 듯합니다.
그러나 프로그램을 작성할 때마다 매뉴얼을 뒤져서 적절한 UBRR 값을 찾는 일이 상당히 번거롭기도 합니다. 그래서 매뉴얼의 173쪽의 Table 74에서 제시한 공식에 따라 UBRR을 계산하여 사용하는 것이 편리합니다.
다음과 같이 매크로로 정의해서 사용합니다.
#ifdef U2X0_ON .EQU BAUDRATE_DEVIDER0 = 8 .EQU UCSR0A_VALUE = (1 << U2X0) #else .EQU BAUDRATE_DEVIDER0 = 16 .EQU UCSR0A_VALUE = 0 .EQU F_CPU = 16000000 // clock of cpu .EQU BAUDRATE0 = 115200 .EQU UBRR0_VALUE = (F_CPU / (BAUDRATE_DEVIDER * BAUDRATE0) - 1)
맨 첫 행에서 U2X 기능을 사용할 것인지 여부를 정합니다. 이전의 강좌에서 언급했듯이 매크로 U2X0_ON이 선언되어 있으면 BAUDRATE_DEVIDER0는 8, UCSR0A_VALUE는 (1 << FU2x0)의 값을 가지게 되며, U2X0_ON이 선언되어 있지 않으면 BAUDRATE_DEVIDER0는 16, UCSR0A_VALUE는 0의 값을 가집니다.
매크로 F_CPU에 CPU 동작 속도를 지정하고, BAUDRATE0에 원하는 통신 속도를 지정하면 UBRR0_VALUE 매크로에 적절한 UBRR의 값이 계산되어 있습니다. 이 값을 다음과 같이 UBRR레지스터에 기록합니다.
(ATMEGA128)
STS UBRR0H,HIGH(UBRR0_VALUE) OUT UBRR0L,LOW(UBRR0_VALUE)
(ATMEGA32)
OUT UBRR0H,HIGH(UBRR0_VALUE) OUT UBRR0L,LOW(UBRR0_VALUE)
AVR에서 UBRR 레지스터처럼 16비트의 값을 갖는 레지스터에 값을 기록할 때에는 위의 예에서 보듯이 상위 값(UBRRH)을 먼저 기록하고, 하위 값(UBRRL)을 나중에 기록해야 합니다. 반대로 16비트의 값을 갖는 레지스터를 읽을 때에는 하위 값을 먼저 읽고, 상위 값을 나중에 읽어야 합니다.
② DATA BIT
atmega128을 비롯한 대부분의 avr에서 serial 통신 시에 데이터 비트를 5비트에서부터 9비트 중 하나를 선택해야 합니다. 거의 대부분의 serial 통신은 8비트를 사용하는 듯합니다. Data Bit는 세 개의 제어 비트 UCSZ02, UCSZ01, UCSZ00로 결정합니다. 왜 그렇게 되었는지는 모르지만 UCSZ02 비트는 UCSR0B 레지스터에 있고, 다른 두 레지스터는 UCSR0C 레지스터에 있습니다
Data Bit를 설정하기 위해 각각의 제어 비트에 기록해야할 값을 알려 주는 표입니다.
Data Bit |
UCSZ02 |
UCSZ01 |
UCSZ00 |
5 Bit |
0 |
0 |
0 |
6 Bit |
0 |
0 |
1 |
7 Bit |
0 |
1 |
0 |
8 Bit |
0 |
1 |
1 |
Reserved |
1 |
0 |
0 |
Reserved |
1 |
0 |
1 |
Reserved |
1 |
1 |
0 |
9 Bit |
1 |
1 |
1 |
Data Bit를 8로 설정하기 위해서는 다음과 같이 합니다.
1) UCSZ02를 0으로 해야하므로 여기에서 특별히 UCSR0B 레지스터에 값을 기록할 필요는 없습니다. 다만, 나중에 UCSR0B 레지스터에 값을 기록할 때에 UCSZ02 비트가 1로 설정되지 않도록 하는 점만 유의하면 됩니다.
2) UCSZ01과 UCSZ00 비트를 1로 설정합니다.
(ATMEGA128)
STS UCSR0C,((1 << UCSZ01) | (1 << UCSZ00))
(ATMEGA32)
OUT UCSR0C,((1 << URSEL) | (1 << UCSZ01) | (1 << UCSZ00))
※ atmega32에서는 UBRRH 레지스터와 UCSRC 레지스터가 같은 번지를 사용하기 때문에 UCSRC 레지스터에 내용을 기록하려면 반드시 URSEL 비트를 1로 설정해야 합니다. 즉, atmega32에서 Data Bit를 8비트로 설정하려면 위에서와 같이 (1 << URSEL)을 해 주어야 합니다. 아래의 Parity Bit, Stop Bit를 설정하는 경우에도 마찬가지입니다.
③ PARITY BIT
Parity Bit는 UCSR0C 레지스터의 UPM01, UPM00 두 개의 비트로 결정합니다.
Parity Bit를 지정하기위해서 각 각의 비트에 기록해야 하는 값을 알려 주는 표입니다.
Parity Bit |
UPM01 |
UPM00 |
No Parity |
0 |
0 |
Reserved |
0 |
1 |
Even Parity |
1 |
0 |
Odd Parity |
1 |
1 |
대부분의 serial 통신은 No Parity로 하기 때문에 이 경우에는 UCSR0C 레지스터에 특별한 조치를 할 필요가 없습니다. 만약 Data Bit는 8에 Even Parity를 사용한다면 다음과 같이 합니다.
(ATMEGA128)
STS UCSR0C,((1 << UCSZ01) | (1 << UCSZ00) | (1 << UPM01))
(ATMEGA32)
OUT UCSR0C,((1 << URSEL) | (1 << UCSZ01) | (1 << UCSZ00) | (1 << UPM01))
③ STOP BIT
Stop Bit는 UCSR0C 레지스터의 USBS0 비트로 결정합니다.
Stop Bit |
USBS0 |
1 |
0 |
2 |
1 |
대부분의 serial 통신은 Stop Bit를 1로 사용하기 때문에 이 경우에는 UCSR0C 레지스터에 특별한 조치를 할 필요가 없습니다. Data Bit는 8, Even Parity, Stop Bit 2로 설정할 때에는 다음과 같이 합니다.
(ATMEGA128)
STS UCSR0C,((1 << UCSZ01) | (1 << UCSZ00) | (1 << UPM01) | (1 << USBS0))
(ATMEGA32)
OUT UCSR0C,((1 << URSEL) | (1 << UCSZ01) | (1 << UCSZ00) | (1 << UPM01) | (1 << USBS0))
④ 기타 초기화 필수 사항
1) 비공기식 통신으로 지정
RS232 serial 통신은 비동기식(Asynchronous) 통신입니다. avr은 동기식 통신도 지원하는데 비동기식과 동기식을 구분하는 제어 비트는 레지스터 UCSR0B의 UMSEL0 비트입니다. 이 비트가 0이면 비동기식 통신이고, 이 비트가 1이면 동기식 통신을 합니다. RS232 통신을 할 때에는 이 비트는 0으로 두어야 합니다.
2) 송신 수신 사용 여부 설정
serial 통신 기능 중에서 수신과 송신을 모두 다 사용할 것인지, 수신 기능만 사용할 것인지, 송신 기능만 사용할 것인지를 설정할 수 있습니다. 레지스터 UCSR0B의 RXEN0 비트와 TXEN0 비트의 값을 지정함으로써 기능을 선택할 수 있습니다. 수신 기능을 사용하려면 RXEN0(Receiver Enable) 비트를 1로 설정합니다. 송신 기능을 사용하려면 TXEN0(Transmitter Enable) 비트를 1로 지정합니다.
3) Interrupt 사용 여부 지정
데이터 송신이나 수신 완료시 interrupt를 사용할지 여부를 결정할 수 있습니다. UCSR0B 레지스터의 RXCIE0(Rx Complete Interrupt Enable) 비트를 1로 설정하면, 데이터 수신 완료 후에 UCSR0A 레지스터의 RXC 비트가 1로 설정되면서USART0_Rx_Complete interrupt가 발생합니다. 본 글에서는 데이터 수신시에 interrupt를 이용하여 수신한 데이터를 queue에 넣을 것이므로 이 비트를 1로 설정해야 합니다.
atmega128의 경우 USART0_Rx_Complete interrupt는 19번째 interrupt이고, USART1_Rx_Complete interrupt는 31번째 interrupt입니다.
atmega32의 경우 USART_Rx_Complete interrupt는 14번째 interrupt입니다.
UCSR0B 레지스터의 TXCIE0(Rx Complete Interrupt Enable) 비트를 1로 설정하면, 데이터 송신 완료 후에 UCSR0A 레지스터의 TXC 비트가 1로 설정되면서USART0_Tx_Complete interrupt가 발생합니다.
atmega128의 경우 USART0_Tx_Complete interrupt는 21번째 interrupt이고, USART1_Tx_Complete interrupt는 33번째 interrupt입니다.
atmega32의 경우 USART_Tx_Complete interrupt는 16번째 interrupt입니다.
serial 통신 시에 상대방이 언제 데이터를 보낼지 모르기 때문에 데이터 수신시에는 interrupt를 사용할 필요가 있습니다. 하지만 데이터 송신은 프로그램이 필요할 때에 하면 되므로 굳이 인터럽트를 사용할 필요가 없습니다. 따라서 본 글에서도 송신 완료 인터럽트는 사용하지 않을 예정입니다.
한 가지 주의할 점은 SREG의 I 비트를 설정하지 않으면 모든 인터럽트가 발생하지 않습니다. 즉 SREG의 I 비트 값이 0이면 RXCLE0 비트와 TXCIE0 비트를 1로 설정했어도 인터럽트가 발생하지 않습니다. 수신 완료 인터럽트를 사용하려면 RXCIE0 비트를 1로 설정하고, SEI 명령을 실행해서 SREG의 I비트를 1로 설정해야 합니다.
다음은 atmega128에서 USART0와 USART1, atmega32의 USART를 통신 속도 115200, Data Bit 8, No Parity, 1 Stop Bit, 수신 송신 모두 사용하고, 수신 완료 인터럽트를 사용하도록 초기화하는 함수입니다.
(ATMEGA128 USART0)
#define U2X_ON #ifdef U2X_ON .EQU BAUDRATE_DEVIDER = 8 .EQU UCSR0A_VALUE = 2 #else .EQU BAUDRATE_DEVIDER = 16 .EQU UCSR0A_VALUE = 0 #endif .EQU BAUDRATE0 = 115200 .EQU UBRR0_VALUE = (F_CPU / (BAUDRATE_DEVIDER * BAUDRATE0) - 1) ;==================================================== ;USART0 사용 준비(BAUDRATE0,N,8,1) ;==================================================== USART0_INIT: PUSH AL LDI AL,UCSR0A_VALUE OUT UCSR0A,AL LDI AL,HIGH(UBRR0_VALUE) STS UBRR0H,AL LDI AL,LOW(UBRR0_VALUE) OUT UBRR0L,AL LDI AL,((1 << UCSZ01) | (1 << UCSZ00)) STS UCSR0C LDI AL,((1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0)) OUT UCSR0B,AL POP AL RET
(ATMEGA128 USART1)
#define U2X_ON #ifdef U2X_ON .EQU BAUDRATE_DEVIDER = 8 .EQU UCSR1A_VALUE = 2 #else .EQU BAUDRATE_DEVIDER = 16 .EQU UCSR1A_VALUE = 0 #endif .EQU BAUDRATE1 = 115200 .EQU UBRR1_VALUE = (F_CPU / (BAUDRATE_DEVIDER * BAUDRATE1) - 1) ;==================================================== ;USART1 사용 준비(BAUDRATE1,N,8,1) ;==================================================== USART1_INIT: PUSH AL LDI AL,UCSR1A_VALUE STS UCSR1A,AL LDI AL,HIGH(UBRR1_VALUE) STS UBRR1H,AL LDI AL,LOW(UBRR1_VALUE) STS UBRR1L,AL LDI AL,((1 << UCSZ11) | (1 << UCSZ10)) STS UCSR1C LDI AL,((1 << RXCIE1) | (1 << RXEN1) | (1 << TXEN1)) STS UCSR1B,AL
POP AL RET
(ATMEGA32 USART)
#define U2X_ON #ifdef U2X_ON .EQU BAUDRATE_DEVIDER = 8 .EQU UCSRA_VALUE = 2 #else .EQU BAUDRATE_DEVIDER = 16 .EQU UCSRA_VALUE = 0 #endif .EQU BAUDRATE = 9600 //115200 // 38400 .EQU UBRR_VALUE = (F_CPU / (BAUDRATE_DEVIDER * BAUDRATE) - 1) ////////////////////////////////////////////////// // USART_INIT: // PARAM NONE BAUDRATE,N,8,1 // RETURN NONE // CHANGED NONE ////////////////////////////////////////////////// USART_INIT: PUSH AL #ifdef U2X_ON LDI AL,1 << U2X OUT UCSRA,AL #endif LDI AL,HIGH(UBRR_VALUE) OUT UBRRH,AL LDI AL,LOW(UBRR_VALUE) OUT UBRRL,AL LDI AL,((1 << RXCIE) | (1 << RXEN) | (1 << TXEN)) OUT UCSRB,AL LDI AL,((1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0)) OUT UCSRC,AL POP AL RET
이번 글에서는 atmega128과 atmega32에서 serial 포트를 사용하기 위해서 설정하는 부분을 다루었습니다. 다음 글에서는 데이터를 송신하는 루틴을 작성해 보고, 송신 완료 인터럽트를 받아 queue에 넣고 필요할 때에 가져다 쓰는 루틴을 작성하도록 하겠습니다
'AVR > USART' 카테고리의 다른 글
atmega128, atmega32의 USART 통신(Queue를 이용하는 방식) - 2편 (0) | 2018.11.09 |
---|