Develop Knowledge/Atmega, AVR

10강. USART, UART 통신

정수열 2023. 1. 10. 14:30

본 글에서는 Atmega의 USART통신, UART 통신을 사용하는 방법을 '처음 배우는 사람의 입장'에서 배워본다.

즉, 처음 보았을 때 어떻게 데이트시트에 접근하는지 알아보고,

앞으로는 혼자서도 인터넷 글을 검색하기 보다는 데이터시트를 보면서 스스로 개발할 수 있는 역량을 기른다.

 

- USART란?

- USART와 UART의 차이는??

나보다 글솜씨가 뛰어나게 정리한 분이 있으니 이걸로 대체합니다.

https://blog.naver.com/PostView.naver?blogId=ycpiglet&logNo=222147465770&redirect=Dlog&widgetTypeCall=true&directAccess=false 

 

동기 비동기 / USART와 UART 통신의 차이 완벽 정리 (Serial 통신) / PC 포트 (RS232C) / 직렬 통신과 병렬

직렬 통신과 병렬 통신 통신의 가장 큰 분류에는 직렬과 병렬이 있다. 직렬 통신 직렬(Serial) 통신은 보...

blog.naver.com

 

- 자. 그럼 이제 이론은 알겠는데.. 어떻게 개발을 시작해야 하지?

데이터시트를 볼 때, 처음 보는 입장에서는 글도 길고 영어여서 혼란이 올수도 있다. 물론 나도 그렇다..

하지만, 그동안 GPIO, Timer, PWM, ... 등을 하면서, 동일하게 접근하는 방식이 있었다.

첫번쨰는, PinMap이다. 이 기능이 어느 핀에서 나오는지 확인한다.

두번째는, 회로 구성이다.

세번째는, 예제 코드이다. 가장 간단한 기능이 어떻게 구현되는지 알아본다.

네번재는, 레지스터이다. 그 후 추가기능을 하나하나 파악해본다.

앞으로 배울 모든 기능을 이 순서대로 데이터시트를 보면서 익히면, 큰 문제 없이 전부 익힐 수 있을거라고 생각한다.

 

그럼 이제 데이터시트를 열고, 차차 하나씩 살펴보자!

구글에 "Atmega128A datasheet" 등으로 자신이 쓰는 모델명에 맞게 검색하고, MicroChips 사이트에 걸려있는 곳으로 들어간다.

이 사이트는 한국에서 가장 많이 쓰는 NewTC 사의 AVR 버전인, Atmega128A 데이터시트 사이트이다.

http://ww1.microchip.com/downloads/en/devicedoc/atmel-8151-8-bit-avr-atmega128a_datasheet.pdf

 

1. USART PinMap

데이터 시트 맨 처음을 보면 목차에 있다.

PinMap에 관한 정보에 들어가준다.

(밑의 사진은 대충 훑기만 하고 넘어가주세요!! 중요한 사진은 아닙니다.)

 

 

 

(여기까지입니다.)

이게 6. Pin Configuration 의 내용이다.

맨 위에 PinMap 사진에는 어느 핀이 어떤 기능을 하는지 '기호'로 저장되어 있고,

밑의 내용을 보면 Port A부터 Port G까지 내용이 있다.

기본적으로, Atmega는 기본적으로 모든 핀을 GPIO로 사용하지만

특정 레지스터를 설정할 경우 다른 기능으로 동작한다.

우리가 원하는 UART 기능을 PortE 에 있다. (실제로 찾아볼 때는 PortA부터 뒤져봐야 한다)

'Alternate Functions of Port E'에 들어가보자.

UART 개념 사진을 보면, TX와 RX가 필요한데,

그 기능을 찾아보니 Port E에 있었다. PE0과 PE1 핀이 그 기능을 한다고 한다.

이 때 헷갈릴 수 있는게, PE2를 보면 'USART0의 외부 클럭을 입력하거나 출력합니다'라고 되어있다.

우리가 원하는건 TX와 RX이지, 클럭을 설정하고 하는건 아니므로 일단 건너뛴다.

물론.. 전문적으로 공부할 경우에는 저런것들도 나중에 하나하나 알아볼 필요는 있다.

 

2. 회로 구성

이제 핀을 알았으니 회로 구성을 할 차례이다.

이걸 검색하는게 어려운데, 고려할 것은 다음과 같다.

1. 개념 설명에서, 어떤 핀이 필요한지 파악한다. UART 통신의 경우는 TX와 RX 2가지이다.

