Harman 세미콘 아카데미/Atmega128a

[ATmega 128a]⑦ - UART 통신

U_Pong 2024. 6. 18. 20:00

2024.6.11 수업날

이번에는 시리얼 통신 방법 중 하나인 UART 통신에 대해 알아본다.

 

<관련교재>

ATmega128a pdf 교재 - 174p~

ATmega 128로 배우는 마이크로컨트롤러 프로그래밍 - 200p~


먼저 USART 통신에 대해 알아보자.

< USART 통신 >

 

USART 통신은 반이중 방식 통신이다.

USART는 동기 모드로, 데이터 동기화를 위해 별도의 클록 신호를 전송한다.

클락의 유무만 체크하면 되기 때문에 데이터 송수신 효율이 높다.

하지만 클락 핀을 추가로 요구하므로 구조적인 어려움이나 추가적인 비용을 가지고 있다.

 

아래는 RXBn(수신), TXBn(송신)을 가지고 있는 UDRn의 datasheet이다.

 

 

아래는 UCSRnA의 datasheet이다.

 

다음으로 UART 통신에 대해 알아보자.

 

 

< UART 통신 >

UART 통신은 전이중 방식(full duplex) 통신으로, 송신과 수신을 동시에 진행할 수 있다.

이를 위해 2개의 범용 입출력 핀을 필요로 한다.

 

ATmega 128a는 UART 통신을 위한 2개의 포트를 포함하고 있는데, UART0, UART1이다.

UART 통신에서 데이터 수신을 위한 핀은 RX로 표시하며, 수신 데이터를 의미한다.

UART 통신에서 데이터 송신을 위한 핀은 TX로 표시하며, 송신 데이터를 의미한다.

이처럼 데이터 송신과 수신을 위한 전용 핀이 정해져 있으므로 시리얼 통신을 위하 2개의 장치를 연결하는 경우,

RX와 TX는 아래의 그림처럼 서로 교차하여 연결해야한다.

 

아래의 그림은 UART 통신의 데이터 전송에 대해 나타낸 것이다.

UART에서는 0의 시작비트(start bit)와 1의 정지비트(stop bit)를 사용한다.

UART는 바이트 단위로 통신하며, 여기에 시작 및 정지 비트를 추가하여 10비트 데이터를 전송하는 것이 일반적이다.

송신 측에서 데이터를 보내지 않으면 데이터 핀은 항상 1의 상태에 있다.

데이터가 수신되기 시작하는 시점에서 데이터 핀은 0의 상태로 변하고, 이어서 8비트의 실제 데이터가 수신된 후 데이터 전송이 끝났음을 알리는 1이 수신된다.

 

 

이제 직접 ComPortMaster 프로그램을 다운받아서 통신 결과를 확인해보자

http://withrobot.com/data/?mod=document&uid=12

 

ComPortMaster 설치 파일

그동안 많은 분들이 사용해 주셨던 ComPortMaster 프로그램을 제공해 드립니다. 첨부파일을 다운로드 받으셔서 ZIP 압축을 해제하시고 실행하면 ComPortMaster 프로그램이 설치됩니다.

withrobot.com

위의 링크에 들어가면 하단에 comportmaster.zip 파일이 있는데 이것을 선택하여 설치하면 된다.

 

결과를 확인하기 전에 먼저 소스코드를 작성한다.

USART 통신
/*
 * UART.c
 */ 

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

void UART0_Init()
{
	UBRR0H = 0x00;	// 9~11 bit
	UBRR0L = 207;	// 9600bps 설정
	UCSR0A = (1<<U2X0);	// 2배속 설정
	// 비동기 모드, 8비트, 패리티비트 없음, 스톱비트 1개
	// UCSR0C = 0x06; // 초기값으로 대체 0000 0110
	UCSR0B |= (1<<RXEN0);	// 수신가능
	UCSR0B |= (1<<TXEN0);	// 송신가능
}

void UART0_Transmit(char data)
{
	while(!(UCSR0A & (1<<UDRE0)));	// 송신 가능 하냐?(대기중), UDR이 비어 있는지? //비슷한 구문이 ctc쯤에 있을것이다
	UDR0 = data;
}

unsigned UART0_Receive()
{
	while(!(UCSR0A & (1<<RXC0)));	// 수신 대기중
	return UDR0;
}

/*void TX0_ch(unsigned char data)
{
	while(!(UCSR0A & (1<<UDRE0)));
	
	UDR0=data;
}
*/

int main(void)
{
	UART0_Init();
	
    while (1) 
    {
		UART0_Transmit(UART0_Receive());
		//TX0_ch('A');
		//_delay_ms(1000);
    }
}

 

