티스토리 뷰

마이크로컨트롤러

[시리얼 통신] UART

김마룽지 2021. 8. 2. 12:32

 

 

 

UART에 대해서 알아보도록 하겠습니다.

 

 

 

UART를 알아보기에 앞서 '시리얼 통신'이란 무엇인가 알아보자면?

 

 

시리얼(Serial) 통신 : '직렬'통신을 의미하며, 한 개의 신호선을 이용하여 데이터 송수신을 진행한다.

                              적은 선을 이용하기 때문에 비용이 저렴하다는 장점이 있다.

+) 병렬(Parallel) 통신

더보기

병렬(Parallel) 통신 : 여러 개의 신호 선을 이용하여 데이터 송수신을 진행한다.

                            많은 선을 이용하기 때문에 비용이 비싸다는 단점이 있다.

 

 

 

 


 

UART 란?

 

출처 : https://www.amebaiot.com/en/uart/

병렬 데이터를 직렬 방식으로 전환하여 데이터를 전송하는 방법입니다.

 

UART는 비동기식 전이중 방식이기 때문에 '클락 신호'를 사용하지 않습니다.

따라서 이 통신을 이용하기 위해서는 데이터를 보내는 속도와 수신하는 속도를 '보율(baudrate)'로 정해주어야 합니다.

 

 

+) '보율(baudrate)' 을 알아보자!

더보기

보율(baudrate)

: 1초당 전달하는 데이터의 개수를 나타내는 단위

 초반에는 bps와 같았지만 최근 통신 기술의 발달로 bps가 보율보다 크거나 같은 값을 가지게 되었다.

 

+) Bit Rate(= bps)

 : 특정 시간(ex. 1초)마다 처리하는 비트 수

 

Baud Rate vs BPS

8bit 데이터를 1초당 200개를 보내는 경우

Baud Rate : 200

BPS : 8bit * 200 = 1600

출처 : http://melonicedlatte.com/2020/04/28/204700.html

 

+)

보율 = 4 (1초에 4번 변화)

bps = 8bit (한번 변할 때 2bit씩 전달 되므로)


 

 

그리고 UART를 이용하기위해서는 송신되는 데이터의 시작비트(Start Bit)와 끝(Stop Bit)을 알아야합니다.

(데이터를 보내지 않고있는 송신측은 항상 '1'의 상태에 있고 시작하면 비트가 변화하기 때문에 시작비트는 항상 '0', 종료 비트는 항상 '1'임을 알 수 있다.)

 

 

UART에서 데이터 송신 측 핀은 TX(또는 TXD)라고 하며, 데이터 수신 측 핀은 RX (또는 RXD)라고 표시하는데, 두 개의 장치를 연결하기 위해서는 RX와 TX를 교차해서 연결해 주어야 합니다.

 


 

 

UART 시리얼 통신 레지스터

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

void UART0_init(void);
void UART0_write(uint8_t data);
uint8_t UART0_read(void);

void UART0_init(void) {
	UBRR0H = 0x00;
	UBRR0L = 207;
	
	UCSR0A |= _BV(U2X0); //_BV : 지정한 위치의 비트만 1의 값을 가지도록
	UCSR0C |= 0x06;
	
	UCSR0B |= _BV(RXEN0);
	UCSR0B |= _BV(TXEN0);
}

void UART0_write(uint8_t data){
	while( !(UCSR0A & (1<< UDRE0)) );
	UDR0 = data;
}

uint8_t UART0_read(void){
	while( !(UCSR0A & (1<< RXC0)) );
	return UDR0;
}

int main(void)
{	UART0_init();
	
    /* Replace with your application code */
    while (1) 
    {
		UART0_write(UART0_read());
    }
	
	return 0;
}

위 코드는 참고 자료 속에 존재하는 

'UART 시리얼 통신을 통해서 컴퓨터가 보내는 데이터를 다시 컴퓨터로 돌려보내는 동작' 을 수행하는 예시입니다.

다음 코드를 보게되면 UART에 여러개의 레지스터가 사용되고 있음을 확인할 수 있습니다. 

 

 

 

이제 여기서 사용하고 있는 레지스터들에 대해서 알아보도록 하겠습니다 (❛ ᗜ❛)ฅ !

 

 

 

UBRRnH , UBRRnL ( n = 0 , 1, 2, 3)

UBRR0H = 0x00;
UBRR0L = 207;

UBRRn 레지스터는 통신 속도를 설정하기 위해서 사용되는 레지스터입니다.

8비트 레지스터 2개를 조합한 가상의 16비트 레지스터로 구성됩니다.

상위 4비트는 UBRRnH 레지스터에(UBRRnH 상위 4비트는 사용하지 않음), 하위 8비트는 UBRRnL 레지스터로 구성되며, 보율은 총 12비트로 표현됩니다.

 

 

 

 

UCSRnA (n = 0, 1, 2, 3)

UCSR0A |= _BV(U2X0);

출처 : https://hoban123.tistory.com/42

 

UCSRnA 는 송수신의 동작을 제어하거나 상태를 확인 및 저장하는 레지스터입니다.

해당 코드에서는 2배속 모드 설정과 송수신 가능 여부 판단을 위해서 사용되었습니다.

 

이처럼 각 비트마다 다른 의미를 가집니다.

7번 비트 RXCn 수신 버퍼의 상태 플래그
수신 버퍼에 읽지 않은 데이터가 있다면 '1'이 되고, 비어있는 상태라면 '0' 이 된다.
6번 비트 TXCn 송신 버퍼의 상태 플래그
송신 시프트 레지스터에 데이터가 모두 송신되고 새로운 데이터가 저장되지 않은 상태라면 '1'
5번 비트 UDREn 송신 데이터를 받기 위한 상태 플래그
송신 버퍼가 비어있어 데이터를 받을 준비가 되어 있다면 '1'
4번 비트 FEn 수신 프레임 에러 상태 플래그
프레임 끝 신호를 수신하지 못하여 프레임 수신에 오류가 발생하면 '1'을 갖게 된다.
수신 데이터가 없는 경우에는 수신 값이 'HIGH' 상태지만, 데이터 프레임 수신이 시작되는 시점에는 'LOW' 상태로 바뀌고, 수신이 끝나고 정지 될 때 수신 값은 다시 'HIGH' 상태로 변한다.
3번 비트 DORn 수신 동작 오버런 에러 상태 플래그
수신 버퍼가 가득 찬 상태에서 새로운 문자가 수신 완료되고, 다시 그 다음 문자의 시작 비트가 검출되는 오버런 상황이 발생했을 때, '1'이 된다.
2번 비트 UPEn 수신 버퍼에 저장된 문자 데이터에 '패리티 오류'가 발생하면 '1' 이 된다.
1번 비트 U2Xn 비동기 전송 모드에서만 사용, 2배속 모드면 '1', 1배속 모드면 '0'
0번 비트 MPCMn 멀티 프로세서 통신 모드로 설정 시 '1'

출처 : http://artoa.hanbat.ac.kr/lecture_data/microprocessor/2014/%EC%A0%9C8%EC%9E%A5%20UART%20(HBE-MCU-Multi%20AVR).pdf 

 

 

 

UCSRnB (n = 0, 1, 2, 3)

UCSR0B |= _BV(RXEN0); //수신 가능
UCSR0B |= _BV(TXEN0); //송신 가능

출처 : https://hoban123.tistory.com/42

UCSRnB 레지스터는 UART 제어 및 상태 레지스터로, 데이터 송수신 가능 여부 설정에 사용됩니다.

 

UCSRnB 역시 UCSRnA와 같이 각 비트 별로 기능이 존재합니다.

7번 비트 RXCIEn 수신 완료 인터럽트 발생 허용
6번 비트 TXCIEn 송신 완료 인터럽트 발생 허용
5번 비트 UDRIEn 송신 데이터 레지스터 준비 완료 인터럽트 발생 허용
4번 비트 RXENn UART 수신기의 수신 기능을 활성화
3번 비트 TXENn UART 송신기의 송신 기능을 활성화
2번 비트 UCSZn2 UCSRnC 레지스터와 함께 전송 문자의 데이터 비트 수 결정
1번 비트 RXB8n 수신 문자가 9비트인 경우에 수신된 9번째 비트를 저장
0번 비트 TXB8n 송신 문자가 9비트인 경우에 송신된 9번째 비트를 저장

출처 : http://artoa.hanbat.ac.kr/lecture_data/microprocessor/2014/%EC%A0%9C8%EC%9E%A5%20UART%20(HBE-MCU-Multi%20AVR).pdf

 

 

 

 

UCSRnC (n = 0, 1, 2, 3)

UCSR0C |= 0x06;

출처 : https://hoban123.tistory.com/42

UCSRnC 레지스터는 UART 통신 도중 사용되는 데이터 형식 및 통신 방법에 의해 결정됩니다.

 

비트들 중에서 6번 비트와 7번 비트를 이용해서 통신 모드를 지정해 줍니다. 

7번 비트  6번 비트 모드
0 0 비동기 모드
0 1 동기 모드
1 0 -
1 1 마스터 SPI

+) 마스터 SPI : SPI 통신의 마스터 모드와 호환되는 모드, UART 를 통해서 SPI 통신 수행 가능

 

 

4번 비트와 5번 비트를 이용해서는 패리티 모드를 설정할 수 있습니다.

5번 비트  4번 비트 패리티 비트
0 0 없음
0 1 -
1 0 짝수 패리티
1 1 홀수 패리티

+) 패리티 비트란?

더보기

패리티 비트(Parity bit) : 정보의 전달 과정에서 오류가 생겼는지를 검사하기 위해 추가된 비트

- 짝수 패리티 : 전체 비트에서 1의 개수가 짝수가 되도록 ( 1의 개수가 홀수면 패리티 비트는 '1')

- 홀수 패리티 : 전체 비트에서 1의 개수가 홀수가 되도록 ( 1의 개수가 짝수면 패리티 비트는 '0')

 

출처 : https://ko.wikipedia.org/wiki/%ED%8C%A8%EB%A6%AC%ED%8B%B0_%EB%B9%84%ED%8A%B8

 

3번 비트는 정지 비트 수를 정하는데 사용되며, '0' 이면 정지 비트는 '1비트'가 되고, '1' 이면 정지 비트는 '2비트'가 됩니다.

 

마지막으로 1번 비트와 2번 비트를 이용해서는 데이터 비트의 수를 결정할 수 있으며, 0번 비트는 클록의 극성을 지정합니다. (0번 비트는 동기 모드에서만 사용됩니다.)

 

 

 

UDRn ( n =0, 1, 2, 3)

UDR0 = data;

UDRn 은 송수신된 데이터가 저장되는 버퍼 레지스터로, 전송을 위한 버퍼인 TXB 와 수신을 위한 버퍼인 RXB, 총 2가지의 레지스터로 구성되어 있습니다.

두 레지스터는 같은 입출력 주소를 사용하기 때문에, 레지스터에 데이터를 쓰게 되면 송신 데이터 버퍼인 TXB에 저장되고, 수신된 데이터를 읽으면 수신 데이터 버퍼인 RXB에 데이터가 읽히게 됩니다.

 


UART 통신을 이용해서 문자열과 정수 출력하기

 

UART 통신 - 문자열 출력

void UART0_print(char *str) { 		// 문자열 송신
	while(*str){			// '\0' 문자를 만날 때 까지 계속 반복
		UART0_write(*str++);	// 바이트 단위 송신
	}
}

 

UART 통신 - 정수 출력

void UART0_print(int num, int radix = 10) {
	char buffer[sizeof(int) * 8 + 1 ];
	itoa(num, buffer, radix);	// 문자열 변환 함수
	UART0_print(buffer);		// 변환된 문자열 송신
}

해당 코드는 정수형 int 값을 '문자열'로 변환하고, 10 진수를 기본값으로 지정하였다.

 

+) itoa : int 형(or long 형) 값을 문자 배열로 전환

 

 

가변길이 문자열 수신

#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include "UART0.h"

#define TERMINATOR '\n'

int main(void){
	uint8_t counter = 100;
	int index = 0;				//수신 버퍼에 저장할 위치
	int process_data = 0;		//문자열 처리 알림
	char buffer[20] ="";		//수신 데이터 버퍼
	char data;					//수신 데이터
	
	UART0_init();				//UART 시리얼 통신 초기화
	
	UART0_print("Current Counter Value : ");
	UART0_print(counter);
	UART0_write('\n');
	
	while(1){
		data = UART0_read();		//데이터 수신
		if (data == TERMINATOR){	//종료 문자 수신한 경우
			buffer[index] = '\0';	
			process_data = 1;		//수신 문자열 처리
		}
		else {
			buffer[index] = data;	//수신 버퍼에 저장
			index++;
		}
		
		if(process_data == 1) {		//문자열 처리
			if (strcmp(buffer, "DOWN") == 0) {	//카운터 감소
				counter--;
				UART0_print(buffer);
				UART0_write('\n');
				UART0_print("Current Counter Value : ");
				UART0_print(counter);
				UART0_write('\n');
			}
			else if (strcmp(buffer, "UP") == 0 ){ //카운터 증가
				counter++;
				UART0_print(buffer);
				UART0_write('\n');
				UART0_print("Current Counter Value : ");
				UART0_print(counter);
				UART0_write('\n');
			}
			else{					//잘못된 명령어
				UART0_print(buffer);
				UART0_write('\n');
				UART0_print("** Unknown Command ** \n");
			}
			
			index = 0 ;
			process_data = 0;
		}
	}
	return 0;
}

참고 자료 속 존재하는 코드입니다.

해당 코드를 통해서 길이가 변하는 문자열을 수신하는 동작을 할 수 있습니다.

 

+) 'UART0.h' 는?

더보기
#ifndef _UART0_LIBRARY_
#define _UART0_LIBRARY_

#include <avr/io.h>
#include <stdlib.h>

void UART0_init();				//UART 통신 초기화
uint8_t UART0_read();				//1바이트 데이터 수신
void UART0_write(uint8_t data);			//1바이트 데이터 송신
void UART0_print(char *str);			//문자열 송신
void UART0_print(int no, int radix = 10);	//int 정수를 문자열로 변환 후 송신
void UART0_print(long no, int radix = 10);	//long 정수를 문자열로 변환 후 송신

#endif

해당 헤더파일에는 함수의 선언이 존재하며, UART0 포트를 이용해서 데이터를 주고 받는데 사용되는 코드에 포함시켜야 합니다.

 

해당 헤더 파일 속 전처리 지시자(#define, #ifndef, #endif) 문장은 헤더 파일이 두 번 이상 포함되지 않도록 사용되었습니다.

 

 

 

 

 

 

참고 자료 : 따라하면서 배우는 마이크로컨트롤러(ATmega2560으로 프로그래밍하기), 허경용 저, 한빛아카데미, 2019

댓글
공지사항