keitanakのアイコン画像
keitanak 2024年02月17日作成 (2024年02月26日更新) © MIT
製作品 製作品 閲覧数 268
keitanak 2024年02月17日作成 (2024年02月26日更新) © MIT 製作品 製作品 閲覧数 268

Seeed XIAO BLE nRF52840を使ってBLE IoT センサーを作る(実験編)

Seeed XIAO BLE nRF52840を使ってBLE IoT センサーを作る(実験編)

はじめに

ラズパイでウエザーステーションを作ったのですが、できれば、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

プログラム

センサを初期化して、10秒毎にデータを取得、BLEでアドバタイズするだけの簡単なものです。Arduinoで組みました。

#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

シリアルポートからはこのようにデータが表示されます。
image.png

さて肝心のBLEの状況確認ですが、スマホにインストールしてnRF Connectから状況確認です。

こんな感じでみつかりました。これであっているのかあまり自信がありません。

image.png

最初の7バイトが製造元データです。
次の20バイトがムセンコネクトさんのデータフォーマットのつもりのデータです。
image.png

LEN=Length
TYPE=AD Type
そのあと2バイトはUUID(バイトの順番が逆になっているがあっているのか?)
次のバイトは01で固定
そして4バイトはMACアドレスの下四桁(98:87:68:0f)

image.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

1
ログインしてコメントを投稿する