BLE Uart通信 (XIAO ESP32S3)
BLE Uart通信の舞台裏はclass Handy_BLE_Uartに任して便利に使う

2026.4.17 2025.7.12 Coskx Lab  

1 はじめに

ESP32でBLE Uart通信を行いますが,BLE Uart通信に関する記述をクラス化し,初期化,送信,受信を自分で記述するようにします。
ここではAndroidデバイスとの通信を例として載せましたが,iOS機器やWindowsPC相手の通信も可能です。
BLE UART Profileを使って通信します。(Bluetooth Classicではありません。)


BLEコネクション通信では,Central(or Master or Observer)とPeripheral(or Slave or Broadcaster)の間で通信が行われます。
ここでは,AndroidをCentral,XIAO-ESP32S3をPeripheralとして使うことにします。
(PeripheralはServer,CentralはClientと説明されることもあります。)

起動手順は次のようになります。

  1. Peripheralはアドバタイジングを行い,デバイス名やデバイスアドレスなどを発信します
  2. Centralはスキャンして,アドバタイジングしているデバイスを見つけ,リストで表示します
  3. ユーザはCentralのリスト表示からデバイスを選択して,Centralから接続要求します
  4. Peripheralが接続を確認すると,交信可能となります
  5. (交信が終了したら,ユーザはCentralから切断要求します)

なお,ここで紹介する交信例では,Central側の通信アプリは,アプリ起動の度に通信相手を探して接続するようになっているため,ペアリングを必要としていません。
ただし,Peripheral側がペアリング・ボンディングを要求する設定になっている場合は,Central側の通信アプリが接続動作の際に,Central側の設定機構(OSの一部)がペアリング・ボンディングを行ないます。(ユーザの作業が必要です)

2 使用環境

3 準備

3.1 ArduinoIDE

(1)Arduino IDEのメニューバーから[ファイルFile] -> [環境設定(Preferences)]
Additional board manager URLsに
https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
および
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
を設定してOK
(2)ツール[Tools] > ボード[Board] > Boards Manager
検索boxに「ESP」を入力すると複数のボード候補が出てくるので
esp32 by Espressif Systems
をinstall
(3)ツール[Tools] > ボード[Board]でESP32を探すとXIAO ESP32S3が見つかるので,これを選ぶ。

3.2 XIAO ESP32S3

XIAO ESP32S3の説明書を読んで,ArduinoIDEで制作したプログラムをXIAO ESP32S3に転送する方法を確認しておきます。
(USBケーブルでつないで書き込むだけです。)

4 XIAO ESP32S3のスケッチコード(Peripheralプログラム)

プログラムでは,BLEスタックを初期化して,通信先が接続操作を行うと接続が完了します。
 (1)ESP32S3側がbleuart.begin() でアドバタイジングします。
  (自分の名前を送信しながら私はここです。誰か見つけて!)
  (ただし,この作業は舞台裏で進行するので,プログラムコードには現れません)
 (2)この時,スマホなどのターミナルソフトが,ESP32S3のアドバタイジングを
  見つけるので,スマホ側でユーザが接続(コネクト)操作をすると,接続が完成します。

ここでのプログラム例では,コネクトが完成した後,通信相手の機器から文字列を受け取り,文字列が「go」だったら,現在時刻(起動してからmilli秒)を1秒ごとに送信し,文字列が「stop」だったら現在時刻の送信をやめます。

このプログラムでは文字列受信時にやりたいことを記述した関数は,void StringReceptionReport(String str)としました。送られてくるstrが受信した文字列です。 (この関数の名前は別のものでもOKです)
やりたいこととは,受信した文字列「go」「stop」によりブール変数「requested」のtrue falseを切り替えることです。
ESP32S3側が文字列を送信するときは,関数transferString()を使います。

DEVICENAME "eps32-uart-demo"
は自分の名前です。通信相手のAndroidスマホなどから見つけてもらう名前です。初期化の時点で決定します。

