메인 콘텐츠로 건너뛰기

4장 ESP32 BLE 통신 기초와 예제 정리

로버트
로버트
조회수 7

생성형 AI 도구를 활용하여 작성 및 편집된 노트입니다.

다시 보는 ESP32

개요

ESP32는 Wi-Fi뿐 아니라 Bluetooth Classic과 Bluetooth Low Energy(BLE)를 모두 지원하는 마이크로컨트롤러로, 무선 통신 프로젝트에 매우 널리 사용됩니다. 이 중 BLE는 저전력·저대역폭 통신에 특화되어 배터리 기반 센서나 웨어러블, 간단한 데이터 전송 장치에 특히 유용합니다.

Generated Image

이 노트에서는 BLE의 기본 개념과 ESP32에서 BLE를 다루는 흐름을 정리합니다. BLE와 Bluetooth Classic의 차이, 서버·클라이언트 구조, GATT 계층(프로필-서비스-특성-설명자), 그리고 Arduino IDE와 ESP32 BLE 라이브러리를 사용한 실제 서버·스캐너 예제를 단계별로 설명합니다. 마지막으로 두 대의 ESP32를 이용한 간단한 통신 시나리오까지 살펴봅니다.


1. BLE 기본 개념과 특성

BLE(Bluetooth Low Energy)는 이름 그대로 저전력에 초점을 맞춘 블루투스 변형입니다. 기본적인 설계 목표는 "소량의 데이터를 짧은 거리에서 간헐적으로 주고받는 것"입니다. 따라서 대용량 데이터를 계속 스트리밍하기보다는, 센서 값처럼 짧고 작은 데이터를 주기적으로 전송하는 용도에 더 적합합니다.

일반적인 Bluetooth Classic이 연결된 동안 계속 활성 상태를 유지하는 것과 달리, BLE는 연결을 맺는 순간을 제외하고는 대부분 절전 모드에 머무릅니다. 필요한 순간에만 깨어나 통신하고 다시 깊은 절전 상태로 들어가기 때문에, 동일한 조건에서 Bluetooth Classic보다 훨씬 적은 전력을 사용합니다. 문서에서는 사용 사례에 따라 BLE가 Bluetooth보다 약 100배까지 낮은 전력 소모가 가능하다고 설명합니다.

BLE는 기본적인 1:1(지점 간) 연결뿐 아니라, 여러 기기에 동시에 광고(브로드캐스트)를 보내는 모드와, 기기들이 서로 복수로 연결되는 메시(mesh) 네트워크 구조도 지원합니다. 이 기능들 덕분에 단순 센서뿐 아니라 위치 비콘, 건물 내 무선 센서 네트워크 등 다양한 구조로 확장할 수 있습니다.


2. BLE와 Bluetooth Classic 비교 및 활용 분야

BLE는 설계 철학부터 Bluetooth Classic과 다릅니다. Bluetooth Classic은 오디오 스트리밍, 파일 전송, HID(키보드·마우스) 등 비교적 많은 데이터를 계속 주고받는 용도에 맞춰져 있습니다. 반면 BLE는 "자주, 조금씩, 오래"라는 키워드에 맞춰져 있어, 오랜 시간 배터리로 동작해야 하는 기기에서 강점을 보입니다.

BLE가 특히 잘 맞는 대표적인 활용 분야는 다음과 같습니다.

  • 의료·헬스케어 기기 혈압계, 혈당계, 심박 센서, 체온계 등은 측정 데이터가 크지 않고, 측정 간격도 상대적으로 길기 때문에 BLE의 저전력 특성이 매우 중요합니다.

  • 피트니스·웨어러블 스마트워치, 피트니스 밴드, 심박 벨트, 걸음수 센서 등은 하루 종일 착용하면서도 자주 충전하기 어렵기 때문에 BLE가 사실상 표준처럼 사용됩니다.

  • 추적·비콘(Beacon) BLE 비콘은 자신의 ID나 간단한 정보를 주기적으로 브로드캐스트하여 위치 추적, 실내 내비게이션, 근접 마케팅 등에 활용됩니다.

  • 보안·출입 시스템 BLE 태그나 스마트폰을 키처럼 사용하여 출입문을 제어하는 시스템에서, BLE는 근접 인식과 저전력 유지에 유리합니다.

  • 홈 오토메이션 조명, 스위치, 콘센트, 간단한 센서류(온습도, 문열림 센서 등)를 BLE로 연결하면, 배터리로 오래 동작하면서도 스마트폰이나 허브를 통해 제어할 수 있습니다.

