2017年04月18日

大幅にパワーアップした「ESP32」で mruby を動かす

はてなブックマークに登録

安価な Wi-Fi 対応モジュールとして人気を博した「ESP8266」の後継機種「ESP32」が各所で話題になっていますね。僕も少し前に秋月にパーツを仕入れに行った際に開発ボードが山積みされているのを見かけて買って(積んで)いました。このところプリント基板ばかり作っていたのですが、最近になってようやく弄りはじめたので記事を書きます。

ESP32−DevKitC

ESP8266 と ESP32 の比較

ざっくりですが ESP8266 と ESP32 のスペックを比べてみます。

(念のため、ESP8266 と ESP32 は、それぞれ SoC 単体の名称です。SoC 単体では容易に利用できないため、外部接続のフラッシュメモリやアンテナなど必要な部品をまとめてモジュール化した製品が複数のベンダーから提供されています。ここでは、最も広く流通している Espressif 謹製の ESP-WROOM シリーズにて比較しています)

ESP-WROOM-02 (ESP8266EX)ESP-WROOM-32 (ESP32-D0WDQ6)
ProcessorTensilica Xtensa L106 (32bit Single-Core 80/160 MHz)Tensilica Xtensa LX6 (32bit Dual-Core 160/240 MHz)
SRAM160KB(available to user < 50KB)520KB
Flash4MB4MB
Wi-Fi802.11 b/g/n (2.4GHz)802.11 b/g/n (2.4GHz)
Bluetooth-4.2 BR/EDR + BLE

ESP-WROOM-32 では、プロセッサがシングルコアの Xtensa L106 からデュアルコアの LX6 に強化され、SRAM の容量が大幅に拡大されています。新たに Bluetooth も搭載されています。

データシートを細かく見てみると、ESP-WROOM-32 では利用可能なペリフェラルの多さにも目を惹かれます。

こういったリソースの限られた環境ではプロセッサの性能よりもメモリの制限に悩まされることが多いので、SRAM の容量が格段に上がっているのは大きなポイントです。

このブログでも以前に ESP8266 に関する記事(ESP8266 モジュールの AT コマンドに SSL クライアント機能を追加する)を載せていますが、その際に「メモリが足りなくて SSL の処理が正常に行われない」という問題に遭遇しています。このケースでは、最終的には SDK 側で「メモリを 12KB 節約する」という修正が加わって解決しましたが、たかだか十数 KB のメモリをやりくりしなければならない世界なのです。

そんなわけで、520KB のメモリを搭載しているのは「ん?富豪かな?」というくらいの衝撃です。そして、このくらいの水準になってくると「NetBSD 動かないかな」とか「Mono 動かないかな(*同僚が実践済み)」とか妄想し始めるわけです。

ちょうど個人的に mruby に興味を持ちはじめていたこともあり「mruby のフットプリントってどのくらいだっけ?」と思って調べてみたら 400KB くらいという情報が出てきてたので、ワンチャンありそうという感じでやってみましたというお話です。

開発環境のセットアップ

ESP32 の公式な開発環境は「ESP-IDF」と「Arduino-ESP32」の二種類あります。

前者は ESP32 の動作に必要なブートローダや FreeRTOS、各種コンポーネント一式をまとめたスタンダードなビルドシステムで、今回はこちらを使用します。ちなみに、後者は ESP32 上で Arduino 互換の API を実装したもので、Arduino IDE を使って開発する仕組みになっているようです。

toolchain と ESP-IDF のインストール

ドキュメントがしっかりと整備されているので、これを参照すれば困ることはあまりなさそうです。

一応、ここでは Mac OS X での手順を載せておきます。

フラッシュメモリへのデータ転送やシリアルモニタで pyserial が必要になるので予めインストールしておきます。

$ sudo easy_install pip
$ sudo pip install pyserial

クロスコンパイル用の toolchain(コンパイラやリンカなど諸々一式)のバイナリが用意されているので、ダウンロードして展開します。展開後はパスを通しておくだけで OK です。

$ mkdir ~/esp
$ cd ~/esp
$ curl -O https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-61-gab8375a-5.2.0.tar.gz
$ tar -zxvf xtensa-esp32-elf-osx-1.22.0-61-gab8375a-5.2.0.tar.gz
$ echo 'export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin' >> ~/.profile
$ source ~/.profile