2. GND와 VCC는 일반적으로 사용한다.

3. 그 이상의 핀을 쓰는건 다른 모듈을 끌어오는 것이니 무시한다.

따라서 우리가 사용해야 할 핀은 TX, RX, GND, VCC이다.

이 4개로 통신하는 회로도를 찾아본다.

소소한 팁이라면, 한글보다는 영어로 검색하는게 원하는 결과가 좀 더 잘 나온다.

그랬더니 이런 그림이 나왔다.

TX와 RX는 엇갈려서 연결하는 것에 주의한다.

 

이런 그림도 나왔다.

그런데 핀을 보니.. TX와 RX만 사용하긴 하는데..

왼쪽 빨간색 네모에 다른 모듈을 사용하는것이 보이는가??

이건 UART 통신의 일종인 RS232 통신이다.

이런것까지 처음에 사용하면 골치 아파지므로.. 나중에 배우자.

 

3. 예제 코드 (데이터시트)

이제 회로를 구성했으니, 코드를 Atmega에 넣으면 된다.

사실 레지스터를 보면서 구현하는게 가장 정석이긴 하지만,

전문가의 시범을 한 번 본 후에 따라하면 더 잘 되지 않을까?

그래서 예제 코드로 '가장 간단한 기능'을 익혀본 뒤에, 거기에 살을 덧붙여본다.

마치 C언어를 처음 배울 때 예제 코드로 반복문, 조건문 등을 익혔을 때 처럼,

MCU(마이크로컨트롤러 유닛)을 배울때도 동일하다.

 

여기서 중요한 점은, '가장 간단한 기능'을 배우는 것이다.

USART 통신에서 가장 기본적인 기능은 무엇일까? 바로 '통신'이다.

더 구체적으로는, '데이터 1개를 통신' 해 보는 것이다.

 

복잡한 예제의 경우, 데이터시트에 예제 코드가 있다. 그러면 그걸 따라서 치면 된다.

가장 기본적인 설정을 하고 가장 기본적인 기능만 구현하기 떄문에, 정말 잘 나와있다.

Atmega128A 의 USART 통신 예제 코드는 데이터시트에 나와있다.

USART 목차에 가면 있는데, 밑에도 그림으로 첨부하겠다.

> USART Initialization

> USART Transmit (5~8 bit)

> USART Receive (5~8 bit)

이 외에도 많은 내용이 있고,

코드를 이해하려면 데이터시트에 첨부된 레지스터, 블럭 다이어그램 등을 보면서 하나하나 비교해 나가면 된다.

코드 한 줄 한 줄이 왜 이런 의미로 쓰였는지 데이터시트를 찾아보면 전부 나와있으므로, 커피 한잔의 여유를 가지며 분석해 보길 바란다.

 

3. 예제 코드 (인터넷 검색)

문제는 이 경우이다. 인터넷에는 양질의 코드가 많이 나와있지 않다.

레지스터 설정 난잡하게 하고... (특히 UCSRA=0x45 이런식으로 적는 사람을 제일 싫어한다.. 주석이라도 달아주세요..)

그리고 그 사람이 구성한 회로와 내가 구성한 회로가 동일하지 않아서 분석하기도 까다롭다... (이건 어쩔수 없다)

그리고 쓸데없이 복잡하게 작성한 사람도 많다... (제발..)

그래서 임베디드 진입 장벽이 높은가 보다.. 싶다.

할말은 많지만 더이상은 생략하고, 가장 간단한 접근 방식으로 알아보면 다음의 프로세스를 거친다.

 

1. 여러 블로그를 검색해보면서, 가장 줄이 짧고 읽기 쉬운 코드를 찾는다. 특히, 주석이 많을수록 좋다.

가장 기본적인 기능을 구현하려는 경우, 보통은 20줄 정도면 다 작성할 수 있다.

2. 이제 코드를 한줄한줄 분석하면서, 자신의 회로에 맞게 작성한다.

 

검색하는게 제일 중요한데, 한 3명중 1명꼴로 이런식으로 깔끔하게 적어두었다. 이런걸 쓰면 된다.

(사실 이것도 조금 주석이 부실하지만.. 이정도만 해도 충분하다)

코드를 한줄한줄 분석하면서, 레지스터를 검색해가면서, 코드를 자신의 회로에 맞게 작성하자.

이 분은 USART1 채널을 썻으므로, USART0 채널에 맞게 바꾸려면 좀 많이 건드려야 할 거 같다.

