(この記事は「Elixir Advent Calendar 2018」の17日目です)
昨日の記事は @leafia78 さんの「Plug.ErrorHandlerとPlug.DebuggerでPhoenixの標準のエラーをカスタマイズする)」でした.
はじめに
こんにちは
fukuoka.exでIoT芸人をやっております.これまでの連載記事は下記をご参照ください.
この記事から新篇#3に突入しようかと思います.
ヒトはなぜ関数型言語でIoTをやりたくなるのか??
みなさま『IoT』と言われたらまずはラズパイを真っ先に思い浮かべるでしょうか.
Elixir界隈でもIoTやりたくなる勢は一定数いて,連載記事でも取り上げてきた『Nerves』や『GrovePi』といったランタイムやライブラリの開発が進んでいます.
Elixirでラズパイ!
|> ElixirでIoT#2.1:Nervesって何者?ラズパイでLチカできんの!?
|> ElixirでIoT#2.2:関数型言語からGroveモジュールを使ってみた
|> ElixirでIoT#2.3:ラズパイの温湿度と超音波センサ値をPhoenixでサクッと?リアルタイム表示
まぁElixirからラズパイでこんなことができるようになります.
ライブデモこれです。手ぶれ酷くてすんません。
— TAKASE hideki (@TAKASEhideki) 2018年6月22日
温湿度センサと超音波センサの値をラズパイ3B&GrovePi+で取得して、LCDに表示させつつWebページにもリアルタイム表示!もちろんすべてElixir/Phoenix!! #fukuokaex pic.twitter.com/loGk3yZxzW
でもココで私は声を大にして言っておきたい!
ラズパイはIoTなのは認めていいけど,1.2GHzの64bitコア✕4に1GB LPDDR2も積んどいて組込みとか言わんで!!
# あれっ別に誰も言ってない??><;
じゃあ組込みでElixirってできないの!??と思っていたところ,
『AtomVM』というすっげー面白いモノを見つけましたので,この記事で紹介したいと思います.
AtomVMとは?
ざっくり一言で言えば,Erlang VMの軽量実装です.
低コストなマイコンでもErlangはもちろんElixirが稼働できるように抜本的に実装を作り変えているようです.詳細は下記の技術ブログGitHubリポを見てもらえればと思います.
|> 技術ブログon Medium: 「AtomVM: how to run Elixir code on a 3 $ microcontroller」
|> GitHubリポジトリ: bettio/AtomVM
シュリンク版ですのでもちろん全ての機能・ライブラリが使えるわけではないですし,まだまだUnder Constructionです.
対応中の組込みマイコン
まずはESP-WROOM-32搭載のESP32-DevKitCで開発が進められています.ESP32の良いところはWiFiやBLEも使える点で,某雑誌では新定番IoTマイコンなどと銘打っています.
そしてつい1週間ほど前から,STM32F407VGT6搭載のSTM32F4DISCOVERYに対応したPRがmergeされました!!
今できること
libs/
や src/libAtomVM
, tests/
以下を見るのが良いかと.
組込み的には GPIO はもちろん叩けるとして, (スリープ)タイマやネットワーク辺りの機能がどこまでまともに使えるか,とっても気になるところです.
Elixir on ESP32でLチカ
組込み界隈の"Hello, World!"はLチカ(LEDの点滅制御)と謂われています.
じゃあAtomVMでやってみよう!ということで,ひとまず筆者はESP32版で試しました.
環境設定
母艦をMacでやったところ結構ハマったのですが,これだけで長くなるのでコマンドだけ紹介し,あとの詳細解説は別記事にてT.B.Aとしたいと思います.
$ brew instal cmake gperf zlib
$ sudo easy_install pip
$ sudo pip install pyserial
$ wget https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz
$ tar xzvf xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz
$ export PATH=${PATH}:${PWD}/xtensa-esp32-elf/bin
$ git clone -b v3.1 --recursive https://github.com/espressif/esp-idf.git
$ export IDF_PATH=${PWD}/esp-idf
$ git clone https://github.com/bettio/AtomVM
$ cd AtomVM/
$ cmake .
$ make
$ cd src/platforms/esp32/
# 必要に応じて make menuconfig で ESP32-DevKitC マウント先を設定
$ make
$ make flash
これでAtomVMのファームがESP32-DevKitCに焼かれます.
ファームのelfサイズはこんな感じです.
$ xtensa-esp32-elf-size build/atomvvm-esp32.elf
text data bss dec hex filename
552739 99284 24768 676791 a53b7 build/atomvvm-esp32.elf
おぉっ!驚きの約676KB!!
ソースコード
examples
を参考にして,下記のようなソースを用意しました.
defmodule HelloBlink do
def start() do
gpio = do_open_port("gpio", [])
set_direction(gpio, 2, :output) # Port2: RED
set_direction(gpio, 4, :output) # Port4: BLUE
console = do_open_port("console", [])
write(console, "Hello Blinking World\n")
loop(gpio, console, 0)
end
defp loop(gpio, console, 0) do
write(console, "Lightning RED\n")
set_level(gpio, 2, 1)
set_level(gpio, 4, 0)
sleep(1000)
loop(gpio, console, 1)
end
defp loop(gpio, console, 1) do
write(console, "Lightning BLUE\n")
set_level(gpio, 2, 0)
set_level(gpio, 4, 1)
sleep(2000)
loop(gpio, console, 0)
end
defp do_open_port(port_name, param) do
Port.open({:spawn, port_name}, param)
end
defp sleep(t) do
receive do
after t -> :ok
end
end
defp set_direction(gpio, gpio_num, direction) do
send gpio, {self(), :set_direction, gpio_num, direction}
receive do
ret ->
ret
end
end
defp set_level(gpio, gpio_num, level) do
send gpio, {self(), :set_level, gpio_num, level}
receive do
ret ->
ret
end
end
defp write(console, string) do
send console, {self(), string}
receive do
return_status ->
return_status
end
end
end
start()
にて,GPIOのPort2と4,コンソールを初期化しています.
あとはPort2と4を交互にHigh/Low(1/0)して,光らせる色をコンソール出力しています.
ちなみにビルドログとかsrc/
を見てみるとFreeRTOSも走っているようです.
なのでマルチタスク化もできそうな気がしますが,ErlangプロセスとRTOSタスクの関係性はどうなんだろう??そのうち追いかけてみたいと思います.
実行してみた
elixirc
でコンパイルしたbeamファイルを,同梱のPackBEAM
で.avm形式に変換してから焼いていきます.
$ elixirc HelloBlink.ex
$ ../../../tools/packbeam/PackBEAM HelloBlink.avm Elixir.HelloBlink.beam
$ $IDF_PATH/components/esptool_py/esptool/esptool.py \
--chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 115200 \
--before default_reset --after hard_reset write_flash \
-u --flash_mode dio --flash_freq 40m \
--flash_size detect 0x110000 HelloBlink.avm
あとはボードのリセットボタンを押したら動き出しますし,make monitorすればシリアル出力も確認できます.
$ cd src/platforms/esp32/
$ make monitor
MONITOR
--- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched.
--- Using /dev/cu.SLAB_USBtoUART instead...
--- idf_monitor on /dev/cu.SLAB_USBtoUART 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:5788
load:0x40078000,len:9020
ho 0 tail 12 room 4
load:0x40080000,len:6064
0x40080000: _iram_start at /Users/takase/lab3.kuis/research/elixir/esp/esp-idf/components/freertos/xtensa_vectors.S:1685
entry 0x40080330
0x40080330: _KernelExceptionVector at ??:?
I (30) boot: ESP-IDF v3.1-dirty 2nd stage bootloader
I (30) boot: compile time 16:40:13
I (30) boot: Enabling RNG early entropy source...
I (35) boot: SPI Speed : 40MHz
I (39) boot: SPI Mode : DIO
I (43) boot: SPI Flash Size : 4MB
I (47) boot: Partition Table:
I (51) boot: ## Label Usage Type ST Offset Length
I (58) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (66) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (73) boot: 2 factory factory app 00 00 00010000 00100000
I (81) boot: 3 main.avm RF data 01 01 00110000 00100000
I (88) boot: End of partition table
I (92) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x14a40 ( 84544) map
I (131) esp_image: segment 1: paddr=0x00024a68 vaddr=0x3ffb0000 size=0x03994 ( 14740) load
I (137) esp_image: segment 2: paddr=0x00028404 vaddr=0x3ffb3994 size=0x00000 ( 0) load
I (138) esp_image: segment 3: paddr=0x0002840c vaddr=0x40080000 size=0x00400 ( 1024) load
0x40080000: _iram_start at /Users/takase/lab3.kuis/research/elixir/esp/esp-idf/components/freertos/xtensa_vectors.S:1685
I (147) esp_image: segment 4: paddr=0x00028814 vaddr=0x40080400 size=0x077fc ( 30716) load
I (168) esp_image: segment 5: paddr=0x00030018 vaddr=0x400d0018 size=0x76bf0 (486384) map
0x400d0018: _stext at ??:?
I (339) esp_image: segment 6: paddr=0x000a6c10 vaddr=0x40087bfc size=0x08738 ( 34616) load
0x40087bfc: rcGetSched at ??:?
I (354) esp_image: segment 7: paddr=0x000af350 vaddr=0x400c0000 size=0x00000 ( 0) load
I (354) esp_image: segment 8: paddr=0x000af358 vaddr=0x50000000 size=0x00000 ( 0) load
I (370) boot: Loaded app from partition at offset 0x10000
I (370) boot: Disabling RNG early entropy source...
I (372) cpu_start: Pro cpu up.
I (376) cpu_start: Starting app cpu, entry point is 0x40080fcc
0x40080fcc: call_start_cpu1 at /Users/takase/lab3.kuis/research/elixir/esp/esp-idf/components/esp32/cpu_start.c:225
I (0) cpu_start: App cpu up.
I (386) heap_init: Initializing. RAM available for dynamic allocation:
I (393) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (399) heap_init: At 3FFB9A58 len 000265A8 (153 KiB): DRAM
I (405) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (411) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (418) heap_init: At 40090334 len 0000FCCC (63 KiB): IRAM
I (424) cpu_start: Pro cpu start user code
I (107) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Found AVM partition: size: 1048576, address: 0x110000
Booting file mapped at: 0x3f420000, size: 1048576
Starting: Elixir.HelloBlink.beam...
---
Hello Blinking World
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
Lightning BLUE
Lightning RED
こんな感じで動きます
どやさぁ!!
静止画だとこんな感じ
— TAKASE hideki (@TAKASEhideki) 2018年12月17日
そして解説記事はこちら!! #kyotoex #fukuokaexhttps://t.co/j7IPdGiNAH pic.twitter.com/dnwdPer3v6
やっぱ動画で見たいっすよねぇ!??
『ElixirでIoT』の世界に新機軸: AtomVMhttps://t.co/sIC3g2OH0d
— TAKASE hideki (@TAKASEhideki) 2018年12月17日
ESP32やSTM32でElixir/Erlangが動かせる軽量ランタイム.ファームelfのサイズは驚きの約676KB!!
こうなったらとりまLチカや!の様子です^^; #fukuokaex #kyotoex pic.twitter.com/BPtTkLnkF7
おわりに
この記事では『ElixirでIoT』の新定番!!?となりそうな 『AtomVM』について紹介しました.
もちろん現状でできることや機能は限られていますが,こりゃあ継続的にwatchしていかねばなりませんし,なんかしらcontributeもしていこうと思っています.
とりえあずランタイムとしては軽量でしょうが,実行プロセスについてはどうなんでしょうか?そもそも生のErlang VMってどのくらいのサイズなんだろ??
この辺りの情報や計測方法をご存知の方がいましたら,ぜひ教えていただけると嬉しいです.
明日の「Elixir Advent Calendar 2018」は, @taoshotaro さんです!!