Category (Click)
개발보드 덕질하기

[IoT 맛보기] HC-06 → 스마트폰 LED 원격제어

 차완기 - @8/27/2024, 10:45:00 PM
IoT 맛보기 코스
아두이노와 라즈베리파이만 있으면 나도 IoT 할 수 있다! 주말 모임에서 IoT 맛보기를 진행하며 겸사겸사 블로그 포스팅으로 내용을 공유합니다.
IoT 오마카세, 함께 하실레요?
#아두이노 #라즈베리파이 #FW #Bluetooth #가짜IoT #Wi-Fi #IoT #MQTT #Home Assistant
TODO
[IoT 맛보기] 간단한 웹서버 만들어보기
[IoT 맛보기] MQTT 시작

Bluetooth

위키피디아 - BluetoothLogo.svg [링크]
Bluetooth는 전파를 활용한 무선 통신 기술 중 하나로, 주로 가까운 거리에서의 데이터 전송을 위해 사용합니다.
피처폰 시절에 배터리가 없다면 가장 먼저 척결 당했던 바로 그 친구인데요, 다들 스마트폰을 가지고 다니는 지금, 특히 iPhone을 사용한다면 블루투스는 끌 수가 없는 핵심 기능으로 자리하게 되었습니다. 요즘 출시되는 블루투스 비컨은 몇 달, 심지어 년 단위로 가기도 하죠.

HC-06

작동 전압: 3.6~6 V
작동 전류: ~30 mA
통신 거리: ~10 m
Bluetooth 버전: 2.0 + EDR
통신: UART
이번 포스팅에서 소개할 HC-06은 Bluetooth 2.0 버전의 블루투스 모듈입니다.
Bluetooth 5.x 버전을 사용하는 요즘 스마트폰보다 한참 낮은 버전이죠? 기왕 만드는 김에 처음부터 높은 버전을 사용하면 좋겠지만, Bluetooth 4.0 이후 주요 기능인 BLE(Bluetooth Low Energy)는 내부 구조가 상당히 복잡해, 여기서는 다루지 않기로 했습니다. 이후 기회가 된다면 ESP32나 Arduino NANO 33 BLE 등을 다뤄보겠습니다.

Serial로 LED 제어

앞의 HC-06 사양에서 확인할 수 있듯이 HC-06은 UART, 아두이노에서 “시리얼 통신”이라 이야기하는 인터페이스를 사용합니다. 즉, 스마트폰에서 HC-06에 연결해 데이터를 보낼 때 “Hello, World!” 같은 문자열이 전송되는 것이죠.
따라서 우선 시리얼 통신을 사용할 줄 알아야 합니다.

시리얼 통신 - 전송

