🦷

ESP32でBLEデバイスを作る

2022/12/03に公開約7,300字

※この記事はLuup Advent Calendarの4日目の記事です。

以前、あるBLEデバイスを操作するAndroidアプリを開発するのに、肝心のBLEデバイスの実機が入手できないということがありました。そこで通信仕様の資料を元にESP32でダミーのBLEデバイスを作ってみたところ、よい感じに動いてくれたので事なきを得たのでした[1]

ESP32は安価で入手性もよく、Arduinoで手軽にプログラムを開発できるという利点があります。
本記事では、ESP32を使った簡単なBLEデバイスの作成例をご紹介します。

ところで、ハーラル1世という10世紀のデンマークの王様に、神経の死んだ歯があったことから付けられたあだ名「青歯王」が、Bluetoothの名前の由来だそうです[2]

ブレッドボード上に回路を作る

このサンプルでは、LEDとボタンスイッチを使ったシンプルな構成のBLEデバイスを作成します。以下の図のように、ESP32-DevKitC-32Eの GPIO22にLED、GPIO23にボタンスイッチを接続します。

breadboard

GPIO22がHIGHになるとLEDが点灯します。LEDには電流制限用の抵抗を繋ぎます。
GPIO23はプルアップ設定で使うので、ボタンを離すとHIGH、ボタンを押すとGNDと短絡してLOWになります。

BLEのしくみ

BLEの通信はセントラルペリフェラルの間で行います。

  • セントラル:接続の主体となるクライアント側(PCやスマートフォンなど)
  • ペリフェラル:セントラルの要求を受けるサーバ側(BLEデバイスなど)

通信でやり取りする値ごとに、Characteristic が定義されます。CharacteristicはServiceに、ServiceはServer(ペリフェラル、BLEデバイス)に含まれます。

GATT_Profile

CharacteristicとServiceには、識別のためのUUIDが割り当てられます。本サンプルでは、事前に生成したランダム値をUUIDとしています。

Advertisingという仕組みによって、ブロードキャスト方式のデータ送信が可能です。相互通信はできませんが、省電力で済むのでiBeaconなどに使われています。

Arduinoのソースコード解説

ソースコード
ESP32_BLE.ino
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

//---------------------------------------------------------
// Constants
//---------------------------------------------------------
#define SERVICE_UUID        "55725ac1-066c-48b5-8700-2d9fb3603c5e"
#define CHARACTERISTIC_UUID "69ddb59c-d601-4ea4-ba83-44f679a670ba"
#define BLE_DEVICE_NAME     "MyBLEDevice"
#define LED_PIN             22
#define BUTTON_PIN          23

//---------------------------------------------------------
// Variables
//---------------------------------------------------------
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
std::string rxValue;
std::string txValue;
bool bleOn = false;
bool buttonPressed = false;
int buttonCount = 0;
//---------------------------------------------------------
// Callbacks
//---------------------------------------------------------
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
    Serial.println("onConnect");
  };
  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
    Serial.println("onDisconnect");
  }
};

class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    Serial.println("onWrite");
    std::string rxValue = pCharacteristic->getValue();
    if( rxValue.length() > 0 ){
      bleOn = rxValue[0]!=0;
      Serial.print("Received Value: ");
      for(int i=0; i<rxValue.length(); i++ ){
        Serial.print(rxValue[i],HEX);
      }
      Serial.println();
    }
  }
};

//---------------------------------------------------------
void setup() {
  //
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  Serial.begin(115200);
  //
  BLEDevice::init(BLE_DEVICE_NAME);
  // Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  // Service
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // Characteristic
  pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID,
    BLECharacteristic::PROPERTY_WRITE  |
    BLECharacteristic::PROPERTY_NOTIFY
  );
  pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
  pCharacteristic->addDescriptor(new BLE2902());
  //
  pService->start();
  // Advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);
  BLEDevice::startAdvertising();
  Serial.println("startAdvertising");
}

