今回は BLE Nano と焦電センサーを使って人感センサーを作ってみました。焦電センサーは人間等が放出する熱エネルギーに変化が生じた時に発生する赤外線を検知するため、人感センサーとして使うことができます。ただし熱エネルギーの「変化」を検知するので、人がいても動かずじっとしている時には検知されませんので、ずっとそこにいることを検知するというよりは、入室や通過等を検知するのに向いているようです。
DAPLink と接続して配線する
今回使用している焦電センサーは、焦電型赤外線センサー PaPIRs 5m EKMC1601111 です。
また、今回下記サイトを参考に BLE Nano と EKMC1601111 の Fritzing パーツを自作してみました。
Fritzingパーツ作成方法 | Home Made Garbage
Fritzing カスタムパーツの作り方 – jumbleat
そして配線図はこちらです。
プログラムの書き込みとシリアル接続でのデバック用に DAPLink と BLE Nano を接続しているので、電源も DAPLink から VIN ピンで取り、VDD ピンから LED と焦電センサーに供給しています。 LED は動作確認用で、焦電センサーが人の動きを感知しているときは点灯させます。 EKMC1601111 には 3本のピンがあり、上記配線図では左から VDD, OUT, GND となっていますので、 OUT は抵抗を噛ませて BLE Nano の P0_5 ピンと接続しています。
ソースコード
今回実装したコードの全文は下記の通りです。以前の照度センサーの時と大体同じで、焦電センサーの値を扱うカスタムサービスを PyroelectricService.h として実装しています。
#ifndef __BLE_PYROELECTRIC_SERVICE_H__ #define __BLE_PYROELECTRIC_SERVICE_H__ class PyroelectricService { public: const static uint16_t PYROELECTRIC_SERVICE_UUID = 0xA000; const static uint16_t PYROELECTRIC_VALUE_CHARACTERISTIC_UUID = 0xA001; PyroelectricService(BLE &_ble, bool initialValueForPyroelectricCharacteristic) : ble(_ble), pyroelectricState(PYROELECTRIC_VALUE_CHARACTERISTIC_UUID, &initialValueForPyroelectricCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY) { GattCharacteristic *charTable[] = {&pyroelectricState}; GattService pyroelectricService(PYROELECTRIC_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); ble.gattServer().addService(pyroelectricService); } void updatePyroelectricState(bool newState) { ble.gattServer().write(pyroelectricState.getValueHandle(), (uint8_t *)&newState, sizeof(bool)); } private: BLE &ble; ReadOnlyGattCharacteristic<bool> pyroelectricState; }; #endif
そして main.cpp から PyroelectricSerivce.h を使用しています。
#include "mbed.h" #include "ble/BLE.h" #include "PyroelectricService.h" Serial pc(USBTX, USBRX); DigitalOut led(P0_4, 0); AnalogIn pyro(P0_5); const static char DEVICE_NAME[] = "PyroSample"; static const uint16_t uuid16_list[] = {PyroelectricService::PYROELECTRIC_SERVICE_UUID}; static volatile bool triggerSensorPolling = false; unsigned short pyroValue; const int threshold = 500; bool pyroelectricState; PyroelectricService *pyroelectricServicePtr; Ticker ticker; BLE ble; void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params) { BLE::Instance().gap().startAdvertising(); } void periodicCallback(void) { pyroValue = pyro.read_u16(); pyroelectricState = pyroValue >= threshold; if (pyroelectricState) { led = 1; } else { led = 0; } triggerSensorPolling = true; } void onBleInitError(BLE &ble, ble_error_t error) { } void bleInitComplete(BLE::InitializationCompleteCallbackContext *params) { BLE& ble = params->ble; ble_error_t error = params->error; if (error != BLE_ERROR_NONE) { onBleInitError(ble, error); return; } if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) { return; } ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME); ble.gap().onDisconnection(disconnectionCallback); bool initialValueForPyroelectricCharacteristic = false; pyroelectricServicePtr = new PyroelectricService(ble, initialValueForPyroelectricCharacteristic); ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)); ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME)); ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); ble.gap().setAdvertisingInterval(1000); ble.gap().startAdvertising(); } int main(void) { pc.printf("Starting BLE_PYROELECTRIC...\r\n"); ticker.attach(periodicCallback, 1); pc.printf("Initializing BLE Controller...\r\n"); ble.init(bleInitComplete); pc.printf("Initialized.\r\n"); while (ble.hasInitialized() == false) { /* spin loop */ } pc.printf("Starting loop...\r\n"); while(true) { if (triggerSensorPolling) { triggerSensorPolling = false; pc.printf("%d\r\n", pyroelectricState); pyroelectricServicePtr->updatePyroelectricState(pyroelectricState); } else { ble.waitForEvent(); } } }
焦電センサーからの出力はアナログ入力として受け取り、入力値が閾値を上回ったかどうかでステータスを切り替えています。
AnalogIn pyro(P0_5);
void periodicCallback(void) { pyroValue = pyro.read_u16(); pyroelectricState = pyroValue >= threshold; if (pyroelectricState) { led = 1; } else { led = 0; } triggerSensorPolling = true; }
最初はデジタル入力として試していたのですが、一度検知して true を返すようになると、その後正しく false に戻らなかったため、アナログ入力の値を使うようにしてみました。 BLE のカスタムサービスで扱う値が bool になったという以外は、基本的には照度センサーを使用した時と同様の構成です。
動作確認
ではプログラムをコンパイルして書き込んで、動作確認してみます。
[vagrant@localhost BLE_PYROELECTRIC]$ mbed compile Building project BLE_PYROELECTRIC (RBLAB_BLENANO, GCC_ARM) Scan: . Scan: mbed Scan: env Compile [100.0%]: main.cpp Link: BLE_PYROELECTRIC Elf2Bin: BLE_PYROELECTRIC +-----------+-------+-------+------+ | Module | .text | .data | .bss | +-----------+-------+-------+------+ | Fill | 127 | 3 | 47 | | Misc | 41520 | 141 | 1649 | | Subtotals | 41647 | 144 | 1696 | +-----------+-------+-------+------+ Allocated Heap: 2256 bytes Allocated Stack: 2048 bytes Total Static RAM memory (data + bss): 1840 bytes Total RAM memory (data + bss + heap + stack): 6144 bytes Total Flash memory (text + data + misc): 41791 bytes Image: ./BUILD/RBLAB_BLENANO/GCC_ARM/BLE_PYROELECTRIC.hex [vagrant@localhost BLE_PYROELECTRIC]$ [vagrant@localhost BLE_PYROELECTRIC]$ cp ./BUILD/RBLAB_BLENANO/GCC_ARM/BLE_PYROELECTRIC.hex /run/media/vagrant/DAPLINK/ [vagrant@localhost BLE_PYROELECTRIC]$
するとシリアル接続していると下記のように、人の動きを検知しているときは 1 を、検知していないときは 0 を出力します。
Starting BLE_PYROELECTRIC... Initializing BLE Controller... Initialized. Starting loop... 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0
また、 LightBlue でも下記のように Peripheral のリストに PyroSample が表示され、接続して Notification を Listen すると値が更新されていくのが確認できます。
ボタン電池で動かす
動作確認ができたので、実際にセンサーとして使う場合を想定して、DAPLink から切り離してボタン電池で動かしてみます。配線は下記のようにしました。
CR2032 のボタン電池ホルダーを配置し、+側から BLE Nano の VIN ピンに給電しています。ボタン電池をはめればすぐに起動して動き始めます。ちなみに「人感」センサーと言ってますが猫でもちゃんと検知します。
[:450]
まとめ
冒頭でも書きましたが、焦電センサーは動きを検知することになるため、そこに人間や動物がいても、ほとんど動いてなければ検知されません。猫が昼寝スポットにいるかどうかを検知するものでも作ってみようかと思ってたのですが、焦電センサーだとその用途にはマッチしないようです。
また、動作確認をしていると、実際はセンサー前で動き続けていても時々検知結果が false になるので、在室検知のようなことをするとしたら、状態の管理としてはある程度 false の時間が続いたら false と判定するのが良さそうです。