출처 : https://hastudent.tistory.com/18

 

밑의 분도 잘 작성은 하셨지만,

한 번에 너무 많은것을 설명하려다보니 처음 공부하는 입장에서는 다소 난감한 글이다.

이런건 일단 기본 기능을 공부한 후에 해도 늦지 않다.

출처 : https://blog.naver.com/xisaturn/220750649418

 

4. 레지스터

이제 기본 기능을 구현하고 테스트 해보았으면, 레지스터에 깊게 알아볼 차례이다.

특별히 설명할 건 없고, 필요한 설정은 대부분 레지스터에서 하므로,

새로운 기능을 추가하고 싶다면 레지스터 항목을 아이스 아메리카노 한 잔 마시면서 천천히 읽어보면 된다.

보통 3~4 페이지밖에 안해서 금방 읽는다.

 

 


마지막으로, 내가 공부한 예제 코드를 여기에 남겨둔다.

// 이 코드는 Atemga128 데이터시트에 있는 예제 코드에 기반하여 작성되었습니다.
// 회로 설정 : Atemga128A, USART0 채널

#define F_CPU 16000000 // Clock Speed
#define BAUD 9600 // Baud Rate. 통신 속도.
#define MYUBRR F_CPU/16/BAUD-1 // UBRR 계산. Asynchronous Normal Mode(U2X = 0)

#include <avr/io.h>
#include <util/delay.h>

void USART_init(unsigned int ubrr)
{
	/* set baud rate */
	UBRR0H = (unsigned char) (ubrr>>8);
	UBRR0L = (unsigned char) ubrr;
	
	/* enable receiver and transmitter */
	UCSR0B = (1<<RXEN0)|(1<<TXEN0);
	
	/* Set frame format : 8 data, 2 stop bit */
	UCSR0C = (1<<USBS0)|(3<<UCSZ00);
}

/* Sending Frames with 5 to 8 data bit */
void USART_Transmit(unsigned char data)
{
	/* Wait for empty transmit buffer */
	// UCSR0A 레지스터 안에 있는 UDRE0 비트는, 버퍼가 비어있는지를 확인한다. 비어있으면 1, 안비어있으면 0.
	while (!(UCSR0A & (1<<UDRE0)));
		
	/* Put data into buffer, sends the data */
	// Block diagram 를 보면, UBR 데이터 레지스터에 값을 넣기만 하면 자동으로 ATmega에 전송해주는걸 알 수 있다.
	UDR0 = data; // UDR0에 데이터를 넣으면 Transmit
}

/* Receiving Frames with 5 to 8 Data Bits */
unsigned char USART_Receive(void)
{
	/* Wait for data to be received */
	// UCSR0A 레지스터 안에 있는 RXC0 비트는, Receive 가 완료되었는지를 확인한다. 완료됬으면 1, 안됬으면 0.
	while (!(UCSR0A & (1<<RXC0)));
		
	/* Get and return received data from buffer */
	// Block diagram 를 보면, UBR 데이터 레지스터에 값이 들어오면 자동으로 ATmega에 전송해주는걸 알 수 있다.
	return UDR0; // UDR0의 데이터를 반환하면 Receive
}

/* Sending String type data */
void USART_Transmit_String(unsigned char *data, unsigned short length, char lineBreak)
{	
	unsigned short i;
	for(i=0; i<length; i++)
	{
		while (!(UCSR0A & (1<<UDRE0))); // Wait for empty transmit buffer
		UDR0 = data[i];
	}
	while (!(UCSR0A & (1<<UDRE0))); // Wait for empty transmit buffer
	if(lineBreak == 1) UDR0 = 13; // if true, Press Enter Key.
}

/* Receiving String type data */
/* Divided by Enter key on terminal. */
/* To use, you should send Array address of index 0. */
unsigned short USART_Receive_String(unsigned char *arr)
{
	unsigned short i = 0;
	
	for(i=0; i<65535; i++)
	{
		arr[i] = USART_Receive();
		if(arr[i] == 13) // Enter 라면
		{
			arr[i] = '\0'; // Enter 대신에 공백을 넣고
			return i; // length 를 출력한다.
		}
	}
}

int main(void)
{
	USART_init(MYUBRR);
	unsigned char a[100];
	unsigned short length;

    while (1) 
    {
		length = USART_Receive_String(&a[0]);
		USART_Transmit_String(&a[0], length, 1);
    }
}