概要
以前、M5StampC3UにてUARTライクなBLE通信を試しましがセキュリティ設定が無いことや、
ライブラリが重いという問題がありました。NimBLEという軽量ライブラリがあることを
知りさっそく試してみました。コードについては生成AIの手を借りています。
セキュリティ設定は下記コードで実装し暗号化も行われます。
NimBLEDevice::setSecurityAuth(true, true, true); // bonding, MITM, secure
引数 有効時の意味(セキュリティ効果)
bond = true ペアリング情報を保存 次回以降の再接続で即暗号通信
mitm = true 中間者攻撃対策(PINや確認あり) PIN一致が必要になる
secure = true LE Secure Connections方式 AES-128強度の暗号通信
また、1回に送信できる文字数については下記で設定しています。
NimBLEDevice::setMTU(247);
247バイト設定ですが、3バイトはATTヘッダに使われるため
実質244バイト(1バイト文字で244文字)となります。
日本語の2バイト文字も問題無く送受信できています。
これでUARTライクなセキュアBLE通信が確立できました。

接続待機でLED緑点灯、接続確立で青点灯します。
切断後は再度アドバダイズされるので自動で再接続されます。

COM10がSever(Peripheral)、COM23がClient(Central)になります。
5秒毎にそれぞれが定期送信を行っています。
また、任意の文字列(test 123,test 456)送信しています。
環境など
エディタ:VisualStudioCode(PlatformIO IDE)
マイコン:M5Stamp C3U Mate 2台
フレームワーク:Aruduino
コード(Server側)
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Adafruit_NeoPixel.h>
// ==============================
// BLE UART Configuration
// ==============================
#define SECURITY_PIN 123456
#define DEVICE_NAME "ESP32C3-BLE-UART"
// ==============================
// BLE UART Service UUIDs
// ==============================
static const char* SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
static const char* CHARACTERISTIC_UUID_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
static const char* CHARACTERISTIC_UUID_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
// ==============================
// Global Variables
// ==============================
NimBLECharacteristic* g_tx = nullptr;
bool g_connected = false;
// ==============================
// LED Configuration
// ==============================
#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);
// ===================================================
// BLE Server Callbacks
// ===================================================
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
(void)pServer; (void)connInfo;
g_connected = true;
Serial.println("✅ Central connected!");
// BLE接続でLED青点灯
pixels.setPixelColor(0, pixels.Color(0, 0, 50));
pixels.show();
}
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
(void)pServer; (void)connInfo;
g_connected = false;
Serial.printf("❌ Central disconnected (reason=%d)\n", reason);
// BLE切断でLED緑点灯
pixels.setPixelColor(0, pixels.Color(0, 50, 0));
pixels.show();
// 再度アドバタイズ開始
NimBLEDevice::getAdvertising()->start();
Serial.println("🔁 Advertising restarted!");
}
};
// ===================================================
// RX Characteristic Callback
// ===================================================
class RxCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic* ch, NimBLEConnInfo& info) override {
(void)info;
std::string v = ch->getValue();
if (!v.empty()) {
Serial.print("📩 RX: "); Serial.println(v.c_str());
}
}
};
// ===================================================
// Setup and Loop
void setup() {
// Initialize NeoPixel
pixels.begin();
pixels.setPixelColor(0, pixels.Color(0, 50, 0));// Green color
pixels.show();// Turn on LED to indicate startup
// Initialize Serial
Serial.begin(115200);
delay(200);
Serial.println("🚀 Peripheral Advanced (v2.3.x)");
// Initialize NimBLE
NimBLEDevice::init(DEVICE_NAME);
// Transmit Power
NimBLEDevice::setPowerLevel(ESP_PWR_LVL_P21, ESP_BLE_PWR_TYPE_ADV);
// Security (Static PIN, v2.x API) & MTU
NimBLEDevice::setSecurityAuth(true, true, true); // bonding, MITM, secure
NimBLEDevice::setSecurityPasskey(SECURITY_PIN);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO);
NimBLEDevice::setMTU(247); // 実効244 bytes(安定・実装上限)Payload + 3バイト(ATTヘッダ)
// Create Server
NimBLEServer* server = NimBLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
// Create Service and Characteristics
NimBLEService* svc = server->createService(SERVICE_UUID);
g_tx = svc->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::NOTIFY);
NimBLECharacteristic* rx = svc->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
rx->setCallbacks(new RxCallbacks());
svc->start();
// Start Advertising
NimBLEAdvertising* adv = NimBLEDevice::getAdvertising();
NimBLEAdvertisementData advData; advData.addServiceUUID(SERVICE_UUID);
adv->setAdvertisementData(advData);
NimBLEAdvertisementData scanData; scanData.setName(DEVICE_NAME); scanData.addServiceUUID(SERVICE_UUID);
adv->setScanResponseData(scanData);
adv->setMinInterval(0x20);
adv->setMaxInterval(0x40);
adv->start();
Serial.println("✅ Advertising started!");
}
void loop() {
static uint32_t last = 0;
// 定期送信
if (g_connected && millis() - last > 5000) {
last = millis();
const char* msg = "Hello from Peripheral";
g_tx->setValue(msg);
g_tx->notify();
}
// USBシリアル受信⇒BLE送信
if(Serial.available()>0){
String rev_str = Serial.readStringUntil('\n'); //終端文字までを取得
g_tx->setValue(rev_str.c_str());
g_tx->notify();
}
}
// ===================================================
plathomeio.ini設定(Server)
[env:m5stampc3u]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
build_flags =
-DARDUINO_USB_CDC_ON_BOOT
-DARDUINO_USB_MODE
lib_deps =
h2zero/NimBLE-Arduino @ ^2.3.6
adafruit/Adafruit NeoPixel@^1.12.2
コード(Client側)
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Adafruit_NeoPixel.h>
// ==============================
// BLE UART Configuration
// ==============================
#define SECURITY_PIN 123456
#define TARGET_NAME "ESP32C3-BLE-UART"
// =============================
// UUIDs for Nordic UART Service (NUS)
// =============================
static const char* SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
static const char* CHARACTERISTIC_UUID_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; // Peripheral RX (write target)
static const char* CHARACTERISTIC_UUID_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; // Peripheral TX (notify source)
// ===================================================
// Global Variables
// ===================================================
static NimBLEAdvertisedDevice* g_adv = nullptr;
static NimBLEClient* g_client = nullptr;
static NimBLERemoteCharacteristic* g_remoteTx = nullptr; // subscribe
static NimBLERemoteCharacteristic* g_remoteRx = nullptr; // write target
static bool g_connected = false;
static bool g_doConnect = false;
static void startScan();
// ==============================
// LED Configuration
// ==============================
#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);
// ===================================================
// Notification Callback
// ===================================================
static void notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t len, bool isNotify) {
(void)pChar; (void)isNotify;
String s; s.reserve(len + 1);
for (size_t i=0;i<len;i++) s += (char)pData[i];
//log_json("rx", s.c_str(), (g_client ? g_client->getRssi() : 0));
Serial.print("📩 RX: "); Serial.println(s);
}
// ===================================================
// BLE Client Callbacks
// ===================================================
class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) override {
(void)pClient;
Serial.println("✅ Connected!");
// BLE接続でLED青点灯
pixels.setPixelColor(0, pixels.Color(0, 0, 50));
pixels.show();
}
void onDisconnect(NimBLEClient* pClient, int reason) override {
(void)pClient;
g_connected = false;
Serial.printf("❌ Disconnected (reason=%d)\n", reason);
// BLE切断でLED緑点灯
pixels.setPixelColor(0, pixels.Color(0, 50, 0));
pixels.show();
// 再度スキャン開始
startScan();
}
};
// ===================================================
// BLE Scan Callbacks
// ===================================================
class MyScanCallbacks : public NimBLEScanCallbacks {
void onResult(const NimBLEAdvertisedDevice* dev) override {
bool matchUuid = dev->haveServiceUUID() && dev->isAdvertisingService(NimBLEUUID(SERVICE_UUID));
bool matchName = !std::string(TARGET_NAME).empty() && (dev->getName() == std::string(TARGET_NAME));
Serial.printf("🔎 Found: %s RSSI=%d UUIDmatch=%d Namematch=%d\n",
dev->getName().c_str(), dev->getRSSI(), matchUuid, matchName);
if (matchUuid || matchName) {
Serial.println("🎯 Target found, stopping scan...");
NimBLEDevice::getScan()->stop();
g_adv = (NimBLEAdvertisedDevice*)dev;
g_doConnect = true;
}
}
};
// ===================================================
// Connect to Server
// ===================================================
static bool connectToServer() {
if (!g_adv) return false;
g_client = NimBLEDevice::createClient();
g_client->setClientCallbacks(new ClientCallbacks(), false);
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityPasskey(SECURITY_PIN);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO);
Serial.printf("🔗 Connecting to %s\n", g_adv->getName().c_str());
if (!g_client->connect(g_adv)) {
Serial.println("❌ Connection failed.");
return false;
}
NimBLERemoteService* svc = g_client->getService(SERVICE_UUID);
if (!svc) {
Serial.println("❌ Service not found!");
g_client->disconnect();
return false;
}
g_remoteTx = svc->getCharacteristic(CHARACTERISTIC_UUID_TX);
if (g_remoteTx && g_remoteTx->canNotify()) g_remoteTx->subscribe(true, notifyCB);
g_remoteRx = svc->getCharacteristic(CHARACTERISTIC_UUID_RX);
if (!g_remoteRx || !g_remoteRx->canWrite()) {
g_client->disconnect();
return false;
}
g_connected = true;
Serial.println("✅ Ready!");
return true;
}
// ===================================================
// Start Scan
// ===================================================
static void startScan() {
NimBLEScan* scan = NimBLEDevice::getScan();
scan->setScanCallbacks(new MyScanCallbacks(), false);
scan->setActiveScan(true);
scan->start(0, false);
Serial.println("🔍 Scanning...");
}
// ===================================================
// Setup and Loop
void setup() {
// Initialize NeoPixel
pixels.begin();
pixels.setPixelColor(0, pixels.Color(0, 50, 0));// Green color
pixels.show();// Turn on LED to indicate startup
Serial.begin(115200);
delay(200);
Serial.println("🚀 Central Advanced (Renamed)");
NimBLEDevice::init("ESP32C3-Central-Adv");
// Transmit Power
NimBLEDevice::setPowerLevel(ESP_PWR_LVL_P21, ESP_BLE_PWR_TYPE_ADV);
NimBLEDevice::setMTU(247); // 実効244 bytes(安定・実装上限)Payload + 3バイト(ATTヘッダ)
startScan();
}
void loop() {
// Handle Connection
if (g_doConnect) {
g_doConnect = false;
if (!connectToServer()) {
delay(500);// Wait before retrying
startScan();
}
}
// 定期送信処理
if (g_connected && g_remoteRx) {
static uint32_t lastTx = 0;
if (millis() - lastTx > 5000) {
lastTx = millis();
const char* msg = "Hello from Central";
g_remoteRx->writeValue((uint8_t*)msg, strlen(msg), false);
}
}
// USBシリアル受信⇒BLE送信処理
if(Serial.available()>0){
String rev_str = Serial.readStringUntil('\n'); //終端文字までを取得
g_remoteRx->writeValue((uint8_t*)rev_str.c_str(), strlen(rev_str.c_str()), false);
}
delay(20);
}
// ===================================================
plathomeio.ini設定(Client)
[env:m5stampc3u]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
build_flags =
-DARDUINO_USB_CDC_ON_BOOT
-DARDUINO_USB_MODE
lib_deps =
h2zero/NimBLE-Arduino @ ^2.3.6
adafruit/Adafruit NeoPixel@^1.12.2