void setup() { Serial.begin(9600); // 시리얼 통신 초기화 (시작) } void loop() { Serial.println("Hello, World!"); // Hello, World! 출력 delay(1000); // 1초 대기 (너무 빠르게 글씨 나오지 않도록) }
C++
복사
어디서 많이 보던 예제죠?
너무나도 간단하기 때문에 설명은 주석으로 생략하겠습니다.

시리얼 통신 - 수신

void setup() { Serial.begin(9600); // 시리얼 통신 초기화 (시작) } void loop() { // Serial 통신 버퍼에 새문자 존재 여부 확인 bool is_serial_available = Serial.available(); if (is_serial_available) { char c = Serial.read(); // 버퍼에서 수신한 문자 하나를 꺼내 변수 c에 저장 Serial.println(c); // 변수 c 내용 출력 } }
C++
복사
시리얼 모니터에 전송한 문자를 그대로 출력하도록 하는 예제입니다.
아두이노는 loop 함수의 소스코드를 실행하며 .begin() 메소드로 초기화된 시리얼 객체에 새로운 데이터가 있는지 수시로 체크합니다. 여기서는 Serial 객체를 초기화했기 때문에, 아두이노는 loop문을 실행하며 수시로 컴퓨터와 연결된 시리얼 포트를 계속 지켜보게 됩니다. 만약 사용자가 시리얼 모니터에 글씨를 입력해, 아두이노에 새로운 데이터가 들어온다면, 아두이노는 이를 놓치지 않도록 아두이노 내부의 “시리얼 버퍼”에 입력된 문자를 잠시 쌓아둡니다. 이렇게 데이터를 버퍼에 쌓아두면 Serial.available() 메소드를 통해 버퍼에 새로운 문자가 있는지(=새 데이터가 입력되었는지) 확인한 후 Serial.read() 메소드를 통해 가장 먼저 입력된 문자 하나를 버퍼에서 뽑아갈 수 있습니다.
데이터를 어딘가에 저장해두지 않으면 잊어버리겠죠? 데이터가 들어온 시점과 소스코드에서 데이터를 읽는 시점이 일치한다는 보장이 없기 때문에 시리얼 버퍼를 사용합니다.
시리얼 버퍼는 큐(Queue)의 자료 구조를 가지면서 환형 버퍼의 특성을 가집니다. FIFO(First In, First Out) 즉, 먼저 들어온 문자가 먼저 튀어나오고, 만약 데이터를 제때 읽지 못해(읽는 속도 < 쓰는 속도) 너무 많은 데이터가 들어온다면 아직 읽지 않은 데이터 중 가장 앞의 데이터가 지워집니다. 이때 버퍼의 크기는 64 byte로, 문자 64개까지 저장할 수 있습니다.

시리얼 통신 - LED 켜고 끄기

그럼 마지막으로 시리얼 통신을 이용해 LED를 켜고 꺼보겠습니다. “on”, “off”같은 지극히 지적인 단어가 있지만, 아두이노의 시리얼은 문자열이 “off”'o’, ‘f’, ‘f’ 처럼 쪼개져서 날라오고, C언어에서 이런 문자열을 처리하는것은 상당히 고통스럽기 때문에 .. 간단히 ‘1’‘0’으로 LED를 켜고 끄기로 하겠습니다.
void setup() { Serial.begin(9600); // 시리얼 통신 초기화 (시작) pinMode(13, OUTPUT); } void loop() { // Serial 통신 버퍼에 새문자 존재 여부 확인 bool is_serial_available = Serial.available(); if (is_serial_available) { char c = Serial.read(); // 버퍼에서 수신한 문자 하나를 꺼내 변수 c에 저장 // Serial.println(c); // 변수 c 내용 출력 if (c == '0') { // 만약 문자 0이 입력된 경우 digitalWrite(13, LOW); // LED를 끔 } else if (c == '1') { // 만약 문자 1이 입력된 경우 digitalWrite(13, HIGH); // LED를 켬 } } }
C++
복사
앞의 “시리얼 통신 - 수신”을 이해했다면 쉽게 이해될 것 같습니다.

다시, HC-06

HC-06으로 다시 돌아와, 이제는 아두이노에서 블루투스를 사용할 준비를 해보겠습니다.
Arduino UNO
HC-06
5V
VCC
GND
GND
2
TX
3
RX
결선도 보기
아두이노의 하드웨어 시리얼 포트인 0, 1번 핀은 컴퓨터와 연결되어 있어, 컴퓨터와 USB로 연결된 상태에서는 사용할 수 없습니다. 컴퓨터에 데이터를 전송할 일이 없다 하더라도, USB로 전원을 공급받는 이상 USB는 반드시 연결해야하죠. 따라서 여기서는 소프트웨어적으로 시리얼 통신을 구현하는 SoftwareSerial 라이브러리를 이용해 2, 3번핀을 추가적인 시리얼 포트로 사용하였습니다.
하드웨어 vs 소프트웨어 시리얼포트
컴퓨터의 USB 포트가 정해져 있듯이, 아두이노에서 사용할 수 있는 시리얼 포트는 개수가 정해져 있습니다. 이처럼 기본적으로 제공되는 시리얼 포트가 바로 “하드웨어” 시리얼 포트입니다.
만약 하드웨어 시리얼 포트가 부족하다면 소프트웨어적으로 만들어 사용할 수 있는데, 이렇게 만들어진 시리얼 포트가 “소프트웨어” 시리얼 포트입니다. 이때 소프트웨어적으로 만들어진 시리얼 포트는 속도, 기능 등에 제한이 있습니다.

HC-06 설정

당장 스마트폰 설정에 들어갔을 때 다양한 이름의 블루투스 장치들이 보이듯이, HC-06 모듈 역시 이름을 바꿀 수 있습니다. 한 공간에서 여러 모듈이 켜졌을 때 이름을 잘 설정해두면 편하겠죠.
HC-06에서는 “AT 커맨드”라 부르는 일종의 터미널을 지원해, 이를 통해 여러 설정을 수정할 수 있습니다. 모듈로 AT+NAME(이름) 이라는 명령을 보내면 이름이 변경되는 식이죠.
1.
아두이노 프로그램 업로드
AT 커맨드를 사용하기 위해서는 PC에서 입력한 명령을 HC-06으로 보내고, 반대로 HC-06의 응답을 PC로 전송하는 프로그램을 아두이노에 업로드해야합니다.
#include <SoftwareSerial.h> SoftwareSerial BTSerial(2, 3); // HC-06을 위한 Serial void setup() { Serial.begin(9600); // 시리얼 통신 초기화 (시작) BTSerial.begin(9600); } void loop() { if (Serial.available()) { // 만약 컴퓨터에서 데이터가 들어오면, BTSerial.write(Serial.read()); // 그것을 읽어서 HC-06으로 전달 } if (BTSerial.available()) { // 만약 HC-06에서 답변이 오면, Serial.write(BTSerial.read()); // 그것을 읽어서 컴퓨터로 전달 } }
C++
복사
그것을 구현한 코드가 바로 위 코드입니다.
11번 줄에서 if문 속의 Serial.available()를 통해 컴퓨터로부터 새로운 데이터가 들어왔는지 확인하고, 만약 있다면 그 값을 읽어(Serial.read()), 블루투스 모듈과 연결된 시리얼 포트로 전달합니다. (BTSerial.write())
.print() 메소드 대신 .write() 메소드를 사용하는 이유?
Serial.print(1) 명령이 실행된다면 시리얼 모니터에는 어떻게 표시될까요? 당연히 숫자 1이 표시될 겁니다. 그런데, 모든 문자는 그대로 전송되는 것이 아니라 ASCII코드로 인코딩되어 전송됩니다. 따라서 이 1은 사실은 ASCII코드표에서 문자 ‘1’에 대응하는 10진수 숫자 49가 전송되어 표시된 것이죠. 시리얼 모니터에서는 49를 ASCII코드표에서 찾아 문자 ‘1’으로 다시 표시해줍니다.
그럼 Serial.write(49)는 어떻게 될까요? 쉽게 예상할 수 있듯이 10진수 숫자 49이 그대로 전송되고, 시리얼 모니터에서는 ASCII코드표의 49에 대응하는 문자인 ‘1’을 시리얼 모니터에 보여줍니다.
블루투스 모듈은 이미 문자를 ASCII코드로 인코딩해, 숫자를 던져주기 때문에 .print()메소드를 사용하게 되면 이중으로 인코딩됩니다. 이런 문제를 방지하기 위해 .write() 메소드를 사용해, 수신한 내용을 컴퓨터로 그대로 던져주는 것이죠.
프로그램을 업로드한 후에는 시리얼 모니터를 켠 후 “No Line Ending”을 선택합니다.
다음으로 AT를 입력한 후 엔터를 입력해 OK가 출력되는지 확인합니다.
만약 여기까지 확인되었다면 AT 커맨드가 잘 먹힌다고 볼 수 있습니다.
이제 이름을 변경하기 위해 명령을 입력합니다.
이름을 변경하는 명령은 AT+NAME으로, 바로 뒤에 띄워쓰기 없이 이름을 입력합니다. 만약 설정할 이름이 varofla인 경우 AT+NAMEvarofla를 입력합니다.

HC-06으로 LED 제어

앞의 코드를 조금 수정해, HC-06에서 1이 들어온다면 LED를 켜고, 0이 들어온다면 LED를 끄도록 해보겠습니다.
#include <SoftwareSerial.h> SoftwareSerial BTSerial(2, 3); // HC-06을 위한 Serial void setup() { Serial.begin(9600); // 시리얼 통신 초기화 (시작) BTSerial.begin(9600); pinMode(13, OUTPUT); } void loop() { // BTSerial 통신 버퍼에 새문자 존재 여부 확인 bool is_serial_available = BTSerial.available(); if (is_serial_available) { char c = BTSerial.read(); // 버퍼에서 수신한 문자 하나를 꺼내 변수 c에 저장 if (c == '0') { // 만약 문자 0이 입력된 경우 digitalWrite(13, LOW); // LED를 끔 } else if (c == '1') { // 만약 문자 1이 입력된 경우 digitalWrite(13, HIGH); // LED를 켬 } } }
C++
복사

어플리케이션 다운로드 및 설정

설정한 이름의 블루투스 모듈 선택. 기본 PIN은 1234
Switch 선택
우측 상단의 … 버튼 클릭
켜졌을 때 1, 꺼졌을 때 0 설정
이후 스위치를 켜고 끄면 아두이노의 온보드 LED(내장 LED)가 점등하는 것을 확인할 수 있습니다.