정리하면, "코인 셀(동전형 배터리)로 오랜 기간 동작하면서, 자주 조금씩만 데이터를 주고받는 장치"에는 Bluetooth Classic보다 BLE가 훨씬 적합합니다.


3. BLE 서버와 클라이언트, 브로드캐스트와 메시

BLE 통신 구조를 이해할 때 가장 먼저 알아야 할 개념은 서버(Server)클라이언트(Client)입니다. ESP32는 상황에 따라 둘 중 어떤 역할도 수행할 수 있습니다.

3.1 서버와 클라이언트

  • BLE 서버 자신이 가진 데이터(예: 센서 값)를 외부에서 읽을 수 있도록 "서비스" 형태로 공개하는 장치입니다. 서버는 "광고(advertising)"를 통해 자신의 존재와 일부 정보를 주변에 알립니다. 클라이언트는 이 광고를 보고 서버를 찾게 됩니다.

  • BLE 클라이언트 주변의 BLE 기기를 스캔하여 특정 서버를 찾아 연결하고, 그 안에 있는 서비스와 특성 값을 읽거나, 값을 쓰거나, 알림을 구독하는 장치입니다. ESP32, 스마트폰, PC 모두 클라이언트가 될 수 있습니다.

ESP32 두 대를 사용할 경우, 한 대는 서버로 설정하고 다른 한 대는 클라이언트(스캐너)로 설정하여 손쉽게 지점 간 통신 구조를 테스트할 수 있습니다.

3.2 브로드캐스트 모드와 메시 네트워크

BLE는 단순한 1:1 연결을 넘어 다음과 같은 구조도 지원합니다.

  • 브로드캐스트 모드 서버가 자신과 연결된 여러 클라이언트에게 동시에 데이터를 전송하는 방식입니다. 또는 연결 없이 광고 패킷만으로 간단한 정보를 주변에 계속 흘려보내는 비콘 형태도 넓은 의미의 브로드캐스트입니다.

  • 메시(mesh) 네트워크 여러 BLE 노드가 서로 다대다로 연결되어, 한 노드에서 다른 먼 노드까지 데이터를 여러 홉을 통해 전달하는 구조입니다. 건물 전체 센서망, 스마트 조명 시스템 등에서 사용됩니다.

다만 ESP32의 Arduino 예제에서는 주로 가장 기본적인 1:1 구조(서버-클라이언트)와 스캔·광고 기능에 초점이 맞춰져 있으며, 브로드캐스트·메시 고급 예제는 상대적으로 적습니다.


4. GATT 계층 구조와 UUID

BLE에서 실제 데이터를 주고받는 구조는 GATT(Generic Attributes)라는 계층 모델로 표현됩니다. GATT를 이해하면 "서비스와 특성을 어떻게 설계할지", "어떤 값을 어디에 넣을지"를 명확히 정리할 수 있습니다.

4.1 GATT 개요

GATT는 "두 BLE 기기가 표준화된 방식으로 데이터를 주고받는 구조"를 정의합니다. GATT를 따라 각 BLE 장치는 다음과 같은 계층 구조를 가집니다.

  • 프로필(Profile)

  • 서비스(Service)

  • 특성(Characteristic)

  • 설명자(Descriptor)

이 구조는 상자 안에 상자가 있는 형태라고 생각하면 이해가 쉽습니다. 프로필 안에 여러 서비스가 들어 있고, 서비스 안에 여러 특성이 들어 있으며, 특성에 대한 추가 정보는 설명자로 붙습니다.

