keitanak が 2024年02月17日22時44分12秒 に編集
初版
タイトルの変更
Seeed XIAO BLE nRF52840を使ってBLE IoT センサーを作る(実験編)
タグの変更
BLE
IoT
nRF52840
XIAO
Arduino
メイン画像の変更
記事種類の変更
製作品
ライセンスの変更
(MIT) The MIT License
本文の変更
# はじめに ラズパイでウエザーステーションを作ったのですが、できれば、BLEセンサーのIoTゲートウエイとしても動作をしてほしいと思っています。 そこで、まずはBLEのセンサ(温度・湿度・気圧・CO2)を作るところに取り掛かりたいと思います。 BLEセンサーで室内の温度・湿度・気圧・CO2を測定し、一定間隔でアドバタイズするようなものを想定しています。 ## データフォーマットに関して BLEですが、データフォーマットに関しては、何か標準的なものがあるのか調べてみたのですが、基本的にベンダが定義するもののようで、共通の仕様はみつけられませんでした。 しかし、ムセンコネクトさんのHPでオープンセンササービスなるフォーマットが公開されていることを発見しました。 https://www.musen-connect.co.jp/blog/course/product/howto-16bituuid-ble-beacon-open-sensor-service/ Service Data 16-bit UUIDに関してはムセンコネクトさんが自社で取得されたものを利用可能として公開されているようです。 データ構造定義を確認してみると、ちょうど想定していた、温度・湿度・気圧・CO2はカバーされていることがわかりました、これは大変ありがたいので、こちらのフォーマットを採用して製作をすすめてみます。 # システム構成 * マイコン Seeed XIAO BLE nRF52840を利用してみたいと思います。 小ぶりなので、かさばらず製作ができそうです。 バッテリー駆動にも対応する端子が裏面についていますので、バッテリー駆動も可能とできそうです。ただ、今回のCO2センサーはかなり消費電力が大きいので、今後BME280のみで製作する際にバッテリーを利用しようと思います。 * センサー 温度、湿度、気圧はBME280を利用 CO2に関しては、MH-Z19Eを利用します。 ## 回路図 BME280はI2Cで、MH-Z19EはUARTで通信します。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/956b8ed0-b164-849c-da74-fe13b3f4cfdc.png) ## プログラム センサを初期化して、10秒毎にデータを取得、BLEでアドバタイズするだけの簡単なものです。Arduinoで組みました。 ```XIAO-BLE-IoT-Sensor.ino #include <Wire.h> #include <SPI.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <ArduinoBLE.h> // CO2センサ用コマンドです。 byte ReadCO2[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}; byte SelfCalOn[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6}; byte SelfCalOff[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86}; byte retval[9]; // CO2読み取り値の保管用 uint16_t uartco2; // BLE用変数 BLEAdvertisingData advData; // BLE製造元で追加するデータです。まだここの意味がよくわかっていません。 const uint8_t manufactData[4] = {0x01, 0x02, 0x03, 0x04}; // BLEでアドバタイズするデータのフォーマットです。 // 0x01 // 4byte identifier(BLEのMACアドレスの下4バイトを後で設定します。) // 0x10 (温度) + 2 byte data // 0x11 (湿度) + 2 byte data // 0x14 (気圧) + 2 byte data // 0x17 (CO2) + 2 byte data byte serviceData[17] = { // fix value 0x01, // 4 byte identifier 0x00,0x00,0x00,0x00, // 温度 0x10,0x00,0x00, // 湿度 0x11,0x00,0x00, // 気圧 0x14,0x00,0x00, // CO2 0x17,0x00,0x00 }; // BLEのMACアドレスを取得するための変数 String address; // 温度センサ用変数 Adafruit_BME280 bme; // I2C // ループ時間の待ち時間を設定します。 unsigned long delayTime; // センサから読み取った値を格納する変数です。 float temp,humidity,pressure; void setup() { // 状態表示用にLEDピンを初期化します。 pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_RED, HIGH); digitalWrite(LED_BLUE, HIGH); digitalWrite(LED_GREEN, HIGH); Serial.begin(9600); while(!Serial){ // シリアル待ちの間青色LEDを点滅します。 digitalWrite(LED_BLUE, LOW); delay(500); digitalWrite(LED_BLUE, HIGH); delay(500); } // BME280の初期化です。 Serial.println(F("BME280 test")); if (! bme.begin(0x76, &Wire)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1){ // センサーが接続されていない場合は赤色LEDを点滅します。 digitalWrite(LED_RED, LOW); delay(500); digitalWrite(LED_RED, HIGH); delay(500); } } Serial.println("-- Default Test --"); Serial.println("normal mode, 16x oversampling for all, filter off,"); Serial.println("0.5ms standby period"); // For more details on the following scenarious, see chapter // 3.5 "Recommended modes of operation" in the datasheet // weather monitoring Serial.println("-- Weather Station Scenario --"); Serial.println("forced mode, 1x temperature / 1x humidity / 1x pressure oversampling,"); Serial.println("filter off"); bme.setSampling(Adafruit_BME280::MODE_FORCED, Adafruit_BME280::SAMPLING_X1, // temperature Adafruit_BME280::SAMPLING_X1, // pressure Adafruit_BME280::SAMPLING_X1, // humidity Adafruit_BME280::FILTER_OFF ); // 10秒間隔の取得にしています。 delayTime = 10000; // in milliseconds Serial.println(); delay(2000); if (!BLE.begin()) { Serial.println("failed to initialize BLE!"); while (1){ // 初期化失敗した場合は赤色LEDを点滅します。 digitalWrite(LED_RED, LOW); delay(500); digitalWrite(LED_RED, HIGH); delay(500); } } // CO2センサの初期化です。 Serial1.begin(9600); Serial1.write(SelfCalOn,sizeof SelfCalOn); // BLEのMACアドレスを取得します。 address = BLE.address(); Serial.print("Local BLE address is: "); Serial.println(address); //スキャン応答ペイロードのデバイス名称==> 31バイトを消費するので、利用しない //advData.setLocalName("Xiao"); // Set parameters for advertising packet // 0xFFFF はテスト用のIDです。 advData.setManufacturerData(0xFFFF, manufactData, sizeof(manufactData)); // BLE MACアドレスの下4バイトをムセンコネクトさんの個体識別番号に設定します。 byte u; char buf[3]; address.substring(6,8).toCharArray(buf, sizeof(buf)); sscanf(buf, "%x", &u); serviceData[1]=u; address.substring(9,11).toCharArray(buf, sizeof(buf)); sscanf(buf, "%x", &u); serviceData[2]=u; address.substring(12,14).toCharArray(buf, sizeof(buf)); sscanf(buf, "%x", &u); serviceData[3]=u; address.substring(15,17).toCharArray(buf, sizeof(buf)); sscanf(buf, "%x", &u); serviceData[4]=u; // 0xfcbe = ムセンコネクトさんの16bit UUID advData.setAdvertisedServiceData(0xfcbe, serviceData, sizeof(serviceData)); BLE.setAdvertisingData(advData); BLE.advertise(); Serial.println("advertising ..."); //WDTも設定しておきます。 NRF_WDT->CONFIG = 0x01; // Configure WDT to run when CPU is asleep NRF_WDT->CRV = 9830401; // Timeout set to 300 seconds, timeout[s] = (CRV-1)/32768 NRF_WDT->RREN = 0x01; // Enable the RR[0] reload register NRF_WDT->TASKS_START = 1; // Start WDT } void loop() { // Only needed in forced mode! In normal mode, you can remove the next line. bme.takeForcedMeasurement(); // has no effect in normal mode // 温度を取得してシリアル出力します。 Serial.print("Temperature = "); temp=bme.readTemperature(); Serial.print(temp); Serial.println(" *C"); // 湿度を取得してシリアル出力します。 Serial.print("Pressure = "); pressure=bme.readPressure()/ 100.0F; Serial.print(pressure); Serial.println(" hPa"); // 気圧を取得してシリアル出力します。 Serial.print("Humidity = "); humidity=bme.readHumidity(); Serial.print(humidity); Serial.println(" %"); //UARTでCO2データ取得してシリアル出力します。 Serial1.write(ReadCO2,sizeof ReadCO2); Serial1.readBytes((char *)retval, sizeof retval); uartco2 = retval[2]*256 + retval[3]; Serial.print("CO2 = "); Serial.print(uartco2); Serial.println(); // 各データをムセンコネクトさんのフォーマットに変換します。 int var; var = temp*100; serviceData[6] = var >>8; serviceData[7] = var & 0xFF; var = humidity*100; serviceData[9] = var >>8; serviceData[10] = var & 0xFF; var = pressure*10; serviceData[12] = var >>8; serviceData[13] = var & 0xFF; serviceData[15] = uartco2 >>8; serviceData[16] = uartco2 & 0xFF; //データをアドバタイズします。 advData.setAdvertisedServiceData(0xfcbe, serviceData, sizeof(serviceData)); BLE.setAdvertisingData(advData); BLE.advertise(); // 緑LEDを一瞬光らせます。 digitalWrite(LED_GREEN, LOW); delay(100); digitalWrite(LED_GREEN, HIGH); //待ち時間を入れます。 delay(delayTime); // WDT処理です。 NRF_WDT->RR[0] = WDT_RR_RR_Reload; } ``` # 動作確認 ブレッドボードで組んだ状況 ![PXL_20240217_131359950.MP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/3fbd5fc0-ef02-ab66-1d3e-16173a8cf8f5.jpeg) シリアルポートからはこのようにデータが表示されます。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/c9224b9c-8b4d-f9d4-e4bf-eb40bc74011b.png) さて肝心のBLEの状況確認ですが、スマホにインストールしてnRF Connectから状況確認です。 こんな感じでみつかりました。これであっているのかあまり自信がありません。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/22be05e8-f5c1-125d-2121-f7bead27de08.png) 最初の7バイトが製造元データです。 次の20倍とがムセンコネクトさんのデータフォーマットのつもりのデータです。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/dddb27b2-2a3a-540a-f737-7d177cfff1c0.png) LEN=Length TYPE=AD Type そのあと2バイトはUUID(バイトの順番が逆になっているがあっているのか?) 次のバイトは01で固定 そして4バイトはMACアドレスの下四桁(98:87:68:0f) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015969/34ded7e2-d6f9-bd07-7021-82c8de040c3e.png) そのあとが温度、湿度、気圧、CO2のデータとなります。 一旦デコードしてみましょう 気温:082D = 2093 x 0.01℃ = 20.93℃ 湿度:109B = 4251 x 0.01% = 42.51% 気圧:27DF = 10207 x 0.1 hPa = 1020.7 hPa CO2:04D9 = 1241 ppm 一応大丈夫そうですね。 # 参考リンク MH-Z19Eはこちらから入手しました。 https://electronicwork.shop/ ムセンコネクトさんのデータフォーマットに関してのページです。 https://www.musen-connect.co.jp/blog/course/product/howto-16bituuid-ble-beacon-open-sensor-service/ XIAO BLEでBLEデバイスのプログラミングの参考にさせていただきました。 https://smtengkapi.com/seeed-xiao-nrf52-connect