[아두이노] [강좌] 45. 블루투스 통신 (4) - 프로토콜 만들기 (1)

이번 강좌에서는 프로토콜에 대해 알아볼 예정이다. 


(+ 추가 이전 내용에서 '프로토콜'과 '커맨드' 사이의 혼동이 있어 내용이 약간 수정되었다. )



프로토콜이란 무엇인가? 두둥.


 

정보기기 사이 즉 컴퓨터끼리 또는 컴퓨터와 단말기 사이 등에서 정보교환이 필요한 경우, 이를 원활하게 하기 위하여 정한 여러 가지 통신규칙과 방법에 대한 약속 즉, 통신의 규약을 의미한다.

[네이버 지식백과] 프로토콜 [protocol] (두산백과)

 


라고 되어 있다. 간단히 말해 데이터를 주고 받기 위한 순서나 형태를 미리 약속하는 것. 



사실 프로토콜은 블루투스 뿐 아니라 모든 통신에 사용된다. 예를 들어 시리얼 통신에서, 양 쪽의 통신 속도를 같게 맞추는 것이 프로토콜이다. 서로 통신 속도를 9600bps로 약속하는 것이다. 


I2C 통신의 경우, SDA, SCL 신호가 HIGH일 때 SDA 신호가 LOW로 바뀌면 시작 신호다, 라는 것도 프로토콜이다. 또 첫 바이트는 무조건 슬레이브의 주소 값이며, 슬레이브의 주소 뒤에 붙는 마지막 비트가 '0'이면 쓰기,N'1'이면 읽기를 나타낸다, 라고 정해놓은 것도 프로토콜이다. 



프로토콜을 광범위하게 말하면, 통신 방식, 속도, 오류검출방법, 데이터 전송 형식, 암호화방식 등등 모든 것이 포함되지만, 이번 시간에 설명할 프로토콜은 "데이터 전송 형식"에 대한 이야기이다.




우리는 블루투스 통신을 사용하기로 상대 기기와 약속했고, 통신 속도나 오류 검출, 암호화 방식 등은 이미 블루투스의 통신 규약에 모두 약속되어 있다. 


우리가 정해야 할 것은, 내가 보내는 데이터가 무엇을 의미하는 데이터인지. 그래서 받는 쪽에서 그 데이터를 받았을 때, 정확히 무슨 동작을 해야 하는 것인지 알 수 있도록 하는 것이다. 



시작해볼까?


이 전 강좌에서 실습했던 스마트 폰의 "RN Bluetooth Chat" 어플에서 아두이노로 데이터를 보낼거다. 어떤 데이터가 오면 LED를 켜고, 어떤 데이터가 오면 LED를 끌까? 프로토콜은 정하는 사람 마음이다.


제일 간단하게,N'0'을 보내면 LED를 끄고,N'1'을 보내면 LED를 켜보자. 


스마트 폰에서야 그냥 '0'이나 '1'을 보내면 되고. 아두이노에서는?



 ProtocolTest01.ino

 

 int ledPin = 13;


 void setup() {

   pinMode(ledPin, OUTPUT);

  

   Serial1.begin(115200);

 }


 void loop() {

   if(Serial1.available()) {

     byte data = Serial1.read();

     if(data == '0') {

       digitalWrite(ledPin, LOW);

     } else if(data == '1') {

       digitalWrite(ledPin, HIGH);

     }

   }

 }


 



아, 쉽다. Serial1 포트로 받은 데이터가 '0'일 경우 LED를 끄고,N'1'일 경우 LED를 켠다. 만일 우노를 이용한다면 SoftwareSerial 포트로 받은 데이터를 확인하면 된다. 


여기서 유의해야 할 점은 '0'과 '1'이 문자라는 점. 스마트 폰에서 보내는 데이터는 숫자가 아닌 문자 데이터라는 것에 유의하자. 


왜? 라는 생각이 드는 사람은 시리얼 강좌를 참조.


업로드 한 후 실행해볼까?



brown_and_cony-35 



자, 이 것이 가장 간단한 프로토콜의 예이다. 한 자리의 문자 데이터,라는 데이터 형식.


'0'을 보내면 LED를 끄고,N'1'을 보내면 LED를 켜기로 한 것은 커맨드의 종류를 정한 것이다. 완전 간단하지!? 이걸 확장해서,N'2'를 보내면 모터를 돌리고,N'3'을 보내면 부저를 울리고 하면 된다.