ComPortMaster를 사용하여 통신 결과를 확인하기 위해

먼저 장치 관리자에서 포트에 어떤 번호의 포트와 연결되어 있는지 확인한다

장치 관리자에서 포트의 COM이 몇번인지 확인한다.

 

아래의 사진은 ComPortMaster를 사용하여 통신 결과를 확인한 것이다.

Device에 장치 관리자에서 확인한 포트의 번호를 적는다.

다음으로 Baudrate는 9600으로 적어준다.

그리고 Open port를 클릭하면서

회로의 ATmega 128a에 있는 스위치(ISP/UART 선택 스위치)를 오른쪽으로 이동시킨다.

그리고 Send 칸에 원하는 문자를 입력하고 우측의 Send를 클릭하여 아래의 Recv 칸에 잘 나타나는지 확인한다.

이때 Recv의 Auto CR/LF를 체크해주면 보기 쉽게 Send를 클릭할때마다 자동으로 줄바꿈을 해준다.

통신을 마쳤으면 Close port를 클릭해주고, ATmega 128a에 있는 스위치(ISP/UART 선택 스위치)를

왼쪽으로 이동시켜준다.

즉, 소스코드를 빌딩하고 디바이스 프로그래밍을 실행시킬때는 스위치를 왼쪽으로 이동시킨 채로 진행해야한다.

 

 

< UART_LED 통신 해보기 >

 

교수님께서 ComPortMaster에 a,b,c,d,e를 띄웠을때

아래와 같은 조건으로 LED가 작동되도록 만들어보라고 하셨다.

 

 

UART_LED_ABCDE 결과 출력 소스코드
/*
 * UART_LED_ABCDE.c
 */ 

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

void UART0_Init()
{
	// 비동기, 8비트, 패리티비트없음	, 스톱비트 1개
	UBRR0H = 0x00;			// 9-11 bit
	UBRR0L = 207;			// 9600bps 설정
	UCSR0A = (1<<U2X0);		// 2배속 설정
	UCSR0B |= (1<<RXEN0);	// 수신가능
	UCSR0B |= (1<<TXEN0);	// 송신가능
	//UCSR0C = 0x06;		// 초기값으로 대체
}

void UART0_Transmit(char data)
{
	while(!(UCSR0A & (1<<UDRE0)));	// 송신 가능 하나?(대기중), UDR이 비어 있는지?
	UDR0 = data;
}

unsigned UART0_Receive()
{
	while(!(UCSR0A & (1<<RXC0)));	// 수신 대기중
	return UDR0;
}

void Led_Init()
{
	DDRC = 0xff;
	PORTC = 0x00;
}

void Led_On()
{
	PORTC = 0xff;
}

void Led_Off()
{
	PORTC = 0x00;
}

void Led_Leftshift()
{
	for (uint8_t i = 0; i < 8; i++)
	{
		PORTC = (1 << i);
		_delay_ms(100);
	}
}

void Led_Rightshift()
{
	for (int8_t i = 7; i >= 0; i--)
	{
		PORTC = (1 << i);
		_delay_ms(100);
	}
}

void Led_Blinking()
{
	PORTC =0xff;
	_delay_ms(500);
	PORTC =0x00;
	_delay_ms(500);
}

int main(void)
{
	DDRG = 0xff;
	UART0_Init();
	Led_Init();
	
	while (1)
	{
		char Led = UART0_Receive();
		
		switch(Led)
		{
			case 'a':
			Led_On();
			UART0_Transmit('a');
			break;
			
			case 'b':
			Led_Off();
			UART0_Transmit('b');
			break;
			
			case 'c':
			Led_Leftshift();
			UART0_Transmit('c');
			break;
			
			case 'd':
			Led_Rightshift();
			UART0_Transmit('d');
			break;
			
			case 'e':
			Led_Blinking();
			UART0_Transmit('e');
			break;
			
			default:
			UART0_Transmit('?');
			break;
		}
	}
}

 

 

Device, Baudrate를 설정해주고

a,b,c,d,e를 적은 다음 차례대로 Send를 클릭하여 결과를 확인한다.

 
UART_LED_ABCDE 결과

 

 

< UART_INT1 >

이번에는 h 파일, c 파일, main.c 파일로 나누어서 특정조건을 만족할때만 ComportMaster에서 결과가 출력되도록 소스코드를 작성한다.

UART_INT1 소스코드
/*
 * uart0.h
 *
 * Created: 2024-06-11 오후 2:11:59
 *  Author: USER
 */ 

#ifndef UART0_H_
#define UART0_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>