//---------------------------------------------------------
void loop() {
  // disconnecting
  if(!deviceConnected && oldDeviceConnected){
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising();
    Serial.println("restartAdvertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if(deviceConnected && !oldDeviceConnected){
    oldDeviceConnected = deviceConnected;
  }
  // LED
  digitalWrite(LED_PIN,bleOn?HIGH:LOW);
  // BUTTON
  if(digitalRead(BUTTON_PIN) == LOW){
    if(buttonPressed == false){
      buttonCount++;
      String str = "BTN:"+String(buttonCount);
      Serial.println(str);
      // Notify
      if( deviceConnected ){
        txValue = str.c_str();
        pCharacteristic->setValue(txValue);
        pCharacteristic->notify();
      }
      buttonPressed = true;
      delay(50);
    }
  }else{
    if(buttonPressed == true){
      buttonPressed = false;
      delay(50);
    }
  }
}

LEDとボタンにはそれぞれ別のCharacteristicを定義するべきですが、本サンプルでは簡略化のために1つのCharacteristicの読み書き(NOTIFY/WRITE)に対応させています。

コールバック関数

BLEのコールバック処理を記述する2つの派生クラスを定義しています。
BLEServerCallbacksの派生クラスでは、BLEの接続状態が変化した時のコールバック関数を定義します。
BLECharacteristicCallbacksの派生クラスでは、Characteristicに関するイベントのコールバック関数を定義します。本サンプルではonWrite()のみを使用し、セントラル側から受信したデータを元に、LEDの点灯状態を切り替える変数bleOnの値を更新しています。

setup() ー 初期化処理

ピンの入出力モード、デバッグ用のシリアル通信を設定した後に、BLEを初期化しています。
BLEDevice::init()の引数で指定する文字列は、セントラル側で表示されるデバイス名になります。ServiceとCharacteristicにはそれぞれ対応するUUIDとコールバック関数クラスを指定します。Advertiseの設定も行っていますが、本サンプルではあまり使わないので特にデータは追加していません。

loop() ー メインループ

BLEの切断を検知したらすぐに再接続するのではなく、500ms待機してから再接続を試みています。[3]
bleOn変数でLEDの点灯状態を更新し、ボタンが押されたことを検知したら、カウント値を更新してnotify()でセントラル側に送信します。delay(50)はチャタリング対策のウェイトですが、メインループに影響が出てしまうため、やや手抜きの処理です。

BLE通信を試してみる

ESP32にソフトを書き込んだらスマートフォンから操作してみましょう。ここではBLEの検証ツールとしてnRF Connect for Mobileというアプリを使います。アプリを起動後にBLEデバイスをスキャンすると、Arduinoのコードで記述したデバイス名が表示されるので、CONNECTをタップして接続します。

app_01
"MyBLEDevice"というデバイスが表示されている

接続が完了すると、定義したServiceとCharacteristicが確認できます。値を書き込むために上向き矢印をタップします。

app_02
上向き矢印をタップ

表示されるダイアログからBYTE型の0x01という値を送信すると、ESP32に接続したLEDが点灯します。0x00を送信すると消灯します。

app_03
データ書き込み(WRITE)ダイアログ

次は、ESP32からデータを受信してみましょう。3本の下向き矢印のボタンをタップすると、NOTIFYの有効/無効が切り替わります。NOTIFYが有効化された状態(Notifications enabledと表示)で、ESP32側に接続したボタンスイッチを押すと、ESP32から送信されたデータがValueとして表示されます。ボタンスイッチを押すたびにValueの値が更新されます。

app_04
Valueに受信したデータ"BTN:1"がセットされている

おわりに

BLEが無線通信で使用する2.4GHzの周波数帯は、ISMバンドと呼ばれる混信の多い周波数帯です。BLEは周波数ホッピングという技術で、高速に通信周波数を切り替えて混信を回避しています。この技術は第二次世界大戦中、無線誘導の魚雷が妨害電波を回避するための仕組みとして発明されました。ハリウッド女優のヘディ・ラマーさんはこの技術の発明者です[4]

Hedy_Lamarr
Hedy Lamarr (1914-2000)

脚注
  1. AndroidのBLE実装の困難さについては、あえてここでは触れていません。 ↩︎

  2. https://www.bluetooth.com/ja-jp/about-us/bluetooth-origin/ ↩︎

  3. Espressif社が提供しているサンプルコードを参考にしています。ESP32固有の処理のようです。AndroidのBLE実装でもこの手の待機処理を入れることがあり、俗に「思いやりタイマー」と呼ばれています。 ↩︎

  4. https://patents.google.com/patent/US2292387A ↩︎

Discussion

ログインするとコメントできます