Wi-FiマイコンXIAO ESP32S3とWindowsPCとのBLE Uart通信
(1) ESP32S3単体とWindowsPCの通信
(2) ESP32S3+温度センサBMP280とWindowsPCの通信

2023.10.30 Coskx Lab  

1 はじめに

温度センサBMP280を載せたWi-FiマイコンXIAO ESP32S3からWindowsパソコンに向けてBLE(Bluetooth Low Energy)を使って温度情報を送ります。
Wi-FiマイコンとWindowsパソコン間の通信は無線LAN環境の下ではHTTPでの通信ができますが,無線LAN環境がない場合では,Bluetooth通信が便利です。
XIAO ESP32S3はBLE UART Profileを使って通信します。(Bluetooth Classicではありません。)


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

ESP32 Bluetooth4.2でのBLE通信のペアリングでは,セキュリティに関する記述をしなくてもよかったのですが,ESP32S3 Bluetooth5.0ではセキュリティに関する記述が必要になったようです。記述していないとペアリング出来ず,エラーを通知してきます。
セキュリティの関する記述には次の2つの方法があります。

(1)PIN(暗証番号)を設定する
6桁のPINをESP32S3のプログラム(setup中)に設定しておき,Androidデバイスとペアリングする際におなじPINを入力することでPairingが完了します。(ここではPINは123456の6桁です。PINは6桁にする必要があります。)
(2)PINは設定しないが認証モードを簡易なものに設定する

なお,ここで使用するWindowsPCのアプリ"BLE_serial_terminal"は起動の度に,通信相手を探して接続するようになっているため,ペアリングを必要としていません。

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の説明書を読んでプログラムの転送方法を確認します。
(USBケーブルでつないで書き込むだけでした。)

4 XIAO ESP32S3と温度センサBMP280の配線

(BLE Uart通信のテストだけならBMP280は使用しないので,ここは読み飛ばしてください)

温度センサBMP280は温度をデジタル値に変換しているので,そのデータをXIAO ESP32S3が読み出します。
XIAO ESP32S3と温度センサBMP280はI2C接続ですので,電源線,GND線の他はSCLとSDAの2本のみで通信できます。

CSBをVCCにつなぐとBMP280はI2C接続になります。(データシートによれば確実にVCCにつなぐべきとなっています)
SDO(シルク印刷は間違えていてSDDとなっている)をVCCにつなぐとBMP280のI2Cアドレスは77になります。GNDにつなぐとBMP280のI2Cアドレスは76になります。Groveライブラリのデフォルトは77なのでVCCにつないでいます。

なおBMP280ではSDA,SCLをpullupせずに動作しています。

   XIAO ESP32S3と温度センサBMP280の配線

ブレッドボードには載せないので,XIAO ESP32S3のピンを上向きに付けています。
   XIAO ESP32S3と温度センサBMP280

5 XIAO ESP32S3のスケッチコード(サーバプログラム)

ここでは,
(1)BMP280を使用せず,BLE Uart通信のテストだけのプログラム
(2)BMP280を使用して,BLE Uart通信で温度などの情報を送信するプログラム
の2つを紹介します。

XIAO ESP32S3のスケッチコードは,クライアントAndroidデバイスであってもがiOSデバイスであっても同じものを使っています。
  Androidデバイス版の解説
  iOSデバイス版の解説

5.1 BMP280を使用せず,BLE Uart通信のテスト

プログラムの概要
クライアント(WindowsPCなど)からのリクエストで,サーバであるXIAO ESP32S3は,文字列"Hello"と経過時間を,クライアントに返します。
具体的には,XIAO ESP32S3は文字列"start"を受け取ったら1秒間隔で文字列"Hello"と経過時間を送信し,文字列"stop"を受け取ったら送信をやめるようにします。

