일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- AWS
- JavaScript
- nodejs
- Ai
- error
- ssh
- React
- elasticsearch
- JS
- Python
- MySQL
- 바보
- 구글
- Linux
- gemini
- mariadb
- MSSQL
- 유니티
- Windows
- dart
- Kibana
- docker
- FLUTTER
- app
- ChatGPT
- API
- unity
- 연동
- build
- Today
- Total
가끔 보자, 하늘.
ESP32에서 BLE 서버 구축하기: PlatformIO와 ESP32-PICO-DevKitM-2 활용 가이드 본문
2025.05.19 - [개발 이야기/개발 및 서비스] - ESP32 IDF + VSCode 환경 구축
2025.05.31 - [개발 이야기/개발 및 서비스] - ESP32와 Flutter간 Bluetooth 통신
물론입니다! ESP32-PICO-DevKitM-2 보드와 ESP-Prog 디버거를 활용하여 PlatformIO에서 BLE(Bluetooth Low Energy) 서버를 구축하는 과정을 블로그 포스팅 형식으로 상세하게 정리해 드릴게요. 단순 연결을 넘어, 데이터 전송 및 응답의 기본 구조까지 다룹니다.
ESP32에서 BLE 서버 구축하기: PlatformIO와 ESP32-PICO-DevKitM-2 활용 가이드
안녕하세요! 오늘은 ESP32-PICO-DevKitM-2 보드를 사용하여 BLE(Bluetooth Low Energy) 서버를 구축하는 방법을 자세히 알아보겠습니다. 특히 PlatformIO와 VS Code Extension을 활용하여 프로젝트를 생성하고, 기본적인 BLE 연결 및 데이터 전송을 구현하는 과정을 단계별로 안내해 드릴게요. IoT 장치 개발이나 스마트 디바이스 연동을 꿈꾸시는 분들께 유용한 정보가 될 것입니다.
1. 프로젝트 초기 설정: PlatformIO로 시작하기
가장 먼저, PlatformIO를 사용하여 새로운 프로젝트를 생성해야 합니다. VS Code의 PlatformIO 확장 기능을 활용하면 명령줄에서 손쉽게 프로젝트를 초기화할 수 있습니다.
1.1. 프로젝트 폴더 생성
BLE 서버 프로젝트를 위한 새로운 디렉토리를 만들어 작업 공간을 정리합니다. VS Code 내장 터미널을 열고 다음 명령어를 입력하세요.
mkdir ble_sample
cd ble_sample
1.2. PlatformIO 프로젝트 초기화
이제 ble_sample
디렉토리 안에서 PlatformIO 프로젝트를 초기화합니다. 이때 사용할 보드(esp32-pico-devkitm-2
)와 IDE(vscode
)를 명시해줍니다.
pio project init --board esp32-pico-devkitm-2 --ide vscode
이 명령어를 실행하면 PlatformIO가 자동으로 필요한 파일과 폴더 구조를 생성해줍니다. 주요 생성 파일은 다음과 같습니다.
.pio/
: PlatformIO 빌드 관련 파일 및 임시 파일lib/
: 라이브러리 추가 폴더src/
: 소스 코드 폴더 (여기에main.cpp
파일을 생성할 것입니다)platformio.ini
: 프로젝트 설정 파일 (가장 중요!).gitignore
: Git 버전 관리 시 무시할 파일 설정test/
: 테스트 코드 폴더
2. platformio.ini
파일 설정
생성된 platformio.ini
파일을 열어 프로젝트 환경을 정확하게 설정해야 합니다. 특히 ESP32-PICO-DevKitM-2 보드와 ESP-Prog 디버거를 사용하는 경우, 다음 설정을 반영해주세요.
[env:esp32-pico-devkitm-2]
platform = espressif32
board = esp32-pico-devkitm-2
framework = arduino ; <-- 이 부분이 중요합니다! Arduino 프레임워크 사용
build_flags = -D CONFIG_IDF_TARGET_ESP32_PICO_DEVKITM_2
build_type = debug
upload_protocol = esp-prog ; <-- ESP-Prog 디버거 사용 설정
debug_tool = esp-prog ; <-- 디버깅 툴 설정
monitor_speed = 115200 ; 시리얼 모니터 속도
핵심 설정 설명:
board = esp32-pico-devkitm-2
: 사용하시는 보드 모델을 정확히 지정합니다.framework = arduino
: BLE 기능을 사용하려면 Arduino 프레임워크를 명시해야 합니다. ESP32의 BLE 라이브러리(BLEDevice.h
등)는 Arduino 코어에 포함되어 있습니다.upload_protocol = esp-prog
,debug_tool = esp-prog
: ESP-Prog 보드를 통해 펌웨어를 업로드하고 디버깅하기 위한 설정입니다. 이 설정을 통해 USB-UART 변환기 없이 ESP-Prog의 JTAG/SWD 기능을 활용할 수 있습니다.build_flags = -D CONFIG_IDF_TARGET_ESP32_PICO_DEVKITM_2
: 특정 ESP-IDF 타겟을 명시하는 빌드 플래그입니다. Arduino 프레임워크를 사용하더라도, 하위 레벨에서 ESP-IDF 설정을 맞추는 데 도움이 될 수 있습니다.
3. src/main.cpp
BLE 서버 코드 작성
이제 src
폴더 안에 main.cpp
파일을 생성하고 BLE 서버 코드를 작성합니다. 이 코드는 ESP32가 BLE 장치로 광고하고, 클라이언트의 연결을 받아들이며, 주기적으로 데이터를 전송하는 간단한 서버 역할을 합니다.
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h> // BLE Descriptor for notifications/indications
// BLE Service and Characteristic UUIDs
// IMPORTANT: For real-world applications, generate unique UUIDs!
// Use a UUID generator like https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Global BLE objects
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
// Connection status flags
bool deviceConnected = false;
bool oldDeviceConnected = false;
// Data counter for notifications
uint32_t value = 0;
// --- BLE Server Callbacks ---
// This class handles connection and disconnection events
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("*** CLIENT CONNECTED ***");
// Stop advertising to prevent multiple connections if only one is desired
// pServer->getAdvertising()->stop();
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("*** CLIENT DISCONNECTED ***");
// Allow a small delay for BT stack to settle, then restart advertising
delay(500);
pServer->startAdvertising();
Serial.println("Restarted advertising after disconnect.");
}
};
// --- Setup Function ---
// Initializes the ESP32 and sets up the BLE server
void setup() {
Serial.begin(115200);
delay(1000); // Give time for Serial to initialize
Serial.println("=== ESP32 BLE Server Starting ===");
Serial.print("Free heap: ");
Serial.println(ESP.getFreeHeap());
// 1. Create the BLE Device
Serial.println("1. Initializing BLE Device...");
BLEDevice::init("ESP32 BLE Test"); // Set the local device name
Serial.println(" BLE Device initialized successfully");
// 2. Create the BLE Server
Serial.println("2. Creating BLE Server...");
pServer = BLEDevice::createServer();
if (pServer == NULL) {
Serial.println(" ERROR: Failed to create BLE Server!");
return; // Halt if server creation fails
}
pServer->setCallbacks(new MyServerCallbacks()); // Register connection callbacks
Serial.println(" BLE Server created successfully");
// 3. Create the BLE Service
Serial.println("3. Creating BLE Service...");
BLEService *pService = pServer->createService(SERVICE_UUID);
if (pService == NULL) {
Serial.println(" ERROR: Failed to create BLE Service!");
return; // Halt if service creation fails
}
Serial.print(" Service UUID: ");
Serial.println(SERVICE_UUID);
// 4. Create a BLE Characteristic within the service
Serial.println("4. Creating BLE Characteristic...");
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | // Client can read its value
BLECharacteristic::PROPERTY_WRITE | // Client can write to it
BLECharacteristic::PROPERTY_NOTIFY | // Server can notify clients of value changes
BLECharacteristic::PROPERTY_INDICATE // Server can indicate value changes (more reliable than notify)
);
if (pCharacteristic == NULL) {
Serial.println(" ERROR: Failed to create BLE Characteristic!");
return; // Halt if characteristic creation fails
}
Serial.print(" Characteristic UUID: ");
Serial.println(CHARACTERISTIC_UUID);
// Add a 2902 descriptor to enable notifications/indications
// This is crucial for clients to subscribe to updates.
pCharacteristic->addDescriptor(new BLE2902());
Serial.println(" BLE2902 Descriptor added successfully (for Notify/Indicate)");
// 5. Start the BLE Service
Serial.println("5. Starting BLE Service...");
pService->start();
Serial.println(" BLE Service started successfully");
// 6. Start BLE Advertising
Serial.println("6. Starting BLE Advertising...");
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); // Advertise the service UUID
pAdvertising->setScanResponse(false); // Simple advertisement, no scan response for now
pAdvertising->setMinPreferred(0x0); // Set preferred connection interval (optional, 0x0 for default)
try {
BLEDevice::startAdvertising(); // Begin broadcasting advertisement packets
Serial.println(" BLE Advertising started successfully");
Serial.println("=== SETUP COMPLETE ===");
Serial.println("Device name: ESP32 BLE Test");
Serial.println("Status: Waiting for client connection...");
Serial.print("MAC Address: ");
Serial.println(BLEDevice::getAddress().toString().c_str());
} catch (const std::exception& e) {
Serial.print(" ERROR starting advertising: ");
Serial.println(e.what()); // Print any exceptions during advertising start
}
}
// --- Loop Function ---
// Main loop for continuous operation and data transmission
void loop() {
static unsigned long lastHeartbeat = 0;
static int loopCount = 0;
// Print status every 5 seconds for debugging
if (millis() - lastHeartbeat > 5000) {
Serial.print("[");
Serial.print(millis()/1000);
Serial.print("s] Status: ");
Serial.print(deviceConnected ? "CONNECTED" : "DISCONNECTED");
Serial.print(" | Loop count: ");
Serial.print(loopCount);
Serial.print(" | Free heap: ");
Serial.println(ESP.getFreeHeap());
lastHeartbeat = millis();
}
// If a client is connected, send data via notification
if (deviceConnected) {
// Send a 4-byte integer value
pCharacteristic->setValue((uint8_t*)&value, 4);
pCharacteristic->notify(); // Notify the connected client(s)
if (value % 100 == 0) { // Print every 100 values to avoid spamming serial
Serial.print("Notified client with value: ");
Serial.println(value);
}
value++; // Increment value for next notification
delay(3); // Small delay to prevent overwhelming the BLE stack
}
// If device was connected but now disconnected, restart advertising
if (!deviceConnected && oldDeviceConnected) {
delay(500); // Give some time for the stack to clean up
Serial.println("Client disconnected. Restarting advertising...");
pServer->startAdvertising(); // Re-enable advertising for new connections
Serial.println("Advertising restarted.");
oldDeviceConnected = deviceConnected; // Update old state
}
// If device just connected, log it
if (deviceConnected && !oldDeviceConnected) {
Serial.println("New client connected. Starting continuous notifications...");
oldDeviceConnected = deviceConnected; // Update old state
}
loopCount++; // Increment loop counter
delay(10); // General delay to prevent busy-looping
}
코드 분석: BLE 서버의 핵심 동작
위 코드는 ESP32가 BLE 서버로 작동하기 위한 필수 구성 요소를 포함하고 있습니다.
- 헤더 파일 포함:
Arduino.h
: ESP32의 기본 Arduino 기능BLEDevice.h
,BLEServer.h
,BLEUtils.h
,BLE2902.h
:flutter_blue
패키지와 마찬가지로, ESP32 Arduino 환경에서 BLE 기능을 제공하는 핵심 라이브러리입니다. 특히BLE2902.h
는 클라이언트가 서버의 특성(Characteristic) 변경을 알림(Notify) 받거나 확인(Indicate) 받을 수 있도록 해주는 Descriptor를 추가하는 데 사용됩니다.
- UUID 정의:
SERVICE_UUID
및CHARACTERISTIC_UUID
: BLE 서비스와 특성의 고유 식별자입니다. 이 UUID들을 통해 클라이언트 장치는 ESP32 BLE 서버를 찾아 통신할 수 있습니다. 반드시https://www.uuidgenerator.net/
같은 도구를 사용하여 고유한 UUID를 생성하여 사용하세요. 예제에서는 임시 UUID를 사용했습니다.
MyServerCallbacks
클래스:onConnect()
: 클라이언트가 ESP32 BLE 서버에 성공적으로 연결될 때 호출됩니다.deviceConnected
플래그를true
로 설정하여 연결 상태를 관리합니다.onDisconnect()
: 클라이언트가 서버에서 연결을 해제할 때 호출됩니다.deviceConnected
플래그를false
로 설정하고, 다시 광고를 시작하여 다른 클라이언트가 연결할 수 있도록 합니다.
setup()
함수:- BLE 장치 초기화:
BLEDevice::init("ESP32 BLE Test")
를 통해 ESP32의 BLE 기능을 활성화하고, 다른 장치에서 검색될 때 표시될 이름을 "ESP32 BLE Test"로 설정합니다. - BLE 서버 생성:
BLEDevice::createServer()
를 호출하여 BLE 서버 객체를 생성하고, 위에서 정의한MyServerCallbacks
를 연결 콜백으로 등록합니다. - BLE 서비스 생성:
pServer->createService(SERVICE_UUID)
를 통해 특정SERVICE_UUID
를 가진 서비스를 생성합니다. BLE 통신의 기본 단위입니다. - BLE 특성 생성:
pService->createCharacteristic(CHARACTERISTIC_UUID, ...)
를 통해 서비스 내부에 특성을 생성합니다.PROPERTY_READ
: 클라이언트가 이 특성 값을 읽을 수 있게 합니다.PROPERTY_WRITE
: 클라이언트가 이 특성 값을 변경할 수 있게 합니다.PROPERTY_NOTIFY
: 서버(ESP32)가 특성 값 변경 시 클라이언트에게 알림을 보낼 수 있게 합니다.PROPERTY_INDICATE
:NOTIFY
와 유사하지만, 클라이언트로부터 수신 확인 응답을 받습니다.NOTIFY
보다 더 신뢰성 있는 전송 방식입니다.
- Descriptor 추가:
pCharacteristic->addDescriptor(new BLE2902());
는 클라이언트가 알림/지시를 구독할 수 있도록 하는 표준 BLE Descriptor입니다.NOTIFY
나INDICATE
속성을 사용할 때 필수적입니다. - 서비스 및 광고 시작:
pService->start()
로 서비스를 활성화하고,BLEDevice::startAdvertising()
으로 ESP32가 주변에 자신을 알리기 시작합니다.addServiceUUID()
를 통해 광고 데이터에 서비스 UUID를 포함시켜 클라이언트가 특정 서비스를 쉽게 찾을 수 있도록 합니다.
- BLE 장치 초기화:
loop()
함수:- 상태 모니터링: 5초마다 현재 연결 상태, 루프 카운트, 사용 가능한 힙 메모리 정보를 시리얼 모니터로 출력하여 디버깅을 돕습니다.
- 데이터 전송 (Notify):
deviceConnected
가true
일 때,pCharacteristic->setValue()
와pCharacteristic->notify()
를 사용하여value
변수의 값을 주기적으로 클라이언트에게 알립니다. 클라이언트는 이 알림을 구독하여 실시간으로 데이터를 받을 수 있습니다. - 연결 해제 처리:
!deviceConnected && oldDeviceConnected
조건을 통해 클라이언트가 연결 해제되었음을 감지하면, 다시 광고를 시작하여 새로운 연결을 기다립니다. - 새로운 연결 처리:
deviceConnected && !oldDeviceConnected
조건을 통해 새로운 클라이언트가 연결되었음을 감지하고 메시지를 출력합니다.
4. 코드 빌드 및 업로드
PlatformIO를 통해 코드를 빌드하고 ESP32-PICO-DevKitM-2 보드에 업로드합니다.
- PlatformIO 빌드: VS Code 하단의 PlatformIO 툴바에서 'Build' (체크 표시 아이콘) 버튼을 클릭합니다.
- PlatformIO 업로드: 빌드가 성공하면 'Upload' (오른쪽 화살표 아이콘) 버튼을 클릭하여 ESP-Prog 보드를 통해 ESP32에 펌웨어를 업로드합니다.
- 시리얼 모니터 확인: 업로드 후 'Serial Monitor' (플러그 아이콘)를 열어 ESP32의 시리얼 출력을 확인합니다. BLE 서버의 초기화 과정과 현재 상태를 확인할 수 있습니다.
5. 결과 확인 및 테스트
ESP32에 펌웨어 업로드가 완료되고 실행되면, 이제 스마트폰의 BLE 스캐닝 앱(예: nRF Connect for Mobile (Android/iOS) 또는 LightBlue (iOS))을 사용하여 테스트할 수 있습니다.
- 장치 검색: 앱을 실행하고 주변 BLE 장치를 스캔합니다. "ESP32 BLE Test"라는 이름의 장치를 찾을 수 있을 것입니다.
- 연결: 해당 장치를 탭하여 연결합니다. ESP32의 시리얼 모니터에 "* CLIENT CONNECTED *" 메시지가 출력되는 것을 확인할 수 있습니다.
- 서비스 및 특성 확인: 연결되면 ESP32가 제공하는 서비스와 특성 목록을 볼 수 있습니다. 정의한 서비스(
SERVICE_UUID
)와 특성(CHARACTERISTIC_UUID
)을 찾을 수 있습니다. - 데이터 수신 (Notify 확인):
CHARACTERISTIC_UUID
특성을 선택하고 "Enable notifications" 또는 "Subscribe" 옵션을 활성화합니다. 그러면 ESP32가 주기적으로 보내는value
(증가하는 숫자)가 앱에 표시되는 것을 실시간으로 확인할 수 있습니다. - 데이터 전송 (Write 테스트 - 선택 사항): 이 예제 코드에는
onWrite
콜백이 명시적으로 구현되어 있지 않지만,PROPERTY_WRITE
속성을 주었으므로 클라이언트 앱에서 이 특성에 값을 쓸 수 있습니다. 만약 값을 쓴다면, ESP32 코드에BLECharacteristicCallbacks
를 추가하여 이벤트를 처리할 수 있습니다.
(* github link : https://github.com/blackwitch/pio_ble_test)
다음 단계: 데이터 송수신 처리 심화
현재 예제는 BLE 서버가 클라이언트에게 주기적으로 데이터를 전송(Notify)하는 기능에 초점을 맞추고 있습니다. 다음 포스팅에서는 클라이언트가 서버로 데이터를 전송(Write)하고, 서버가 이 데이터를 받아 특정 동작을 수행하는 양방향 통신 처리 과정을 더 자세히 다뤄보겠습니다.
이 가이드가 ESP32와 PlatformIO를 사용하여 BLE 서버 개발을 시작하는 데 도움이 되었기를 바랍니다. 궁금한 점이 있으시면 언제든지 질문해주세요!
'개발 이야기 > 개발 및 서비스' 카테고리의 다른 글
PyNest vs FastAPI (3) | 2025.06.13 |
---|---|
AWS Bedrock과 Sagemaker 요점 정리 (1) | 2025.06.09 |
ESP32와 Flutter간 Bluetooth 통신 (1) | 2025.06.02 |
Flutter 로 앱 개발 및 릴리즈 - 08. google play console 연결하기 #1 (2) | 2025.05.30 |
구글 플레이 개발자 등록 절차 ver 2025 (1) | 2025.05.29 |