ESP8266 上で Lua インタプリタが動くファームウェア、NodeMCU を使う
NodeMCU は、Node.js のように非同期でイベントドリブンなコードを書く事が出来る、ネットワークを利用する ESP8266 とは相性が良い開発環境(ファームウェア)。Lua インタプリタが動くので、マイコン上でプログラムを動的に実行できる。また NodeMCU 用のハードウェアも売っているが、ESP8266 シリーズならどれでも動く(もちろんESP-WROOM-02もね!)。NodeMCU という名前がわかりにくいので、名前で損してる感ある…。
また40個以上のモジュールがすでに本家に入っていたり、SSLにも対応していたり、コードが綺麗だったりと、なにやら良さそうなプロジェクトな予感がする。
firmware を取得する
以前は firmware が github 上で公開されていたようだが、今は三つの方法で取得する。
クラウド上でのビルド
で、必要なモジュールをチェックすると、ビルド終了時メールにて firmware の URL が送られてくる。
普通はこれを使えばOK。
Docker を使ったビルド
ESP8266 の xtensa のツールチェインをビルドするのは面倒くさい、という人に。
Linux 上でのビルド
ソースから普通にビルドする。Ubuntu 16.04 LTS環境で簡単にできた。
まずは esp nonos
をコンパイルする。ドキュメント通りでおk。なお esp8266/Arduino の為に入れていた xtensa のバイナリツールチェインではうまく動かず、新たにコンパイルした。
sudo apt-get install make unrar autoconf automake libtool gcc g++ gperf \
flex bison texinfo gawk ncurses-dev libexpat-dev python-dev python python-serial \
sed git unzip bash help2man wget bzip2
sudo apt-get install libtool-bin
これでビルドに必要なパッケージが全部入るのであとは make してしばらく待つ。単体動作するtoolchain 群をコンパイルしてるので遅い。
Xtensa toolchain is built, to use it:
export PATH=$HOME/src/github.com/pfalcon/esp-open-sdk/xtensa-lx106-elf/bin:$PATH
Espressif ESP8266 SDK is installed, its libraries and headers are merged with the toolchain
と書かれてるように、xtensa-* にパスを必要に応じて通す。続いて
https://github.com/nodemcu/nodemcu-firmware
を make すると bin 以下に 0x00000.bin, 0x00001.bin ができあがるので、それを利用する。なお
app/.output/eagle/debug/image/eagle.app.v6.out
に bin ファイル分割前の ELF バイナリができあがってる。
[7]>_<X file app/.output/eagle/debug/image/eagle.app.v6.out
app/.output/eagle/debug/image/eagle.app.v6.out: ELF 32-bit LSB executable, Tensilica Xtensa, version 1 (SYSV), statically linked, not stripped
[7]>_<X xtensa-lx106-elf-size app/.output/eagle/debug/image/eagle.app.v6.out
text data bss dec hex filename
380472 2196 27512 410180 64244 app/.output/eagle/debug/image/eagle.app.v6.out
プログラムサイズは400KB弱(Arduinoは220KB)だけど、利用RAMが初期値で30KB(Arduinoは32KBほど)で、空きRAMが50KBもあるとは、大分コンパクトだなぁ。
なお、app/include/*config*.h
を編集する(もしくは適当な環境変数をつけてコンパイルする)ことで、様々な値を変更することが出来る。
また必要な組み込みライブラリは app/include/user_modules.h
のコメントアウトを適宜編集することで組み込める。組み込むことによってもちろんプログラムサイズは増えるので注意。
firmware の書き込み
- https://nodemcu.readthedocs.io/en/master/en/flash/
- https://learn.adafruit.com/building-and-running-micropython-on-the-esp8266/flash-firmware
あたりに CUI のesptool.py を使った方法、GUI の NodeMCU Flasher を使った方法などなどがのってる。
ただ、前述の Linux 上に firmware のビルド環境を用意した場合
$ make flash
make -C ./app flash
make[1]: Entering directory '/home/yuichi/src/github.com/nodemcu/nodemcu-firmware/app'
../tools/esptool.py --port /dev/ttyUSB0 write_flash 0x00000 ../bin/0x00000.bin 0x10000 ../bin/0x10000.bin
esptool.py v1.2-dev
Connecting...
Running Cesanta flasher stub...
Flash params set to 0x0000
Writing 28672 @ 0x0... 28672 (100 %)
Wrote 28672 bytes at 0x0 in 2.5 seconds (91.8 kbit/s)...
Writing 356352 @ 0x10000... 356352 (100 %)
Wrote 356352 bytes at 0x10000 in 30.9 seconds (92.2 kbit/s)...
Leaving...
make[1]: Leaving directory '/home/yuichi/src/github.com/nodemcu/nodemcu-firmware/app'
で一発。なお esptool.py は --baud オプションをつけないと 115200 で転送するので、Makefile をちょっと修正して --baud 921600
をつけて転送するとあからさまに速い。この速度で転送できるかどうかはチップによるけど。
$ make flash
...
Writing 28672 @ 0x0... 28672 (100 %)
Wrote 28672 bytes at 0x0 in 0.5 seconds (449.4 kbit/s)...
Writing 356352 @ 0x10000... 356352 (100 %)
Wrote 356352 bytes at 0x10000 in 4.8 seconds (591.5 kbit/s)...
...
Hello World!
シリアルコンソールで繋いで Hello World! してみる。標準だとシリアルの baudrate は 115200 なのでそれにあわせる。
$ platformio device monitor -b 115200
--- Miniterm on /dev/ttyUSB0 115200,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
r␘␂␀l��r�␀�#␂�n�␄��␌␘�␌␜��␜p�<���␀�8␂��ǒ��␜p␌␘␌�nn�␂�;�nĒ��␌␛�␌b�$�r␘␂r␘␂p�n��܀␌␜���l␜�␌␜�␌␜�␌b�␄n��n��␎�l�ߌ�8␌␘��nn�␃����␀l`␛��␒�#�n�␄␏r␘␂␎␂nr���;␂��␌?��␜r␘␂p�n��܀␌␜�r�␜��␛␂␌␜�␌␜p␌␘p��<␂��l␜�␌␟p␌␘��nn�␃��␌␘r␘␂�␒�#�n�$��l`␛�`rn|��n�␄��␒�␜�`␛␄�~␂��␌␘�␒�#�n�$��␌␘��nn�␃��l`␛␄nn��܀␌␜�r�␜��␛␂␌␟��8p␌␘p��<␂��␌b�ľ~�n�␃���␜␀l`␛␄�␃␒n�␂��l`␛�`rn|��n�␄���␄�`␛与��;␂��␌?␒b�;�␂���␀�8␂�n��␒r���n�b␌␘�␌␜�l␟r␘␂����I8��␜�␛ ��
NodeMCU 1.5.4.1 build unspecified powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
lua: cannot open init.lua
最初のスタートアップファイル、init.lua が無いと言われるけど、問題なく繋げる。文字化けしてるのは ESP8266 起動時のメッセージ(baudrate が違うから)だけど無視して問題ない。
> print("Hello World!")
Hello World!
おおお。マイコン上でインタプリタが動くと地味に感動するね。
インタプリタ接続時のヒープの空き容量は
> print(node.heap())
45056
で、44KBほど。それなりにプログラミングできそう。
コードをアップロードして実行
行実行ではなく、書いたソースコードを実行したい。
で Java で書かれた IDE、ESPlorer や CUI の nodemcu-uploader を使っての方法が書かれている。
ESPlorer の場合
> file.remove("hello.lua");
> file.open("hello.lua","w+");
> w = file.writelinew([==[print("hello NodeMCU!")]==]);
> file.close();
とエディタ上の文字列をシリアルコンソール経由で書き込んで(!)ファイルとして保存する。その後
> dofile('hello.lua')
hello NodeMCU!
で実行してる。
ちなみに ESPlorer、左がエディタ、右がシリアルコンソールになっていて、すぐ実行結果を見たり、ボタン一つで heap の容量が見れたりと、結構良く出来ている。
nodemcu-uploader
nodemcu-uploader は recv() / send() コマンドの後に、ACK しつつ送信、という一般的なシリアルアップロードの方法でファイルを書き込む。pip でインストールできるので、すぐ使うことが出来る。
に利用方法が載ってるが、
nodemcu-uploader upload hello.lua --do
で転送してそのままファイルを実行したり、
nodemcu-uploader upload *.lua --compile
でコンパイルして .lc ファイルにした後アップロードしたりできる。
Lチカする
さて、やっとLチカ…。NodeMCU は冒頭の通り、Node.js のようなイベントドリブンのアーキテクチャを採用しており、メインループを回すのではなく、イベントモデルで実行する。
LED_PIN = 2
gpio.mode(LED_PIN, gpio.OUTPUT)
ledState = gpio.HIGH
gpio.write(LED_PIN, ledState)
toggleLED = function()
if ledState == gpio.HIGH then
ledState = gpio.LOW
else
ledState = gpio.HIGH
end
gpio.write(LED_PIN, ledState)
end
tmr.alarm(0, 1000, tmr.ALARM_SEMI, toggleLED)
タイマー0に関数をセットし、1秒おきに実行することで Lチカを実現している。
もちろん、このLチカが動いているときに、hello world! を再度書き込んで実行しても、Lチカは止まらず実行されている。
なお、GPIO の番号が ESP8266 の pin と NodeMCU では違うので注意。例えば ESP8266 で IO4 のピンは NodeMCU では 2 となる。
今回はLチカまで。触ってみた感じ、やはり良く出来るてる感がある。あとはネットワーク周りと、実アプリケーションを作ったときに、はてして heap を食いつぶさずに作れるのかどうか。