説明
UART通信ですので,プログラム中の受信のパートと送信のパートが分かれば,そこにやりたいことを書けばよいことになります。
受信時はclass MyCallbacks: public BLECharacteristicCallbacksのonWrite()が呼び出され,String rxValueに受信文字列が保存されます。
ここが受信のパートになります。
受信パートでは受信文字列が"start","stop"であった時には,作業フラグisrequestedをそれぞれtrue,falseに設定します。

loop()内でdeviceConnected およびisrequestedがtrueで,一定時間が経過したら,送信すべきデータ文字列(文字列"Hello"と経過時間)を作って,送信します。
ここが送信パートになります。

もう少し要点を絞ってみます。
◎受信時は,
 void onWrite(BLECharacteristic *pCharacteristic)
が呼び出されて,
 String rxValue = pCharacteristic->getValue();
で,String型の文字列rxValueに受信文字列が得られます。
◎送信時は,送信したいString型の文字列を
 pTxCharacteristic->setValue();
で送って
 pTxCharacteristic->notify();
で送信相手に送ったよと知らせて完了です。

なお,
 #define DEVICENAME "UART ESP32S3"
は自分の名前です。通信相手のWindowsPCなどから見つけてもらう名前です。
 #define INTERVAL 1000 //millisec
を変更するとデータ送信間隔が変わります。

 BLESecurity *pSecurity = new BLESecurity();
 pSecurity->setStaticPIN(123456);
上記2行がペアリング時に使われるPINの設定です。PINを設定しない場合は,次の2行に置き換えてください。
 BLESecurity *pSecurity = new BLESecurity();
 pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY);

次のコードは,Arduino IDEの既存スケッチ例"BLE_uart.ino"を元に作ったものです。
Arduino IDEでコンパイルするときは,ライブラリ"Grove_-_Barometer_Sensor_BMP280"を予めIDEに追加しておいてください。

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

#define DEVICENAME "UART test"
#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

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

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

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);
      pTxCharacteristic->notify();
      delay(10);

      if (rxValue.indexOf("start") == 0) { //"start" 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);
  // Create the BLE Device
  BLEDevice::init(DEVICENAME); //BLE Device Name scaned and found by clients

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

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

  pRxCharacteristic->setCallbacks(new MyCallbacks());

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

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("start advertising");
  Serial.println("Waiting a client connection to notify...");

  //The ESP32S3 bluetoth 5.0 requires security settings.
  //Without it, an error will occur when trying to pair with other devices.
  //Using a 6-digit PIN as the authentication method seems to work.
  //This PIN allows the device to be paired with an Client device.
  //Client device users will be prompted to key in a 6-digit PIN, '123456'.
  BLESecurity *pSecurity = new BLESecurity();
  pSecurity->setStaticPIN(123456);
  //Setting ESP_LE_AUTH_REQ_SC_ONLY instead of the PIN setting eliminates the need for PIN input during pairing.
  //pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY);

}