다시. '한 자리의 문자 데이터'라는 것은 데이터의 형식, 즉 프로토콜을 지정한 것. 그리고 '0'일 때 LED를 끄고,N'1'일 때 LED를 켜는 것은 커맨드를 지정한 것이다. 



위 예제의 프로토콜과 커맨드를 표로 만들면 다음과 같다. 



* 데이터 형식 : 한 자리의 숫자형 문자. '0' 또는 '1'.

데이터 (문자)

동작 

0

LED Off 

1

LED On 




'0',N'1' 대신에 'A',N'B' 등의 문자로 정해도 좋다. 마음대로.




그런데, 소스를 구현하다보니 헷갈린다. '0'일 때 Off였나? '1'일 때 Off였나?


안헷갈리게 단어를 보내자. "ON"과 "OFF".


그런데, 단어를 보내려니 하나는 두 글자고 하나는 세 글자네. 둘 다 두 글자로 맞추는 게 편할 듯. "ON"이랑 "OF"로 하자. 모로 가도 산으로만 가면 됨. ㅋㅋㅋㅋㅋ



loop() 함수 안의 내용만 바꿔보자.



 

 void loop() {

   if(Serial1.available()) {

     char data[2] = {0};

     byte len = Serial1.readBytes(data, 2);

     if(data[1] == 'F') {

       digitalWrite(ledPin, LOW);

     } else if(data[1] == 'N') {

       digitalWrite(ledPin, HIGH);

     }

   }

 }


 



두 개의 문자를 읽어온다. 그래서 두 번째 문자가 'F'면 LED Off,N'N'이면 LED On. 


이번 프로토콜은 '두 자리의 문자' 형식이 되겠지.



* 데이터 형식 : 두 자리의 문자. "ON" 또는 "OF".

데이터 (문자)

동작 

ON

LED On 

OF

LED Off 

 



업로드 한 후 스마트 폰에서 데이터를 보내볼까?





아, 목돌아갔..moon_and_james-40



정확하게 동작하는 것을 확인할 수 있다. 




모터를 추가해보자.


모터는 모터 드라이버마다 동작 방식이 다르므로, 아래 소스는 참고만 하도록 하고 실제로 모터를 구동할 때는 자신의 모터 드라이버에 맞는 소스로 변경해서 사용하면 된다. 


참고로 내가 가진 모터 드라이버는 두 개의 선으로 제어하는 방식이며, 하나의 선에 PWM 신호를 보내면 정방향으로 PWM 값(속도)만큼 회전, 다른 선에 PWM 신호를 주면 역방향으로 회전하는 모터 드라이버이다.



자, 프로토콜을 정해야지. "ON"이 오면 LED On, "OF"가 오면 LED Off였으니까, "MN"이 오면 모터 On, "MF"가 오면 모터 Off로 해볼까? 프로토콜은 여전히 '두 자리의 문자'.



* 데이터 형식 : 두 자리의 문자. 

데이터 (문자)

동작 

ON

LED On 

OF

LED Off 

MN

Motor On 

MF

Motor Off 

 



 

 int ledPin = 13;

 int dcPin1 = 7;

 int dcPin2 = 8;


 void setup() {

   pinMode(ledPin, OUTPUT);

   pinMode(dcPin1, OUTPUT);

   pinMode(dcPin2, OUTPUT);

   digitalWrite(dcPin2, LOW);

  

   Serial1.begin(115200);

 }


 void loop() {

   if(Serial1.available()) {

     char data[2] = {0};

     byte len = Serial1.readBytes(data, 2);

     if(data[0] == 'O') {

       if(data[1] == 'F') {

         digitalWrite(ledPin, LOW);

       } else if(data[1] == 'N') {

         digitalWrite(ledPin, HIGH);

       }

     } else if(data[0] == 'M') {

       if(data[1] == 'F') {

         analogWrite(dcPin1, 0);

       } else if(data[1] == 'N') {

         analogWrite(dcPin1, 255);

       }

     }

   }

 }


 



brown_and_cony-80 



이거시 바로 프로토콜!



자, 위 예제들에서는 '한 자리의 문자', 또는 '두 자리의 문자'를 데이터 형식으로 정하고, 거기에 맞는 커맨드를 정해 LED와 모터를 동작 시켰었다. 더 길게,N'세 자리의 문자' 또는 '열 자리의 문자' 정도로 해도 상관 없다. 

 