4.2 프로필과 서비스

  • 프로필(Profile) 하나 이상의 서비스를 묶어 놓은 상위 개념입니다. 예를 들어, "심박 프로필"은 심박 측정 서비스와 관련 설정 서비스를 함께 포함할 수 있습니다. 실제 구현에서는 프로필보다는 서비스 단위를 더 자주 직접 다루게 됩니다.

  • 서비스(Service) 같은 범주의 데이터를 묶어 놓은 단위입니다. 예를 들어, "배터리 서비스"에는 배터리 잔량 특성이 들어 있고, "온도 서비스"에는 온도 값 특성이 들어 있습니다. 실제로 하나의 BLE 장치는 여러 개의 서비스를 가질 수 있습니다.

Bluetooth SIG에서는 자주 사용하는 서비스에 대해 미리 정의된 표준 서비스를 제공하고 있습니다. 예를 들어 배터리 수준, 혈압, 심박수, 체중계 등에 대한 서비스가 이미 정의되어 있어, 표준 장치끼리는 서로 쉽게 호환되도록 설계됩니다.

4.3 특성(Characteristic)과 속성

  • 특성(Characteristic) 서비스가 소유하는 실제 데이터의 저장소입니다. 센서 값, 설정 값, 상태 값 등이 모두 특성에 들어가는 실제 "값(value)"입니다. 특성은 두 부분으로 구성됩니다.

    • 특성 선언(메타데이터: 이름, 속성 등)

    • 특성 값(실제 데이터)

특성에는 이 값과 상호 작용하는 방법을 정의하는 속성(property)이 붙습니다. 대표적인 속성은 다음과 같습니다.

  • Broadcast (방송)

  • Read (읽기)

  • Write Without Response (응답 없이 쓰기)

  • Write (쓰기)

  • Notify (알림; 서버→클라이언트 자동 전송)

  • Indicate (인디케이트; 확인 응답을 요구하는 알림)

  • Signed Write, Extended Properties 등

예를 들어, 센서 값은 보통 Read와 Notify 속성을 가지도록 설계하고, 설정 값은 Read와 Write 속성을 가지도록 구성합니다.

4.4 설명자(Descriptor)

특성 값 뒤에 붙는 설명자(Descriptor)는 특성에 대한 메타데이터를 조금 더 확장하는 역할을 합니다. 예를 들어, 값의 단위(℃, %, m/s² 등), 값의 범위, 사용자 친화적인 이름 등을 설명자로 명시할 수 있습니다. 필수 요소는 아니지만, 클라이언트 앱이 데이터를 더 잘 해석하도록 도와줍니다.

4.5 UUID(Universally Unique Identifier)

각 서비스, 특성, 설명자는 서로를 구분하기 위해 UUID라는 식별자를 가집니다. UUID는 128비트(16바이트) 길이의 고유한 값이며 보통 다음과 같은 문자열 형태로 표현됩니다.

  • 예: 55072829-bc9e-4c53-938a-74a6d4c78776

Bluetooth SIG는 표준 서비스와 특성에 대해 짧은 16비트 또는 32비트 UUID를 정의해 두었습니다. 하지만 사용자가 임의의 서비스를 만들고 싶다면, 일반적인 UUID 생성 웹사이트나 도구를 활용하여 임의의 128비트 UUID를 만들어 사용할 수 있습니다.

정리하면, UUID는 "이 서비스/특성이 무엇인지"를 고유하게 구분하는 ID입니다. 클라이언트는 광고 패킷이나 연결 후 GATT 구조를 읽어, 특정 UUID를 가진 서비스나 특성을 찾아 그 값에 접근합니다.


5. ESP32에서 Arduino IDE로 BLE 서버 구현하기

ESP32에서는 Arduino IDE와 함께 기본 제공되는 ESP32 BLE 라이브러리를 사용하여 손쉽게 BLE 서버·클라이언트를 구현할 수 있습니다. 이 라이브러리는 ESP32 보드를 Arduino IDE에 추가 설치할 때 함께 설치됩니다.

