Seeed Studio XIAO nRF52840でBLEのセントラルデバイスを実装する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Seeed Studio XIAO nRF52840はBLE(Bluetooth Low Energy)を搭載しており、ArduinoBLEライブラリでBLE通信ができます。BLEのセントラルデバイスを実装しペリフェラルデバイスと双方向通信を行う方法をまとめました。

本記事ではSeeed Studio XIAO nRF52840の2台使用します。1台目をペリフェラルデバイス(以下nRF52840(P)とする)とし、2台目をセントラルデバイス(以下nRF52840(C)とする)として使用します。ペリフェラルデバイスは下記記事のソースコードを使用しています。

Seeed Studio XIAO nRF52840でBLEのサービスを生成する

Seeeduino XIAO(Seeed Studio XIAO SAMD21)で動作確認したことについてリンクをまとめています。

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

Seeed Studio XIAOの開発環境を作る

Seeed Studio XIAOの開発環境はArduino環境で開発する場合はArduino IDEのインストールが必要です。Arduino IDEでSeeed Studioが公開しているボードマネージャーのURLを追加するとSeeed StudioのボードがArduino IDEでインストールできるようになります。下記記事にSeeed Studio XAIOの開発環境の作り方を説明しています。

Seeed Studio XIAO nRF52840の開発環境を作る

BLEを使用するにあたって追加するボードの選択について説明します。ボードマネージャーでSeeed nRF52 Boards mbed-enabled Boardsをインストールします。

ボードマネージャでSeeed nRF52 mbed-enabled Boardsを選択してインストール
ボードマネージャでSeeed nRF52 mbed-enabled Boardsを選択してインストール

Arduino IDEのツールからボードマネージャーを選択するとボードの検索画面に遷移します。

ボードマネージャで検索欄に「seeed」と入力するとSeeed nRF52 mbed-enabled Boardsが候補として表示されます。(URLを追加していないと表示されません)このボードマネージャをインストールします。インストール後はボードの選択を行います。

BLEで使用するArduinoBLEライブラリを使用する場合はSeeed nRF52 mbed-enabled Boardsをインストールする必要があります。

広告

ArduinoBLEライブラリを実装する

BLE通信の大きな概念としてセントラルデバイスペリフェラルデバイスの定義があります。

セントラルデバイスはパソコンに加えてスマホやタブレットなどの高い処理とメモリを備えたデバイスです。

ペリフェラルデバイスはセントラルデバイスに接続できる小型で低電力のデバイスであり、BLE対応のイヤホンやFitBit Charge4(私が愛用しているスマートウォッチ)などです。IoT機器の多くがペリフェラルデバイスに分類されます。

セントラルデバイスとペリフェラルデバイスのイメージ
セントラルデバイスとペリフェラルデバイスのイメージ

イメージ図のようにセントラルデバイスはnRF52840(C)を使用します。ペリフェラルデバイスはnRF52840(P)を使用します。nRF52840(C)はnRF52840(P)のアドバタイジングパケットを確認して接続要求を行います。接続が成功すると1対1の双方向通信を行います。

ペリフェラルデバイスはサービスとキャラクタリスティックを構成するサーバーとして動作します。セントラルデバイスはサーバーに接続要求を送信するクライアントとして動作します。

サーバーはアドバタイジングパケットでデバイスの存在を通知してクライアントからの接続要求を待ちます。クライアントから接続要求によってトランザクション(データの送受信)を開始します。

トランザクションはプロフィール、サービス、キャラクタリスティックによって構成されています。プロフィールはサービスの組み合わせたものです。サービスはデータの種別を区別するために使用され、1つ以上のキャラクタリスティックを持つことができます。

サービスはUUIDで他のサービスと区別されます。16ビットUUIDはBlutooth認証したサービスに使用しますが、それ以外の任意のサービスは128ビットUUIDを使用します。キャラクタリスティックについても同様です。

キャラクタリスティックはデータの読み書きに使用します。送受信するデータの種別(整数、テキスト形式)に応じて使い分けます。

セントラルデバイスからペリフェラルデバイスにデータを読み書きする場合はキャラクタリスティックのUUIDが一致する必要があります。

スポンサーリンク

ペリフェラルデバイスと共通の設定

ペリフェラルデバイスが生成しているサービスとキャラクタリスティックについて下記記事からセントラルデバイスで使用する分を抜粋して説明します。詳細は下記記事を参考にしてください。

Seeed Studio XIAO nRF52840でBLEのサービスを生成する

ペリフェラルデバイスではキャラクタリスティックのUUIDを下記のように指定しています。UUIDは”19740bd~ee27″の値です。

//キャラクタリスティックのインスタンス化
BLEByteCharacteristic switchCharacteristic("197f0bd9-de9c-6d0c-eaf4-09728681ee27", BLERead | BLEWrite);

BLE.setLocalName("LED3Control"); //サービスの名前

ペリフェラルデバイスでキャラクタリスティックをBLEByteCharacteristicクラスの変数としてswitchCharacteristic()をインスタンス化しています。第1引数のUUIDはセントラルデバイスにおいて同じUUIDを指定して書き込み操作を行います。第2引数では操作の種別を指定していますが、Read/Writeを指定しています。

アドバタイジングパケットで表示するサービス名を「LED3Control」としています。セントラルデバイスではスキャン後、対象のデバイスであるかの判断として「LED3Control」のローカル名を使用します。

広告

セントラルデバイスを実装する

セントラルデバイスはクライアントとして動作し、ペリフェラルデバイスを確認すると接続要求を行います。スキャンからキャラクタリスティックを使ったデータの書き込みまでの動作を説明します。

#include <ArduinoBLE.h>

void setup() {
  
  if (!BLE.begin()) { //BLE開始
    while (1);
  }

  BLE.scan(); //ペリフェラルデバイスのスキャン開始
}

BLEを使用するためにArduinoBLEライブラリをインクルードします。

BLEのbegin()関数で初期化を行います。begin()関数による処理が正常であれば0が戻り値になるので下記のスキャンに移ります。例ではBLEの初期化に失敗した場合無限ループとしています。

BLEのscan()関数でペリフェラルデバイスのスキャンを開始します。次にペリフェラルデバイスが確認できた場合の処理を追加します。

BLEDevice peripheral = BLE.available();

if (peripheral) {
  if (peripheral.localName() != "LED3Control") {
    return;
  }
  else{ //ローカル名がLED3Controlの場合
    BLE.stopScan(); //スキャンをストップ

    if (peripheral.connect()) {
      Serial.println("Connected");
    }else {
      Serial.println("Failed to connect!");
      return;
    }
    //ペリフェラルデバイスの属性の確認
    Serial.println("Discovering attributes ...");
    if (peripheral.discoverAttributes()) {
      Serial.println("Attributes discovered");
    }else {
      Serial.println("Attribute discovery failed!");
      peripheral.disconnect();
      return;
    }
    
    switchCharacteristic = peripheral.characteristic("197f0bd9-de9c-6d0c-eaf4-09728681ee27"); //UUIDはペリフェラルデバイスと同じものを使用

    if (!switchCharacteristic) {
      Serial.println("Peripheral does not have switch characteristic!");
      peripheral.disconnect();
      return;
    } else if (!switchCharacteristic.canWrite()) {
      Serial.println("Peripheral does not have a writable switch characteristic!");
      peripheral.disconnect();
      return;
    }
  }
}

ペリフェラルデバイスの存在を確認するためBLEDeviceクラスのperipheralをインスタンス化して使用します。

BLEのavailable()関数でペリフェラルデバイスを確認します。ペリフェラルデバイスが確認できた場合はペリフェラルデバイスのペリフェラルデバイスのローカル名を確認します。ローカル名が「LED3Control」であればnRF52840(P)なので接続要求します。

接続開始すると1対1の双方向通信になるため、BLEのstopScan()関数でスキャンを停止します。

peripheralのconnect()関数でペリフェラルデバイスに接続します。接続が失敗した場合は再度スキャンを開始してペリフェラルデバイスの確認に戻ります。接続が成功した場合はnRF52840(P)(サーバー)に属性(Attribute)が存在しているかを確認します。

BLEの概念ではサーバーが管理している属性にクライアントがアクセスします。属性の確認はATTプロトコルで行われます。属性を集約したGATT(Generic ATTribute Profile)と呼ばれるデータベースを使用して1対1の双方向通信を行います。

属性(Attribute)が存在なければGATTによる双方向通信ができないため、属性が存在しない場合はdisconnect()関数で接続を切断します。

サーバーにアクセスするキャラクタリスティックをcharacteristic()関数で指定します。引数にキャラクタリスティックのUUIDを指定します。UUIDはペリフェラルデバイスのキャラクタリスティックと同じUUIDを指定する必要があります

キャラクタリスティックが存在しないか、キャラクタリスティックが書き込み許可の属性(共通の設定のBLEWrite相当)を持っていない場合はdisconnet()で接続を切断します。

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

ペリフェラルデバイスを操作する

if(peripheral.connected()) { //接続中
  switchCharacteristic.writeValue((byte)btncnt); //btncntを書き込む
}
else{ //接続が遮断
  BLE.scan();
}

connected()関数で接続状態を確認し、接続中であればキャラクタリスティックにデータを書き込みます。ペリフェラルデバイスと共通のUUIDを持つswichCharacteristicにおいてwriteValue()関数でデータを指定します。引数に書き込むデータをbyte型で指定します。複数バイト書き込む場合は第2引数に書き込むバイト数を指定します。

connected()関数の結果がfalseの場合、接続が切断しているのでBLEのscan()関数でペリフェラルデバイスの確認を再開します。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

nRF52840(C)のDIに接続したSW1で更新したカウント値をnRF52840(P)にBLE通信し、nRF52840(P)の3色LEDを操作します。

USBを接続するとnRF52840(C)とnRF52840(P)が動作を開始します。nRF52840(C)はシリアルモニターで動作の確認を行います。nRF52840(C)がnRF52840(P)に接続するまではスマホで「LED3Control」が確認できますが、接続が成功すると「LED3Control」は表示されなくなります。

