ElixirでIoT#3.1：ESP32やSTM32でElixirが動く！AtomVMという選択肢

(この記事は「[Elixir Advent Calendar 2018](https://qiita.com/advent-calendar/2018/elixir)」の17日目です)
昨日の記事は @leafia78 さんの「[Plug.ErrorHandlerとPlug.DebuggerでPhoenixの標準のエラーをカスタマイズする）](https://qiita.com/leafia78/items/7d03ac5d616aa8a42488)」でした．

# はじめに

こんにちは
[fukuoka.ex](https://fukuokaex.fun/)でIoT芸人をやっております．これまでの連載記事は下記をご参照ください．

- [ElixirでIoT：連載目次](https://qiita.com/takasehideki/items/8f43f1853ce88cbbe82e)

この記事から新篇#3に突入しようかと思います．
**ヒトはなぜ関数型言語でIoTをやりたくなるのか？？**

みなさま『IoT』と言われたらまずはラズパイを真っ先に思い浮かべるでしょうか．
Elixir界隈でもIoTやりたくなる勢は一定数いて，連載記事でも取り上げてきた[『Nerves』](https://nerves-project.org)や[『GrovePi』](https://github.com/adkron/grovepi)といったランタイムやライブラリの開発が進んでいます．

**Elixirでラズパイ！**
 　 |> [ElixirでIoT#2.1：Nervesって何者？ラズパイでLチカできんの！？](https://qiita.com/takasehideki/items/94820516ec95b85bae32)
 　 |> [ElixirでIoT#2.2：関数型言語からGroveモジュールを使ってみた
](https://qiita.com/takasehideki/items/bc6051a86fe1ac8f4464) 　 |> [ElixirでIoT#2.3：ラズパイの温湿度と超音波センサ値をPhoenixでサクッと？リアルタイム表示](https://qiita.com/takasehideki/items/b0a07455d88a1f349a6e)


まぁElixirからラズパイでこんなことができるようになります．

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ライブデモこれです。手ぶれ酷くてすんません。<br>温湿度センサと超音波センサの値をラズパイ3B＆GrovePi+で取得して、LCDに表示させつつWebページにもリアルタイム表示！もちろんすべてElixir/Phoenix！！ <a href="https://twitter.com/hashtag/fukuokaex?src=hash&amp;ref_src=twsrc%5Etfw">#fukuokaex</a> <a href="https://t.co/loGk3yZxzW">pic.twitter.com/loGk3yZxzW</a></p>&mdash; TAKASE hideki (@TAKASEhideki) <a href="https://twitter.com/TAKASEhideki/status/1010117388174422016?ref_src=twsrc%5Etfw">2018年6月22日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

でもココで私は声を大にして言っておきたい！
ラズパイはIoTなのは認めていいけど，1.2GHzの64bitコア✕4に1GB LPDDR2も積んどいて**組込みとか言わんで！！**
＃ あれっ別に誰も言ってない？？><;

じゃあ組込みでElixirってできないの！？？と思っていたところ，
[『AtomVM』](https://medium.com/@Bettio/atomvm-how-to-run-elixir-code-on-a-3-microcontroller-b414773498a6)というすっげー面白いモノを見つけましたので，この記事で紹介したいと思います．

# AtomVMとは？

ざっくり一言で言えば，Erlang VMの軽量実装です．
低コストなマイコンでもErlangはもちろんElixirが稼働できるように抜本的に実装を作り変えているようです．詳細は下記の技術ブログGitHubリポを見てもらえればと思います．
 　 |> [技術ブログon Medium: 「AtomVM: how to run Elixir code on a 3 $ microcontroller」](https://medium.com/@Bettio/atomvm-how-to-run-elixir-code-on-a-3-microcontroller-b414773498a6)
 　 |> [GitHubリポジトリ: bettio/AtomVM](https://github.com/bettio/AtomVM)

シュリンク版ですのでもちろん全ての機能・ライブラリが使えるわけではないですし，まだまだUnder Constructionです．


## 対応中の組込みマイコン

まずはESP-WROOM-32搭載の[ESP32-DevKitC](https://www.espressif.com/en/products/hardware/esp32-devkitc/overview)で開発が進められています．ESP32の良いところはWiFiやBLEも使える点で，[某雑誌では新定番IoTマイコンなどと銘打って](https://cc.cqpub.co.jp/lib/system/doclib_item/1183/)います．

そしてつい1週間ほど前から，STM32F407VGT6搭載の[STM32F4DISCOVERY](https://www.st.com/ja/evaluation-tools/stm32f4discovery.html)に対応した[PRがmerge](https://github.com/bettio/AtomVM/pull/29)されました！！


## 今できること

[`libs/`](https://github.com/bettio/AtomVM/tree/master/libs/) や [`src/libAtomVM`](https://github.com/bettio/AtomVM/tree/master/src/libAtomVM)， [`tests/`](https://github.com/bettio/AtomVM/tree/master/tests/) 以下を見るのが良いかと．
組込み的には [GPIO](https://github.com/bettio/AtomVM/blob/master/libs/eavmlib/gpio.erl) はもちろん叩けるとして， [（スリープ）タイマ](https://github.com/bettio/AtomVM/blob/master/libs/estdlib/timer.erl)や[ネットワーク](https://github.com/bettio/AtomVM/blob/master/libs/eavmlib/network.erl)辺りの機能がどこまでまともに使えるか，とっても気になるところです．

# Elixir on ESP32でLチカ

組込み界隈の"Hello, World!"はLチカ（LEDの点滅制御）と謂われています．
じゃあAtomVMでやってみよう！ということで，ひとまず筆者はESP32版で試しました．

## 環境設定

母艦をMacでやったところ結構ハマったのですが，これだけで長くなるのでコマンドだけ紹介し，あとの詳細解説は別記事にてT.B.Aとしたいと思います．

```shell-session
$ 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サイズはこんな感じです．

```shell-session
$ 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` を参考にして，下記のようなソースを用意しました．

```elixir:HelloBlink.ex
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と４，コンソールを初期化しています．
あとはPort2と４を交互にHigh/Low(1/0)して，光らせる色をコンソール出力しています．

ちなみにビルドログとか`src/`を見てみるとFreeRTOSも走っているようです．
なのでマルチタスク化もできそうな気がしますが，ErlangプロセスとRTOSタスクの関係性はどうなんだろう？？そのうち追いかけてみたいと思います．

## 実行してみた

`elixirc`でコンパイルしたbeamファイルを，同梱の`PackBEAM`で.avm形式に変換してから焼いていきます．

```shell-session
$ 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すればシリアル出力も確認できます．

```shell-session
$ 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
```

## こんな感じで動きます

どやさぁ！！

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">静止画だとこんな感じ<br>そして解説記事はこちら！！ <a href="https://twitter.com/hashtag/kyotoex?src=hash&amp;ref_src=twsrc%5Etfw">#kyotoex</a> <a href="https://twitter.com/hashtag/fukuokaex?src=hash&amp;ref_src=twsrc%5Etfw">#fukuokaex</a><a href="https://t.co/j7IPdGiNAH">https://t.co/j7IPdGiNAH</a> <a href="https://t.co/dnwdPer3v6">pic.twitter.com/dnwdPer3v6</a></p>&mdash; TAKASE hideki (@TAKASEhideki) <a href="https://twitter.com/TAKASEhideki/status/1074498756077187073?ref_src=twsrc%5Etfw">2018年12月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



やっぱ動画で見たいっすよねぇ！？？

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">『ElixirでIoT』の世界に新機軸: AtomVM<a href="https://t.co/sIC3g2OH0d">https://t.co/sIC3g2OH0d</a><br><br>ESP32やSTM32でElixir/Erlangが動かせる軽量ランタイム．ファームelfのサイズは驚きの約676KB！！<br>こうなったらとりまLチカや！の様子です^^; <a href="https://twitter.com/hashtag/fukuokaex?src=hash&amp;ref_src=twsrc%5Etfw">#fukuokaex</a> <a href="https://twitter.com/hashtag/kyotoex?src=hash&amp;ref_src=twsrc%5Etfw">#kyotoex</a> <a href="https://t.co/BPtTkLnkF7">pic.twitter.com/BPtTkLnkF7</a></p>&mdash; TAKASE hideki (@TAKASEhideki) <a href="https://twitter.com/TAKASEhideki/status/1074494095404367873?ref_src=twsrc%5Etfw">2018年12月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# おわりに

この記事では『ElixirでIoT』の新定番！！？となりそうな　[『AtomVM』](https://github.com/bettio/AtomVM)について紹介しました．
もちろん現状でできることや機能は限られていますが，こりゃあ継続的にwatchしていかねばなりませんし，なんかしらcontributeもしていこうと思っています．

とりえあずランタイムとしては軽量でしょうが，実行プロセスについてはどうなんでしょうか？そもそも生のErlang VMってどのくらいのサイズなんだろ？？
この辺りの情報や計測方法をご存知の方がいましたら，ぜひ教えていただけると嬉しいです．

明日の「[Elixir Advent Calendar 2018](https://qiita.com/advent-calendar/2018/elixir)」は， @taoshotaro さんです！！