5.1 준비 단계

  1. Arduino IDE에 ESP32 보드 패키지 설치 ESP32를 Arduino로 프로그래밍할 수 있도록 보드 매니저에서 ESP32 패키지를 설치합니다.

  2. 보드 선택 도구 > 보드 메뉴에서 사용하는 ESP32 보드(예: "DOIT ESP32 DEVKIT V1")를 선택합니다.

  3. BLE 예제 확인 Arduino IDE 메뉴에서 파일 > 예제 > BLE로 들어가면 다양한 BLE 예제를 볼 수 있습니다. 여기서는 그 중 "server" 예제를 사용합니다.

5.2 BLE 서버 예제 코드 구조

문서에서 제시한 BLE 서버 예제는 다음과 같은 헤더와 정의로 시작합니다.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

이 코드는 BLE 장치를 제어하기 위한 기본 라이브러리를 포함하고, 사용자 정의 서비스와 특성의 UUID를 상수로 정의합니다. 실제 동작은 setup() 함수 안에서 이루어집니다.

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("MyESP32");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("Hello World says Neil");
  pService->start();

  // BLEAdvertising *pAdvertising = pServer->getAdvertising();   // old way
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);   // help with iPhone connections
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  delay(2000);
}

5.3 코드 동작 흐름 정리

위 코드를 단계별로 정리하면 다음과 같습니다.

  1. 시리얼 초기화 Serial.begin(115200); - 디버깅 출력을 위해 시리얼 모니터를 사용합니다.

  2. BLE 장치 초기화 BLEDevice::init("MyESP32"); - ESP32를 BLE 장치로 초기화하고, 광고에 사용될 기기 이름을 설정합니다.

  3. BLE 서버 생성 BLEServer *pServer = BLEDevice::createServer(); ESP32를 BLE 서버 역할로 설정합니다.

  4. 서비스 생성 BLEService *pService = pServer->createService(SERVICE_UUID); 앞에서 정의한 UUID를 사용해 커스텀 서비스를 생성합니다.

  5. 특성 생성 및 속성 지정 createCharacteristic() 함수에서 특성 UUID와 속성을 지정합니다.

    • PROPERTY_READ | PROPERTY_WRITE 를 사용했으므로, 클라이언트는 이 특성을 읽고 쓸 수 있습니다.

  6. 초기 값 설정 pCharacteristic->setValue("Hello World says Neil"); 특성의 초기 문자열 값을 설정합니다. 클라이언트가 이 특성을 읽으면 이 문자열을 받게 됩니다.

  7. 서비스 시작 pService->start(); - 서비스와 그 안의 특성들이 실제로 GATT에 등록되어 활성화됩니다.

  8. 광고(Advertising) 설정 및 시작

    • BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); - 광고 객체를 가져옵니다.

    • pAdvertising->addServiceUUID(SERVICE_UUID); - 이 서비스 UUID를 광고에 포함시켜, 스캐너가 이 서비스를 가진 기기임을 알 수 있게 합니다.

    • pAdvertising->setScanResponse(true); - 스캔 응답을 활성화합니다.

    • setMinPreferred() 설정은 일부 기기(특히 iPhone)와의 연결 안정성을 돕는 설정입니다.

    • BLEDevice::startAdvertising(); - 실제 광고를 시작하여 주변 기기에서 이 ESP32를 찾을 수 있도록 합니다.

  9. loop 함수 이 예제에서는 BLE 통신 측면에서 loop에서 하는 일이 없습니다. 단순히 delay(2000);으로 2초마다 쉬기만 합니다. 실제 프로젝트에서는 센서 값을 읽어 특성 값에 업데이트하고, Notify를 통해 전송하도록 구현하는 식으로 확장할 수 있습니다.

정리하면, ESP32 BLE 서버 구현은 "장치 초기화 → 서버 생성 → 서비스 생성 → 특성 생성 → 서비스 시작 → 광고 시작"이라는 깔끔한 순서로 진행됩니다.


6. ESP32 BLE 스캐너(클라이언트) 구현 흐름

ESP32를 BLE 클라이언트(스캐너)로 사용하려면, Arduino IDE의 BLE 예제 중 "Scan" 예제를 사용할 수 있습니다. 이 예제는 주변의 BLE 기기를 스캔하고, 광고 정보를 시리얼 모니터에 출력합니다.