このプログラムではアドバタイジングを含むBLE通信の面倒なところをラッパークラスHandy_BLE_Uartに任せることにして,別ファイルに書いています。

ラッパークラスHandy_BLE_Uartの定義は「Handy_BLE_Uart.h」と「Handy_BLE_Uart.cpp」に書かれています。
ダウンロード,解凍して次のプログラムコードBLE_uart_DEMO.inoと同じフォルダに入れてください。

  Handy_BLE_Uartのダウンロード

フォルダBLE_uart_DEMOにプログラムBLE_uart_DEMO.ino,Handy_BLE_Uart.h,Handy_BLE_Uart.cppを入れたところを示します。 そうすると,ArduinoIDEの画面の上部にそれぞれのファイル名が表示されるようになり,クリックするとArduinoIDEでファイルの中を見ることができます。



なお,ペアリング・ボンディングを要求したい場合は,プログラムBLE_uart_DEMO.inoの 初期化のときに
bleuart.begin(DEVICENAME, true);
ではなく
bleuart.begin(DEVICENAME, true, 123456);
のように6桁のパスキーを追加します。(デフォルトでパスキーは必ず6桁の数です。)


   BLE_uart_DEMO.inoのプログラムコード

//BLE_uart_DEMO.ino//
//XIAO-ESP32S3

#include <Handy_BLE_Uart.h>

#define DEVICENAME "eps32-uart-demo"

Handy_BLE_Uart bleuart;

void setup() {
  Serial.begin(115200);
  delay(300);
  //受信時に呼び出されるコールバック関数の登録 必須
  bleuart.setCallbackFunc(StringReceptionReport);
  //接続時にCentralに送信するメッセージの設定 必要があったら
  bleuart.setInitialMessage("send me 'go' or 'stop'");
  //初期化 (パスキーによるペアリングを行う場合は6桁のパスキーを付ける)
  //引数:自分のデバイス名登録,受信文字列のエコーバックの有無,(パスキー)
  bleuart.begin(DEVICENAME, true);
  //bleuart.begin(DEVICENAME, true, 123456);
  delay(1000);
}

bool requested = false;

void loop() {
  if (bleuart.isConnected()) {
    //Centralに接続中の場合
    if (requested) {
      long currenttime = millis();
      //文字列 String(currenttime) を送信
      //起動からの経過時間をミリ秒で表示
      bleuart.transferString(String(currenttime));
    }
  } else {
    requested = false;
  }
  delay(1000);
}

//受信時に呼び出されるコールバック関数
void StringReceptionReport(String str) {
  Serial.println("RX_Value = " + str);
  if (str.indexOf("go") == 0) requested = true;
  else if (str.indexOf("stop") == 0) requested = false;
}

プログラムコードに関して補足します。
setup中では,
 〇受信時に呼び出されるコールバック関数の登録
  bleuart.setCallbackFunc()
 〇接続時にCentralに送信するメッセージの設定
  bleuart.setInitialMessage()
 〇初期化
  bleuart.begin()
の3つを行い,loop中では
 〇centralに接続中なら何かの仕事をします
  if (bleuart.isConnected()) {
で作業を記述します。

centralへ文字列を送信するときは
 bleuart.transferString()
を使い,centralから文字列を受信した場合はコールバック関数
 StringReceptionReport()
が舞台裏から呼び出されます。


参考 よくあるサンプルプログラムコード

ここでは細かな説明をしませんが,ラッパークラスHandy_BLE_Uartを使用しないで, すべてを1つのプログラムコードとして記述すると次のようになります。

   BLE_uart_justtest.inoのプログラムコード

//BLE_uart_justtest.ino//

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLESecurity.h>
#include <BLE2902.h>
#include <Wire.h>

#define DEVICENAME "UART-E-2"
#define INTERVAL 1000 //millisec

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
boolean isrequested = false;
int interval = INTERVAL; //millisec
int currenttime = 0; //millisec
int previoustime = 0; //millisec
#define PASSKEY 123456

/* Securityを使うときに使用される それ以外のときでもあっても問題ない*/
class MySecurityCallbacks : public BLESecurityCallbacks {
  // パスキーを表示する場合
  uint32_t onPassKeyRequest(){ return PASSKEY; }
  void onPassKeyNotify(uint32_t pass_key){}
  bool onConfirmPIN(uint32_t pass_key){ return true; }
  bool onSecurityRequest(){ return true; }
};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    delay(300);
    deviceConnected = true;
    Serial.println("** device connected");
    delay(300);
  };

  void onDisconnect(BLEServer* pServer) {
    delay(300);
    deviceConnected = false;
    Serial.println("** device disconnected");
    delay(300);
  }
};

class MyCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    String rxValue = pCharacteristic->getValue();

    if (rxValue.length() > 0) {
      //Display on serial monitor for debug
      Serial.println("*********");
      Serial.print("Received Value: ");
      rxValue.trim();
      Serial.println(rxValue);
      Serial.println("*********");
      //Reply as is
      pTxCharacteristic->setValue(rxValue+"\n");
      pTxCharacteristic->notify();
      delay(10);
      int ConnectedCount = pServer->getConnectedCount();
      Serial.print("onnectedCount = ");
      Serial.println(ConnectedCount);

      if (rxValue.indexOf("go") == 0) { //"go" has found
        isrequested = true;
        previoustime = millis(); //elapsed time(ms)
        previoustime -= interval;
      } else if (rxValue.indexOf("stop") == 0) { //"stop" has found
        isrequested = false;
      }
    }
  }
};

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println("Starting BLE UART");

  Serial.print("Using BLE stack: ");
  Serial.println(BLEDevice::getBLEStackString());

  // Create the BLE Device
  BLEDevice::init(DEVICENAME);

  //MTU: Maximum Transmission Unit
  BLEDevice::setMTU(256);

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
                    CHARACTERISTIC_UUID_TX,
                    BLECharacteristic::PROPERTY_NOTIFY
                  );
                      
  pTxCharacteristic->addDescriptor(new BLE2902());
  //標準でNimBLEが使われるため,明示的に書く必要がなくなっている
  //書いておいてもよい

  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
                    CHARACTERISTIC_UUID_RX,
                    BLECharacteristic::PROPERTY_WRITE
                  );

  pRxCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();

  BLEAdvertising* adv = BLEDevice::getAdvertising();
  //adv->setName(DEVICENAME); //これでもよいがデバイス名は8文字までになってしまう
  //adv->addServiceUUID(SERVICE_UUID); //これでもよい

  BLEAdvertisementData advData = BLEAdvertisementData();
  advData.setName(DEVICENAME);
  //advData.setCompleteServices(BLEUUID(SERVICE_UUID));
  advData.setPartialServices(BLEUUID(SERVICE_UUID));
  adv->setAdvertisementData(advData);

  BLEAdvertisementData scanData = BLEAdvertisementData();
  scanData.setName(DEVICENAME);
  //                                  advData.setCompleteServices(BLEUUID(SERVICE_UUID));
  //scanData.setPartialServices(BLEUUID(SERVICE_UUID));
  adv->setScanResponseData(scanData);

  adv->setScanResponse(true); // スキャンレスポンスを有効に

  adv->setMinPreferred(0x06); // 7.5ms
  adv->setMaxPreferred(0x12); // 22.5ms
  adv->start();

  Serial.println();
  Serial.println(DEVICENAME);
  Serial.println("start advertising");
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  if (deviceConnected) {
    if (isrequested) {
      currenttime = millis(); //elapsed time(ms)
      if (interval <= currenttime - previoustime) {
        previoustime += interval;
        char string0[256];
        sprintf(string0, "Hello %.3f\n", currenttime/1000.f);
        pTxCharacteristic->setValue((String)string0);
        pTxCharacteristic->notify();
        delay(10);
      }
    }
  } else {
    isrequested = false;
  }
  
  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }

  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
  delay(2);//allow the cpu to switch to other tasks

  if (!deviceConnected) {
    static int cntr = 0;
    if (++cntr == 5000) {
      Serial.print(".");
      cntr = 0;
    }
  }
}


5 Androidスマホアプリ"Serial Bluetooth Terminal"の準備

Google Playから,"Serial Bluetooth Terminal"をダウンロードしてインストールします。

  "Serial Bluetooth Terminal"のアイコン

最初の起動で,メニュー等の探検をします。
〇左上三本線メニューでは
settings→terminal→Show timestampをONにしておくとよいでしょう。
settings→terminal→Buffer sizeは表示内容を保存する作業メモリのサイズです。できるだけ大きくとるのが良いと思います。(200kBでは100文字のデータ約2000行分です。場合によっては無制限もあり)
settings→Receiveは,NLを設定してください。
settings→Send→Local echoはONが標準です。自分が送信した文字列も表示されます。
settings→Misc.→Save....は表示内容(LOG)保存先フォルダの設定です。
〇右上3点メニューには
Data→Saveがあって,表示内容を保存の時使います。

6 XiaoESP32S3と"Serial Bluetooth Terminal"の通信

事前のペアリング作業は不要です。
先にXiaoESP32S3に電源を与え,立ち上げておきます。(XiaoESP32S3はアドバタイジング中となる)

その後,"Serial Bluetooth Terminal"で 左上三本線メニューでDevices→BluetoothLE→SCAN で付近のBLEデバイスを見つけて表示してくれます。
ここでは,XiaoESP32S3のプログラムの
   #define DEVICENAME "eps32-uart-demo"
でつけた名前"eps32-uart-demo"が見つかるのでこれをタップします。

  Serial Bluetooth Terminalデバイス選択画面

これでコネクトが完成します。

"Serial Bluetooth Terminal"から"go"を送信すると,1秒ごとに時刻データが送信されてきます。
"Serial Bluetooth Terminal"から"stop"を送信すると,データ送信が止まります。
画面上の受信データは,右上3点メニューからData→Saveでファイル保存することができます。

"Serial Bluetooth Terminal"から"go"や"stop"を送信するときは,画面下部のテキストボックスに入力して送信ボタンをタップするか,予めマクロボタンに"go"や"stop"の文字列を登録(マクロボタンのタップ&ホールドで登録出来る)しておいて,それらのボタンをタップます。
次の通信画面では,予めマクロボタンに"go"や"stop"の文字列が登録されています。


    Serial Bluetooth Terminal通信画面

7 まとめ

ESP32S3でBLEuart通信する舞台裏作業をラッパークラスHandy_BLE_Uartに封入して,メイン側プログラムの負担を減らしました。
Androidスマホからの指令によって文字列をXIAO ESP32S3が送るプログラム例を示しました。



この記事ではAndroidスマホのアプリ"Serial Bluetooth Terminal"を使いました。
他にも"Bluefruit Connect"というアプリがあり,同じような使い方が出来ます。 → 付録1
iPhoneにもアプリ"Bluefruit Connect"がありAndroid版"Bluefruit Connect"と同じ使い方ができます。
WindowsPCにも同様なアプリ"BLE_serial_terminal"があり,同様の使い方ができます。 → 付録2
接続作業などは以下を参考にしてください。
  iOSデバイスとの接続
  WindowsPCとの接続

追加情報

ボードパッケージ「ESP32 by Espressif Systems Ver3.3.6」ではBLE関連のライブラリが動作不調になりましたが,「ESP32 by Espressif Systems Ver3.3.7」で解消されました。
これによって,ESP32の標準ライブラリのBLEが舞台裏でNimBLEに変更されています。

ライブラリNimBLE-Arduino/h2zero 2.4.0は,これまでNimBLEを使う際の標準的なライブラリでした。しかしセキュリティ・ペアリング・ボンディングが,このライブラを利用した機器同士でしか動作が保証されないため,今後はESP32の標準ライブラリのBLEを使用するのが良いと思われます。
20260218




