BLE Nano V2 を mbed CLI で Lチカ

 以前の記事で BLE Nano V2 をオンラインの開発環境で動かしてみました。

blog.akanumahiroaki.com

 その時は mbed CLI ではコンパイル&実行まではできていなかったので、今回試してみました。

書き込み用のボードの違い

 BLE Nano で開発するには書き込み用のボードが必要なので、初めて買う時には書き込み用ボードとセットになったキットを買う必要がありますが、私が BLE Nano V1 の時に買ったものと V2 の時に買ったものでは DAPLink のバージョンが異なっています。下記の写真の左側が V1 の時にセットになっていたもので DAPLink の v1.0、右側が V2 の時にセットになっていたもので DAPLink の v1.5 でした。

f:id:akanuma-hiroaki:20180804075656j:plain

 結論から言っておくと、 BLE Nano V2 と DAPLink v1.0 の組み合わせでは、 mbed CLI での書き込み&実行はうまくいきませんでした。 DAPLink のそれぞれのバージョンで試した結果を下記に記載していきます。

BLE Nano V2 + DAPLink v1.0

 まずは Lチカのサンプルをインポートして、そのディレクトリに移動します。

$ mbed import http://os.mbed.com/teams/mbed-os-examples/code/mbed-os-example-blinky/                                                                                                                                            
[mbed] Importing program "mbed-os-example-blinky" from "https://os.mbed.com/teams/mbed-os-examples/code/mbed-os-example-blinky" at latest revision in the current branch
[mbed] Adding library "mbed-os" from "https://github.com/ARMmbed/mbed-os" at rev #485bdeee150e
$ cd mbed-os-example-blinky

 LED のピン番号の指定を、 BLE Nano V2 の本体の LED のピン番号になるように、 led1 のピン番号の指定だけ変更して P0_11 にします。変更後のコードは下記の通りです。

$ cat main.cpp 
#include "mbed.h"

DigitalOut led1(P0_11);

// main() runs in its own thread in the OS
int main() {
    while (true) {
        led1 = !led1;
        wait(0.5);
    }
}

 BLE Nano V2 + DAPLink v1.0 を Mac に挿した状態で mbed detect してみると、下記のように検知されています。

$ mbed detect
WARNING: MBED_GCC_ARM_PATH set as environment variable but doesn't exist

[mbed] Detected RBLAB_BLENANO, port /dev/tty.usbmodem1412, mounted /Volumes/DAPLINK, interface version 0241:
[mbed] Supported toolchains for RBLAB_BLENANO
+----------------+-----------+-----------+-----------+-----------+-----------+
| Target         | mbed OS 2 | mbed OS 5 |    ARM    |  GCC_ARM  |    IAR    |
+----------------+-----------+-----------+-----------+-----------+-----------+
| RBLAB_BLENANO2 |     -     | Supported | Supported | Supported | Supported |
+----------------+-----------+-----------+-----------+-----------+-----------+
Supported targets: 1
Supported toolchains: 3

 TOOLCHAIN は今回はどのケースでも GCC_ARM ですが、 TARGET は複数のパターンで試しました。まずは Detected RBLAB_BLENANO となっているので、 TARGET を RBLAB_BLENANO としてみます。

$ mbed target RBLAB_BLENANO
[mbed] RBLAB_BLENANO now set as default target in program "mbed-os-example-blinky"
$ mbed toolchain GCC_ARM
[mbed] GCC_ARM now set as default toolchain in program "mbed-os-example-blinky"
$ mbed config --list
[mbed] Global config:
GCC_ARM_PATH=/Users/akanuma/Documents/mbed_connect_ws/mac-workshop-content/MacOS/Section-2/gcc-arm-none-eabi-6-2017-q2-update/bin

[mbed] Local config (/Users/akanuma/workspace/mbed_cli_mac/mbed-os-example-blinky):
TOOLCHAIN=GCC_ARM
TARGET=RBLAB_BLENANO

 そしてコンパイル実行。

