ATmega128_UART 통신

JS·2023년 3월 6일
0

ATmega128

목록 보기
9/9

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

  • 통신 Protocol: 약속된 형태로 통신하기 위한 규약, 대화하기 위한 일련의 절차/약속

ATmega128A의 UART의 설명을 데이터 시트를 살펴보자


ATmega128은 UART0와 UART1 2개가 존재하며, 필자는 UART0와 PC를 연결해서 사용할 것이다.

타이밍 다이어그램


TxD 기준

  • IDLE 상태에서는 High 신호
  • Start 상태일 때는 Low 신호를 전송한다.
  • 0~8까지 Data bit -> 보내는 bit의 수를 조정가능(최근에는 8bit(1byte로 고정되는 추세)
  • Data bit뒤의 1bit는 error check 용 Parity bit
    -> 최근에는 사용안하는 추세
  • Stop 상태일 경우, High 신호를 전송한다.
    -> 여러 데이터를 전송할 경우, 다시 Start 상태를 위한 Low신호를 전송한다.

Paity는 None, 홀수, 짝수로 설정이 가능하다.

  • Parity가 홀수인 경우
    -> Data가 짝수인 경우, Parity는 1이 되어 홀수로 만들어줌
    -> Data가 홀수인 경우, Parity는 0이 되어 홀수를 유지함
  • Parity가 짝수인 경우
    -> Data가 짝수인 경우, Parity는 이 되어 짝수를 유지함
    -> Data가 홀수인 경우, Parity는 1이 되어 짝수로 만들어줌
  • Parity가 None인 경우
    -> parity bit를 사용안함

  • 일반 모드
  • 2배 속도 모드

위의 계산 값을 토대로 작성된 주파수에 따른 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를 동작시킨다.

0개의 댓글