[아두이노 기초] Serial 통신
1. 기초이론
개발 경험상 아두이노를 포함해서 임베디드를 개발하는 데 있어 통신 서비스는 매우 중요합니다. 우선 무얼 개발할 지부터 개발 완성에 다다를 때까지 디버깅을 하는데 큰 도움을 주는 친구이기 때문입니다. 물론 개발하는 데 있어 금전적인 여유가 있어 Trace32와 같은 디버깅 툴이 있다면, 필요가 없을 수도 있습니다.
저도 현업 와서 처음 써본 거지만 해당 툴 없이 개발하라고 하면 역체감이 심할거 같네요.
아무튼 본론으로 돌아와서 통신은 임베디드를 설계할때 거의 항상 들어가게 됩니다. 통신에는 여러 종류가 있는데 해당 글에서 다루고자 하는 Serial 통신부터 해서 SPI, I2C, CAN, Ethernet 등 통신하면 수도 없이 많습니다. 그중에서 가장 기초인 Serial 통신에 대해 배워봅시다. 기본적으로 통신을 하려면 수신부와 송신부로 나뉩니다. 무선통신이 아니라면 수신부와 송신부를 잇는 케이블도 필요할 겁니다. Serial 통신의 경우에는 딱 2가닥이 필요합니다. 하나는 송신하는 선, 하나는 수신하는 선입니다.
회로 자체가 직관적이다 보니 개념 또한 간단합니다. Tx에서 나간 신호는 반대편 Rx로 들어가며 수신 측에서는 해당 신호를 받아 해석하고 필요 데이터를 올려주게 되죠. 이런 식으로 서로 통신을 하게 됩니다.
신호 자체를 들여다보면, 시작 신호에 맞춰 데이터 8 bit를 보내고 중단 신호를 보냅니다. 물론 통신 설정에 따른 약간 차이가 있을 수 있습니다. 그래도 기본적인 틀은 가져갑니다.
이제 여기서 중요한 점은 Baudrate라는 개념입니다. 사실상 위의 신호니 뭐니 하는 것들은 하드웨어에서 모두 처리하여 올려주기 때문에 크게 고려하지 않아도 됩니다. 물론 정말 Low Level 단계에서 새로운 Device Driver를 포팅한다고 했을 때는 다시 열어보아야겠죠. 우린 Arduino라는 플랫폼을 사용하니까 넘어갑시다.
다시 본론으로 돌아와서 Baudrate란 위키피디아를 참고해 보면 아래와 같은 뜻을 가집니다.
즉 간단하게 초당 얼마나 많은 심볼을 보낼 수 있는가입니다. 여기서 '심볼'이라는게 관건인데 '심볼'은 지극히 주관적인 개념입니다. 우리가 '유효' 하다고 판단하는 '데이터 단위'입니다. 여기서 아두이노 기준 유효하다고 판단하는 기준은 bit단위가 제일 객관적일 것으로 보입니다.
왜냐하면 사용자가 몇 바이트 단위로 사용할지 그건 아무도 모르니까요. 그렇기 때문에 우리는 Serial 통신에 대해 초기화할 때 Baudrate를 초당 몇 bit를 보낼지 결정해야 합니다. 주로 대부분의 예제를 보면 9600 baudrate를 사용합니다. 그 이유는 우리가 사용하는 아두이노 클럭 16 MHz에서 가장 적절한 타협점을 보여주기 때문입니다. 학부 시절 교수님이 수식을 가져와서 이것저것 따져주셨던 거 같은데, 기억이 가물가물 합니다. 우리 또한 적절한 플랫폼 위에서 설계를 할 것이기 때문에 이 정도만 짚고 넘어갑시다. 추가 설명이 필요하시면 댓글로 남겨주세요.
하지만 제 교수님도 그랬고 저도 그랬지만 115200 Baud Rate를 제일 선호합니다. 전송 속도가 빠른 편에 속하면서도, 낮은 오차율을 보여주니까요. 사실상 학부 시절 습관처럼 예제를 따라 쓰다 보니 주로 115200을 채택하여 사용합니다.
2. 실습 예제
아무래도 통신 관련해서는 이래저래 할 얘기가 많아서 서론이 길었습니다. 이제 우리가 원하는 Code 예제를 보며 이해해 봅시다. 요즘 세상은 무엇보다 누가 더 잘 가져다 쓰냐가 중요하니까요. 이제부터 핵심입니다.
무슨 통신이든 가장 먼저 해야 하는 것은 통신 속도를 설정하는 것입니다. 물론 나 혼자 설정한다고 되는 것은 아니구 내가 통신하고자 하는 대상이랑 맞춰야 하는 것이죠. 위에서 말했듯 우리는 115200 bps를 사용할 것입니다. 통신 속도를 설정하고 나면 해야 할 일은 두 가지만 남습니다. '데이터를 받는다'와 '데이터를 보낸다'입니다. 가장 기본적으로 받은 데이터를 똑같이 되돌려서 보내는 메아리 예제를 짜봅시다.
다시 정리해 보면 3가지로 나뉠 것 같습니다.
- 통신 속도를 설정
- 데이터를 받음
- 받은 데이터를 다시 돌려보냄
1. 통신 속도를 설정
먼저 '통신 속도를 설정'은 Serial.begin(bps 값)으로 설정이 됩니다. 통신 속도의 경우 아두이노가 초기 부팅 시 1회만 수행되면 되기 때문에 setup 함수에 배치되면 되겠죠?
2. 데이터를 받음
데이터를 단순히 수신하는 함수는 Serial.read() 함수입니다. 해당 함수의 경우 unsigned 8 bit data type을 반환합니다. 우리가 c언어에서 char type으로 불리는 값으로 반환을 해주게 되는 거죠. 여기서 조금 더 나아가 우리는 항상 읽는 것이 아니라 '수신된 데이터가 있을 때'만 값을 읽는 게 효율적 일 것 같습니다. Serial.available() 함수는 데이터가 수신되었을 때 수신받은 데이터의 byte 수를 반환해줍니다. 즉, 수신된 데이터가 있으면 1 이상의 값을 반환하게 됩니다. 그럼 데이터가 수신되었을 때, 1이상의 값을 반환해주는 함수인 Serial.available() 함수를 이용하면 우리는 수신 되었을 때만 serial buffer에서 값을 읽어올 수 있습니다.
3. 받은 데이터를 다시 돌려보냄
수신받은 데이터를 담아둔 변수를 그대로 Serial.write(전송할 1 Byte 데이터)를 통해 송신해 줍니다.
위의 내용을 종합하여 코드로 구현해 보면 아래와 같이 나옵니다.
#include <Arduino.h>
void setup() {
Serial.begin(115200);
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
char data;
if(Serial.available()){
data = Serial.read();
Serial.write(data);
}
}
시리얼 모니터 결과 사진을 보면 제가 보낸 값을 아두이노가 동일하게 반환하는 것을 볼 수 있습니다.
근데 해당 Code의 단점은 main Code에서 데이터가 수신될 때까지 기다려야 한다는 점입니다. 현재 Code 자체가 Simple해서 Serial 통신을 기다리는 일밖에 안 하지만 다른 Code가 추가될 경우에 누락되는 메시지가 존재할 수도 있습니다. 그렇다고 해서 마냥 언제 올지 모르는 Serial 통신을 기다리고 있을 수 없죠. 이러한 방법을 해결하기 위해서는 이전 시간에 배웠던 Interrupt를 활용하면 좋을 것 같습니다. Arduino IDE에서는 Serial Recieve Interrupt를 지원합니다.
> Interrupt에 대한 설명은 아래 링크를 참고해 주세요.
https://enginbear.tistory.com/18
별다른 사전 작업 없이 사용이 가능합니다. serialEvent라는 함수만 추가해 주면 되는데, 아래 예제 코드를 보면 이해가 단번에 되실 겁니다.
#include <Arduino.h>
void setup() {
Serial.begin(115200);
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
void serialEvent(){
char data;
if(Serial.available()){
data = Serial.read();
Serial.write(data);
}
}
앞서 설명드린 이유 때문에 Interrupt 방식으로 짜인 코드를 기반으로 활용해 나가는 것이 좋습니다.