2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NimBLE-ArduinoによるUARTライクなBLE通信

Last updated at Posted at 2025-10-19

概要

以前、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通信が確立できました。

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

image.png
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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?