void loop() {
  if (deviceConnected) {
    if (isrequested) {
      currenttime = millis(); //elapsed time(ms)
      if (interval <= currenttime - previoustime) {
        previoustime += interval;
        char string0[256];
        sprintf(string0, "Hello %.3f\r\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
}

5.2 BMP280を使用して,BLE Uart通信で温度などの情報を送信

プログラムの概要
クライアント(WindowsPCなど)からのリクエストで,サーバであるXIAO ESP32S3は,温度センサBMP280の値を取得し,クライアントに返します。
具体的には,XIAO ESP32S3は文字列"start"を受け取ったら1秒間隔で温度などのデータを送信し,文字列"stop"を受け取ったら送信をやめるようにします。

説明
UART通信ですので,プログラム中の受信のパートと送信のパートが分かれば,そこにやりたいことを書けばよいことになります。
受信時はclass MyCallbacks: public BLECharacteristicCallbacksのonWrite()が呼び出され,String rxValueに受信文字列が保存されます。
ここが受信のパートになります。
受信パートでは受信文字列が"start","stop"であった時には,作業フラグisrequestedをそれぞれtrue,falseに設定します。

loop()内でdeviceConnected およびisrequestedがtrueで,一定時間が経過したら,温度などを測定して,送信すべきデータ文字列("温度","気圧","標高")を作って,送信します。
ここが送信パートになります。

もう少し要点を絞ってみます。
◎受信時は,
 void onWrite(BLECharacteristic *pCharacteristic)
が呼び出されて,
 String rxValue = pCharacteristic->getValue();
で,String型の文字列rxValueに受信文字列が得られます。
◎送信時は,送信したいString型の文字列を
 pTxCharacteristic->setValue();
で送って
 pTxCharacteristic->notify();
で送信相手に送ったよと知らせて完了です。

なお,
 #define DEVICENAME "UART ESP32S3"
は自分の名前です。通信相手のWindowsPCなどから見つけてもらう名前です。
 #define INTERVAL 1000 //millisec
を変更するとデータ送信間隔が変わります。

 BLESecurity *pSecurity = new BLESecurity();
 pSecurity->setStaticPIN(123456);
上記2行がペアリング時に使われるPINの設定です。PINを設定しない場合は,次の2行に置き換えてください。
 BLESecurity *pSecurity = new BLESecurity();
 pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY);

次のコードは,Arduino IDEの既存スケッチ例"BLE_uart.ino"を元に作ったものです。
Arduino IDEでコンパイルするときは,ライブラリ"Grove_-_Barometer_Sensor_BMP280"を予めIDEに追加しておいてください。

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Wire.h>
#include "Seeed_BMP280.h"

#define DEVICENAME "UART ESP32S3"
#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;
BMP280 bmp280;
int interval = INTERVAL; //millisec
int currenttime = 0; //millisec
int previoustime = 0; //millisec

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

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

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

    if (rxValue.length() > 0) {
      Serial.println("*********");
      Serial.print("Received Value: ");
      rxValue.trim();
      Serial.println(rxValue);
      Serial.println("*********");
      //Reply as is
      pTxCharacteristic->setValue(rxValue);
      pTxCharacteristic->notify();
      delay(10);

      if (rxValue.indexOf("start") == 0) { //"start" has found
        pTxCharacteristic->setValue("tempreture[degC], pressure[hPa], altitude[m]\r\n");
        pTxCharacteristic->notify();
        delay(10);
        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);
  // Create the BLE Device
  BLEDevice::init(DEVICENAME); //BLE Device Name scaned and found by clients

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

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

  pRxCharacteristic->setCallbacks(new MyCallbacks());

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

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("start advertising");
  Serial.println("Waiting a client connection to notify...");

  //The ESP32S3 bluetoth 5.0 requires security settings.
  //Without it, an error will occur when trying to pair with other devices.
  //Using a 6-digit PIN as the authentication method seems to work.
  //This PIN allows the device to be paired with an Client device.
  //Client device users will be prompted to key in a 6-digit PIN, '123456'.
  BLESecurity *pSecurity = new BLESecurity();
  pSecurity->setStaticPIN(123456);
  //Setting ESP_LE_AUTH_REQ_SC_ONLY instead of the PIN setting eliminates the need for PIN input during pairing.
  //pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY);

  //start BMP280
  if(!bmp280.init()){
    Serial.println("Device error!");
  }
}

void loop() {
  if (deviceConnected) {
    if (isrequested) {
      currenttime = millis(); //elapsed time(ms)
      if (interval <= currenttime - previoustime) {
        previoustime += interval;
        float tempreture = bmp280.getTemperature();
        float pressure = bmp280.getPressure();
        float altitude = bmp280.calcAltitude(pressure);
        char string0[256];
        sprintf(string0, "%.1f, %.0f, %.2f\r\n", tempreture, pressure/100.f, altitude);
        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
}

6 WindowsパソコンとXIAO ESP32S3のペアリング

◎このペアリング作業は不要です。
XIAO ESP32S3を起動後,WindowsパソコンのBluetoothの設定で新しいデバイスとペア設定に進むと,XIAO ESP32S3のデバイス名として設定した"UART ESP32S3"が見つかると思います。
そこでPINを要求されるので,XIAO ESP32S3のプログラム中で設定したPIN "123456"を入力するとpairingが完了します。

7 Windowsアプリ"BLE_serial_terminal"の準備

"BLE_serial_terminal"をダウンロードしてインストールします。
  "BLE_serial_terminal"β版のダウンロード

  "BLE_serial_terminal"のアイコン

8 XiaoESP32S3と"BLE_serial_terminal"の通信

先にXiaoESP32S3に電源を与え,立ち上げておきます。

  "BMP280が接続されたXIAO ESP32S3(紙のアンテナがついています。)

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

  BLE_serial_terminal デバイス選択画面

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

"BLE_serial_terminal"から"start"を送信すると,1秒ごとにデータが送信されてきます。
受信した3つの数値は温度[℃],気圧[hPa],標高[m]を表しています。
"BLE_serial_terminal"から"stop"を送信すると,データ送信が止まります。
"BLE_serial_terminal"から"start"や"stop"を送信するときは,画面上部のテキストボックスに入力してボタンSendをタップします。


    BLE_serial_terminal 通信画面


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

9 通信距離

WindowsPCをCentralとして,XIAO ESP32S3に付属の紙のようなアンテナをつけた状態で通信距離を測定しました。
見通しの良い直線で130mまでの通信が確認できました。
測定場所の都合でそれ以上距離を伸ばせなかったですが,通信限界はもう少し先になると思います。
Bluetooth4.0(LE)は通信範囲100mと言われています。(手元で試したら直線の見通せる場所で50mでした。)
Bluetooth5.0(LE)は通信範囲400mと言われています。

10 発熱に関して

通信時には指先で触っていられないほどESP32S3が高温になっていたので気になったので長時間通信を行いました。
WindowsPCをCentralとして,XIAO ESP32S3に付属の紙のようなアンテナをつけた状態で通信を行いました。
通信は1秒ごとに行われ,XIAO ESP32S3からWindowsPCに温度[℃],気圧[hPa],標高[m]を送信していました。

30℃を超える気温の中で,連続送信していたところ,1時間程度で通信が途絶えてしまいました。
再度送信を開始したところ,今度は3時間ほどで通信が途絶えてしまいました。
気温が30℃を下回ったところで,再再度送信を開始したところ,8時間の送信で異常は起きませんでした。
周囲温度が高いときは,異常終了する可能性があることがわかりました。

一般に通信距離が2倍になると4倍の電力を消費するため,この発熱はBluetooth5.0(LE)になったことに起因していると考えられます。

11 まとめ

(1)Wi-FiマイコンXIAO ESP32S3とWindowsパソコンでBLE Uart通信し,Windowsパソコンからの指令によって文字列をXIAO ESP32S3が送るプログラムを示しました。
(2)Wi-FiマイコンXIAO ESP32S3とWindowsパソコンでBLE Uart通信し,Windowsパソコンからの指令によって温度などのデータをXIAO ESP32S3が送るプログラムを示しました。

XIAO ESP32S3側のプログラムで,受信文字列を取り出す部分と,送信したい文字列を送信する部分を示しました。

XIAO ESP32S3がBLE通信するときは,発熱が大きく問題が生ずるの可能性があるため,放熱対策をしたほうが安全と思われます。

この記事ではWindowsパソコンのアプリ"BLE_serial_terminal"を使いました。
AndroidデバイスにもiOSデバイスにも同様なアプリがあり,"BLE_serial_terminal"と同じ使い方ができます。
  Androidデバイス版の解説
  iOSデバイス版の解説