void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive();

#endif /* UART0_H_ */

 

/*
 * uart0.c
 *
 * Created: 2024-06-11 오후 2:11:35
 *  Author: USER
 */ 


#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>


void UART0_Init()
{
	UBRR0H = 0x00;	// 9~11 bit
	UBRR0L = 207;	// 9600bps 설정
	UCSR0A = (1<<U2X0);	// 2배속 설정
	// 비동기 모드, 8비트, 패리티비트 없음, 스톱비트 1개
	// UCSR0C = 0x06; // 초기값으로 대체 0000 0110
	UCSR0B |= (1<<RXEN0);	// 수신가능
	UCSR0B |= (1<<TXEN0);	// 송신가능
	
	UCSR0B |= (1<<RXCIE0);	// 수신 인터럽트 인에이블
}

void UART0_Transmit(char data)
{
	while(!(UCSR0A & (1<<UDRE0)));	// 송신 가능 하냐?(대기중), UDR이 비어 있는지? //비슷한 구문이 ctc쯤에 있을것이다
	UDR0 = data;
}

unsigned UART0_Receive()
{
	while(!(UCSR0A & (1<<RXC0)));	// 수신 대기중
	return UDR0;
}

 

/*
 * UART_INT1.c
 *
 * Created: 2024-06-11 오후 2:09:47
 * Author : USER
 */ 
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

#include "uart0.h"

//출력 스트림 설정 <stdio.h>
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

char rxBuff[100] = {0};	// 수신 버퍼 초기화
uint8_t rxFlag = 0;		// 수신 완료 깃발	

char ledon[] = "LED_ON";


ISR(USART0_RX_vect)	// 수신 완료 인터럽트 핸들러
{
	static uint8_t rxHead = 0;	//수신된 데이터의 인덱스
	uint8_t rxData = UDR0;
	if (rxData == '\n' || rxData == '\r')
	{
		rxBuff[rxHead] = '\0';	// Null 문자 추가, '\0'대신에 NULL이라고 해도 됨
		rxHead = 0;				// 인덱스 초기화
		rxFlag = 1;				// 문자열 수신 깃발 세움
	}
	else
	{ 
		rxBuff[rxHead] = rxData;	// 버퍼에 수신 데이터 추가
		rxHead++;					// 인덱스 올림....
	}
}

int main(void)
{
	UART0_Init();
	// uint8_t rxData;
	stdout = &OUTPUT;
	
	sei();
	
    while (1) 
    {
		if (rxFlag == 1)
		{
			rxFlag = 0;
			printf(rxBuff);
		}
    }
}

 

먼저 ComportMaster에서 Device, Baudrate를 적어주고 ATmega128a의 스위치를 오른쪽으로 이동시킨다.

그리고 Send에 임의의 문자를 적어서 Send를 클릭하면 Recv에는 아무것도 출력되지 않는다.

하지만 Send의 우측에 있는 ASCII를 선택하고 Send를 클릭하면 Recv에 ASCII가 체크된 내용만 출력되는 것을 확인할 수 있다.

강의실에서 캡쳐한 사진이라 Device의 숫자가 앞의 사진과 다르다.

 

아래의 사진에는 456이라는 숫자가 연속적으로 출력되는데,

만약 456의 우측에 있는 ASCII를 선택하지 않고 Send를 연속해서 클릭한 다음 ASCII를 선택하고 Send를 클릭하면 연속해서 클릭한 만큼 숫자를 출력해준다.

강의실에서 캡쳐한 사진이라 Device의 숫자가 앞의 사진과 다르다.

 

아래의 사진에는 a가 연속적으로 출력되었다.

마찬가지로 a의 우측에 있는 ASCII를 선택하지 않고 Send를 연속해서 클릭한 다음 ASCII를 선택하고 Send를 클릭하면 연속해서 클릭한 만큼 문자를 출력해준다.

강의실에서 캡쳐한 사진이라 Device의 숫자가 앞의 사진과 다르다.

 

 

< UART_INT2 >

이번에는 UART_INT1 소스코드보다 더 자세하게 풀어서 소스코드를 작성해본다.

UART_INT2 소스코드
/*
 * uart0.h
 *
 * Created: 2024-06-11 오후 2:11:59
 *  Author: USER
 */ 

#ifndef UART0_H_
#define UART0_H_

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

uint8_t uartRxBuff[100];
uint8_t uart0RxFlag;

void UART0_Init();
void UART0_Transmit(char data);
unsigned UART0_Receive();