6.1 스캐너 예제 코드 구조

예제의 기본 구조는 다음과 같습니다.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

int scanTime = 5; // in seconds
BLEScan* pBLEScan;

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Scanning...");
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); // create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);   // active scan: more power, faster results
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // <= setInterval
}

void loop() {
  BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
  Serial.print("Devices found: ");
  Serial.println(foundDevices->getCount());
  Serial.println("Scan done!");
  pBLEScan->clearResults();  // free memory
  delay(2000);
}

6.2 코드 동작 흐름 정리

  1. 시리얼 초기화 Serial.begin(115200); - 스캔 결과를 시리얼 모니터에 출력하기 위한 준비입니다.

  2. BLE 장치 초기화 BLEDevice::init(""); - 이름 없이 BLE 기능만 초기화합니다. 이 장치는 서버가 아니라 스캐너 역할이므로, 광고 이름을 설정할 필요가 없습니다.

  3. 스캔 객체 생성 pBLEScan = BLEDevice::getScan(); - 스캔을 담당할 객체를 가져옵니다.

  4. 콜백 클래스 등록 setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); 스캔 도중 새로운 광고 패킷을 발견할 때마다 MyAdvertisedDeviceCallbacks::onResult()가 호출됩니다. 이 콜백에서는 advertisedDevice.toString()을 출력하여 장치 이름, RSSI, 서비스 UUID 등 기본 정보를 확인할 수 있습니다.

  5. 스캔 모드와 타이밍 설정

    • setActiveScan(true); - Active Scan을 사용하면 더 많은 정보를 얻을 수 있지만, 그만큼 전력을 더 소모합니다.

    • setInterval(100); - 스캔 주기(단위: ms)

    • setWindow(99); - 실제로 스캔하는 시간(단위: ms), 보통 Window <= Interval로 설정합니다.

  6. 주기적 스캔 수행 loop()에서 다음을 반복합니다.

    • pBLEScan->start(scanTime, false); - 지정한 시간(여기서는 5초) 동안 스캔을 수행합니다.

    • 스캔이 끝나면 발견한 장치 수를 출력합니다.

    • clearResults()로 결과를 비워 메모리를 확보합니다.

    • 2초간 대기 후 다시 스캔을 반복합니다.

이 예제 코드를 ESP32에 업로드하고, 다른 ESP32에서 BLE 서버 예제를 실행한 상태로 두 보드에 전원을 공급하면, 스캐너 ESP32의 시리얼 모니터에서 MyESP32라는 이름의 BLE 서버를 포함해 주변 BLE 장치를 탐지하는 것을 확인할 수 있습니다.


7. ESP32 두 대로 서버-스캐너 구성하기

문서에서 제시하는 실습 흐름은 ESP32 두 대를 사용하여 가장 기본적인 BLE 통신 구조를 경험해보는 방식입니다.

7.1 준비물

  • ESP32 개발 보드 2개 (예: DOIT ESP32 DEVKIT V1)

  • USB 케이블 2개

  • Arduino IDE 설치 및 ESP32 보드 패키지 설치 완료

7.2 운영 시나리오

  1. ESP32 A - BLE 서버

    • "BLE_server" 예제를 ESP32 A에 업로드

    • 전원을 공급하면, "MyESP32"라는 이름으로 BLE 광고를 시작

    • 서비스 UUID와 특성 UUID에 해당하는 GATT 구조가 활성화됨

  2. ESP32 B - BLE 스캐너(클라이언트)

    • "BLE_scan" 예제를 ESP32 B에 업로드

    • 전원을 공급하면, 주기적으로 주변의 BLE 광고를 스캔하고 결과를 시리얼 모니터에 출력

    • 출력 내용 중에 "MyESP32"라는 이름과 BLE 서버의 광고 정보가 포함되어 있음

  3. 실행 확인

    • 각 ESP32가 어느 스케치를 실행 중인지 헷갈리지 않도록, 업로드할 때 한쪽 보드를 잠시 분리해 두는 것이 좋습니다.

    • 스캐너의 시리얼 모니터에는 Advertised Device: ... 형식의 로그가 표시되며, 그 중 하나가 ESP32 서버입니다.

    • 스마트워치 등 다른 BLE 장치가 주변에 있다면, 그것들도 함께 검색되어 출력됩니다.