付録1 アプリ"Bluefruit Connect"

アプリ"Bluefruit Connect"にはAndroid版とiOS版があり,同じ操作で使うことができて便利です。

付録 1-1 Androidデバイスアプリ"Bluefruit Connect"の準備

Android playから,"Bluefruit Connect"をダウンロードしてインストールします。

  "Bluefruit Connect"のアイコン

付録 1-2 XiaoESP32S3と"Bluefruit Connect"の通信

事前のペアリング作業は不要です。
先にXiaoESP32S3に電源を与え,立ち上げておきます。

その後,"Bluefruit Connect"を立ち上げると最初にSelect Deviceの画面になります。
ここでは,XiaoESP32S3のプログラムの
   #define DEVICENAME "eps32-uart-demo"
でつけた名前"eps32-uart-demo"が見つかるのでconnectボタンをタップします。

  Bluefruit Connectデバイス選択画面

次にModulesと書かれたモジュール選択画面になりますので,UARTを選択します。

  Bluefruit Connectモジュール選択画面

UARTの白い画面になったら,"Bluefruit Connect"から"go"を送信すると,1秒ごとにデータが送信されてきます。
"Bluefruit Connect"から"stop"を送信すると,データ送信が止まります。
"Bluefruit Connect"から"go"や"stop"を送信するときは,画面下部のテキストボックスに入力してSENDボタンをタップします。


    Bluefruit Connect通信画面

画面上の送受信データの表示形式は,右上メニューからDisplay Modeで切り替えられます。
(time stamp付きの表示もできます。)

画面上の送受信データのファイルへの保存は,右上メニューからExportで出来ます。
保存形式をcsvにするとtime stamp付きでの保存になります。




付録2 アプリ"BLE_serial_terminal"

アプリ"BLE_serial_terminal"はWindowsPC用のターミナルアプリです。
WindowsPCにはbluetooth(4.2以上)が使えることが必要です。
次のように使います。


付録 2-1 WindowsPCアプリ"BLE_serial_terminal"の準備

Githubから"BLE_serial_terminal"β版をダウンロードします。
  Github "BLE_serial_terminal"


  "BLE_serial_terminal"のアイコン

付録 2-2 XiaoESP32S3と"BLE_serial_terminal"の通信

事前のペアリング作業は不要です。
先にXiaoESP32S3に電源を与え,立ち上げておきます。

その後,"BLE_serial_terminal.exe"を立ち上げるとすぐにBruetoothLEデバイスをスキャンして選択リストに表示します。
ここでは,XiaoESP32S3のプログラムの
   #define DEVICENAME "eps32-uart-demo"
でつけた名前"eps32-uart-demo"が見つかるのでこれをクリックして選択します。
見つからなかったら,ボタン"Scan BLE Dev"をクリックして,にBruetoothLEデバイスを再スキャンします。

選択したら,ボタンConnectをクリックします。>connectedが表示されたら,通信準備完了です。
"Time Stamp"および"Local Echo"のオプションをONにしておくとよいでしょう。

"BLE_serial_terminal"から"go"を送信すると,1秒ごとにデータが送信されてきます。
"BLE_serial_terminal"から"stop"を送信すると,データ送信が止まります。
"BLE_serial_terminal"から"go"や"stop"を送信するときは,画面上部のテキストボックスに入力してボタンSendをクリックします。

"BLE_serial_terminal"から"go"や"stop"を送信するときは,画面下部のテキストボックスに入力して送信ボタンをタップするか,予めマクロボタンに"go"や"stop"の文字列を登録(マクロボタンを右ボタンクリックで登録出来る)しておいて,それらのボタンをクリックします。
次の通信画面では,予めマクロボタンに"go"や"stop"の文字列が登録されています。


    BLE_serial_terminal 通信画面

画面上の送受信データのファイルへの保存は,右上のボタン"Save Log"で出来ます。