이번에는 아두이노에서 사용되는 칩셋 Atmega328P와 PC간 시리얼 통신하는법을 배워보자.
원칙적으로 UART통신을 하려면 별개의 단자를 이용해야 하지만 아두이노의 USB b타입 단자를 연결하면 업로드와 UART가 동시에 된다.
(왜 이렇게 되는지 원리를 아시는분은 댓글로 설명좀...)
UART 란?
UART란 다양한 직렬(serial)통신방식중 하나이다. 대표적으로 시리얼통신방식으로 UART, SPI, I2C등이 있지만 이번에는 UART를 이용해서 putty로 통신하는것을 확인해보려고 한다.
직렬통신방식이 있으니 당연히 병렬(parallel)통신 방식도 있다.
직렬, 병렬통신은 각각의 장단점이 있다.
병렬통신
병렬통신을 이용하면 간단한 프로젝트를 할 때는 쉽고 직관적으로 코드를 작성 할 수 있으며 1개의 비트를 1개의 핀으로 사용하기 때문에 속도도 빠른 편이다. 그러나 사용하려는 핀의 개수가 많아지면 연결이 상당히 복잡해지며 나중에 회로를 알아볼 수 없을 정도로 난잡해 진다.
시리얼통신
시리얼통신은 하나의 핀으로 8개의 비트를 8번에 나누어 관리를 한다. 따라서 속도는 병렬보다 느리지만 더욱 안정적이며 오늘날 임베디드 mcu를 보면 사실상 직렬, 병렬 속도간의 차이가 거의 없다.
다시 UART로 넘어와서 시리얼 통신을 하기 위해서는 4개의 핀(VCC, GND, TX, RX)이 필요하다.
이때, 주의해야 할 점은 데이터 수신부과 송신부끼리 TX, RX는 교차해서 연결해야 한다.
참고로 TX는 Transmitter(송신부), RX는 Receiver(수신부)라는 뜻을 의미한다.
위와같이 연결하는 방법은 USB/UART변환기를 통해 PC와 USB로 연결하면 된다.
일반적으로 프로그램 업로드용 ISP연결, 통신확인을 위한 UART선을 따로 연결해야 하지만 무슨이유에서인지 아두이노의 USB b타입 단자와 연결하면 업로드와 시리얼통신동시에 가능하네..?
마침 USB/UART변환기가 없어서 다행이지만 이게 어떻게 가능한건지 아는분은 댓글로 알려주세요!
통신을 위한 프로그램 설정
코드 작성
#define F_CPU 16000000UL
#define _BV(bit) (1<<bit)
#include <stdio.h>
#include <util/delay.h>
#include <avr/io.h>
void UART_0_init(void);
void UART1_transmit(char data);
unsigned char UART1_receive(void);
void UART_0_init(void){
UBRR0H = 0x00; // 9,600보율로 설정
UBRR0L = 207;
UCSR0A = UCSR0A | (1<<1); // 2배속 모드(= _BV(U2X1))
UCSR0C |= 0x06;
UCSR0B |= _BV(RXEN0); // 송수신 가능
UCSR0B |= _BV(TXEN0);
}
void UART1_transmit(char data){
while(!(UCSR0A & (1 << UDRE0))); // 송신 가능 대기
UDR0 = data; //데이터 전송
}
unsigned char UART1_receive(void){
while(!(UCSR0A & (1<<RXC0)));
return UDR0;
}
int main(void){
UART_0_init(); // UART0 초기화
while(1){
UART1_transmit(UART1_receive());
}
return 0;
}
위의 코드를 보면 _BV라는 코드가 나오는데 이는 2배속 모드로 설정해주기 위한 코드이다.
Atmega328P는 정확히 USART통신을 지원하며 이는 비동기 모드로 별도의 클록을 사용하지 않고 시작, 정지비트를 통해서 데이터 동기화가 이루어 진다. 따라서 일반적인 1배속 속도보다 정확한 타이밍과 전송속도를 얻기 위해 2배속 모드로 설정해 준다.
또, 코드를 작성해주기 위해 해당 칩셋의 데이터시트에 나와있는 레지스터 설명을 보고 숙지를 해야한다.
현재 보고있는 책에서는 Atmega128을 기준으로 작성해서 레지스터번호가 다르며 사용하는 명령어도 조금씩 다르다.
따라서 해당 칩셋의 데이터시트를 참고하면서 코드를 수정할 수 있을만큼 개념을 알고있어야 한다.
PUTTY설정
시리얼 통신이기 때문에 Serial 클릭 후 연결된 포트번호 입력하기.(COM3)
포트번호는 내pc의 장치관리자에서 확인 할 수 있다. 포트번호는 사용자마다 다르니 반드시 확인할 것.
다시 putty로 넘어와서
Terminal - Implicit LF in every CR 체크, Local echo, Local line editing 둘다 Force on으로 체크해준다.
Implicit LF in every CR : 터미널에서 줄바꿈 가능하도록 함.
Local echo : 키보드로 입력한 화면이 터미널에 출력됨.
Local line editing : 디폴트로 바이트 단위로 입력되지만 해당부분을 체크하면 엔터를 누르기 전까지 입력됨.
Connection - Serial - 포트번호 설정(COM3), Speed 9600으로 설정
이렇게 설정하고 Open을 눌러준다.
키보드로 문장을 입력하면 다음과같이 메아리(하울링)가 울리듯 입력한 키와 똑같이 출력하는 것을 알 수 있다.
헤더파일 나누기
앞으로 시리얼통신을 많이 이용할텐데 매번 프로젝트를 만들때마다 모든 함수를 다 작성기엔 무리가 있다.
따라서 자주 사용하는 함수는 헤더파일을 만들어서 필요할때마다 불러올 수 있도록 하자.
보통 비주얼스튜디오로 헤더파일 하나만 만들어서 관리했지만 Microchip Studio에서는 .c파일와 .h파일 두개를 만들어서 관리를 한다.
.c 파일 : 메인함수를 제외한 .c파일은 정의부라고 생각하면 된다. 함수의 주된 내용이 담겨있기 때문이다.
.h 파일 : 선언부의 모음
파일을 나누는 방법은 메인 프로젝트 디렉토리 우클릭 - Add - New Item을 클릭하면 다음과 같은 화면이 나온다.
C file을 누르면 .c파일이 생성되고 Include File을 누르면 헤더파일이 생성된다.
이제 두 파일을 생성했으면 파일에 맞게 코드를 작성해 준다.
이번에는 printf와 scanf를 통한 가변길이 문자열 송수신하는 코드를 작성해 본다.
파일 이름은 각각 UART1.h 와 UART1.c로 설정했다.
UART1.h
#ifndef UART1_H_
#define UART1_H_
void UART_1_init(void); //초기화
void UART1_transmit(char data); //송신
unsigned char UART1_receive(void); //수신
void UART1_print_string(char* str); //문자열 출력
void UART1_print_1_byte_number(uint8_t n); //1바이트 크기 정수 출력
#endif
UART1.c
#define _BV(bit) (1<<bit)
#include <avr/io.h>
void UART_1_init(void){
UBRR0H = 0x00; // 9,600보율로 설정
UBRR0L = 207;
UCSR0A = UCSR0A | (1<<1); // 2배속 모드(= _BV(U2X1))
UCSR0C |= 0x06;
UCSR0B |= _BV(RXEN0); // 송수신 가능
UCSR0B |= _BV(TXEN0);
}
void UART1_transmit(char data){
while(!(UCSR0A & (1 << UDRE0))); // 송신 가능 대기
UDR0 = data; //데이터 전송
}
unsigned char UART1_receive(void){
while(!(UCSR0A & (1<<RXC0))); //데이터 수신 대기
return UDR0;
}
void UART1_print_string(char* str){
for(int i=0;str[i];i++){ //'\0'문자를 만날 때까지 반복
UART1_transmit(str[i]); //바이트 단위 출력
}
}
void UART1_print_1_byte_number(uint8_t n){
char numString[4] = "0";
int i, index = 0;
if(n>0){ //문자열 변환
for(i=0;n!=0;i++){
numString[i]=n%10 + '0';
n=n/10;
}
numString[i]='\0';
index = i-1;
}
for(i=index;i>=0;i--){ //변환된 문자열을 역순으로 출력
UART1_transmit(numString[i]);
}
}
main.c
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include "UART1.h"
FILE OUTPUT = FDEV_SETUP_STREAM(UART1_transmit, NULL, _FDEV_SETUP_WRITE);
FILE INPUT = FDEV_SETUP_STREAM(NULL, UART1_receive, _FDEV_SETUP_READ);
int main(void){
uint8_t counter = 100; //초기값(카운터)
char buffer[20] = ""; //수신데이터 버퍼
stdout = &OUTPUT;
stdin = &INPUT;
UART_1_init(); // UART0 초기화
printf("Current Counter Value : ");
printf("%d\r\n",counter);
while(1){
scanf("%s",buffer); //문자열 수신
if(strcmp(buffer,"DOWN")==0){ //카운터 감소
counter--;
printf("Current Counter Value : ");
printf("%d\r\n",counter);
}
else if(strcmp(buffer,"UP")==0){ //카운터 증가
counter++;
printf("Current Counter Value : ");
printf("%d\r\n",counter);
}
else{ //잘못된 명령어
printf("**Unknown Command**\r\n");
}
}
return 0;
}
위와같이 코드를 업로드 하고 putty를 실행시키면 터미널에서 UP/DOWN을 작성함에 따라 카운터가 변화되는것을 확인 할 수 있다.
참고자료
Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
[ _BV(bit) ] 의 이해 : 네이버 블로그 (naver.com)
ATMEGA128로 배우는 마이크로 : 네이버 쇼핑 (naver.com)
'AVR(Microchip Studio)' 카테고리의 다른 글
[AVR] Atmega328P 외부 인터럽트 (0) | 2022.01.12 |
---|---|
[AVR] Atmega8 테스트보드 만들기(납땜 & 테스트장비) (0) | 2022.01.08 |
[AVR] 아두이노를 이용한 AVR코딩 & 버튼 채터링(Microchip Studio) (0) | 2021.12.25 |
[AVR] Atmel Studio7 레지스터 제어, Simulator 디버깅 (0) | 2021.12.23 |
[µ-Processor] 내부구조(ALU) (1) | 2021.07.20 |