続いて、GitHub から ESP-IDF のプロジェクトをクローンします。submodule を使っているので --recursive を忘れずに指定してください。環境変数 IDF_PATH を設定したらインストール完了です。

$ cd ~/esp
$ git clone --recursive https://github.com/espressif/esp-idf.git
$ echo 'export IDF_PATH=$HOME/esp/esp-idf' >> ~/.profile
$ source ~/.profile

サンプルプログラムで動作確認

アプリケーション作成時にテンプレートとして使えるプロジェクトが用意されているので、これを使って動作確認をします。

$ cd ~/esp
$ git clone https://github.com/espressif/esp-idf-template.git myapp
$ cd myapp

テンプレートのプロジェクトをクローンしてきたら make menuconfig を実行して設定メニューを立ち上げます。

$ make menuconfig

立ち上がったメニューから Serial flasher config > Default serial port と進み、使用するシリアルポート(DevKitC を使っている場合には /dev/cu.SLAB_USBtoUART になると思います)を入力して Save & Exit します。

シリアルポートの設定だけすれば OK なので、make でプロジェクト全体をビルドします。

$ make

ビルドに成功したら、make flash で ESP32 のフラッシュメモリに生成したバイナリを転送します。make monitor でシリアルモニタを接続できるので、同時に指定しておけば転送後にそのままモニタリングできます。

$ make flash monitor
esptool.py v2.0-beta2

...(snip)...

Hello world!
Restarting in 10 seconds...
Restarting in 9 seconds...
Restarting in 8 seconds...
Restarting in 7 seconds...
Restarting in 6 seconds...
Restarting in 5 seconds...
Restarting in 4 seconds...
Restarting in 3 seconds...
Restarting in 2 seconds...
Restarting in 1 seconds...
Restarting in 0 seconds...
Restarting now.

サンプルプログラムは Hello world! を出力した後にカウントダウンを始めて10秒後にリブートする、という動作を繰り返します。なお、シリアルモニタからは Ctrl+] で抜けられます。

困った時の対処法

あえて理由は述べませんが、フラッシュメモリの内容をリセットしたい時というのは往々にしてあるものです。そういった時には make erase_flash を叩くと幸せになれます。

$ make erase_flash

ちなみに make erase_flash した後、どういう状態になっているかシリアルモニタを接続して確認してみると、おもむろに Basic ROM が立ち上がってくるなど中々に趣があります。

$ make monitor

...(snip)...

> help
A very Basic ROM console. Available commands/functions:
LIST
NEW
RUN
NEXT
LET
IF
GOTO
GOSUB
RETURN
REM
FOR
INPUT
PRINT
PHEX
POKE
STOP
BYE
MEM
?
'
DELAY
END
RSEED
HELP
ABOUT
IOSET
IODIR
PEEK
ABS
RND
IOGET
USR
>

mruby を動かす

開発環境の準備が整ったので mruby を動かす作業に移ります。世の中は偉大な先人達で溢れていて、大抵のことは既に他の誰かがやっていたりします。そんな訳で Google 先生に聞いてみたところ、3秒で「mruby-esp32」というプロジェクトがあることを教えてくれました。

mruby-esp32

そのものズバリ、ESP32 で mruby を動かす PoC です。

細かな説明はさておき、まずは動かしてみましょう。

GitHub から mruby-esp32 のリポジトリをクローンします。内部で submodule として mruby を参照しているため、--recursive が必要です。

$ git clone --recursive https://github.com/carsonmcdonald/mruby-esp32.git
$ cd mruby-esp32

サンプルプロジェクトの時と同様に、自分の環境に合わせてシリアルポートの設定をします。

$ make menuconfig

以下のようにビルドしてフラッシュへ転送します。make に渡している MRUBY_EXAMPLE については後ほど説明します。

$ make MRUBY_EXAMPLE=system_mrb.rb
$ make MRUBY_EXAMPLE=system_mrb.rb flash monitor

...(snip)...

SDK Version: v2.0-rc1-401-gf9fba35-dirty
Memory free: 209.488K
Delaying 10 seconds

正常に動作していれば、シリアルモニタに SDK のバージョンや空きメモリの情報が出力されます。

ざっくりと追ってみる

以下は mruby-esp32 のディレクトリ構成です。