uint8_t UART0_getFlag();
void UART0_clearxFlag();
void UART0_setRxFlag();
uint8_t *UART0_readRxBuff();
void UART0_print(char *str);
uint8_t UART0_avail();
void UART0_ISR_Process();
void UART0_execute();


#endif /* UART0_H_ */

 

/*
 * uart0.c
 *
 * Created: 2024-06-11 오후 2:11:35
 *  Author: USER
 */ 
#include "uart0.h"

uint8_t uart0RxBuff[100];
uint8_t uart0RxFlag;

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>


void UART0_Init()
{
	UBRR0H = 0x00;	// 9~11 bit
	UBRR0L = 207;	// 9600bps 설정
	UCSR0A = (1<<U2X0);	// 2배속 설정
	// 비동기 모드, 8비트, 패리티비트 없음, 스톱비트 1개
	// UCSR0C = 0x06; // 초기값으로 대체 0000 0110
	UCSR0B |= (1<<RXEN0);	// 수신가능
	UCSR0B |= (1<<TXEN0);	// 송신가능
	
	UCSR0B |= (1<<RXCIE0);	// 수신 인터럽트 인에이블
	sei();
}

void UART0_Transmit(char data) //보내다
{
	while(!(UCSR0A & (1<<UDRE0)));	// 송신 가능 하냐?(대기중), UDR이 비어 있는지? //비슷한 구문이 ctc쯤에 있을것이다
	// UDREn이 1이면 송신할 준비가 되다-> 송신할 준비가 되지 않으면 while 문에서 갇히고 송신할 준비가 되면 나온다
	// 1을 UDREn의 자리까지 옮긴다, 1 & x가 되면 비트를 유지-> 즉, UDREn 자리만 유지하고 나머지는 비트를 끈다
	// while(!1);은 while(0);이기 때문에 밑에 중괄호가 없어 while문 안에서 갇힌다, 
	// 반대로 while(!0);은 while(1);이기 때문에 밑에 중괄호가 없어 while문에서 나온다
	UDR0 = data;
}

unsigned UART0_Recevive()
{
	while(!(UCSR0A & (1<<RXC0)));	// 수신 대기중
	// 1이면 받았고 빼다(사용하지 않음) 혹은 새로운 것이 있다, 0이면 새로운것이 없다
	return UDR0;
}

///////////////////////////////////////////// 여기서부터 uart_int1과 다르게 추가로 작성한 함수
uint8_t UART0_getFlag()
{
	return uart0RxFlag;
}

void UART0_clearxFlag()
{
	uart0RxFlag = 0;
}

void UART0_setRxFlag()
{
	uart0RxFlag = 1;
}

uint8_t *UART0_readRxBuff()
{
	return uart0RxBuff;
}

void UART0_print(char *str)
{
	for (uint8_t i=0; str[i]; i++)
	{
		UART0_Transmit(str[i]);
	}
	UART0_Transmit('\n');
}

uint8_t UART0_avail()
{
	if (!(UCSR0A & (1<<RXC0)))
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

void UART0_ISR_Process()
{
	uint8_t rxData = UDR0; //지역변수
	static uint8_t uartRxTail = 0;
	if (rxData == '\n')
	{
		uart0RxBuff[uartRxTail] = rxData;
		uartRxTail++;
		uart0RxBuff[uartRxTail] = NULL;
		uartRxTail = 0;
		UART0_setRxFlag();
	}
	else
	{
		uart0RxBuff[uartRxTail] = rxData;
		uartRxTail++;
	}
}

void UART0_execute()
{
	if (UART0_getFlag())
	{
		UART0_clearxFlag();
		uint8_t *rxString = UART0_readRxBuff();
		
		printf(rxString);
	}
	_delay_ms(300);
}

 

/*
 * UART_INT2.c
 *
 * Created: 2024-06-11 오후 4:07:51
 * Author : USER
 */ 

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

#include "uart0.h"

FILE OUTPUT = FDEV_SETUP_STREAM(UART0_Transmit, NULL, _FDEV_SETUP_WRITE);

ISR(USART0_RX_vect)
{
	UART0_ISR_Process();
}

int main(void)
{
	UART0_Init();
	stdout = &OUTPUT;
	
    while (1) 
    {
		UART0_execute();
    }
}

 

UART_INT1과 마찬가지로 ASCII를 선택했을때만 임의의 내용을 출력하고,

ASCII를 선택하지 않은 상태에서 Send를 연속적으로 클릭하고 다시 ASCII를 선택한 후 Send를 클릭하면

연속적으로 클릭한 만큼의 임의의 내용을 출력한다.

강의실에서 캡쳐한 사진이라 Device의 숫자가 앞의 사진과 다르다.


UART 통신 끝!