接続中にSW1を押すとカウント(0~7)を更新してnRF52840(C)からnRF52840(P)に対してキャラクタリスティックにカウント値を書き込みnRF52840(P)の3色LEDを操作します。

nRF52840(C)のシリアルモニターの結果
nRF52840(C)のシリアルモニターの結果

nRF52840(C)のシリアルモニターを確認すると、SW1を押したタイミングでカウント値が更新されて3色LEDの点灯パターンが切り替わることが確認できました。

nRF52840(P)の3色LEDをnRF52840(C)で操作した結果
nRF52840(P)の3色LEDをnRF52840(C)で操作した結果

カウント値が0の場合はLEDが消灯、1は赤色で点灯、2では緑色、3では黄色、4では青色、5では紫色、6では水色、7では薄紫(白に近い)色の8パターンが確認できました。

広告

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <ArduinoBLE.h>

#define PIN_DI1 2
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define DI_FILT_MAX 4
#define FILT_MIN 1

struct DIFILT_TYP{
  uint8_t wp;
  uint8_t buf[DI_FILT_MAX];
  uint8_t di1;
};

bool periflg;
BLEDevice peripheralnow;
BLECharacteristic switchCharacteristic;
bool scanflg;
uint8_t btncnt;
uint8_t oldbtncnt;
bool  btnflg;
int16_t timdifilt = TIME_OFF;
int8_t cnt10ms;
uint32_t beforetimCnt = millis();
DIFILT_TYP difilt;

/*** Local function prototypes */
void mainApp(void);
void mainTimer(void);
void DiFilter(void);
void controlLed(BLEDevice peripheral);

void setup() {
  
  Serial.begin(115200);
  pinMode( PIN_DI1, INPUT_PULLUP );

  for( uint8_t i=0; i < 10; i++ ){
    DiFilter();
    delay(10);
    timdifilt = TIME_UP;
  }

  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");
    while (1);
  }

  BLE.scan(); //スキャン開始
}

void loop() {

  mainApp();
  mainTimer();
  DiFilter();

  if(periflg){
    controlLed(peripheralnow);
  }
}
/* Timer Management function add */
void mainTimer(){

  if ( millis() - beforetimCnt >= BASE_CNT ){
    beforetimCnt = millis();
    //10msごとにここに遷移する
    if( timdifilt > TIME_UP ){
      timdifilt--;
    }
  }
}
/* DiFilter function add */
void DiFilter(){

  if( timdifilt == TIME_UP ){
    difilt.buf[difilt.wp] = digitalRead(PIN_DI1);

    if( difilt.buf[0] == difilt.buf[1] &&
      difilt.buf[1] == difilt.buf[2] &&
      difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
      difilt.di1 = difilt.buf[0];
    }
    
    if( ++difilt.wp >= DI_FILT_MAX ){
      difilt.wp = 0;
    }

    timdifilt = FILT_MIN;
  }
}
/* メイン処理 */
void mainApp(void){
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.println();

    if (peripheral.localName() != "LED3Control") {
      periflg = false;
      return;
    }
    else{
      peripheralnow = peripheral;
      BLE.stopScan();
      scanflg = false;
      periflg = true;
      // connect to the peripheral
      Serial.println("Connecting ...");

      if (peripheral.connect()) {
        Serial.println("Connected");
      } else {
        Serial.println("Failed to connect!");
        return;
      }
      //ペリフェラルデバイスの属性の確認
      Serial.println("Discovering attributes ...");
      if (peripheral.discoverAttributes()) {
        Serial.println("Attributes discovered");
      } else {
        Serial.println("Attribute discovery failed!");
        peripheral.disconnect();
        return;
      }
   
      switchCharacteristic = peripheral.characteristic("197f0bd9-de9c-6d0c-eaf4-09728681ee27"); //ペリフェラルデバイスのキャラクタリスティックと同じUUIDを指定

      if (!switchCharacteristic) {
        Serial.println("Peripheral does not have switch characteristic!");
        peripheral.disconnect();
        return;
      } else if (!switchCharacteristic.canWrite()) {
        Serial.println("Peripheral does not have a writable switch characteristic!");
        peripheral.disconnect();
        return;
      }
    }
  }

  if( difilt.di1 == 0 ){
    if( btnflg == false ){
      btnflg = true;
      if(++btncnt >= 8){
        btncnt = 0;
      }
    }
  }
  else{
    btnflg = false;
  }
}
/* ペリフェラルデバイスのLEDを操作する */
void controlLed(BLEDevice peripheral) {

  if(peripheral.connected()) {
    if (oldbtncnt != btncnt) {
      oldbtncnt = btncnt;
      Serial.print("ble send:");
      Serial.println(btncnt);
      switchCharacteristic.writeValue((byte)btncnt);
    }
  }
  else{
    if(scanflg == false){
      scanflg = true;
      Serial.println("Peripheral disconnected");
      BLE.scan();
    }
  }
}

ペリフェラルデバイス(nRF52840(P))は下記記事のソースコードを書き込む必要があります。

Seeed Studio XIAO nRF52840でBLEのサービスを生成する

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR:テックキャンプエンジニア転職

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました