アルファ版がリリースされた Arduino CLI を試してみる

 Arduino の CLI がリリースされたということで試してみました。 Arduino からは下記ブログでアナウンスされています。

blog.arduino.cc

 Makefileやスクリプトに組み込むためというのが目的のようで、Arduino IDE の主要な機能をCLIから実行できるようにすることを目指すとのことです。下記の Github リポジトリで公開されています。

github.com

 まだアルファリリースということなので、バグを含んでいたり、予告なく変更される可能性は大きいですが、ひとまずチュートリアルに沿って試してみました。

ダウンロード

 Arduino CLI は単一のバイナリファイルで提供されているので、ダウンロードするだけで実行できるようになります。私の環境は Mac なので、下記URLからバイナリをダウンロードします。

Mac OSX 0.2.1-alpha.preview

 そのまま実行しても良いのですが、今回は /usr/local/bin にシンボリックリンクを作成してパスを通します。

$ ln -s ~/arduino-cli-0.2.1-alpha.preview-osx /usr/local/bin/arduino-cli
$ arduino-cli version
arduino-cli version 0.2.1-alpha.preview

Sketch の作成

 まず下記コマンドで Sketch を作成します。 ino ファイルが1つ作成されます。

$ arduino-cli sketch new SampleSketch
Sketch created in: /Users/akanuma/Documents/Arduino/SampleSketch
$
$ ls -l Documents/Arduino/SampleSketch/
total 8
-rw-r--r--  1 akanuma  staff  35 Sep  1 11:01 SampleSketch.ino
$
$ cat Documents/Arduino/SampleSketch/SampleSketch.ino                                                                                                                                                                                      

void setup() {
}

void loop() {
}

 今回はとりあえず Arduino 互換機の Seeduino でLチカを試してみます。 Seeeduino ではオンボードの LED は13番ピンに接続されています。

wiki.seeedstudio.com

 ino ファイルを下記のように変更します。

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, HIGH);
  delay(1000);
  digitalWrite(13, LOW);
  delay(1000);
}

ボードの設定とコンパイル、アップロード

 Arduino CLI を最初に使う際にはプラットフォームのインデックス情報をアップデートする必要があります。下記のコマンドでアップデートします。

$ arduino-cli core update-index
Updating index: package_index.json downloaded   

 次に Seeeduino を Mac にUSBケーブルで接続します。 arduino-cli board list コマンドで、現在接続されているボードの情報を表示します。

$ arduino-cli board list
FQBN    Port                    ID              Board Name
        /dev/cu.usbmodem1421    2886:0004       unknown   

 Board Name が unknown になっているので、正しい core をインストールする必要があるということで、下記のように core を検索します。

$ arduino-cli core search arduino
Searching for platforms matching 'arduino'

ID                      Version Installed       Name                                             
Intel:arc32             2.0.2   No              Intel Curie Boards                               
arduino:avr             1.6.21  Yes             Arduino AVR Boards                               
arduino:nrf52           1.0.2   No              Arduino nRF52 Boards                             
arduino:sam             1.6.11  No              Arduino SAM Boards (32-bits ARM Cortex-M3)       
arduino:samd            1.6.19  No              Arduino SAMD Boards (32-bits ARM Cortex-M0+)     
arduino:samd_beta       1.6.23  No              Arduino SAMD Beta Boards (32-bits ARM Cortex-M0+)
arduino:stm32f4         1.0.1   No              Arduino STM32F4 Boards                           
littleBits:avr          1.0.0   No              littleBits Arduino AVR Modules   

 チュートリアルでは MKR1000 を使っていますが、今回は Arduino Uno の互換機の Seeeduino を使うので、下記のように検索してみます。

$ arduino-cli core search uno
Searching for platforms matching 'uno'

ID              Version Installed       Name              
arduino:avr     1.6.21  Yes             Arduino AVR Boards

 見つかった core をインストールします。

$ arduino-cli core install arduino:avr
Downloading arduino:avr-gcc@4.9.2-atmel3.5.4-arduino2...
arduino:avr-gcc@4.9.2-atmel3.5.4-arduino2 downloaded                                                                                                                                                                                          
Downloading arduino:avrdude@6.3.0-arduino9...
arduino:avrdude@6.3.0-arduino9 downloaded                                                                                                                                                                                                     
Downloading arduino:arduinoOTA@1.1.1...
arduino:arduinoOTA@1.1.1 downloaded                                                                                                                                                                                                           
Downloading arduino:avr@1.6.21...
arduino:avr@1.6.21 downloaded                                                                                                                                                                                                                 
Installing arduino:avr-gcc@4.9.2-atmel3.5.4-arduino2
arduino:avr-gcc@4.9.2-atmel3.5.4-arduino2 installed
Installing arduino:avrdude@6.3.0-arduino9
arduino:avrdude@6.3.0-arduino9 installed
Installing arduino:arduinoOTA@1.1.1
arduino:arduinoOTA@1.1.1 installed
Updating arduino:avr@1.6.21 with arduino:avr@1.6.21...
arduino:avr@1.6.21 installed
$
$ arduino-cli core list
ID                      Installed       Latest  Name                                        
arduino:avr             1.6.21          1.6.21  Arduino AVR Boards
espressif:esp32    

 再度ボード情報を確認してみます。

$ arduino-cli board list
FQBN    Port                    ID              Board Name
        /dev/cu.usbmodem1421    2886:0004       unknown   

 まだ正しく認識されていないようです。試しに下記 core もインストールしてみましたが、結果は変わりませんでした。

$ arduino-cli core search ATmega328
Searching for platforms matching 'atmega328'

ID                      Version Installed       Name                    
atmel-avr-xminis:avr    0.6.0   No              Atmel AVR Xplained-minis

 ボードを正しく認識させることはできませんでしたが、 FQBN(Fully Qualified Board Name) を指定すればコンパイルできそうだったので、下記のようにコンパイルしてみます。

