概要
先日投稿したNimBLE-ArduinoによるUARTライクなBLE通信に関して、
より長距離通信向けの設定Coded PHY(S=8)があることを知り
さっそく試してみました。速度は通常の1Mbpsに対して125kbps
まで遅くなるみたいですが普段利用するUARTと同程度なので問題無しです。
規格の詳細については
ムセンコネクトさんで詳しく解説されておりますのでご参照ください。
https://www.musen-connect.co.jp/blog/course/trial-production/coded-phy-basic/
↓特に問題なく送受信できています。
セントラル側(Client側)では受信強度も出力しています。隣合わせなのでとても強いです。

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