0
0

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によるCoded PHY(S=8)の利用

Posted at

概要

先日投稿したNimBLE-ArduinoによるUARTライクなBLE通信に関して、
より長距離通信向けの設定Coded PHY(S=8)があることを知り
さっそく試してみました。速度は通常の1Mbpsに対して125kbps
まで遅くなるみたいですが普段利用するUARTと同程度なので問題無しです。

規格の詳細については
ムセンコネクトさんで詳しく解説されておりますのでご参照ください。
https://www.musen-connect.co.jp/blog/course/trial-production/coded-phy-basic/

↓LEDが紫点灯しているのでS=8で接続できています。
image.png

↓特に問題なく送受信できています。
セントラル側(Client側)では受信強度も出力しています。隣合わせなのでとても強いです。
image.png

コード(Peripheral/Server) 

#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Adafruit_NeoPixel.h>

// ---- S=8 フォールバック定義(環境によりヘッダが無い場合に備える)----
#ifndef BLE_HCI_LE_PHY_CODED_S8_PREF
#define BLE_HCI_LE_PHY_CODED_S8_PREF 0x02   // HCI Set PHY の "phy options" 値(S=8)
#endif

// ==============================
//  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!");

        // ★ 接続直後に Coded PHY (S=8) を明示要求
        const uint16_t handle = connInfo.getConnHandle();
        bool ok = pServer->updatePhy(
            handle,
            BLE_GAP_LE_PHY_CODED_MASK,                // TX: Coded
            BLE_GAP_LE_PHY_CODED_MASK,                // RX: Coded
            BLE_HCI_LE_PHY_CODED_S8_PREF              // ★ S=8 を優先
        );
        Serial.printf("➡ updatePhy(Coded S=8) request: %s\n", ok ? "OK" : "NG");

        // 2) 接続パラメータを安定寄りに(30–50ms / latency 0 / timeout 6s)// 1. おすすめ:安定寄り(Coded S=8向き)
        const uint16_t minItv  = 24;   // 24 * 1.25ms = 30ms
        const uint16_t maxItv  = 40;   // 40 * 1.25ms = 50ms
        const uint16_t latency = 0;    // 応答性優先(省電力なら 4 でも可)
        const uint16_t timeout = 600;  // 600 * 10ms = 6000ms
        pServer->updateConnParams(handle, minItv, maxItv, latency, timeout);
        Serial.println("➡ Connection parameters update requested.");

        /*
        // 2. 近距離・高速寄り(必要時に切替)
        static bool setFastConnParams(uint16_t connHandle) {
        const uint16_t minItv = 6;    // 7.5ms
        const uint16_t maxItv = 12;   // 15ms
        const uint16_t latency = 0;
        const uint16_t timeout = 300; // 3s
        return NimBLEDevice::updateConnParams(connHandle, minItv, maxItv, latency, timeout);
        }
        */

        // 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!");
    }
    // ★ 追加:PHY更新が完了したら呼ばれる(結果確認用)
    void onPhyUpdate(NimBLEConnInfo& info, uint8_t txPhy, uint8_t rxPhy) override {
        (void)info;
        auto nameOf = [](uint8_t phy)->const char*{
            if (phy == 1) return "1M";
            if (phy == 2) return "2M";
            if (phy == 3) return "Coded"; // NimBLE内部表現
            return "?";
        };
        Serial.printf("📶 PHY updated: TX=%s, RX=%s\n", nameOf(txPhy), nameOf(rxPhy));

        // CodedになったらLEDを紫に
        if (txPhy == 3 || rxPhy == 3) {
            pixels.setPixelColor(0, pixels.Color(50, 0, 50));
            pixels.show();
        }
    }
};

// ===================================================
//  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);

    // ★ 既定PHYを Coded 優先に(接続時の初期選好)
    //    TX/RX ともに「Codedのみ」を希望。実際の確定は updatePhy で S=8 を明示。
    bool prefOK = NimBLEDevice::setDefaultPhy(
        BLE_GAP_LE_PHY_CODED_MASK,  // TX prefer
        BLE_GAP_LE_PHY_CODED_MASK   // RX prefer
    );

    // 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();
    }
}
// ===================================================

コード(Central/Client) 

#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Adafruit_NeoPixel.h>

// ---- S=8 フォールバック定義(環境によりヘッダが無い場合に備える)----
#ifndef BLE_HCI_LE_PHY_CODED_S8_PREF
#define BLE_HCI_LE_PHY_CODED_S8_PREF 0x0002   // HCI Set PHY: phy_options, 0x02 = S=8優先
#endif

// ==============================
//  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!");

    // 接続直後に Coded S=8 を要求(中央側からの明示交渉)
    bool ok = pClient->updatePhy(
      BLE_GAP_LE_PHY_CODED_MASK,    // TX: Coded
      BLE_GAP_LE_PHY_CODED_MASK,    // RX: Coded
      BLE_HCI_LE_PHY_CODED_S8_PREF  // S=8 優先
    );
    Serial.printf("➡ Client updatePhy(Coded S=8) request: %s\n", ok ? "OK" : "NG");

    // 安定寄りの接続パラメータ(30–50ms / latency 0 / timeout 6s)
    const uint16_t minItv  = 24;   // 30ms (×1.25ms)
    const uint16_t maxItv  = 40;   // 50ms
    const uint16_t latency = 0;
    const uint16_t timeout = 600;  // 6s (×10ms)
    bool ok2 = pClient->updateConnParams(minItv, maxItv, latency, timeout);
    Serial.printf("➡ Client conn params update: %s\n", ok2 ? "OK" : "NG");

    // LED表示
    if(ok2){
      // S8接続でLED紫点灯
      pixels.setPixelColor(0, pixels.Color(50, 0, 50));
      pixels.show();
    } else{
      // 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();
  }
  // (必要なら)Conn Params 更新通知を受けたい場合:
  // void onConnParamsUpdate(uint16_t interval, uint16_t latency, uint16_t timeout) override {
  //   Serial.printf("🔧 ConnParams updated (client): interval=%.2f ms, latency=%u, timeout=%.1f s\n",
  //                 interval * 1.25f, latency, timeout * 0.01f);
  // }
};

// ===================================================
//  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");

  // ---- 既定PHYを Coded 優先(接続時の初期選好)----
  //     実際の確定は onConnect() で client->updatePhy(S8) を明示
  bool prefOK = NimBLEDevice::setDefaultPhy(
    BLE_GAP_LE_PHY_CODED_MASK,  // TX prefer
    BLE_GAP_LE_PHY_CODED_MASK   // RX prefer
  );
  Serial.printf("setDefaultPhy(Coded): %s\n", prefOK ? "OK" : "NG");


  // 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);

      int rssi = g_client->getRssi(); // 接続中RSSI
      Serial.printf("[LINK] RSSI=%d dBm\n", rssi);
    }
  }

  // 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);
}
// ===================================================
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?