$ mbed compile
WARNING: MBED_GCC_ARM_PATH set as environment variable but doesn't exist
[Error] @,: Compiler version mismatch: Have 7.2.1; expected version >= 6.0.0 and < 7.0.0
Building project mbed-os-example-blinky (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Scan: FEATURE_BLE

Could not compile for RBLAB_BLENANO: Target does not support mbed OS 5

 RBLAB_BLENANO は mbed OS 5 に対応していないということでコンパイルできません。おそらくですが RBLAB_BLENANO は BLE Nano V1 という指定になるので、 mbed OS5 には対応していないということで、コンパイルが実行できないものと思われます。

 次に mbed detect の結果として Target に表示されていた、 RBLAB_BLENANO2 を Target に設定してみます。

$ mbed target RBLAB_BLENANO2
[mbed] RBLAB_BLENANO2 now set as default target in program "mbed-os-example-blinky"

 そしてコンパイルを実行します。

$ mbed compile
WARNING: MBED_GCC_ARM_PATH set as environment variable but doesn't exist
[Error] @,: Compiler version mismatch: Have 7.2.1; expected version >= 6.0.0 and < 7.0.0
Building project mbed-os-example-blinky (RBLAB_BLENANO2, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Scan: FEATURE_BLE
Using regions bootloader, application in this build.
  Region bootloader: size 0x23000, offset 0x0
  Region application: size 0x5d000, offset 0x23000
Compile [  0.2%]: mbed_tz_context.c
Compile [  0.3%]: CAN.cpp
Compile [  0.5%]: Ethernet.cpp
〜〜〜中略〜〜〜
Compile [100.0%]: test_env.cpp
Link: mbed-os-example-blinky_application
Elf2Bin: mbed-os-example-blinky_application
Merging Regions
  Filling region bootloader with /Users/akanuma/workspace/mbed_cli_mac/mbed-os-example-blinky/mbed-os/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_SDK_14_2/TARGET_SOFTDEVICE_S132_FULL/hex/s132_nrf52_5.0.0_softdevice.hex
  Padding region bootloader with 0xb58 bytes
  Filling region application with ./BUILD/RBLAB_BLENANO2/GCC_ARM/mbed-os-example-blinky_application.hex
Space used after regions merged: 0x40c54
+------------------+--------+-------+------+
| Module           |  .text | .data | .bss |
+------------------+--------+-------+------+
| [fill]           |    244 |     4 |   57 |
| [lib]/c.a        |  22447 |  2472 |   89 |
| [lib]/gcc.a      |   3112 |     0 |    0 |
| [lib]/misc       |    208 |    12 |   28 |
| [lib]/stdc++.a   |      1 |     0 |    0 |
| main.o           |    311 |     4 |    1 |
| mbed-os/drivers  |    785 |     0 |    0 |
| mbed-os/features |  62740 |    12 | 2082 |
| mbed-os/hal      |   1758 |     8 |  130 |
| mbed-os/platform |   3820 |   260 |  217 |
| mbed-os/rtos     |  10332 |   168 | 6133 |
| mbed-os/targets  |  13795 |    48 |  759 |
| Subtotals        | 119553 |  2988 | 9496 |
+------------------+--------+-------+------+
Total Static RAM memory (data + bss): 12484 bytes
Total Flash memory (text + data): 122541 bytes

Image: ./BUILD/RBLAB_BLENANO2/GCC_ARM/mbed-os-example-blinky.hex

 エラーなくコンパイルを実行することができましたので、出力された hex ファイルを DAPLink にコピーします。

$ cp ./BUILD/RBLAB_BLENANO2/GCC_ARM/mbed-os-example-blinky.hex /Volumes/DAPLINK/.

 コピー時もエラーは出ないのですが、コピーが終わっても実際には動作しませんでした。どうやら DAPLink v1.0 だと mbed CLI では書き込みがうまくいかないようです。

BLE Nano V2 + DAPLink v1.5

 次に BLE Nano V2 と DAPLink v1.5 の組み合わせで試してみます。この組み合わせで mbed detect すると、下記のような結果になります。

$ mbed detect
WARNING: MBED_GCC_ARM_PATH set as environment variable but doesn't exist

[mbed] Detected None, port /dev/tty.usbmodem1412, mounted /Volumes/DAPLINK, interface version 0242:
[mbed] Supported toolchains for None
+----------------------+-----------+-----------+-----------+-----------+-----------+
| Target               | mbed OS 2 | mbed OS 5 |    ARM    |  GCC_ARM  |    IAR    |
+----------------------+-----------+-----------+-----------+-----------+-----------+
| ARCH_PRO             | Supported | Supported | Supported | Supported | Supported |
| ARM_BEETLE_SOC       | Supported | Supported | Supported | Supported | Supported |
| ARM_CM3DS_MPS2       | Supported | Supported | Supported | Supported | Supported |
| B96B_F446VE          | Supported | Supported | Supported | Supported | Supported |
| DELTA_DFBM_NQ620     |     -     | Supported | Supported | Supported | Supported |
| DISCO_F303VC         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F407VG         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F413ZH         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F429ZI         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F469NI         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F746NG         | Supported | Supported | Supported | Supported | Supported |
| DISCO_F769NI         | Supported | Supported | Supported | Supported | Supported |
〜〜〜中略〜〜〜
| NRF51_DK             | Supported | Supported | Supported | Supported | Supported |
| NRF51_DONGLE         | Supported | Supported | Supported | Supported | Supported |
| NRF52840_DK          |     -     | Supported | Supported | Supported | Supported |
| NRF52_DK             |     -     | Supported | Supported | Supported | Supported |
| NUCLEO_F070RB        | Supported | Supported | Supported | Supported | Supported |
〜〜〜中略〜〜〜
| WIO_3G               | Supported | Supported | Supported | Supported | Supported |
| WIZWIKI_W7500        | Supported | Supported | Supported | Supported | Supported |
| WIZWIKI_W7500ECO     | Supported | Supported | Supported | Supported | Supported |
| WIZWIKI_W7500P       | Supported | Supported | Supported | Supported | Supported |
| XDOT_L151CC          |     -     | Supported | Supported | Supported | Supported |
+----------------------+-----------+-----------+-----------+-----------+-----------+
Supported targets: 138

 mbed に登録されている開発ボードの中にはマッチするものがないということで、対応しているボードのリストが表示されます。この中から NRF52_DK を Target に設定します。

$ mbed target NRF52_DK
[mbed] NRF52_DK now set as default target in program "mbed-os-example-blinky"
$ mbed toolchain GCC_ARM
[mbed] GCC_ARM now set as default toolchain in program "mbed-os-example-blinky"
$ mbed config --list
[mbed] Global config:
GCC_ARM_PATH=/Users/akanuma/Documents/mbed_connect_ws/mac-workshop-content/MacOS/Section-2/gcc-arm-none-eabi-6-2017-q2-update/bin

[mbed] Local config (/Users/akanuma/workspace/mbed_cli_mac/mbed-os-example-blinky):
TOOLCHAIN=GCC_ARM
TARGET=NRF52_DK

 そしてコンパイル実行。

$ mbed compile
WARNING: MBED_GCC_ARM_PATH set as environment variable but doesn't exist
[Error] @,: Compiler version mismatch: Have 7.2.1; expected version >= 6.0.0 and < 7.0.0
Building project mbed-os-example-blinky (NRF52_DK, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Scan: FEATURE_BLE
Using regions bootloader, application in this build.
  Region bootloader: size 0x23000, offset 0x0
  Region application: size 0x5d000, offset 0x23000
Compile [  0.2%]: mbed_tz_context.c
Compile [  0.3%]: CAN.cpp
Compile [  0.5%]: Ethernet.cpp
〜〜〜中略〜〜〜
Compile [100.0%]: test_env.cpp
Link: mbed-os-example-blinky_application
Elf2Bin: mbed-os-example-blinky_application
Merging Regions
  Filling region bootloader with /Users/akanuma/workspace/mbed_cli_mac/mbed-os-example-blinky/mbed-os/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_SDK_14_2/TARGET_SOFTDEVICE_S132_FULL/hex/s132_nrf52_5.0.0_softdevice.hex
  Padding region bootloader with 0xb58 bytes
  Filling region application with ./BUILD/NRF52_DK/GCC_ARM/mbed-os-example-blinky_application.hex
Space used after regions merged: 0x40cd4
+------------------+--------+-------+------+
| Module           |  .text | .data | .bss |
+------------------+--------+-------+------+
| [fill]           |    284 |     4 |   57 |
| [lib]/c.a        |  22447 |  2472 |   89 |
| [lib]/gcc.a      |   3112 |     0 |    0 |
| [lib]/misc       |    208 |    12 |   28 |
| [lib]/stdc++.a   |      1 |     0 |    0 |
| main.o           |    311 |     4 |    1 |
| mbed-os/drivers  |    785 |     0 |    0 |
| mbed-os/features |  62740 |    12 | 2082 |
| mbed-os/hal      |   1758 |     8 |  130 |
| mbed-os/platform |   3832 |   260 |  217 |
| mbed-os/rtos     |  10332 |   168 | 6133 |
| mbed-os/targets  |  13871 |    48 |  759 |
| Subtotals        | 119681 |  2988 | 9496 |
+------------------+--------+-------+------+
Total Static RAM memory (data + bss): 12484 bytes
Total Flash memory (text + data): 122669 bytes

Image: ./BUILD/NRF52_DK/GCC_ARM/mbed-os-example-blinky.hex

 エラーなくコンパイルされたので hex ファイルを DAPLINK にコピーします。

$ cp ./BUILD/NRF52_DK/GCC_ARM/mbed-os-example-blinky.hex /Volumes/DAPLINK/.

 こちらもエラーなくコピーされ、 DAPLink v1.0 との組み合わせの時と違い、 BLE Nano V2 本体で Lチカが実行されました。

まとめ

 BLE Nano のような開発ボードはチップのバージョンによる差や OS のバージョンによる差の影響が大きく、また、うまくいかなくても何が原因なのかがわかりづらいケースが多い印象です。実際何か目的があってボードやチップやOSを選定する場合は、うまくいく組み合わせなのかを少しずつ確認しながら進めた方が良さそうです。

心拍センサ + Raspberry Pi(エッジ検出)

 前回まで心拍センサを Arduino 互換の Seeduino で使ってきましたが、今回は Raspberry Pi で心拍センサを使ってみたいと思います。スクリプトは Python で実装し、 GPIO からの入力を検知したら心拍数の計算等を実行します。

回路構成

 Raspberry Pi には Grove コネクタがついていないので、前々回のケースと同様に、 Grove コネクタに直接ジャンパコードを挿し、 Raspberry Pi の GPIO ピンに接続しました。 Raspberry Pi 側は 3.3V、GND、GPIO17 のピンに接続します。

f:id:akanuma-hiroaki:20180728164820j:plain

f:id:akanuma-hiroaki:20180728164853j:plain

f:id:akanuma-hiroaki:20180728164923j:plain

Raspberry Pi での割り込み処理

 今回スクリプトは Python で実装しますが、内容としては前々回の c++ のコードと同様です。心拍モニタが心拍を検知すると GPIO の17番ピンが High になるので、この入力を待ち受けるようにします。

 電気信号が Low -> High もしくは High -> Low に切り替わる瞬間のことをエッジ(edge)と言い、今回はエッジ検出のために GPIO.wait_for_edge() メソッドを使いました。引数には対象のピンの番号と、検出したいエッジの種類を指定します。エッジの種類には、 Low -> High(立ち上がりエッジ)、 High -> Low(立ち下がりエッジ)、そしてその両方の3種類があり、今回は立ち上がりエッジを検出したいので、 GPIO.RISING を指定しています。このメソッドを実行すると、エッジが検出されるまで待ち受け状態になりますので、無限ループの中でこのメソッドを実行し、エッジが検出されたら処理を行うようにしています。

while True:
    GPIO.wait_for_edge(self.INTERRUPT_PIN, GPIO.RISING)
    self._interrupt()

Queue の使用

 前回までの c++ のスクリプト内では心拍数の計算のために配列を使用していましたが、今回は単純なリストではなく、 Queue として使用したかったので、 Python の collections.deque を使ってみました。普通のリストでも append() と pop() を使うことで同様の処理を行うことができますが、リストの先頭の要素を pop すると、全ての要素の移動処理が行われるため、処理効率は悪いようです。

 まずは下記のように deque をインポートします。

from collections import deque

 queue の初期化は deque() に初期配列を渡します。

self.detected_times = deque([])

 要素の追加は普通のリストと同様に append() で行います。

self.detected_times.append(time.time())

 先頭要素の削除は popleft() で行うことができます。

self.detected_times.popleft()

スクリプト実装

 今回のスクリプト全体は下記のように実装しました。心拍が検知される(エッジが検出される)とその時刻を Queue に格納し、 20回を越えるとその差分から心拍数を計算して出力します。エラーハンドリングは考慮していないので、実際に使用する場合はエラーハンドリングのコードを追加することになると思います。

#!/usr/bin/env python

import time
from collections import deque

import RPi.GPIO as GPIO

class HeartRateMonitor:
    INTERRUPT_PIN = 17
    MAX_DETECTED_TIMES_COUNT = 20
    MAX_PULSE_INTERVAL = 2.0

    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.INTERRUPT_PIN, GPIO.IN)

        self._init_array()

    def _init_array(self):
        self.detected_times = deque([])

    def _calc_heart_rate(self):
        return 1200.0 / (self.detected_times[-1] - self.detected_times[0])

    def _interrupt(self):
        self.detected_times.append(time.time())

        if(len(self.detected_times) == 1):
            return

        interval = self.detected_times[-1] - self.detected_times[-2]
        heart_rate = -1
        if interval > self.MAX_PULSE_INTERVAL:
            print('Heart rate measure error. Monitoring will restart!')
            self._init_array()
            return

        if(len(self.detected_times) >= self.MAX_DETECTED_TIMES_COUNT):
            heart_rate = self._calc_heart_rate()
            self.detected_times.popleft()

        print("HeartRate: {heart_rate}, Interval: {interval}".format(heart_rate = heart_rate, interval = interval))

    def execute(self):
        print('Please ready your heart rate monitor.')
        time.sleep(3)

        while True:
            GPIO.wait_for_edge(self.INTERRUPT_PIN, GPIO.RISING)
            self._interrupt()

if __name__ == '__main__':
    monitor = HeartRateMonitor()
    monitor.execute()

動作確認

 上記のコードを実行すると、ターミナルに下記のように心拍数の計算結果が出力されます。

$ ./heart_rate_monitor.py
Please ready your heart rate monitor.
HeartRate: -1, Interval: 0.8302757740020752
HeartRate: -1, Interval: 0.808060884475708
HeartRate: -1, Interval: 0.7631323337554932
HeartRate: -1, Interval: 0.7801287174224854
HeartRate: -1, Interval: 0.798215389251709
HeartRate: -1, Interval: 0.8021731376647949
HeartRate: -1, Interval: 0.7605118751525879
HeartRate: -1, Interval: 0.7294180393218994
HeartRate: -1, Interval: 0.7548770904541016
HeartRate: -1, Interval: 0.8093967437744141
HeartRate: -1, Interval: 0.8305354118347168
HeartRate: -1, Interval: 0.8199899196624756
HeartRate: -1, Interval: 0.8051409721374512
HeartRate: -1, Interval: 0.8412587642669678
HeartRate: -1, Interval: 0.8870439529418945
HeartRate: -1, Interval: 0.9384407997131348
HeartRate: -1, Interval: 0.933556079864502
HeartRate: -1, Interval: 0.9079298973083496
HeartRate: 76.52472304674038, Interval: 0.8811209201812744
HeartRate: 76.12490143070653, Interval: 0.9126362800598145
HeartRate: 75.64256806120363, Interval: 0.9085769653320312
HeartRate: 75.02947229026323, Interval: 0.8927640914916992
HeartRate: 74.78054308152043, Interval: 0.8333685398101807
HeartRate: 74.81021338673118, Interval: 0.7918510437011719
HeartRate: 74.72784839482473, Interval: 0.8198530673980713
HeartRate: 74.24507270335705, Interval: 0.8649301528930664
HeartRate: 73.53067103751506, Interval: 0.8864498138427734
HeartRate: 73.00755517148106, Interval: 0.8718116283416748
HeartRate: 72.93730864636744, Interval: 0.8252270221710205
HeartRate: 72.89795075840209, Interval: 0.8394181728363037
HeartRate: 72.71868699404068, Interval: 0.860569953918457
HeartRate: 72.42184236861083, Interval: 0.8727796077728271
HeartRate: 72.46996284628561, Interval: 0.830256462097168
HeartRate: 73.06855132933228, Interval: 0.7513935565948486
HeartRate: 74.17623306838857, Interval: 0.6931953430175781

まとめ

 Raspberry Pi は Arduino 系のマイコン等を使用した場合と比べて省電力が大きいので、ボタン電池等で小型化してウェアラブルデバイスを作るというようなことには向いていませんが、個人的には c++ よりも Python の方が馴染みがあるということもあり色々柔軟に扱えて取っつきやすいので、電源を確保できるような環境での使用であればとてもプロトタイピングしやすいですね。 GPIO のエッジ検出も簡単にできたので、今後色々な用途を検討してみたいと思います。

 今回はこちらのサイトを参考にさせていただきました。

ag.hatenablog.com

心拍センサを Seeeduino で使ってみる(Groveコネクタ接続 & ポーリング版)

 前回書いた 心拍センサを Seeeduino で使ってみる 記事では、公式ページで紹介されているサンプルコードをベースに心拍センサでの心拍の検知をインタラプトで受け取るため、 Grove コネクタにジャンパケーブルを挿して D2 ピンを使っていました。

f:id:akanuma-hiroaki:20180721180712j:plain

 ですがこれだと折角 Grove コネクタがついているのにそのメリットを活かすことができません。インタラプトで受け取ることありきで考えていたので仕方ないかなと思っていたのですが、前回の記事公開後に下記のようなコメントをいただきました。

f:id:akanuma-hiroaki:20180721181100p:plain

 そこで今回は Grove コネクタで I2C に接続して、ポーリングで処理する版を試してみました。

回路構成

 今回の回路は Grove コネクタを Seeduino の Grove コネクタの I2C に接続するだけなのですっきりシンプル構成です。

f:id:akanuma-hiroaki:20180721224724j:plain

f:id:akanuma-hiroaki:20180721224745j:plain

ファームウェア

 前回のコードをベースにいくつか変更を加えました。

 前回は setup()attachInterrupt() を使って割り込みを受け付ける設定をしていましたが、その部分を削除し、代わりに pinMode() で A5 ピンからセンサの入力を読み取るための設定をしています。

 また、 前回は loop() では何もしていませんでしたが、前回割り込み検知時に実行していた処理を 100ms の delay で繰り返し実行するようにし、その中で A5 ピンの入力値をチェックするようにしています。

 A5 ピンからの入力値が 0 の場合もしくは 1 の状態が続いているときは何もせずに return し、0 から 1 に変化した時には前回同様の後続の処理を行うことで、インタラプト使用時と同じ挙動になるようにしてみました。もし 100ms 未満の間隔で心拍を検知してまた 0 に戻るようなことがあると検知できないのですが、最初に入力値が 1 かどうかだけをチェックしてやってみたところ、 100ms の delay で 1 の状態が複数回続いていたので、今回はこのやり方でやってみています。

unsigned char counter;
unsigned long temp[21];
unsigned long sub;
bool data_effect = true;
unsigned int heart_rate;
int hrm_output;
int hrm_output_tmp;

const int max_heartpluse_duty = 2000;

void setup()
{
    Serial.begin(9600);
    Serial.println("Please ready your chest belt.");
    delay(5000);
    arrayInit();
    Serial.println("Heart rate test begin.");
    pinMode(A5, INPUT);
}

void loop()
{
  interrupt();
  delay(100);
}

void sum()
{
    if(data_effect)
    {
      heart_rate = 1200000 / (temp[20] - temp[0]); 
      Serial.print("Heart_rate_is:\t");
      Serial.println(heart_rate);
    }
    data_effect = 1;
}

void interrupt()
{
    hrm_output_tmp = digitalRead(A5);

    if(hrm_output_tmp == 0)
    {
      hrm_output = 0;
      return;
    }

    if(hrm_output_tmp == hrm_output)
    {
      return;
    }

    hrm_output = hrm_output_tmp;
    temp[counter] = millis();
    Serial.println(counter,DEC);
    Serial.println(temp[counter]);
    switch(counter)
    {
        case 0:
            sub = temp[counter] - temp[20];
            Serial.println(sub);
            break;
        default:
            sub = temp[counter] - temp[counter - 1];
            Serial.println(sub);
            break;
    }

    if(sub > max_heartpluse_duty)
    {
        data_effect = 0;
        counter = 0;
        Serial.println("Heart rate measure error,test will restart!" );
        arrayInit();
    }

    if (counter == 20 && data_effect)
    {
        counter = 0;
        sum();
    }
    else if(counter != 20 && data_effect)
        counter++;
    else 
    {
        counter = 0;
        data_effect = 1;
    }

}

void arrayInit()
{
    for(unsigned char i = 0; i < 20; i++)
    {
        temp[i] = 0;
    }
    temp[20] = millis();
}

 これを実行すると前回同様に Serial モニタに下記のように出力されます。前回のコードで実行した時と大体同じような結果になったので、ひとまず計測できているようです。

0
1620177
1001
1
1621079
902
2
1622079
1000
3
1623079
1000
4
1623981
902
5
1624881
900
6
1625882
1001
7
1626782
900
8
1627583
801
9
1628583
1000
10
1629485
902
11
1630285
800
12
1631086
801
13
1631987
901
14
1632988
1001
15
1633889
901
16
1634789
900
17
1635690
901
18
1636590
900
19
1637491
901
20
1638292
801
Heart_rate_is:  66

まとめ

 センサからのデータの発生タイミングに依存する処理を行う場合には、インタラプト処理の方がポーリング間隔等を気にする必要もないので向いていると思いますが、一方でプロトタイピングを行う場合は Grove コネクタ等を活用することで半田付けや複雑な配線をしなくて済むのはメリットが大きいですね。特に仕事でプロトタイピングをする場合はいかに機材を少なくして半田付けもなしで試せるかというのは重要なので、やりたい内容によって使い分けるのが重要に思います。

 @maris_HY さん、コメントありがとうございました!

心拍センサを Seeeduino で使ってみる

 自社でヘルスケア関連サービスをやっているということもあり、スイッチサイエンスさんで販売されていた心拍センサが面白そうだったので Seeeduino で使ってみました。

www.switch-science.com

 使い方は Seeed の製品サイトの方にも記載されています。

wiki.seeedstudio.com

回路構成

 今回使う心拍センサは Grove コネクタが使われています。Grove システムについては下記資料でも紹介されています。

Introduction to Grove
http://www.seeedstudio.com/document/pdf/Introduction%20to%20Grove.pdf

 心拍センサの使い方のページの例でも使われている Grove の Basic Shield が上記の資料でも使われていますので、これを参考に下記のように配線しました。

f:id:akanuma-hiroaki:20180710084200j:plain

 Seeeduino にも Grove コネクタが搭載されているのですが、今回は心拍センサが心拍を検知したタイミングでの出力を割り込みで受け取るために D2 ピンを使いたかったので、心拍センサの Grove コネクタに直接ジャンパコードを挿して接続しました。

f:id:akanuma-hiroaki:20180710084253j:plain

 Seeeduino 側は 3.3V、GND、D2ピンにそれぞれ接続します。 Seeeduino の動作電圧も 3.3V に設定してあります。

f:id:akanuma-hiroaki:20180710084227j:plain

ファームウェア

 ファームウェアは先ほどの Seeed の製品ページにサンプルが掲載されていますので、それをベースにして、今回は LED は使っていないので LED 関連部分を削除して下記のようにしました。

 大まかな内容としては、 attachInterrupt() で D2 ピンからの割り込みを受け付けるようにして、心拍センサが心拍を検知した時は interrupt メソッドが実行され、20回検知するとそこまでの検知間隔から心拍数を計算してシリアルモニタに出力します。

unsigned char counter;
unsigned long temp[21];
unsigned long sub;
bool data_effect=true;
unsigned int heart_rate;

const int max_heartpluse_duty = 2000;

void setup()
{
    Serial.begin(9600);
    Serial.println("Please ready your chest belt.");
    delay(5000);
    arrayInit();
    Serial.println("Heart rate test begin.");
    attachInterrupt(0, interrupt, RISING);
}

void loop()
{
}

void sum()
{
    if(data_effect)
    {
      heart_rate=1200000/(temp[20]-temp[0]); 
      Serial.print("Heart_rate_is:\t");
      Serial.println(heart_rate);
    }
    data_effect=1;
}

void interrupt()
{
    temp[counter]=millis();
    Serial.println(counter,DEC);
    Serial.println(temp[counter]);
    switch(counter)
    {
        case 0:
            sub=temp[counter]-temp[20];
            Serial.println(sub);
            break;
        default:
            sub=temp[counter]-temp[counter-1];
            Serial.println(sub);
            break;
    }

    if(sub>max_heartpluse_duty)
    {
        data_effect=0;
        counter=0;
        Serial.println("Heart rate measure error,test will restart!" );
        arrayInit();
    }

    if (counter==20&&data_effect)
    {
        counter=0;
        sum();
    }
    else if(counter!=20&&data_effect)
        counter++;
    else 
    {
        counter=0;
        data_effect=1;
    }

}

void arrayInit()
{
    for(unsigned char i=0;i < 20;i ++)
    {
        temp[i]=0;
    }
    temp[20]=millis();
}

 これを Seeeduino にアップロードして、心拍センサのクリップを耳などにつけると、下記のようにシリアルモニタに心拍検知時と20回測定後の心拍数計算結果が出力されます。

Please ready your chest belt.
Heart rate test begin.
0
32432
0
1
33133
701
2
33133
0
3
34014
881
4
34934
920
5
35849
915
6
36736
887
7
37653
917
8
38588
935
9
39524
936
10
40410
886
11
41291
881
12
42212
921
13
43163
951
14
44107
944
15
44998
891
16
45929
931
17
46887
958
18
47837
950
19
48720
883
20
49581
861
Heart_rate_is:  69

電圧出力の確認

 ここまでで動作確認はできてるのですが、試しにオシロスコープでセンサからの電圧出力を確認してみます。

f:id:akanuma-hiroaki:20180712085629j:plain

 オシロスコープのプローブは心拍センサの Grove コネクタ部分に取り付け、 1系を VCC と GND、 2系は D2 への出力ピンに取り付けます。

f:id:akanuma-hiroaki:20180712085508j:plain

 結果は下記の写真のようになりました。赤いラインは 1系で、こちらは常に一定の電力が供給されているので 3.3V のまっすぐなラインになります。黄色いラインが 2系で、心拍を検知した際にセンサからのデジタル出力により 3.3V の電圧を検知しています。

f:id:akanuma-hiroaki:20180712085545j:plain

まとめ

 市販のセンサーで簡単にバイタルデータを計測できてしまうのは面白いですね。また、オシロスコープで実際の電圧検知までできて、病院の心拍計のようなイメージまで可視化されたのでより面白かったです。今回は測定結果はシリアルモニタに出力するだけだったので、これをネットワーク経由で送信したり、 LED などを使って可視化することもできると楽しそうかなと思っています。

D級アンプボードと Raspberry Pi で音を鳴らしてみる

 電子工作でセンサー等を使って何かを検知する仕組みを作った時に、結果を Slack に通知するなどはよくやるのですが、それ以外にも何か通知する手段を使いたいなと思い、アンプボードと小型スピーカーで音を鳴らせるようにしてみました。今回使ったアンプボードはスイッチサイエンスで販売されていた、 Adafruit の D級アンプボードです。

www.switch-science.com

 これを同じくスイッチサイエンスで販売されていた薄型スピーカーと組み合わせて使ってみました。

www.switch-science.com

 アンプボードのチュートリアルは Adafruit の製品ページで公開されています。 Raspberry Pi 向けのサンプルになっているので、今回は基本的にこのチュートリアルに従って試してみます。

https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp

ピンヘッダ実装 & 回路図

 購入時はピンヘッダなどが実装されていないので、まず下記写真のように半田付けします。

f:id:akanuma-hiroaki:20180707201156j:plain

 そして下記回路図のように配線します。

アンプ側 - Raspberry Pi 側
VIN - 5V
GND - GND
DIN - GPIO 21
BCLK - GPIO 18
LRCLK - GPIO 19

f:id:akanuma-hiroaki:20180707183236p:plain

 実際に配線してみたものは下記の様になりました。

f:id:akanuma-hiroaki:20180707225836j:plain

セットアップ

 Raspberry Pi でのセットアップについてはスクリプトが提供されているので、それを実行するだけでセットアップできます。 Raspberry Pi 上で下記のようにスクリプトを実行します。

$ curl -sS https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2samp.sh | bash
Support for your operating system is experimental. Please visit
forums.adafruit.com if you experience issues with this product.


This script will install everything needed to use
i2s amplifier

--- Warning ---

Always be careful when running scripts and commands
copied from the internet. Ensure they are from a
trusted source.

If you want to see what this script does before
running it, you should run:
    \curl -sS github.com/adafruit/Raspberry-Pi-Installer-Scripts/i2samp

Do you wish to continue? [y/N] y

Checking hardware requirements...

Adding Device Tree Entry to /boot/config.txt
dtoverlay=hifiberry-dac
dtoverlay=i2s-mmap

Commenting out Blacklist entry in 
/etc/modprobe.d/raspi-blacklist.conf

Disabling default sound driver
Configuring sound output

All done!

Enjoy your new i2s amplifier!

Some changes made to your system require
your computer to reboot to take effect.

Would you like to reboot now? [y/N] 

 スクリプトを実行したら一度 Raspberry Pi を再起動します。再起動後に再度同じスクリプトを実行すると、サウンドテストが実行されます。これで音が鳴ればとりあえず問題ありません。

pi@raspberrypi:~ $ curl -sS https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2samp.sh | bash
Support for your operating system is experimental. Please visit
forums.adafruit.com if you experience issues with this product.


This script will install everything needed to use
i2s amplifier

--- Warning ---

Always be careful when running scripts and commands
copied from the internet. Ensure they are from a
trusted source.

If you want to see what this script does before
running it, you should run:
    \curl -sS github.com/adafruit/Raspberry-Pi-Installer-Scripts/i2samp

Do you wish to continue? [y/N] y

Checking hardware requirements...

Adding Device Tree Entry to /boot/config.txt
dtoverlay already active
i2s mmap dtoverlay already active

Commenting out Blacklist entry in 
/etc/modprobe.d/raspi-blacklist.conf

Default sound driver currently not loaded
Configuring sound output

We can now test your i2s amplifier
Set your speakers at a low volume if possible!
Do you wish to test your system now? [y/N] y
Testing...

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
WAV file(s)
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 2.854479
 0 - Front Left
 1 - Front Right
Time per period = 2.996463
 0 - Front Left
 1 - Front Right
Time per period = 3.018638
 0 - Front Left
 1 - Front Right
Time per period = 3.017745
 0 - Front Left
 1 - Front Right
Time per period = 2.996523

All done!

Enjoy your new i2s amplifier!

スピーカーテスト

 改めてスピーカーテストを実行してみます。下記の様に speaker-test コマンドを実行すると、ホワイトノイズでスピーカーテストが実行されます。

$ speaker-test -c2

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 5.823784
 0 - Front Left
 1 - Front Right
Time per period = 5.990661
 0 - Front Left
 1 - Front Right
Time per period = 5.990672
 0 - Front Left
 1 - Front Right

 さらに下記のように wav ファイルを指定すると wav ファイルを再生する形でテストが実行されます。

$ speaker-test -c2 --test=wav -w /usr/share/sounds/alsa/Front_Center.wav

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
WAV file(s)
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 2.684377
 0 - Front Left
 1 - Front Right
Time per period = 2.856224
 0 - Front Left
 1 - Front Right
Time per period = 2.856005
 0 - Front Left
 1 - Front Right
Time per period = 2.855911

mp3 の再生

 先ほどのテストでは wav ファイルを再生しましたが、次は mp3 を再生してみます。再生するためのアプリケーションとして mpg123 を使用します。

mpg123, Fast MP3 Player for Linux and UNIX systems

 まず apt-get で mpg123 をインストールします。

$ sudo apt-get install -y mpg123

 あとは mpg123 コマンドで対象を指定すれば mp3 が再生されます。下記の例では mp3 のストリーミングの URL を指定してそのまま再生しています。

$ mpg123 http://ice1.somafm.com/u80s-128-mp3
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layers 1, 2 and 3
        version 1.23.8; written and copyright by Michael Hipp and others
        free software (LGPL) without any warranty but with best wishes

Directory: http://ice1.somafm.com/

Terminal control enabled, press 'h' for listing of keys and functions.

Playing MPEG stream 1 of 1: u80s-128-mp3 ...
ICY-NAME: Underground Eighties: UK Synthpop and a bit of New Wave. [SomaFM]
ICY-URL: http://somafm.com

MPEG 1.0 L III cbr128 44100 stereo

ICY-META: StreamTitle='Comsat Angels - As Above So Below';StreamUrl='';

ICY-META: StreamTitle='Heaven 17 - Crushed By The Wheels Of Industry [(Parts One And Two) Uninterr';StreamUrl='';

音量調整

 再生用アプリケーション側でも音量調整機能を持っていることもありますが、コマンドラインから alsamixer を実行することで音量を調整することができます。

$ alsamixer

 実行すると下記のような画面が表示されますので、音量を指定し、 ESC で終了します。

f:id:akanuma-hiroaki:20180707173308p:plain

Python スクリプトからの実行

 コマンドラインからではなくプログラム内から音声を再生する方法は色々ありますが、ここでは Python スクリプトの中から音声を再生するために PyGame を使ってみます。インストールは apt-get install を実行するだけです。

$ sudo apt-get install python-pygame

 Adafruit から PyGame のサンプルも提供されていますので、こちらを使ってみます。ダウンロードして解凍します。

$ wget https://cdn-learn.adafruit.com/assets/assets/000/041/506/original/pygame_example.zip
$ unzip pygame_example.zip
$ cd pygame_example/

 サンプルスクリプトを実行してみます。このスクリプトは同じディレクトリにある全ての mp3 を再生します。引数にはボリュームの大きさを指定します。

$ python pygameMP3.py 0.75
Playing at volume: 0.75

['HiTomSamp.mp3', 'SnareSamp.mp3', 'FloorTomSamp.mp3', 'CrashSamp.mp3']
Music file HiTomSamp.mp3 loaded!
Music file SnareSamp.mp3 loaded!
Music file FloorTomSamp.mp3 loaded!
Music file CrashSamp.mp3 loaded!

まとめ

 Raspberry Pi 向けにはセットアップ用のスクリプトやサンプルが用意されているので、とても簡単に音を鳴らすところまでいけてしまいましたが、 Arduino 等で使用する場合にはセットアップ内容を理解して進めないといけないので、少し時間はかかりそうです。ただ今回使ったアンプとスピーカーは両方で1,000円程度でしたが、通知用等で使うには十分だったので、今後何か作ろうとした時にも気軽に取り入れられそうです。

BLE Nano V2 を mbed で Lチカ

 以前 BLE Nano の V1 で Lチカしてみた記事を書いたことがありますが、 BLE Nano は後継機の V2 が出てスイッチサイエンス等でも購入できるのは V2 のみになっています。

www.switch-science.com

 そこで今回は改めて BLE Nano の V2 でとりあえず Lチカまでをやってみました。 V1 と同様に初めて BLE Nano を使う場合は書き込み用のボードが必要なので、セットになったものを買うと良いのですが、 V1 の時に買ったものがあるので、そちらを使ってみました。

www.switch-science.com

 下記の画像のように V1 と V2 ではピン配置なども違うので、実装にあたっては注意が必要ですが、プログラムの書き込みだけであれば V1 の時のものも使えそうです。

f:id:akanuma-hiroaki:20180630233943j:plain

オンラインのコンパイラで環境設定

 まずはオンラインのコンパイラで環境設定から。チュートリアルは下記サイトに公開されていますので、こちらに従ってやってみます。

github.com

 mbed で開発をするにはまずは対象のボードを選択する必要があります。 BLE Nano の V1 の時は対象のボードとしても BLE Nano が選択できたのですが、 V2 はまだ対象のボードの選択肢には含まれていません。 V2 では SoC が V1 の Nordic nRF51822 から nRF52832 に変更になっていたりしますので、 V1 を選択して使うわけにもいきません。現状ではチュートリアルでも紹介れているように、 nRF52832 を搭載した開発ボードである nRF52-DK を対象として選択して開発するようです。

Nordic nRF52-DK | Mbed

 上記開発ボードのページの右側に Add to your Mbed Compiler というボタンがありますので、これをクリックするとオンラインのコンパイラにボードが追加されます。

f:id:akanuma-hiroaki:20180630224131p:plain

 次にコンパイラの画面に移って、右上のボード選択用のボタンをクリックします。

f:id:akanuma-hiroaki:20180630224356p:plain

 ボードの選択画面が開きますので、 nRF52-DK を選択してから右上の Select Platform ボタンをクリックします。

f:id:akanuma-hiroaki:20180630224431p:plain

Lチカ実行

 ここまででひとまず設定は終わりなのでプログラムを用意します。コンパイラの左上の 新規 > 新しいプログラム から新規のプログラムを作成します。

f:id:akanuma-hiroaki:20180630224819p:plain

 新しいプログラムの作成ダイアログが開いたら、プラットフォーム、テンプレート、プログラム名を下記のように設定して OK をクリックします。

f:id:akanuma-hiroaki:20180630225037p:plain

 すると下記のように Lチカのコードが含まれたプログラムが作成されます。

f:id:akanuma-hiroaki:20180630225235p:plain

 デフォルトでは LED のピンの指定が LED となっていますので、これを BLE Nano V2 の LED のピン番号である P0_11 に変更して、下記のようなコードにします。

#include "mbed.h"

DigitalOut led1(P0_11);

// main() runs in its own thread in the OS
int main() {
    while (true) {
        led1 = !led1;
        wait(0.5);
    }
}

 今回のコードはこれだけなので、画面上部の コンパイル をクリックしてコンパイルを実行します。

f:id:akanuma-hiroaki:20180630225533p:plain

 正常にコンパイルされると mbed-os-example-blinky_NRF52_DK.bin というファイルが生成されますので、これを DAPLINK として認識されている BLE Nano V2 に保存(アップロード)します。試した限りではアップロードが終わったら一度 BLE Nano V2 を抜いて挿し直さないとプログラムが実行されませんでした。

まとめ

 BLE Nano V2 そのものについての mbed でのサポートはまだ不十分なようですが、とりあえず Lチカだけであれば nRF52-DK をターゲットとすることで実行することができました。今回は CLI でのコンパイルと実行まではできませんでしたが、今後試してみたいと思います。また、 BLE を使った実装については、 nRF52832 を使ったサンプルはあまり多くなさそうなので、ちょっと苦戦しそうかなと思っています。

空気品質センサとサーボモータで環境の悪化を通知する

 前回ひとまず空気品質センサ CCS811 で二酸化炭素濃度などを測定してみました。今回は二酸化炭素濃度が閾値を超えたら何か目に見える形で通知するようにしてみたいと思います。オフィスの二酸化炭素濃度は業務のパフォーマンスにも影響するということで、チームラボさんも測定して改善されているようなので、二酸化炭素濃度が高くなっていることがわかるようにしてみたいと思います。

ch.nicovideo.jp

サーボモータを使ってみる

 二酸化炭素濃度が閾値を超えたら Slack に通知するというのをそのうちやりたいとは思ってるんですが、それ自体には自分としてはそんなに目新しさもなかったので、もうちょっとギミック的なものが作れないかなと、サーボモータを使ってみることにしました。

www.switch-science.com

 サーボモータは今まで使ったことなかったのですが、使い方は簡単で、 Arduino ではサーボモータ用のライブラリが標準で用意されているので、それを使用します。まずは servo.attach() で制御用のピンを指定して、 servo.write() で角度を指定するだけです。

#include <Servo.h>

Servo servo;

servo.attach(3); // 制御ピンに 3番ピンを指定する例
servo.write(90); // サーボの角度を 90度に指定する例

 今回はサーボモータに LED を組み合わせて、二酸化炭素濃度が閾値を超えたら LED が点灯しつつ持ち上がるようにしてみたいと思います。

回路図

 だいぶごちゃっとしてしまいましたが、回路図は下記のようになりました。

f:id:akanuma-hiroaki:20180619003533p:plain

 CCS811 の配線については基本は前回と同じですが、今回はサーボモータを使う都合で Seeeduino の動作電圧を 5V にしたので、 CCS811 の NOT_WAK ピンへの入力電圧が 3.3V 以下になるように、4.7kΩ の抵抗を使用して分圧しています。

 サーボモータの電源は 5V 出力から取り、制御ピンは 3番ピンを使用しています。

 LED には 330Ωの抵抗を使用しています。

ファームウェア

 ファームウェアも基本的には前回と同様で、サーボモータと LED の初期化と、二酸化炭素濃度の測定値によってサーボモータと LED を制御する処理を追加しています。

#include <SparkFunCCS811.h>
#include <Servo.h>

#define CCS811_ADDR 0x5B

#define PIN_NOT_WAKE 5
#define PIN_NOT_INT 6
#define PIN_SERVO 3
#define PIN_LED 4

#define DEGREE_CLEAN_AIR 90
#define DEGREE_DIRTY_AIR 0
#define CO2_THRESHOLD 1000

CCS811 myCCS811(CCS811_ADDR);
Servo servo;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("...");

  CCS811Core::status returnCode;
  
  returnCode = myCCS811.begin();
  Serial.print("CCS811 begin exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  returnCode = myCCS811.setDriveMode(2);
  Serial.print("Mode request exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_INT, INPUT_PULLUP);
  returnCode = myCCS811.enableInterrupts();
  Serial.print("Interrupt configuation exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_WAKE, OUTPUT);
  digitalWrite(PIN_NOT_WAKE, 1);
  
  servo.attach(PIN_SERVO);
  servo.write(90);
  
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
  if (digitalRead(PIN_NOT_INT) == 0)
  {
    digitalWrite(PIN_NOT_WAKE, 0);
    delay(1);
    myCCS811.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(myCCS811.getCO2());
    Serial.print("] tVOC[");
    Serial.print(myCCS811.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
    
    if (myCCS811.getCO2() >= CO2_THRESHOLD)
    {
      digitalWrite(PIN_LED, HIGH);
      servo.write(DEGREE_DIRTY_AIR);
    }
    else
    {
      digitalWrite(PIN_LED, LOW);
      servo.write(DEGREE_CLEAN_AIR);
    }
    
    digitalWrite(PIN_NOT_WAKE, 1);
    delay(1);
  }
}

void printDriverError( CCS811Core::status errorCode )
{
  switch ( errorCode )
  {
    case CCS811Core::SENSOR_SUCCESS:
      Serial.print("SUCCESS");
      break;
    case CCS811Core::SENSOR_ID_ERROR:
      Serial.print("ID_ERROR");
      break;
    case CCS811Core::SENSOR_I2C_ERROR:
      Serial.print("I2C_ERROR");
      break;
    case CCS811Core::SENSOR_INTERNAL_ERROR:
      Serial.print("INTERNAL_ERROR");
      break;
    case CCS811Core::SENSOR_GENERIC_ERROR:
      Serial.print("GENERIC_ERROR");
      break;
    default:
      Serial.print("Unspecified error.");
  }
}

動作時の様子

 だいぶ雑多な感じですが、とりあえず組んでみた回路は下記のようになりました。

f:id:akanuma-hiroaki:20180619075828j:plain

 二酸化炭素濃度が閾値を超えると下記画像のようにサーボモータが 90度回転して、 LED も点灯します。

f:id:akanuma-hiroaki:20180619075858j:plain

まとめ

 今回はとりあえず配線した感じでしたが、もっと配線をすっきりさせて筐体に入れて、サーボが動いた時だけ筐体から出て見えるようにしたり、何か工夫できると面白そうかなと思いました。また、 LED はワニ口クリップで配線してるのですが、下手をするとショートする可能性もあるので、継続的に使うにはこの辺の安全性も改善したいところです。