.
|-- LICENSE
|-- Makefile
|-- README.md
|-- components
|   `-- mruby_component
|       |-- component.mk
|       |-- esp32_build_config.rb
|       |-- mruby
|       |-- mruby-esp32-system
|       `-- mruby-esp32-wifi
|-- main
|   |-- component.mk
|   |-- examples
|   |   |-- simplest_mrb.rb
|   |   |-- system_mrb.rb
|   |   `-- wifi_example_mrb.rb
|   `-- mruby_main.c
`-- sdkconfig

ESP-IDF のアプリケーションのエントリーポイントである app_main()main/mruby_main.c の中にあります。

#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"

#include "mruby.h"
#include "mruby/irep.h"

#include "example_mrb.h"

void mruby_task(void *pvParameter)
{
  mrb_state *mrb = mrb_open();
  mrb_load_irep(mrb, example_mrb);
  mrb_close(mrb);

  // This task should never end, even if the
  // script ends.
  while (1) {
  }
}

void app_main()
{
  nvs_flash_init();
  xTaskCreate(&mruby_task, "mruby_task", 8192, NULL, 5, NULL);
}

xTaskCreate() は FreeRTOS のタスクを生成する関数です。なので、実質 mruby_task() の処理が全てです。

  • mrb_open()
  • mrb_load_irep()
  • mrb_close()

やっているのはたったこれだけ。mruby の実行環境を準備して mrb_load_irep() で mruby のバイトコードを流し込んでいるだけという実にシンプルな処理です。mrb_load_irep() に渡している example_mrb が mruby のバイトコードですが、これは mruby_main.c には定義されておらず別のところにあるようです。example_mrb.h といういかにもそれっぽいヘッダファイルをインクルードしていていますが、何処にも見当たりません。

いろいろ探ってみると、main/component.mk に記述がありました。

COMPONENT_EXTRA_CLEAN := example_mrb.h

mruby_main.o: example_mrb.h

example_mrb.h: $(COMPONENT_PATH)/examples/$(MRUBY_EXAMPLE)
	$(COMPONENT_PATH)/../components/mruby_component/mruby/bin/mrbc -B example_mrb -o $@ $^

.PHONY: example_mrb.h

どうやら example_mrb.h はビルド時に生成してたようです。mrbc は mruby のコンパイラです。明示的にオプションを指定しない場合は、mruby の VM(RiteVM)が解釈できるバイトコードを生成しますが、-B オプションを付けると C で扱いやついように配列に格納した状態のコードを吐いてくれます。また、make を実行する際にしていた MRUBY_EXAMPLE という環境変数もここで登場しています。main/examples 配下にあるスクリプトの中から、 MRUBY_EXAMPLE で指定されたスクリプトがコンパイルされて使われる仕組みになっています。

以下は、実際に main/examples/system_mrb.rbmrbc -B でコンパイルしたものです。

/* dumped in little endian order.
   use `mrbc -E` option for big endian CPU. */
#include <stdint.h>
extern const uint8_t example_mrb[];
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
example_mrb[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0x8d,0x5c,0x00,0x00,0x01,0xc2,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x01,0x86,0x30,0x30,
0x30,0x30,0x00,0x00,0x01,0x7e,0x00,0x03,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x29,
0x91,0x00,0x80,0x01,0x13,0x00,0x80,0x01,0x20,0x80,0x80,0x01,0x01,0xc0,0x80,0x00,
0x06,0x00,0x80,0x01,0x3d,0x00,0x00,0x02,0x01,0x40,0x80,0x02,0x3e,0x40,0x01,0x02,
0xa0,0xc0,0x80,0x01,0x91,0x00,0x80,0x01,0x13,0x00,0x80,0x01,0x20,0x00,0x81,0x01,
0x83,0xf3,0x41,0x02,0xb1,0x40,0x81,0x01,0x01,0xc0,0x00,0x01,0x06,0x00,0x80,0x01,
0x3d,0x01,0x00,0x02,0x01,0x80,0x80,0x02,0x3e,0x40,0x01,0x02,0xbd,0x01,0x80,0x02,
0x3e,0x40,0x01,0x02,0xa0,0xc0,0x80,0x01,0x06,0x00,0x80,0x01,0x3d,0x02,0x00,0x02,
0xa0,0xc0,0x80,0x01,0x91,0x00,0x80,0x01,0x13,0x00,0x80,0x01,0x83,0x04,0x40,0x02,
0x83,0xf3,0xc1,0x02,0xb0,0xc0,0x01,0x02,0xa0,0x80,0x81,0x01,0x06,0x00,0x80,0x01,
0xbd,0x02,0x00,0x02,0xa0,0xc0,0x80,0x01,0x91,0x00,0x80,0x01,0x13,0x00,0x80,0x01,
0x83,0x04,0x40,0x02,0x02,0x03,0x80,0x02,0xb0,0xc0,0x01,0x02,0xa0,0x00,0x82,0x01,
0x4a,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x0d,0x53,0x44,0x4b,0x20,0x56,
0x65,0x72,0x73,0x69,0x6f,0x6e,0x3a,0x20,0x00,0x00,0x00,0x00,0x00,0x0d,0x4d,0x65,
0x6d,0x6f,0x72,0x79,0x20,0x66,0x72,0x65,0x65,0x3a,0x20,0x00,0x00,0x01,0x4b,0x00,
0x00,0x13,0x44,0x65,0x6c,0x61,0x79,0x69,0x6e,0x67,0x20,0x31,0x30,0x20,0x73,0x65,
0x63,0x6f,0x6e,0x64,0x73,0x00,0x00,0x1c,0x44,0x65,0x65,0x70,0x20,0x73,0x6c,0x65,
0x65,0x70,0x69,0x6e,0x67,0x20,0x66,0x6f,0x72,0x20,0x31,0x30,0x20,0x73,0x65,0x63,
0x6f,0x6e,0x64,0x73,0x01,0x00,0x07,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,
0x00,0x09,0x00,0x06,0x53,0x79,0x73,0x74,0x65,0x6d,0x00,0x00,0x05,0x45,0x53,0x50,
0x33,0x32,0x00,0x00,0x0b,0x73,0x64,0x6b,0x5f,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,
0x00,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x00,0x10,0x61,0x76,0x61,0x69,0x6c,0x61,
0x62,0x6c,0x65,0x5f,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x00,0x00,0x01,0x2f,0x00,0x00,
0x05,0x64,0x65,0x6c,0x61,0x79,0x00,0x00,0x01,0x2a,0x00,0x00,0x0e,0x64,0x65,0x65,
0x70,0x5f,0x73,0x6c,0x65,0x65,0x70,0x5f,0x66,0x6f,0x72,0x00,0x4c,0x56,0x41,0x52,
0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x02,0x00,0x03,0x76,0x65,0x72,0x00,0x03,0x6d,
0x65,0x6d,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x02,0x45,0x4e,0x44,0x00,0x00,0x00,
0x00,0x08,
};

mruby_main.c がインクルードしていた example_mrb.h の実態はコレです。mrbc -B の結果をヘッダファイルとして保存し、それをインクルードすることで .rb ファイルをコンパイルした後のバイトコードが mruby_main.o のオブジェクトファイルにそのまま組み込まれます。

このように mruby-esp32 の中身をざっと追ってみて、以下のことが分かりました。

  • ESP-IDF のフレームワークを利用したアプリケーションとして mruby の VM を動作させている
  • ホスト側で事前にコンパイルした mruby のバイトコードをオブジェクトファイルに組み込んでいる
  • アプリケーション起動時にそのバイトコードを VM に渡して実行している

バイトコードをオブジェクトファイルに組み込んでしまっているため、.rb ファイルを変更するたびにアプリケーションの差し替え(再ビルド&フラッシュ書き込み)が発生してしまうのが少々手間ですが「ESP-IDF ではファイルシステムを扱うのになにかと手間がかかる」という事情を考えると、手軽に試すにはこれが最善の方法かもなと思いました。

また、mruby 本体については components/mruby_component 配下にありますが、これは git の submodule として参照しているだけで、コードには一切手が加えられていません。esp32_build_config.rb にクロスビルド用の設定が書かれているのみで、Xtensa + FreeRTOS という馴染みの薄い環境でサクッと動いてしまうのですから mruby の移植性の高さには驚かされます。

mruby REPL を動かす

ここで終わってしまうと、ただ「mruby-esp32 を動かしてみた」というだけの内容になってしまうため、もう少し踏み込んでみます。

先にも書きましたが、mruby-esp32 の仕組みだとデバイス上で動作しているのは mruby の VM だけです。VM に渡すバイトコードを生成するコンパイル作業はホスト側で行っていまるので、.rb ファイルを修正するたびにホストから ESP32 への転送が必要になり少し面倒です。そこで、デバイス上でコンパイルも含めて完結させられないかなと思いながら試してみました。

まず、mruby のコンパイラである mrbc をデバイス上で動作させようかと思ったのですが、ESP-IDF ではファイルシステムを一手間も二手間もかかりそうという問題があり、ちょっと面倒くさそうなので代替案として mirb でやってみることにしました。mirb は mruby の REPL(Read–eval–print loop)で、読んで字のごとく「読む」「評価する」「表示する」とう動作を繰り返す対話型のコマンドで、コンパイラとしての機能を含んでいます。

mirb - Embeddable Interactive Ruby Shell

> 1 + 1
 => 2

mruby-esp32-app-mirb

そんなこんなで、mirb を ESP32 で動作するようにしたものがコチラ。

mruby のクロスコンパイルの定義ファイルなど、だいぶ mruby-esp32 を参考にして作りました。

細かい話は後にして、実際に動作している様子がこちらです。

GitHub からプロジェクトをクローンしてきてビルドするだけで動かせるので是非試してみてください。

$ git clone --recursive https://github.com/pandax381/mruby-esp32-app-mirb.git
$ cd mruby-esp32-app-mirb
$ make menuconfig
$ make
$ make flash monitor

クロスビルド用の mruby 本体には標準の mgem を全て組み込んであります。カスタマイズしたい場合には components/mruby/build_config.rb を編集してください。

苦労話など

mirb のコードは mruby/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c で、1つのソースファイルに全ての処理が書かれています。とりあえず mruby-compiler の mgem を組み込めば動くんじゃないかという目論見のもと、雑に mirb.c の中身を丸ごとコピペし、リネームした main 関数を呼び出すようにしてみるところからスタートしました。

シグナルが未実装

まず、ジャブとしてビルド時に undefined reference to 'signal' と怒られます。ESP-IDF の足回りは FreeRTOS + newlib で構築されているのですが、シグナル関連の処理が実装されていないためリンクできずにエラーが発生しているようです。mirb のコードを見たところ、SIGINTCtrl+C で飛んでくるシグナル)を処理しているだけだったので、シグナル関連の処理をサクッとコメントアウトするとビルドが通るようになります。

STDIN がノンブロッキング

シグナル関連のケアをしてあげるとビルドが通って起動するようにるのですが、何故かバナーを出力して入力待ちになってすぐに再起動するという動作を繰り返してしまいます。何処でエラーになっているのか調べたところ、getchar()EOF を返して mirb の main 関数が終了してしまっていることが分かりました。getchar() が EOF を返すのは「ファイルの終わりに達した」か「エラーが発生した」場合です。getchar() は getc(stdin) と等価なので、入力対象は標準入力(stdin)であり、通常であればエラーが発生することは考えにくいです。そうなると「ファイルの終わりに達した = stdin が閉じられている = stdin が何処にもつながっていない」可能性がありそうと考え、ESP-IDF のドキュメントを確認したところ、以下のような記述がありました。

Standard IO streams (stdin, stdout, stderr)

(…snip...)

Writing to stdout or stderr will send characters to the UART transmit FIFO. Reading from stdin will retrieve characters from the UART receive FIFO.

Note that while writing to stdout or stderr will block until all characters are put into the FIFO, reading from stdin is non-blocking. The function which reads from UART will get all the characters present in the FIFO (if any), and return.I.e. doing fscanf("%d\n", &var); may not have desired results. This is a temporary limitation which will be removed once fcntl is added to the VFS interface.

どうやら、stdin は UART の RX とつながっているようです。しかし、ここに記述されているように STDIN はノンブロッキングに設定されています(そしてどうやら変更できない)。標準入出力の関数をノンブロッキングで扱うとか普通やらないんですけど、最終的な read システムコールあたりで EAGAINEWOULDBLOCK のエラーを返すので、getchar() がエラーとして EOF を返してしまっているようです。mirb は getchar() が EOF を返すと feof()ferror() の判定をせずに終了してしまっていますが、stdin がノンブロッキングになっていてその結果エラーが返っているとかエッジケースすぎて仕方ないよね...という感じです。この問題は、getchar() が EOF を返してもリトライするよう修正すればとりあえず動作するようになりますが、ビジーループでブロッキングすることになるので気が引けます。みんな困っていないのかな?と思って調べてみたところ、同じようにハマっている人達が見つかりました。どうやら uart_rx_one_char_block() という関数があるらしく、彼らはこれを使っているようです。関数名の通り、UART から 1 文字読むまでブロックしてくれたので、今回はこれで代用しました。

エスケープシーケンス

ようやく文字列の入力ができるようになったものの、シェルも何も介していないため stdin から読み込まれるデータはバッファリングされていません。キーを押した瞬間に getchar() が返り、カーソルキーを押すと ANSI エスケープされた数バイトのコードが飛んできます。エコー出力もされません。また、改行コードの変換もしてくれませんので、改行を \n だけで判定していると Mac の場合は永遠に改行に出会えません。まぁ、なんと言いますか readline の有り難みを再認識する瞬間ですね。

ちなみに、 mirb は、ビルド時に readline(または linenoise)を組み込めるようになっていますが、ESP-IDF には Terminfo がないようで一手間加えないと組み込めそうにありません。とりあえず、普段使っている getchar() の代わりになるようなものを自前実装で書いてみました。

#undef getchar
#define getchar uart_getchar

#define STATE_NML 0x00
#define STATE_ESC 0x01
#define STATE_CSI 0x02

static void
echo_rewrite(const uint8_t *s, size_t n, int cursor) {
  printf("\e[3G\e[J");
  printf("%.*s", n, s);
  if (n && s[n-1] != '\n') {
    printf("\e[%dG", 3 + cursor);
  }
  fflush(stdout);
}

static int
uart_getchar(void)
{
  static size_t n = 0;
  static uint8_t buf[1024];
  static uint8_t last = 0;
  uint8_t c, s = STATE_NML;
  int v = 0, cursor = 0;

  if (n) {
    c = buf[0];
    memmove(buf, buf + 1, --n);
    return (int)c;
  }
  do {
    c = uart_rx_one_char_block();
    if (last == '\r' && c == '\n') {
      continue;
    }
    last = c;
    if (c == '\b' || c == 0x7f) {
      if (n && cursor) {
        memmove(buf + (cursor - 1), buf + cursor, n - cursor);
        --n, --cursor;
        echo_rewrite(buf, n, cursor);
      }
      continue;
    }
    if (c == '\e') {
      s = STATE_ESC;
      continue;
    }
    if (c == '\r') {
      c = '\n';
    }
    switch (s) {
    case STATE_ESC:
      if (c == '[') {
        s = STATE_CSI;
        v = 0;
      } else {
        s = STATE_NML;
      }
      continue;
    case STATE_CSI:
      switch (c) {
      case '0'...'9':
        v = (v * 10) + (c - '0');
      case ';':
        v = 0;
      case 'A'...'z':
        switch (c) {
        case 'C':
          if (!v) {
            v = 1;
          }
          if (n - cursor < v) {
            v = n - cursor;
          }
          cursor += v;
          echo_rewrite(buf, n, cursor);
          break;
        case 'D':
          if (!v) {
            v = 1;
          }
          if (cursor < v) {
            v = cursor;
          }
          cursor -= v;
          echo_rewrite(buf, n, cursor);
          break;
        }
      default:
        s = STATE_NML;
      }
      continue;
    }
    if (c != '\n') {
      memmove(buf + (cursor + 1), buf + cursor, n - cursor);
      buf[cursor] = c;
      ++n, ++cursor;
    } else {
      buf[n++] = c;
      cursor = n;
    }
    echo_rewrite(buf, n, cursor);
  } while (c != '\n');
  return uart_getchar();
}

だいぶ雑に書いてあるのでツッコミどころは多々あると思いますが、以下の機能を実現しています。

  • 行バッファリング
  • カーソルキー(左右)での移動と挿入
  • BackSpace と Delete キー での消去
  • 改行コード変換
  • 入力のエコー出力

すごく地味ですが、mirb をちゃんと動かすために一番苦労した部分です。とりあえず「違和感なく入力できる」くらいにはなっていると思いますが、そのうち readline か linenoise 対応版を作ります。

おわりに

mirb は入力周りで一部修正を加えましたが、それ以外のコンパイラとしての主要部分は一切修正なく移植できました。VM もコンパイラもこんなカンタンに動いてしまって mruby すごいですね。というわけで、ESP32 で mruby が簡単に動くことが分かったので、みんな頑張って mgem を書いて充実させましょう!


@pandax381

pandax381 at 16:38│Comments(0)TrackBack(0)

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント