본문 바로가기
대한상공회의소 스마트팩토리 교육/IoT 디바이스 개발

[IoT 디바이스 개발] AVR(ATmega128A)«수업-10» : UART 시리얼 통신

by 나는영하 2022. 2. 13.

※ 주의사항 

본 블로그는 수업 내용을 바탕으로 제가 이해한 부분을 정리한 블로그입니다.
본 내용을 참고로만 보시고, 틀린 부분이 있다면 지적 부탁드립니다!

감사합니다😁

 

안녕하세요!!

오늘은 아래와 같은 내용을 확인해보겠습니다.

 

UART 통신 이란?

UART 관련 레지스터

UART로 문자열 송수신


1. UART 통신 이란?

1-1. 병렬 전송과 직렬 전송

1) 병렬 전송 : 8개의 핀을 통해 1번에 1바이트(8비트) 데이터 전송
- 연결이 복잡해진다.

- 핀수가 제한된 마이크로컨트롤러에서는 핀 부족 현상이 발생할 수 있다.

 

2) 직렬 전송 : 1개의 핀을 통해 8번에 나누어 1바이트 데이터 전송

- UART 통신은 시리얼(직렬) 통신의 한 종류이다.

- 그외 I2C, SPI 통신 등이 있다.

 

1-2. UART 통신 이란?

UART 블록 다이어그램

1) UART = 비동기 통신 방식

- 데이터를 위한 별도의 클록을 사용하지 않는다.

- 약속된 속도로 송수신을 수행한다.

- 통신 속도의 단위로 보율(Baud Rate)를 사용한다.

※ Baud Rate와 bps는 최근들어 다른 개념을 가지고 있지만 지금은 생략하겠습니다.

 

2) 데이터 전송 규칙

- 동일한 속도로 데이터를 주고 받는다고 해도 항상 데이터를 주고 받는것이 아니기때문에 데이터의 시작과 끝이 언제인지를 알아낼수가 있어야 한다.

- 그래서 데이터 비트의 시작과 끝에 시작비트 '0', 정지비트 '1'을 추가하여 총 10비트의 데이터를 전송한다.

※ 추가적으로 데이터의 오류를 찾아내기 위한 '패리티비트'를 데이터 비트 뒤에 사용하는 경우도 있는데 이는 선택적인 부분이라서 생략하겠습니다..😄

 

- UART 통신은 전이중 방식 통신으로 송신과 수신을 동시에 진행할 수 있으며 이를 위해 2개의 핀을 필요로 한다.

- ATmega128A 에서는 UART0은 PE0, PE1핀을 UART1은 PD2와 PD3핀을 사용한다.

ATmega128A PIN Configurations

 


2. UART 관련 레지스터

※ ATmega128A는 UART 포트가 2개 존재하기 때문에 아래의 레지스터 또는 비트 이름에 적혀있는 n(또는 m)은 0이나 1에 해당 된다.

- UART 관련 레지스터는 총 약 6개의 레지스터가 존재하는데 각 레지스터에서 사용도가 높은 비트만 대략적으로 설명하고 넘어가겠습니다!!! 

 

2-1. UCSRnA(USART Control and Status Register n A) 레지스터

UART 시리얼 통신의 상태를 확인하고 통신 과정을 제어하기 위한 레지스터 중 하나이다.

UCSRnA 레지스터

 

1) U2Xn(1번 비트)

- 비동기 전송모드에서만 사용되며 해당 비트가 1이면 2배속 모드, 0이면 1배속 모드

※ UCSRnA 레지스터에서 U2Xn 비트만 1로 설정하기 위한 프로그래밍 방법은 아래의 4가지가 있다.
(1) UCSR1A = UCSR1A | (1 << 1);
(2) UCSR1A = UCSR1A | (1 << U2X1);
(3) UCSR1A |= (1 << U2X1);
(4) UCSR1A |= _BV(U2X1);
모두 똑같이 U2Xn 비트만 1로 세트한다는 코드이다. (취향에 맞게 사용하길..)

2) UDReN(5번 비트)

- 데이터를 저장하는 버퍼(UDRn)이 비어 데이터가 받을 준비가 되면 1, 버퍼 안에 데이터가 있으면 0

- UDRe 레지스터와 이름이 비슷해서 햇갈릴수 있지만 주의할 것!

UDRe 레지스터는 데이터를 저장하는 8비트 레지스터에 해당된다.(비트가 아니다!)

3) RXCn(7번 비트)

- 데이터 버퍼(UDRn)에 읽지 않은 문자가 있으면 1, 버퍼 안에 데이터가 없으면 0

※ 5번 비트와 초기값이 다르므로 주의해야 한다.

4) TXCn(6번 비트)

- 5번 비트와 비슷한 기능을 한다. 단, 디폴트 값이 0으로 별도의 설정 없이는 데이터 전송이 불가능하다.

- TXCn 비트는 전송 시프트 레지스터에서 데이터를 모두 전송하고 UDRn 레지스터에도 데이터가 없는경우 세트 된다.

- UDReN 비트는 UDRn 레지스터에 데이터가 없는 경우에 세트된다.

- 즉, TXCn 비트를 사용하면 더 번거롭고 시간이 오래걸린다. (지금은 이정도로만 기억하고 PASS 😥)

 

2-2. UCSRnB & UCSRnC 레지스터

1) USCRnB 레지스터

UCSRnB 레지스터는 데이터 송수신을 가능하도록 설정하기 위해 사용된다.

UCSRnB 레지스터

 (1) TXENm (3번 비트)

- UART 송신기의 송신 기능을 활성화 한다. (디폴트 값은 '0', UART 통신 금지 상태)

 (2) RXENm (4번 비트)

- UART 수신기의 수신 기능을 활성화 한다. (디폴트 값은 '0', UART 통신 금지 상태)

 (3) UCSZm2 (2번 비트)

- UCSRnC 레지스터의 1번, 2번 비트와 함께 전송할 데이터 비트 수를 결정한다.

(자세한 내용은 아래 UCSRnC 레지스터에서 설명하겠습니다.)

 

 

2) UCSRnC 레지스터

UCSRnC 레지스터

 (1) UCSZm0, UCSZm1 (1번, 2번 비트)

- UCSRnB 레지스터의 2번 비트와 함께 전송할 데이터 비트 수를 결정한다.

- 3개의 비트의 값에 따라 전송할 데이터 비트수는 다음 표와 같다.

UCSZ 비트의 값에 따른 전송비트 크기

 (2) USBSn (3번 비트)

- 정지 비트의 수를 지정하기 위해 사용된다. (0 : 1비트, 1 : 2비트)

 (3) UPMn0, UPMn1 (4번, 5번 비트)

- 비트 설정에 따라 패리티 비트 의 모드 설정이 가능하다.

(패리티 비트 없음, 홀수 패리티, 짝수 패리티)

 (4) UMSELn (6번 비트)

- 0 : 비동기 USART 설정, 1: 동기 USART 설정

2-3. UBBRnH & UBBRnL 레지스터

UBRRn 레지스터

- 2개의 레지스터중 16비트를 조합해서 Baud Rate를 설정하는 레지스터이다.

- 16Mhz, 비동기 전송 모드에서 보율에 따른 UBRRn 값은 아래의 표를 보면 된다.

BAUD UBRRn 계산값 UBRRn 사용값
1배속 2배속 1배속 2배속
2,400 415.67 832.33 416 832
4,800 207.33 415.67 207 416
9,600 103.17 207.33 103 207
14,400 68.44 137.89 68 138
19,200 51.08 103.17 51 103
28,800 33.72 68.44 34 68
38,400 25.04 51.08 25 51
57,600 16.36 33.72 16 34

2-4. UDRn 레지스터

UDRn 레지스터 구조

- 송수신된 데이터가 저장되는 버퍼 레지스터이다.

- 전송을 위한 TXB 레지스터와 데이터 수신을 위한 RXB 레지스터로 구성되어 있다.

- UDRn 레지스터에 데이터를 Write 하면 TXB 레지스터에 데이터가 기록되고,

UDRn 레지스터에 데이터를 Read 하면 RXBn 레지스터의 데이터가 읽혀진다.

2-5. UART 통신 기본 코드

UART 통신 기본 코드

1) 코드 설명

(1) UART0 통신 초기화 함수부(void UART0_init)

- Line 11 ~ 12 : Baud Rate를 9600으로 설정

- Line 13 : UART 통신을 2배속 모드로 설정 (UART 비동기 통신, 패리티 비트 없음, 정지비트 1비트)

- Line 14 : 데이터 비트 수는 8비트로 설정

- Line 15 ~ 16 : UART 송신기와 수신기의 기능 활성화

 

(2) UART0 데이터 송신 함수부(void UART0_transmit) 

- Line 21 : 데이터를 저장하는 송신버퍼(UDR0)가 비어 있으면 while 문을 탈출한다.

- Line 22 : 데이터를 저장하는 버퍼(UDR0)에 데이터가 수신되면 데이터를 전송한다.

 

(3) UART0 데이터 수신 함수부(void UART0_receive)

- Line 28 : 데이터를 저장하는 수신 버퍼(UDR0)에 읽지 않은 데이터가 있으면 while 문을 탈출한다.

- Line 29 : 데이터를 저장하는 수신 버퍼(UDR0)에 읽지 않은 데이터가 없으면 UDR0 값을 리턴한다.

 

2) 동작 설명

- AVR에 빌드 및 프로그래밍 후 PuTTY를 통해 컴퓨터와 UART 통신한 결과,

받은 데이터(내가 입력한 데이터)를 UART를 통해 다시 내보내는 것을 알 수 있다.


3. UART로 문자열 송수신

3-1. UART로 문자열 출력

UART로 문자열 출력하기 위한 함수부(좌) / 아스키코드(우)

1) 문자열을 출력하는 함수 (UART0_print_string)

- 함수를 호출하는 부분에서 사용하는 인자, 즉 문자열을 매개변수로 받아서 NULL값이 나올때까지 한글자씩 출력하는 함수이다.

- Line 34 : for문 안에 조건식에 문자열(str[i])이 들어간다면 NULL값이 나올때까지 i를 반복한다 라고 생각하면 된다.

(평소에 정수형태로만 사용하던 for문하고는 다른형태라 햇갈리지만, 그냥 단순히 암기하면 될 것 같습니다..ㅠ🤣)

 

2) 1바이트(256) 크기의 정수 출력 함수 (UART0_print_1_byte_number)

- Line 38 : 매개변수로 사용된 uint8_t n 은 unsigned char의 형태를 가지는 n이란 변수라 생각하면 된다.

- Line 46 : 1바이트 크기의 정수를 1의자리, 10자리, 100의 자리로 나누기 위한 문장으로써 연산에 대한 결과값은 아스키코드 값으로 배열에 저장된다. 

'0'을 더하는 이유는 각 자릿수에 해당하는 숫자를 정수가 아닌 문자로 저장하기 위해 아스키코드 값을 구하기 위함이다. 따라서 문자 1~9에 해당하는 아스키 값을 구하기 위해서는 n + '0' 즉 n + 48을 해주면 해당하는 숫자의 아스키코드 값을 구할 수 있다. ( ex. '2'의 아스키코드 값은 2 + 48 = 50, 즉 십진수로 50에 해당 된다.)

 

3-2. UART로 문자열 수신

3-1에서 문자열이나 정수를 UART 통신을 통해 송신을 했다면, 반대로 컴퓨터(Putty를 통해)에서 UART를 통해 ATmega128A로 문자열을 수신할 수도 있다.

문자열 수신

1) 새로운 함수 사용 : strcmp(char a, char b)

- 헤더파일 <stirng.h>

- 매개변수로 들어온 두개의 문자열(a, b)를 비교하여 문자열이 완전히 같다면 '0'을 반환한다.

문자열이 다르다면 그 문자의 아스키 코드 값을 비교해서 음수 또는 양수를 반환한다.

 

2) 코드 설명

- Line 58 : UART 통신으로 통해 들어온 데이터 값(PUTTY를 통해 사용자가 입력한 값)을 data 변수에 저장합니다.

- Line 59 ~ 61 : 입력한 데이터 값에서 줄바꿈(\r)이 수행되면 문자열을 저장하는 변수에 NULL값을 저장한다.

- Line 64 ~ 65 : 줄바꿈이 입력 안되면 데이터를 계속 문자열 변수에 저장한다. 

- Line 67 : 데이터 입력이 완료(줄바꿈 입력)되면 if문을 수행한다.

- Line 68 ~ 73 : 입력한 데이터 값이 "DOWN" 인지 strcmp 함수를 통해 비교하고 맞으면 카운터 변수값을 1개 감소시키고 출력한다.

- Line 74 ~ 79 : 입력한 데이터 값이 "UP" 인지 strcmp 함수를 통해 비교하고 맞으면 카운터 변수값을 1개 증가시키고 출력한다.

※ 주의사항

(1) strcmp 함수는 대소문자를 구별한다. (strcasecmp 함수는 구별하지 않는다.)

(2) PuTTY에서 엔터키가 눌러진 경우 문자열 끝에 \r을 추가하므로 문자열의 끝을 나타내는 종료 문자로 정의하여 사용함.

3-3. printf & scanf 함수 사용

3-1, 3-2를 통해 문자열의 송신과 수신 방법에 대해 배웠는데, 이는 옛날 방법이고 잘 사용하지 않는 방법이다.

더 쉽고 간단한 방법으론 printf, scanf 함수를 사용해서 문자열을 송수신 할 수 있다.

하지만, printf와 scanf 함수를 사용하면 실행파일의 크기가 커지므로 감안해서 사용하여야 한다.

 

아래 코드는 위의 3-2 코드와 동일하게 동작한다.

(단, 3-2 코드는 대소문자를 구별해서 사용해야 한다.)

1) 코드 설명 : 

- Line 100 : printf, scanf 함수를 사용하기 위한 헤더파일(stdio.h, STanDard Input Output)

- Line 102 ~ 105 : 표준 스트림 할당 하는 함수부인데 초보자는 이해하기 어려운 개념이라 생각됩니다..ㅠ

(그냥 printf, scanf 를 사용하기 위한 사전 함수부로 단순히 암기하고 넘어가는게 편할 것 같습니다..🤣)

- Line 112 ~ 113 : 표준 입출력장치와 위에서 정의한 입출렬 객체들을 연결하기 위한 함수부..
(이부분도 동일하게 단순히 암기하고 넘어갑시다..ㅠ)

- Line 123 : 3-2에서 사용한 코드에서 Line 56 ~ 67 까지를 단 한줄로 코딩한 부분.

scanf 함수를 통해 PuTTY에서 사용자가 입력한 데이터를 buffer에 저장한다.

- Line 125 : 사용자가 입력한 문자가 "DOWN" 이면 if문을 수행한다.

(starcasecmp는 starcmp와 다르게 대문자와 소문자를 구별하지 않는다.)

 

 

댓글