$ arduino-cli compile --fqbn arduino:avr:uno Documents/Arduino/SampleSketch                                                                                                                                                              
Sketch uses 928 bytes (2%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.

 コンパイルが成功したようなので、下記コマンドで Seeeduino にアップロードします。

$ arduino-cli upload -p /dev/tty.usbmodem1421 --fqbn arduino:avr:uno Documents/Arduino/SampleSketch

 ターミナル上には特に結果は表示されませんでしたが、正しくアップロードされたようで、オンボードの LED でLチカが実行されました。

M5Stack の Sketch をビルドしてみる

 最近 M5Stack を触っているので、ついでに M5Stack の Sketch がビルドできるか試してみます。まず M5Stack のライブラリを検索してみます。

$ arduino-cli lib search m5stack
Name: "M5Stack"
  Author:  M5Stack
  Maintainer:  Zibin Zheng <bin@m5stack.com>
  Sentence:  Library for M5Stack Core development kit
  Paragraph:  See more on http://M5Stack.com
  Website:  https://github.com/m5stack/m5stack
  Category:  Device Control
  Architecture:  esp32
  Types:  Contributed
  Versions:  [0.1.1, 0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.8, 0.1.9, 0.2.0, 0.2.1, 0.2.2]
Name: "M5Stack-SD-Updater"
  Author:  tobozo@noreply.github.com
  Maintainer:  tobozo@noreply.github.com
  Sentence:  SD Card Loader for M5 Stack
  Paragraph:  Package your apps on an SD card and load them from a menu app, button or MQTT message.
  Website:  https://github.com/tobozo/M5Stack-SD-Updater/
  Category:  Uncategorized
  Architecture:  esp32
  Types:  Contributed
  Versions:  [0.0.2, 0.1.0, 0.2.2, 0.3.0, 0.3.2]
Name: "M5Stack_Avatar"
  Author:  Shinya Ishikawa
  Maintainer:  Shinya Ishikawa<ishikawa.s.1027@gmail.com>
  Sentence:  Yet another avatar module for M5Stack
  Paragraph:  See more on http://M5Stack.com
  Website:  https://platformio.org/lib/show/4529/M5Stack-Avatar
  Category:  Device Control
  Architecture:  esp32
  Types:  Contributed
  Versions:  [0.4.0, 0.5.1, 0.6.0, 0.6.1, 0.6.2, 0.6.3, 0.6.4]

 M5Stack の環境設定は以前の記事で紹介しています。

blog.akanumahiroaki.com

blog.akanumahiroaki.com

 上記記事内で行った設定で、 M5Stack ライブラリと m5stack-avatar-master ライブラリはインストールされています。

$ arduino-cli lib list
Name                            Installed       Location  
Adafruit_Circuit_Playground     1.8.1           sketchbook
Bridge                          1.7.0           sketchbook
Ethernet                        2.0.0           sketchbook
Firmata                         2.5.8           sketchbook
Keyboard                        1.0.2           sketchbook
M5Stack                         0.2.1           sketchbook
SD                              1.2.2           sketchbook
m5stack-avatar-master           0.6.2           sketchbook

 いずれも新しいバージョンがリリースされているので、アップデートします。インストール時と同様のコマンドでアップデートも実行することができます。

$ arduino-cli lib install M5Stack
M5Stack@0.2.2 downloaded                                                                                                                                                                                                                      
Replacing M5Stack@0.2.1 with M5Stack@0.2.2
Installed M5Stack@0.2.2
$
$ arduino-cli lib install M5Stack_Avatar
M5Stack_Avatar@0.6.4 downloaded                                                                                                                                                                                                               
Installed M5Stack_Avatar@0.6.4

 再度インストール済みライブラリを確認してみます。

$ arduino-cli lib list
Name                            Installed       Location  
Adafruit_Circuit_Playground     1.8.1           sketchbook
Bridge                          1.7.0           sketchbook
Ethernet                        2.0.0           sketchbook
Firmata                         2.5.8           sketchbook
Keyboard                        1.0.2           sketchbook
M5Stack                         0.2.2           sketchbook
M5Stack_Avatar                  0.6.4           sketchbook
SD                              1.2.2           sketchbook
m5stack-avatar-master           0.6.2           sketchbook

 以前 M5StackAvatar のライブラリをインストールした際にライブラリマネージャーではなく、ファイルをダウンロードして配置したため、今回インストールしたライブラリとは別の扱いになっているようです。以前のライブラリはアンインストールしておきます。

$ arduino-cli lib uninstall m5stack-avatar-master
Uninstalling m5stack-avatar-master@0.6.2
~  $ 
~  $ arduino-cli lib list
Name                            Installed       Location  
Adafruit_Circuit_Playground     1.8.1           sketchbook
Bridge                          1.7.0           sketchbook
Ethernet                        2.0.0           sketchbook
Firmata                         2.5.8           sketchbook
Keyboard                        1.0.2           sketchbook
M5Stack                         0.2.2           sketchbook
M5Stack_Avatar                  0.6.4           sketchbook
SD                              1.2.2           sketchbook

 新しい Sketch を1つ作成します。

$ arduino-cli sketch new M5StackSampleSketch
Sketch created in: /Users/akanuma/Documents/Arduino/M5StackSampleSketch

 ino ファイルを下記のように変更します。

#include <M5Stack.h>

// the setup routine runs once when M5Stack starts up
void setup(){

  // Initialize the M5Stack object
  M5.begin();

  // LCD display
  M5.Lcd.print("Hello World!!");
}

// the loop routine runs over and over again forever
void loop() {

}

 ボードの認識についてはいくつかの core を試したのですが、 Seeeduino の時と同様に unknown のままでした。

$ arduino-cli board list
FQBN    Port                    ID              Board Name
        /dev/cu.SLAB_USBtoUART  10C4:EA60       unknown   

 FQBN を指定してコンパイルしてみます。

$ arduino-cli compile --fqbn espressif:esp32:m5stack-core-esp32 Documents/Arduino/M5StackSampleSketch
Alternatives for M5Stack.h: [M5Stack@0.2.2]
ResolveLibrary(M5Stack.h)
  -> candidates: [M5Stack@0.2.2]
Alternatives for Wire.h: [Wire@1.0]
ResolveLibrary(Wire.h)
  -> candidates: [Wire@1.0]
Alternatives for SPI.h: [SPI@1.0]
ResolveLibrary(SPI.h)
  -> candidates: [SPI@1.0]
Alternatives for FS.h: [FS@1.0]
ResolveLibrary(FS.h)
  -> candidates: [FS@1.0]
Alternatives for SD.h: [SD@1.0.5 SD@1.2.2]
ResolveLibrary(SD.h)
  -> candidates: [SD@1.0.5 SD@1.2.2]
Multiple libraries were found for "SD.h"
 Used: /Users/akanuma/Documents/Arduino/hardware/espressif/esp32/libraries/SD
 Not used: /Users/akanuma/Documents/Arduino/libraries/SD
Multiple libraries were found for "M5Stack.h"
 Used: /Users/akanuma/Documents/Arduino/libraries/M5Stack
Multiple libraries were found for "Wire.h"
 Used: /Users/akanuma/Documents/Arduino/hardware/espressif/esp32/libraries/Wire
Multiple libraries were found for "SPI.h"
 Used: /Users/akanuma/Documents/Arduino/hardware/espressif/esp32/libraries/SPI
Multiple libraries were found for "FS.h"
 Used: /Users/akanuma/Documents/Arduino/hardware/espressif/esp32/libraries/FS
Error: open /var/folders/l1/5gdn8snd6gj_nfyh5j4nc1sw0000gn/T/arduino-sketch-5C8AE8258C7345D00A6E4E421EB09E5D/core/core.a: no such file or directory
Compilation failed.

 core.a が見つからないということでコンパイルエラーになってしまいました。調べてみたのですが原因はわからず。同じようにエラーになっている方もいるようです。

tech.144lab.com

 まだアルファ版ということで Arduino CLI 側に問題があるのか、私の環境の問題かはわかりませんが、ひとまず Arduino IDE からはコンパイルできるので、今後アップデートがあればまた試してみたいと思います。

まとめ

 アルファリリースということで、Stable版がリリースされるまではまだ時間がかかるかと思いますが、 IDE 以外にも選択肢が増えるのは嬉しいですね。好みとしては IDE と同じことができるならなんとなく CLI の方が好きです。とりあえずプロトタイピング等で試している段階であれば IDE の方が環境設定に苦労しないで済みそうなので便利ですが、実際のプロダクト開発では CLI で自動化できると楽なところは多そうです。今後のアップデートに期待したいですね。

M5Stack でサーモグラフィー

 M5Stack でサーモグラフィー画像を表示している例があったので、試しにやってみました。下記記事を参考にさせていただき、ほぼそのまま実行しています。

mag.switch-science.com

 先に結果を言っておくと、想定したように正しくは表示されませんでしたが、一応動作はするようになったのでやったことを書いておきます。

サーモグラフィーとの接続

 今回使ったサーモグラフィーは、上記記事で紹介されている下記のサーモグラフィーです。

www.switch-science.com

 接続方法も基本的には上記記事の通りで、M5Stack とサーモグラフィーの SCL, SDA, GND, 3.3V をそれぞれ接続します。上記の記事ではエンクロージャーまで作成していますが、そこまではできなかったので、とりあえずブレッドボードにサーモグラフィーを挿して、 M5Stack とジャンパケーブルで接続しました。

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

 ちなみに M5Stack のピン配列は向かい合うピンとソケットで同じ信号が扱えるようになっていて、例えば今回使っている SCL, SDA, GND, 3.3V のピンは、向かいの辺にある 22番, 21番, GND, 3.3V のソケットと同様に扱えます。

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

コードの実装

 今回は参考記事のコードをほぼそのまま使用させていただいていますが、そのままでは動かなかったので、所々変更を入れています。

 まず元のコードのコメントでも書かれていますが、ファームウェアの 0.3.4 からボタンオブジェクトが変更になっていますので、ボタンオブジェクトの import を下記のように変更します。

from m5stack import lcd, buttonA, buttonB, buttonC

 また、 I2C オブジェクトの初期化時に明示的にピン番号を指定するために、 Pin モジュールも import します。

from machine import I2C, Pin

 そして I2C オブジェクトの初期化時に、今回使用しているピンの番号で Pin オブジェクトを作成して sda, scl それぞれに指定します。

i2c = I2C(sda = Pin(21), scl = Pin(22))

 ボタンオブジェクトを変更したことに伴って、 loop() 内でのボタンオブジェクトの指定と、押されたかどうかの判定メソッドを変更します。

while True:
    if buttonA.isPressed():
        ison = True
    if buttonB.isPressed():
        minmax()
    if buttonC.isPressed():
        ison = False
    if ison:
        graph()

 コードの全体は下記のようになります。

from m5stack import lcd, buttonA, buttonB, buttonC
from machine import I2C, Pin
import sys
import ustruct

colors = (0x0000FF, 0x0008FF, 0x0010FF, 0x0018FF, 0x0020FF, 0x0028FF, 0x0030FF, 0x0038FF, 0x0040FF, 0x0048FF, 0x0050FF, 0x0058FF, 0x0060FF, 0x0068FF, 0x0070FF, 0x0078FF, 0x0080FF, 0x0088FF, 0x0090FF, 0x0098FF, 0x00A0FF, 0x00A8FF, 0x00B0FF, 0x00B8FF, 0x00C0FF, 0x00C8FF, 0x00D0FF, 0x00D8FF, 0x00E0FF, 0x00E8FF, 0x00F0FF, 0x00F8FF, 0x00FFFF, 0x00FFF7, 0x00FFEF, 0x00FFE7, 0x00FFDF, 0x00FFD7, 0x00FFCF, 0x00FFC7, 0x00FFBF, 0x00FFB7, 0x00FFAF, 0x00FFA7, 0x00FF9F, 0x00FF97, 0x00FF8F, 0x00FF87, 0x00FF7F, 0x00FF77, 0x00FF6F, 0x00FF67, 0x00FF5F, 0x00FF57, 0x00FF4F, 0x00FF47, 0x00FF3F, 0x00FF37, 0x00FF2F, 0x00FF27, 0x00FF1F, 0x00FF17, 0x00FF0F, 0x00FF07, 0x00FF00, 0x08FF00, 0x10FF00, 0x18FF00, 0x20FF00, 0x28FF00, 0x30FF00, 0x38FF00, 0x40FF00, 0x48FF00, 0x50FF00, 0x58FF00, 0x60FF00, 0x68FF00, 0x70FF00, 0x78FF00, 0x80FF00, 0x88FF00, 0x90FF00, 0x98FF00, 0xA0FF00, 0xA8FF00, 0xB0FF00, 0xB8FF00, 0xC0FF00, 0xC8FF00, 0xD0FF00, 0xD8FF00, 0xE0FF00, 0xE8FF00, 0xF0FF00, 0xF8FF00, 0xFFFF00, 0xFFF700, 0xFFEF00, 0xFFE700, 0xFFDF00, 0xFFD700, 0xFFCF00, 0xFFC700, 0xFFBF00, 0xFFB700, 0xFFAF00, 0xFFA700, 0xFF9F00, 0xFF9700, 0xFF8F00, 0xFF8700, 0xFF7F00, 0xFF7700, 0xFF6F00, 0xFF6700, 0xFF5F00, 0xFF5700, 0xFF4F00, 0xFF4700, 0xFF3F00, 0xFF3700, 0xFF2F00, 0xFF2700, 0xFF1F00, 0xFF1700, 0xFF0F00, 0xFF0700)

i2c_address = 0x68
register = 0x80

ison = False

offset = 0
rate = 1

i2c = I2C(sda = Pin(21), scl = Pin(22))

def getval(val):
    absval = (val & 0x7FF)
    if val & 0x800:
        return - float(0x800 - absval) * 0.25
    else:
        return float(absval) * 0.25

def minmax():
    global offset
    global rate
    reg = register
    min_temp = 1000
    max_temp = -1000
    for i in range(0, 64):
        val = ustruct.unpack('<h', i2c.readfrom_mem(i2c_address, reg, 2))[0]
        tmp = getval(val)
        if tmp < min_temp:
            min_temp = tmp
        if max_temp < tmp:
            max_temp = tmp
        reg += 2

    diff = max_temp - min_temp
    diff *= 1.4
    rate = len(colors) / diff
    offset = min_temp * 0.8

    lcd.clear()
    lcd.print('min:    ' + '{:.2f}'.format(min_temp), 0, 0)
    lcd.print('max:    ' + '{:.2f}'.format(max_temp), 0, 10)
    lcd.print('rate:   ' + '{:.2f}'.format(rate), 0, 20)
    lcd.print('offset: ' + '{:.2f}'.format(offset), 0, 30)
    
def graph():
    global offset
    global rate
    reg = register
    for ic in range(0, 8):
        for ir in range(0, 8):
            val = ustruct.unpack('<h', i2c.readfrom_mem(i2c_address, reg, 2))[0]
            tmp = (getval(val) - offset) * rate
            if tmp < 0:
                tmp = 0
            if 127 < tmp:
                tmp = 127
            lcd.rect(ic * 30 + 40, ir * 30, 30, 30, 0, colors[int(tmp)])
            reg += 2

def loop():
    global ison
    lcd.clear()
    lcd.print('Starting...', 0, 0)
    while True:
        if buttonA.isPressed():
            ison = True
        if buttonB.isPressed():
            minmax()
        if buttonC.isPressed():
            ison = False
        if ison:
            graph()

loop()

動作確認

 それでは動作を確認してみます。今回は上記コードを m5cloud でコーディングして M5Stack にアップロードします。

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

 ボタンBを押すとキャリブレーションを行います。

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

 ボタンAを押すとサーモグラフィーの画像の表示を開始します。実行結果は下記の通りです。一応動作はしましたが、結果としては微妙でした。。。手をかざすなどすると反応はするのですが、全体の一部分だけで、大部分の領域が青色のまま変化しませんでした。

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

まとめ

 残念ながら結果が正しく表示されるところまでは行けませんでしたが、向かい合うピンとソケットが同じように使えることや、最新のボタンモジュールなど色々とわかったのは良かったです。効率的なデバッグ方法がまだわかっていないので、その辺りは効率的な開発のためには必要ですね。あと、サーモグラフィーやLCDの扱いについてはもう少し調べて、正しくサーモグラフィー画像が表示されるようにしてみたいと思います。

 今回ボタンモジュールについては下記記事を参考にさせていただきました。

qiita.com

M5Stack のアバターを試してみる

 前回 m5stack の環境設定だけやってみましたが、購入前からやってみたかった顔表示を試してみたいと思います。と言っても @meganetaaan さんがライブラリを公開されているので、表示するだけならすぐにできてしまいました。

github.com

ライブラリのインストール

 Arduino IDE にライブラリをインストールするには、まず下記URLからライブラリの zip ファイルをダウンロードします。

https://github.com/meganetaaan/m5stack-avatar/archive/master.zip

 次にダウンロードした zip ファイルを Arduino IDE のメニューからアップロードします。

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

 これで Arduino IDE で m5stack-avatar ライブラリが使えるようになります。 Library Manager でも下記のようにライブラリが確認できます。

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

サンプルの実行

 ライブラリが正しくインストールされていれば、 Arduino IDE からサンプルスケッチが選択できるようになっています。顔を表示するだけであれば basic を選択します。

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

 あとはスケッチを m5stack にアップロードすれば、ディスプレイに顔が表示されるようになります。

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

表情をランダムに変更する

 m5stack-avatar では普通の顔以外にもいくつか表情が用意されています。サンプルの中にはボタンを押すごとに順番に表情や画面の色を変更していくものがありますが、それをベースに一定時間ごとに表情をランダムに変更するようにしてみました。また、表情が普通の場合には事前に定義したフレーズの中からランダムに吹き出しで表示するようにしました。

#include <M5Stack.h>
#include <Avatar.h>

using namespace m5avatar;

Avatar avatar;

const Expression expressions[] = {
  Expression::Angry,
  Expression::Sleepy,
  Expression::Happy,
  Expression::Sad,
  Expression::Doubt,
  Expression::Neutral
};
const int expressionsSize = sizeof(expressions) / sizeof(Expression);
int idx = 0;

const char* phrases[] = {
  "How are you?",
  "Hungry...",
  "I'm bored."
  "What's time?",
  "Did you say something?",
  "It's time."
};
const int phrasesSize = sizeof(phrases) / sizeof(char*);
int phrase_idx = 0;

void setup()
{
  M5.begin();
  M5.Lcd.setBrightness(30);
  M5.Lcd.clear();

  avatar.init();
}

void loop()
{
  M5.update();
  idx = rand() % expressionsSize;
  Expression expression = expressions[idx];
  avatar.setExpression(expression);
  if (expression == Expression::Neutral) {
    phrase_idx = rand() % phrasesSize;
    avatar.setSpeechText(phrases[phrase_idx]);
  } else {
    avatar.setSpeechText("");
  }
  delay(30000);
}

 これを実行すると下記のように吹き出しでメッセージを表示したり色々な表情に切り替わって行きます。

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

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

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

まとめ

 顔が表示されるようになるとそれだけで何か愛着が湧いてくるから不思議ですね。今はただ表情を変えてるだけですが、実用的な機能も追加してアシスタントっぽくして行けないかなーと思ってます。もしくは個人的には C++ よりも MicroPython の方が好きなので、 MicroPython でもアバターっぽいものが作れないかなーと考えています。

M5Stack の環境設定(Arduino & MicroPython)

 先週行った Maker Faire Tokyo 2018 のスイッチサイエンスさんのブースで、前から気になっていた M5Stack のデモや販売が行われていたので購入してしまいました。加速度センサーやジャイロが入っていた方が今後色々試すにも手軽かなと思ったので、 Basic ではなく Gray を購入しました。

www.switch-science.com

 今回はひとまず最低限の環境の準備までをやってみました。

アンボックス

 パッケージの外観はこんな感じで、本体と同梱物がコンパクトにまとまっています。

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

 開封すると本体の他に、充電やファームウェアのアップロードを行うための USB Type-C のケーブルやジャンパーケーブル、マニュアル等が入っています。

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

 本体裏面にはピン番号が書かれたステッカーが貼られています。

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

 本体の上部と下部は短いピンヘッダでつながっているだけなので、簡単に外すことができます。

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

ドライバのインストール

 M5Stack の公式サイトには各環境用の設定方法が記載されたチュートリアルがあります。

M5Stack Documentation

 まずはドライバのインストールからで、下記サイトから該当する環境用のドライバをダウンロードしてインストールします。

www.silabs.com

 私は Mac を使用しているので、Mac OSX 用のドライバを使用します。私の場合は以前同じドライバをインストールしたことがあったのでそのまま使いますが、初めて Mac OSX で該当のドライバをインストールした際は、システム環境設定の セキュリティーとプライバシー から、該当のドライバの利用について 許可 する必要があります。

Arduino IDE 環境設定

 M5Stack では Arduino と MicroPython の2つの環境を使用できます。まずはこちらのサイトを参考に Arduino 環境から試してみます。

pages.switch-science.com

 Arduino IDE が既に使用可能な状態になっているという前提で、最初に M5Stack で使われている ESP32 を Arduino IDE で扱うために ESP32 Arduino Core をインストールします。

github.com

 私は以前にインストールしたことがあったので、下記コマンドでアップデートだけ実行しました。

$ mkdir -p ~/Documents/Arduino/hardware/espressif
$ cd ~/Documents/Arduino/hardware/espressif
$ cd esp32
$ git submodule update --init --recursive
$ cd tools/
$ python get.py

 次に M5Stack のライブラリをインストールします。 Arduino IDE の Library Manager から M5Stack ライブラリを検索し、下記画像の例では真ん中の `M5Stack by M5Stack' を選択してインストールします。

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

 これで Arduino IDE で M5Stack が扱えるようになっていますので、 Arduino IDE の「ツール」メニューから、ボード -> M5Stack-Core-ESP32 を選択します。

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

 さらに「ツール」メニューから、 シリアルポート -> /dev/cu.SLAB_USBtoUART を選択します。

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

 これで環境設定は一通り完了なので、サンプルコードで動作確認をしてみます。 Arduino IDE の「ファイル」メニューから、 スケッチ例 -> M5Stack -> Basics -> HelloWorld を選択すると下記のサンプルコードが読み込まれます。

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

 これを M5Stack に Upload して下記のように M5Stack のディスプレイに Hello World と表示されれば成功です。

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

MicroPython 環境設定

 次に下記サイトを参考に MicroPython の環境設定も試してみます。

pages.switch-science.com

 まずはシリアルポートから ESP32 のフラッシュメモリの消去や書き込みを行うために esptool をインストールします。今回使っている Python のバージョンは 3.6.5 です。

$ pip install esptool

 次に Github の下記リポジトリから M5Stack のファームウェアをダウンロードします。 MicroPython 環境では m5cloud という Web IDE を使ってオンラインでファームウェアをアップデートできる m5cloud 用ファームウェアと、オフラインでシリアルポートからアップデートするためのオフライン版のファームウェアがあります。

github.com

 まずは参考サイトと同様にオフライン版を試してみます。上記リポジトリの OFF-LINE ディレクトリから最新の bin ファイルをダウンロードします。

$ wget https://github.com/m5stack/M5Cloud/raw/master/firmwares/OFF-LINE/m5stack-20180516-v0.4.0.bin

 次に一度 M5Stack のフラッシュメモリを esptool で消去します。

$ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash
esptool.py v2.5.0
Serial port /dev/tty.SLAB_USBtoUART
Connecting........__
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse
MAC: 80:7d:3a:c4:71:bc
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 5.9s
Hard resetting via RTS pin...

 そして先ほどダウンロードしたオフライン用のファームウェアを書き込みます。

$ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART write_flash --flash_mode dio -z 0x1000 m5stack-20180516-v0.4.0.bin 
esptool.py v2.5.0
Serial port /dev/tty.SLAB_USBtoUART
Connecting........__
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse
MAC: 80:7d:3a:c4:71:bc
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1747296 bytes to 1119059...
Wrote 1747296 bytes (1119059 compressed) at 0x00001000 in 98.6 seconds (effective 141.8 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

 これで参考サイトの通りなら M5Stack が Wi-Fi の AP として動作して、接続するためのIPアドレス等が表示されるはずなのですが、私が試した限りでは Device ID の表示までで止まってしまいました。

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

 参考サイトの例とはバージョンが違うので、リポジトリにある古いバージョンも試してみましたが、いずれもIPアドレス等の表示までは行きませんでした。

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

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

 リポジトリにはバージョン 0.3.8 までしかなかったので一旦オフライン版は諦めて、 m5cloud 版を使用してみます。

$ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART write_flash --flash_mode dio -z 0x1000 m5cloud-20180516-v0.4.0.bin 
esptool.py v2.5.0
Serial port /dev/tty.SLAB_USBtoUART
Connecting........_
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse
MAC: 80:7d:3a:c4:71:bc
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1747552 bytes to 1119193...
Wrote 1747552 bytes (1119193 compressed) at 0x00001000 in 98.6 seconds (effective 141.8 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

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

 m5cloud 版ではIPアドレス等の表示まで実行されました。ここまで行けば Wi-Fi の AP として M5Stack-XXXX という形で表示されるようになりますので、PC 等から接続します。そしてブラウザから 192.168.4.1 に接続すると Wi-Fi のセットアップ画面が表示されますので、 M5Stack を接続する Wi-Fi AP の情報を設定します。

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

 すると M5Stack のディスプレイに m5cloud にデバイスを登録するためのコードが表示されます。このコードは60秒毎に更新されて行きます。

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

 次に m5cloud にアクセスします。初回アクセスの際はユーザ登録をしてダッシュボードが表示されたら、デバイスを登録するために Add ボタンをクリックします。

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

 そして先ほど M5Stack のディスプレイに表示されていたコードを入力すると、デバイスが登録されます。

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

 M5Stack 側にも登録成功のメッセージが表示されます。

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

 これでひとまず環境としては準備できたので、サンプルコードを作成するために、 m5cloud のプロジェクトを作成します。

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

 プロジェクトの情報を入力します。言語としては Python の他にも Lua が選択できるようですが、今回は Python を使用します。

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

 プロジェクトを作成するとデフォルトで下記の内容で main.py が作成されますので今回はそのまま使用します。

from m5stack import lcd

lcd.clear()
lcd.setCursor(0, 0)
lcd.setColor(lcd.WHITE)
lcd.print("Hello World!")

 M5Stack にアップロードして実行するには、画面左下の Upload&Run ボタンをクリックします。

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

 対象のデバイスを選択して Upload ボタンをクリックするとファームウェアがアップロードされ、 M5Stack 上で実行されます。

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

 下記画像のように M5Stack のディスプレイに表示されれば成功です。

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

 ちなみにオフライン版では、 Wi-Fi AP として接続することはできなかったものの、シリアルポートから接続して操作をすることはできました。

$ sudo cu -s 115200 -l /dev/tty.SLAB_USBtoUART
Password:
Connected.

>>> print('hello world')
hello world
>>> 
>>> from m5stack import lcd
>>> lcd.clear()
>>> lcd.print('hello')
>>> 
>>> ~.

Disconnected.

おまけ

 Maker Faire Tokyo での M5Stack 購入特典として M5Stack のクレードルをいただきました。他の方のツイートによると温湿度センサーが付いているようなので、こちらも今後試してみたいと思います。

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

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

まとめ

 M5Stack はコンパクトなサイズに Wi-Fi、BLE、スピーカー、ボタン、GPIO等が詰め込まれていて、そして何よりディスプレイが付いているというのが良いですね。さらに M5Stack Gray なら加速度センサーやジャイロまで付いてますので、これ1つでいろんなことができそうです。 MicroPython のオフライン版の設定は思う通りには行かないところがありましたが、それ以外での設定は特に難しいところもなくできました。 m5cloud は IDE としては足りないところも多くまだまだこれからという感じですが、 PC からシリアル接続することなくファームウェアのアップロードができるので手軽に使えそうです。今後 IDE として改善されていくことを期待したいと思います。

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 さん、コメントありがとうございました!