그런데, 꼭 데이터의 길이가 정해져야 하는가? 


모터의 경우 단순히 On/Off 하는 동작도 있지만, PWM 값을 이용하여 속도를 조절할 수도 있다. 만일 속도 값을 전달하여 모터의 속도를 제어하고 싶다면 몇 자리의 데이터를 보내야 하는가? 


최대 255까지니까 5자리? "MN255"처럼? 그럼 다시 LED 커맨드는 어떻게 다섯 자리로 늘리지?


LED 제어 신호와 모터 제어 신호의 커맨드 길이가 다를 수는 없을까??



또 한가지 문제. 스마트 폰과의 통신 실습을 처음 할 때, 데이터가 연속해서 들어오지 않고 몇 개씩 끊어져 들어오는 현상을 발견했었다. 



두 개의 문자가 연속해서 들어오는 것이 아니라 하나씩 따로 들어와, Serial.readBytes() 함수에서 하나의 데이터만 읽어오게 된다면 LED와 모터는 제대로 동작할 수 있을까?



사실은 프로토콜이 필요한 이유 중 가장 중요한 이유가 바로 이것이다. 데이터의 오류를 검출하고 방지하는 것.



데이터가 연속해서 들어오지 않는 현상을 방지하고, 데이터의 길이를 유동적으로 만들기 위해 사용하는 프로토콜 중 가장 쉽고 흔한 방법은 바로 '시작 문자'와 '종료 문자'를 정하는 방법이다.



뭐라구?? brown_and_cony-17



'시작 문자'와 '종료 문자'. 말 그대로 데이터의 시작을 의미하는 문자와 데이터 수신 종료를 의미하는 문자를 정해두고, 그 사이의 데이터를 유효한 데이터로 인식하고 처리하는 것. 


예를 들어, 모든 데이터는 '#'으로 시작해서 '@'로 끝난다,라고 정해두면 '#'이라는 문자가 수신되기를 기다리다가 '#'이 수신되면 그 이후의 데이터를 '@'라는 데이터가 수신될 때까지 순서대로 저장해서 '@'이 수신되면 저장된 데이터를 확인하는 것이다. 



brown_and_cony-53 



LED를 켜고 끄던 예제를 생각해보자. 처음에 LED Off 명령을 "OF"로 정한 건 "OFF"는 세 글자이기 때문에 두 글자인 "ON"과 맞춰주기 위한 것이었다. 그럼 이제 시작 문자와 종료 문자를 정해서 데이터의 길이가 상관 없어졌으니 "ON"과, "OFF"를 보내서 LED를 제어해보자.



* 데이터 형식 : '#(시작 문자)' + 데이터(n바이트) + '@(종료 문자)'

데이터 (문자)

동작 

ON

 LED On 

OFF

LED Off 

 



 ProtocolTest02.ino

 

 int ledPin = 13;

 String rxData;

 boolean bStart = false;

 int dataCnt=0;


 void setup() {

   pinMode(ledPin, OUTPUT);

  

   Serial1.begin(115200);

 }


 void loop() {

   if(Serial1.available()) {

     char data = Serial1.read();

    

     if(bStart) {

       if(data == '@') {

         parseData(rxData);

         bStart = false;

         rxData = "";

       } else {

         rxData += data;

       }

     } else {

       if(data == '#') bStart = true;

     }

   }

 }


 void parseData(String cmd) {

   if(cmd == "ON") {

     digitalWrite(ledPin, HIGH);

   } else if(cmd == "OFF") {

     digitalWrite(ledPin, LOW);

   }

 }


 



유동적인 데이터를 처리하기 위해 String 변수를 사용했다. String 변수인 rxData에 수신된 문자를 + 연산으로 추가하기 위해 Serial1.read() 함수의 반환 값을 char 형태로 받은 것에 유의하자.


소스는 어렵지 않다. '#' 문자가 수신되면 데이터의 시작을 의미하는 bStart 변수에 true를 저장한다. 



 if(data == '#') bStart = true;



Serial1.read() 함수는 수신된 순서대로 하나의 데이터씩 반환하므로, 이 후로 수신된 데이터는(bStart 값이 true이므로) rxData에 순서대로 저장된다. 단, 수신된 문자가 '@'라면 종료 문자이므로 수신된 데이터가 저장된 rxData를 확인하여 처리하고, bStart 변수와 rxData 값을 초기화한다. 



 if(data == '@') {

   parseData(rxData);

   bStart = false;

   rxData = "";

 } else {

   rxData += data;

 }

 



