UART(RS232)통신
시리얼 통신에는 크게 세가지 종류가 있다.
- SPI통신
-> MISO, MOSI, CLK
-> 클락이 있음 -> 동기식 통신
-> Data선이 두개 -> FullDuplex(동시에 데이터 송수신이 가능)
->bps
-> 1:n 통신(연결된 디바이스 중 한개와 통신)
- UART 통신
-> 핀의 종류: TxD, RxD
-> 클락이 없음 -> 비동기식 통신
-> Data선이 두개 -> FullDuplex(동시에 데이터 송수신이 가능)
-> Baud Rate: bps(bit per sec)
-> 1:1 통신(디바이스와 1:1연결만 가능)
- I2C 통신
-> 핀의 종류: SCL, SDA
-> 클락이 있음 -> 동기식 통신
-> Data선이 한개 -> HalfDuplex(데이터 송수신이 동시에 불가능)
->bps
-> 1:n 통신(연결된 디바이스 중 한개와 통신)
과거에는 인터넷도 UART통신을 이용하였다.
UART 통신 Protocol
ATmega128A의 UART의 설명을 데이터 시트를 살펴보자
ATmega128은 UART0와 UART1 2개가 존재하며, 필자는 UART0와 PC를 연결해서 사용할 것이다.
타이밍 다이어그램
TxD 기준
Paity는 None, 홀수, 짝수로 설정이 가능하다.
- Parity가 홀수인 경우
-> Data가 짝수인 경우, Parity는 1이 되어 홀수로 만들어줌
-> Data가 홀수인 경우, Parity는 0이 되어 홀수를 유지함
- Parity가 짝수인 경우
-> Data가 짝수인 경우, Parity는 이 되어 짝수를 유지함
-> Data가 홀수인 경우, Parity는 1이 되어 짝수로 만들어줌
- Parity가 None인 경우
-> parity bit를 사용안함
위의 계산 값을 토대로 작성된 주파수에 따른 UBRR 테이블은 다음과 같다.
PC용 시리얼 통신 프로그램(ComPortMaster) 이용
port 3임을 확인
COM3, 9600bps 설정 후, 코드를 upload해보자
※ upload 할 때, PC용 시리얼 통신 프로그램의 port 버튼은 close되어 있어야 한다.
또한 보드의 스위치도 왼쪽으로 가있어야 한다.
그렇지 않는 경우, 다음과 같은 에러를 마주할 것이다.
위는 port 버튼이 Open되어 있는 경우이다.
위는 스위치가 오른쪽에 있을 경우이다.
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
void UART0_Transmit( unsigned char data )
{
/* Wait for empty transmit buffer */
while ( !( UCSR0A & (1<<UDRE0)) )
;
/* Put data into buffer, sends the data */
UDR0 = data;
}
unsigned char UART0_Receive( void )
{
/* Wait for data to be received */
while ( !(UCSR0A & (1<<RXC0)) ) //Recive가 완료(0이 아나면)되면 While문을 탈출 그렇지 않으면 While에서 대기
;
/* Get and return received data from buffer */
return UDR0;
}
void UART0_print(char *str){
for(int i=0; str[i];i++){
UART0_Transmit(str[i]);
}
}
int main(void)
{
// uart0, TXD, RxD, Clk 2배 Mode, 9600 baud rate
// 8bit data, stop 1bit, parity none :default 값
UCSR0B |= (1<<RXEN0) | (1<<TXEN0); //UCSR0B에 있는 TXD, RxD 사용
UCSR0A |= (1<<U2X0); // 내부 clk 2배 -> UCSR0A에 있음
UBRR0L = 207; // 16:115200bps, 207:9600bps
char rxData;
while (1)
{
UART0_Transmit('K');
UART0_Transmit('E');
UART0_Transmit('L');
UART0_Transmit('L');
UART0_Transmit('O');
UART0_Transmit('\n');
_delay_ms(1000);
}
}
결과 값은 다음과 같다
위의 코드에서 문장을 한번에 보내보자
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
void UART0_Transmit( unsigned char data )
{
/* Wait for empty transmit buffer */
while ( !( UCSR0A & (1<<UDRE0)) )
;
/* Put data into buffer, sends the data */
UDR0 = data;
}
unsigned char UART0_Receive( void )
{
/* Wait for data to be received */
while ( !(UCSR0A & (1<<RXC0)) ) //Recive가 완료(0이 아나면)되면 While문을 탈출 그렇지 않으면 While에서 대기
;
/* Get and return received data from buffer */
return UDR0;
}
void UART0_print(char *str){
for(int i=0; str[i];i++){
UART0_Transmit(str[i]);
}
}
int main(void)
{
// uart0, TXD, RxD, Clk 2배 Mode, 9600 baud rate
// 8bit data, stop 1bit, parity none :default 값
UCSR0B |= (1<<RXEN0) | (1<<TXEN0); //UCSR0B에 있는 TXD, RxD 사용
UCSR0A |= (1<<U2X0); // 내부 clk 2배 -> UCSR0A에 있음
UBRR0L = 207; // 16:115200bps, 207:9600bps
char rxData;
while (1)
{
UART0_print("Hello Avatar\n");
if(UART0_Avail()){
rxData = UART0_Receive();
UART0_print("Recived Data: ");
UART0_Transmit(rxData);
UART0_Transmit('\n');
}
_delay_ms(1000);
}
}
위의 코드의 경우,rxData = UART0_Receive()에서 recive 되지 않으면 UART0_Receive()에서 계속 대기한는 문제가 있음.
void UART0_Transmit( unsigned char data )
{
/* Wait for empty transmit buffer */
while ( !( UCSR0A & (1<<UDRE0)) )
;
/* Put data into buffer, sends the data */
UDR0 = data;
}
unsigned char UART0_Receive( void )
{
/* Wait for data to be received */
while ( !(UCSR0A & (1<<RXC0)) ) //Recive가 완료(0이 아나면)되면 While문을 탈출 그렇지 않으면 While에서 대기
;
/* Get and return received data from buffer */
return UDR0;
}
void UART0_print(char *str){
for(int i=0; str[i];i++){
UART0_Transmit(str[i]);
}
}
uint8_t UART0_Avail(){
if( !(UCSR0A & (1<<RXC0)) ){
return 0; // Rx data가 없으면 0
}
return 1; // Rx data가 있으면 1
}
int main(void)
{
// uart0, TXD, RxD, Clk 2배 Mode, 9600 baud rate
// 8bit data, stop 1bit, parity none :default 값
UCSR0B |= (1<<RXEN0) | (1<<TXEN0); //UCSR0B에 있는 TXD, RxD 사용
UCSR0A |= (1<<U2X0); // 내부 clk 2배 -> UCSR0A에 있음
UBRR0L = 207; // 16:115200bps, 207:9600bps
char rxData;
while (1)
{
UART0_print("Hello Avatar\n");
if(UART0_Avail()){
rxData = UART0_Receive();
UART0_print("Recived Data: ");
UART0_Transmit(rxData);
UART0_Transmit('\n');
}
_delay_ms(10); // recive 되지 않으면 UART0_Receive()에서 계속 대기한는 문제가 있음
}
}
위의 코드의 경우, 코드가 복잡해지면서 Mcu가 데이터를 처리하는 시간이 길어졌을 때, 처리하는 시간동안 UART를 통해 전송된 데이터가 수신되지 않아 데이터가 온전하게 전송되지 않을 수 있음 -> UART의 수신 interrupt를 이용하여 전송된 데이터를 buffer에 저장하여 데이터 전송이 제대로 되도록 하자
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
void UART0_Transmit( unsigned char data )
{
/* Wait for empty transmit buffer */
while ( !( UCSR0A & (1<<UDRE0)) )
;
/* Put data into buffer, sends the data */
UDR0 = data;
}
unsigned char UART0_Receive( void )
{
/* Wait for data to be received */
while ( !(UCSR0A & (1<<RXC0)) ) //Recive가 완료(0이 아나면)되면 While문을 탈출 그렇지 않으면 While에서 대기
;
/* Get and return received data from buffer */
return UDR0;
}
void UART0_print(char *str){
for(int i=0; str[i];i++){
UART0_Transmit(str[i]);
}
}
uint8_t UART0_Avail(){
if( !(UCSR0A & (1<<RXC0)) ){
return 0; // Rx data가 없으면 0
}
return 1; // Rx data가 있으면 1
}
uint8_t uart0RxBuff[100]; // 수신 interrupt를 저장하기 위한 버퍼
uint8_t uart0RxCFlag =0;
ISR(USART0_RX_vect){
static uint8_t uart0RxTail =0; // buffer를 채우기 위한 변수-> 주로 변수명을 tail, rear라고 한다
uint8_t rx0Data = UDR0;
if (rx0Data == '\n'){ // data의 값이 개행 문자인지 확인하여 문자열이 끝났는지를 확인
uart0RxBuff[uart0RxTail] = rx0Data;
uart0RxTail++;
uart0RxBuff[uart0RxTail] = 0; // 미지막에 숫자 0(null)을 넣어주어 문자열을 끝마침
uart0RxTail = 0;
uart0RxCFlag = 1; // flag를 true로 하여 문자가 끝난 상태를 저장
}
else {
uart0RxBuff[uart0RxTail] = rx0Data;
uart0RxTail++;
}
}
int main(void)
{
// uart0, TXD, RxD, Clk 2배 Mode, 9600 baud rate
// 8bit data, stop 1bit, parity none :default 값
UCSR0B |= (1<<RXEN0) | (1<<TXEN0); //UCSR0B에 있는 TXD, RxD 사용
UCSR0A |= (1<<U2X0); // 내부 clk 2배 -> UCSR0A에 있음
UCSR0B |= (1<<RXCIE0);
UBRR0L = 207; // 16:115200bps, 207:9600bps
sei();
while (1)
{
UART0_print("Hello\n");
if(uart0RxCFlag){
uart0RxCFlag = 0;
UART0_print("Recived Data: ");
UART0_print(uart0RxBuff);
}
_delay_ms(100); // recive 되지 않으면 UART0_Receive()에서 계속 대기한는 문제가 있음
}
}
만약 1초 동안 delay를 주고싶다.
이럴 때 일반적으로 delay를 많이 쓸 것이다. 그러나 delay를 쓰면 코드전체가 delay가 걸리므로 동작에 방해가 된다. -> Blocking 방식
CLk를 이용하여 interrupt를 이용하는 방식 -> Non-Blocking 방식
이제 Time Tick을 이용하여 Non-Blocking 방식으로 코드를 수정해보자
main
#define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include "periph/UART0/uart0.h" #include <string.h> #include <stdio.h> #include "Common/timeTick/timeTick.h" FILE OUTPUT = FDEV_SETUP_STREAM(USART0_Transmit, NULL, _FDEV_SETUP_WRITE); #define LED_DDR DDRC #define LED_PORT PORTC #define LED_PIN PINC #define LED_1 5 #define LED_2 6 #define LED_3 7 ISR(TIMER2_COMP_vect) { incTick(); //nonblocking 방식. } ISR(USART0_RX_vect) //interrupt 되는 부분. 여기로 jump해서 온다 { USART0_ISR_Process(); } void LED_Machine_Comm() { if(UART0_readyRxFlag()){ //Flag=1이면 실행. 즉 ISR에서 Null값 만났으니까 Flag1됨. UART0_clearRxFlag(); uint8_t *rxString = UART0_readRxBuff(); // if(!strcmp((const char *)rxString, "LED1_TOGGLE\n")){ //rxString에 있는 문자열이 LED1_TOGGLE과 같은지 비교, 같으면 0 -> 괄호안이 "참"이되게 앞에 !을 붙여 1로 LED_PORT ^= (1<<LED_1); //LED를 Toggle하겠다 if(LED_PORT & (1<<LED_1)){ //LED pin에 1이 있으면 ON printf("LED1_ON\n"); } else{ printf("LED1_OFF\n"); } } else if(!strcmp((const char *)rxString, "LED2_TOGGLE\n")){ LED_PORT ^= (1<<LED_2); if(LED_PORT & (1<<LED_2)){ printf("LED2_ON\n"); } else{ printf("LED2_OFF\n"); } } else if(!strcmp((const char *)rxString, "LED3_TOGGLE\n")){ LED_PORT ^= (1<<LED_3); if(LED_PORT & (1<<LED_3)){ printf("LED3_ON\n"); } else{ printf("LED3_OFF\n"); } } } } void PRINT_FUNC() { //1초 간격으로 printf //_delay_ms(1000); //모든 코드들이 1초동안 동작을 안함. static uint32_t prevTime =0; static int counter = 0; if(getTick() - prevTime > 1000){ //tick은 1milisec 간격으로 숫자가 1씩 증가됨. prevTime=getTick(); printf("count %d\n", counter++); } } int main(void) { LED_DDR |= (1<<LED_1) | (1<<LED_2) | (1<<LED_3); //LED3개를 output으로 설정하기 .. TCCR2 |= (0 << CS22) | (1 << CS21) | (1 << CS20); // prescaling / 64로 동작을 의미한다. TCCR2 |= (1 << WGM21) | (0 << WGM20); // CTC모드 TIMSK |= (1 << OCIE2); // OutCopare Interrupt Enable OCR2 = 250 - 1; // period 1ms UART0_init(); stdout=&OUTPUT; //입력받을 수 있음 sei(); UART0_print("Seoul Tech LED Machine!\n"); //char rxData; while (1) { PRINT_FUNC(); LED_Machine_Comm(); } }
TimeTick.c
#include "timeTick.h" uint32_t timeTick = 0; void incTick() { timeTick++; } uint32_t getTick() { return timeTick; } uint32_t clearTick() { timeTick = 0; }
TimeTick.h
#include <avr/io.h> #ifndef TIMETICK_H_ #define TIMETICK_H_ void incTick(); uint32_t getTick(); uint32_t clearTick(); #endif
위의 코드는 Time Tick interrupt를 이용하여 delay 없이 Pc의 시리얼 통신 프로그램과 통신이 가능하게 한 것
시리얼 통신 프로그램을 버튼처럼 활용하여 버튼을 누르면 "LED1_TOGGLE\n", "LED2_TOGGLE\n", "LED3_TOGGLE\n"이라는 문자열이 ATmega로 전송이 되고, 이 전송된 문자열이 일치하는 지 여부에 따라서 LED를 동작시킨다.