이 과정을 통해 "BLE 서버가 광고를 하고, BLE 클라이언트(스캐너)가 이를 탐지한다"는 BLE의 기본 통신 구조를 직접 체험할 수 있습니다.


8. 스마트폰으로 ESP32 BLE 서버 테스트하기

ESP32와 ESP32끼리의 통신뿐 아니라, 스마트폰을 BLE 클라이언트로 사용하는 것도 매우 중요합니다. 대부분의 최신 스마트폰에는 이미 BLE가 내장되어 있으며, 전용 앱을 사용하면 GATT 구조와 특성 값을 직접 확인할 수 있습니다.

8.1 준비

  1. BLE 서버 예제("MyESP32")가 실행 중인 ESP32에 전원을 공급합니다.

  2. 스마트폰에서 Bluetooth를 활성화합니다.

  3. 앱 스토어(Google Play 또는 App Store)에서 Nordic의 "nRF Connect for Mobile" 앱을 설치합니다.

8.2 nRF Connect로 테스트하는 절차

  1. nRF Connect 앱을 실행하고, 스캔 버튼을 눌러 주변 BLE 기기를 검색합니다.

  2. 목록에서 "MyESP32"라는 이름의 기기를 찾습니다.

  3. 해당 기기의 "Connect" 버튼을 눌러 연결합니다.

  4. 연결이 완료되면, ESP32가 내보내는 서비스 목록이 표시됩니다.

  5. 서비스 중에서 코드에서 정의한 SERVICE_UUID를 가진 서비스를 선택하면, 해당 서비스 안에 CHARACTERISTIC_UUID를 가진 특성이 보입니다.

  6. 이 특성에는 READ, WRITE 속성이 있고, 초기 값으로는 서버 코드에서 설정한 문자열("Hello World says Neil")이 들어 있습니다.

  7. 앱에서 READ 버튼을 누르면 이 값을 읽을 수 있고, WRITE 기능을 사용하면 새로운 값을 ESP32로 보낼 수도 있습니다.

이 과정을 통해, ESP32가 GATT 구조를 통해 서비스와 특성을 제공하고, 스마트폰이 BLE 클라이언트로서 이를 읽고 쓰는 전형적인 BLE 사용 패턴을 직접 확인할 수 있습니다.


9. ESP32와 스마트폰을 Bluetooth Classic(Serial)으로 연결하기

BLE와 별개로 ESP32는 Bluetooth Classic도 지원합니다. 문서에서는 Bluetooth Classic을 사용해 ESP32를 "무선 시리얼 포트"처럼 사용하는 기본 예제도 함께 소개합니다. 이는 BLE가 아닌 SPP(Serial Port Profile)에 기반한 통신입니다.

9.1 BluetoothSerial 기본 예제 코드

다음은 Arduino에서 제공하는 대표적인 Bluetooth Classic 시리얼 브리지 예제입니다.

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); // Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