parseData() 함수에서는 매개 변수로 전달된 String 타입의 cmd(rxData) 값에 따라 LED를 켜거나 끈다. 



 void parseData(String cmd) {

   if(cmd == "ON") {

     digitalWrite(ledPin, HIGH);

   } else if(cmd == "OFF") {

     digitalWrite(ledPin, LOW);

   }

 }

 




동작해볼까?




짜잔~!! 이제 데이터 길이가 달라도, 연속해서 들어오지 않고 끊어져 들어와도 걱정 없다구!!


그럼, 아까 말했던 모터 속도 값 전달하는 것도 한 번 해볼까?? 는 다음 강좌에서.


다음 강좌에서는 모터의 속도 값을 전달해서 동작시키는 실습과, 반대로 아두이노에서 스마트 폰으로 데이터를 전달하는 실습을 진행해보자. 


그럼 다음 강좌에서 만나요, 안녕~!!

0
0
이 글을 페이스북으로 퍼가기 이 글을 트위터로 퍼가기 이 글을 카카오스토리로 퍼가기 이 글을 밴드로 퍼가기

임베디드 보드

번호 제목 글쓴이 날짜 조회수
58 아두이노 [강좌] 50. 와이파이 통신 (4) - WebServer 예제 icon 양재동메이커 03-21 9,550
57 아두이노 [강좌] 49. 와이파이 통신 (3) - WebClient 예제 icon 양재동메이커 03-21 13,223
56 아두이노 [강좌] 48. 와이파이 통신 (2) - 커맨드 모드 사용하기 (WiFly 쉴드) icon 양재동메이커 03-20 8,636
55 아두이노 [강좌] 47. 와이파이 통신 (1) - 와이파이란 무엇인가 icon 양재동메이커 03-20 12,400
54 아두이노 [강좌] 46. 블루투스 통신 (5) - 프로토콜 만들기 (2) icon 양재동메이커 03-20 8,926
53 아두이노 [강좌] 45. 블루투스 통신 (4) - 프로토콜 만들기 (1) icon 양재동메이커 03-20 10,414
52 아두이노 [강좌] 44. 블루투스 통신 (3) - RN42 모듈에서 직접 접속하기 icon 양재동메이커 03-20 7,748
51 아두이노 [강좌] 43. 블루투스 통신 (2) - 스마트폰과 통신하기 icon 양재동메이커 03-20 9,849
50 아두이노 [강좌] 42. 블루투스 통신 (1) - RN42 블루투스 모듈 실습하기 icon 양재동메이커 03-20 10,986
49 아두이노 [강좌] 41. 서보 모터 (2) - Servo 함수 알아보기 icon 양재동메이커 03-20 12,708
48 아두이노 [아두이노 강좌] 40. 서보 모터 (1) - 서보 모터 동작 방식 icon 양재동메이커 03-20 12,635
47 아두이노 [강좌] 39. 스텝 모터 (3) - 모터 드라이버 EasyDriver(A3967) 사용하기 icon 양재동메이커 03-20 8,985
46 아두이노 [강좌] 38. 스텝 모터 (2) - Stepper 함수 알아보기 icon 양재동메이커 03-20 12,548
45 아두이노 [강좌] 37. 스텝 모터 (1) - 스텝 모터 동작 방식 icon 양재동메이커 03-20 9,328
44 아두이노 [강좌] 36. DC 모터 (2) - DC 모터 드라이버 실습하기 (TB6612FNG) icon 양재동메이커 03-20 9,194
43 아두이노 [강좌] 35. DC 모터 (1) - DC 모터 동작 방식 icon 양재동메이커 03-20 11,466
42 아두이노 [강좌] 34. SPI 통신 (3) - 기압 센서(MPL115A1) 실습하기 icon 양재동메이커 03-20 8,140
41 아두이노 [강좌] 33. SPI 통신 (2) – SPI 함수 알아보기 icon 양재동메이커 03-20 14,507
40 아두이노 [강좌] 32. SPI 통신 (1) - SPI 통신이란 무엇인가 icon 양재동메이커 03-20 18,981
39 아두이노 [강좌] 31. I2C 통신 (3) - 온도 센서(TMP102) 실습하기 icon 양재동메이커 03-20 14,793