9.2 코드 동작 설명

  1. 헤더 및 설정 확인

    • #include "BluetoothSerial.h": Bluetooth Serial 기능을 사용하기 위한 헤더입니다.

    • CONFIG_BT_ENABLED, CONFIG_BLUEDROID_ENABLED 매크로를 통해 ESP32에서 Bluetooth 기능이 활성화되어 있는지 확인합니다. 비활성화 상태라면 컴파일 시 에러를 발생시킵니다.

  2. BluetoothSerial 인스턴스 생성 BluetoothSerial SerialBT; - ESP32의 Bluetooth Classic 시리얼 통신을 담당할 객체입니다.

  3. setup() 함수

    • Serial.begin(115200); - USB 시리얼(PC-ESP32)을 초기화합니다.

    • SerialBT.begin("ESP32test"); - "ESP32test"라는 이름으로 Bluetooth Classic 장치를 초기화합니다. 스마트폰에서 이 이름으로 기기를 검색·페어링하게 됩니다.

    • 초기화 메시지를 시리얼 모니터에 출력합니다.

  4. loop() 함수 - 양방향 브리지

    • if (Serial.available()) { SerialBT.write(Serial.read()); } PC에서 USB 시리얼로 들어온 데이터를 읽어 BluetoothSerial로 그대로 전달합니다.

    • if (SerialBT.available()) { Serial.write(SerialBT.read()); } 스마트폰에서 Bluetooth Classic으로 보낸 데이터를 읽어 USB 시리얼(PC)로 전달합니다.

    • 결과적으로, PC ↔ ESP32 ↔ 스마트폰 사이에 양방향 시리얼 브리지가 형성됩니다.

9.3 스마트폰에서 연결하는 절차

  1. 스마트폰에 "Serial Bluetooth Terminal" 같은 시리얼 블루투스 앱을 설치합니다.

  2. ESP32를 위 코드로 프로그래밍하고 전원을 공급합니다.

  3. 스마트폰의 Bluetooth 설정에서 새 기기를 검색하여 "ESP32test"를 찾습니다.

  4. 해당 기기와 페어링을 진행합니다. 필요하다면 앱에서 장치를 선택하여 연결합니다.

  5. 앱의 터미널 화면에서 메시지를 입력해 전송하면, Arduino IDE의 시리얼 모니터에 해당 메시지가 그대로 표시됩니다.

  6. 반대로 시리얼 모니터에서 글자를 입력하면, 스마트폰의 블루투스 터미널 앱에서 그 내용을 확인할 수 있습니다.

이 예제는 BLE가 아닌 Bluetooth Classic 기반이지만, "무선 시리얼 포트"처럼 쉽게 데이터를 주고받을 수 있다는 점에서 디버깅, 간단한 명령 전송 등에서 매우 유용합니다. BLE가 너무 복잡하게 느껴질 때, 먼저 Classic으로 통신 패턴을 익혀 보는 것도 좋은 접근입니다.


10. 정리 및 활용 방향

이 노트에서 다룬 핵심 내용을 다시 정리하면 다음과 같습니다.

  • BLE는 저전력·저대역폭 통신에 특화된 블루투스 변형으로, 센서, 웨어러블, 비콘, 홈 오토메이션 등에서 널리 사용됩니다.

  • BLE 통신 구조는 서버(데이터 제공자)와 클라이언트(데이터 소비자)로 나뉘며, ESP32는 양쪽 역할 모두 수행할 수 있습니다.

  • 실제 데이터 구조는 GATT 계층(프로필-서비스-특성-설명자)로 정의되며, 각 요소는 독립적인 UUID로 식별됩니다.

  • ESP32와 Arduino IDE, ESP32 BLE 라이브러리를 통해, BLE 서버(서비스와 특성 제공)와 BLE 스캐너(주변 장치 검색)를 손쉽게 구현할 수 있습니다.

  • ESP32 두 대로 서버-스캐너를 구성하면 BLE의 기본 통신 흐름을 직접 실습할 수 있고, 스마트폰 앱(nRF Connect 등)을 사용하면 GATT 구조와 특성 값까지 눈으로 확인할 수 있습니다.

  • 별도로, ESP32는 Bluetooth Classic Serial(SPP)도 지원하여, "무선 시리얼 포트" 형태로 스마트폰과 간단한 텍스트 기반 통신을 구현할 수 있습니다.

실제 프로젝트를 진행할 때는,

  1. 우선 이 예제 코드들을 그대로 실행해 동작을 확인하고,

  2. 이후 서비스·특성 UUID를 자신에게 맞게 정의한 뒤,

  3. 센서 값 읽기·설정 값 쓰기·알림(Notify) 전송 등의 로직을 추가하며 확장해 나가는 방식으로 개발을 진행하면 효율적으로 BLE 시스템을 설계할 수 있습니다.

이 노트는 요약·비평·학습 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.