ししかわです。
社員研修の一環で、マウスを自作して大会に出場します。
昨年8月にアールティに入社して、8ヶ月が経ちました。「元Web屋さん」の看板も少し古くなったので、シリーズのタイトルを変えました。これからはソフトからハードまで精通した「インタフェース屋さん」としてがんばります。
前回までModdableの環境構築をして、「M5Stackで、JavaScriptで」プログラミングするための準備ができました。
今回と次回で、コアモジュール(M5Stack)とマウスモジュール(STM32)それぞれにI2C通信処理を書いて、両者で指令をやりとりできるようにします。
私が開発するマイクロマウスのソースコードは、GitHubで順次公開していきます。
Moddableのプロジェクトを作る
Moddableのプロジェクトは、マイコンで実行したい処理を記述したJavaScriptファイルと、プロジェクトの設定を記述したマニフェストファイル(manifest.json
)で構成されます。
マニフェストファイルはModdableに固有のもので、多少書き方を覚える必要があります。公式ドキュメント(英語)に詳しい解説があります。英語が苦手な方は、Moddableはサンプルプロジェクトが豊富にあるのでそれらを参考にするとよいです。
例として、いま開発しているコアモジュールのマニフェストファイルは次のようになっています。
{
"build": {
"SRC": "./src",
"ASSETS": "./assets"
},
"include": [
"$(MODDABLE)/examples/manifest_base.json",
"$(MODDABLE)/examples/manifest_piu.json",
"$(MODDABLE)/modules/pins/i2c/manifest.json"
],
"resources": {
"*": ["$(ASSETS)/images/*", "$(ASSETS)/sounds/*", "$(ASSETS)/voices/*"],
"*-alpha": ["$(ASSETS)/fonts/*"]
},
"modules": {
"piu/Sound": ["$(MODULES)/piu/MC/piuSound"],
"*": ["$(SRC)/*"],
"drivers/*": ["$(SRC)/drivers/*"]
},
"define": {
"ili9341": {
"inverted": 1
}
},
"creation": {
"stack": 512,
"static": 65536
}
}
いろいろ書いてありますが、ここで全て理解する必要はありません。次の設定項目について雰囲気が分かればよいです。
include
:(ドキュメント)他のマニフェストファイルを読み込むための設定。例えば再利用可能なJavaScriptモジュールを自分で実装した場合は、マニフェストファイルも分けて定義しておき、includeで読み込むとよい。modules
:(ドキュメント)プロジェクトが使うJavaScriptモジュールの設定。jsonのキーがimportで使う名前、値がソースコードのパスになる。resources
: (ドキュメント)画像や音声などの外部リソースの設定。フォントや画像
I2C処理を実装する
I2C(アイツーシー、アイスクエアドシー)はシリアル通信の一種です。1つのマスタに対して複数のスレーブを接続できます。今回はコアモジュール(M5Stack)をマスタ、マウスモジュール(STM32)をスレーブとします。マスタからスレーブに対して「通信したいスレーブのアドレス送信」に続いて「データの送受信」を行います。
Moddableを使えば簡単にI2C通信ができます。ModdableではI2Cマスタのモジュールが、JavaScriptのクラスとして実装されています。I2Cクラスを使ってマウスモジュールのクラスを書くと、次のようなイメージになります(実際のコードとは細かい点が色々異なります。)
import I2C from 'pins/i2c'
const MOUSE_MODULE_ADDRESS = 0x64
const REGISTORY = {
GET_ENCODER_L: 0x10,
GET_ENCODER_R: 0x11,
SET_MOTOR_VELOCITY_L: 0x20,
SET_MOTOR_VELOCITY_R: 0x21,
/* ... */
WHO_AM_I: 0x00
}
class Mouse {
constructor(param) {
// I2C通信のクラスをインスタンス化して保持しておく
this.i2c = new I2C(param)
}
// モーターの速度を設定
setMotorVelocityL (v) {
// レジスタアドレスとデータを続けて書き込む
this.i2c.write(REGISTORY.SET_MOTOR_VELOCITY_L, v)
}
// エンコーダの値読み出し
getEncoderL () {
// レジスタアドレスを書き込む
this.i2c.write(REGISTORY.GET_ENCODER_L)
// 2バイト読み出す
return this.i2c.read(2)
}
}
どこからどう見ても普通のJavaScriptですが、これがM5Stackの中でオフラインで動いてしまうのがすごいですね。
I2Cのインスタンスメソッドとしてwrite
とread
が使えます。説明は次のとおりです(公式ドキュメント参照)。
API | 説明 |
read(count [, buffer]) | スレーブのデバイスから count バイト分読み取ります。戻り値は Uint8Array です。ライブラリの実装上、count の最大値は34になっています。都度 Uint8Array を生成して返す代わりに、第2引数にArrayBuffer を渡してそこに読み出すこともできます。 |
write(...value [, stop]) | スレーブのデバイスに値を書き込みます。複数の引数を受け付けます。最後の引数に false を渡すとストップコンディションを送信しません(writeを連続して呼び出したいときに使います)。 |
ここまで読んで、他のマイコンで開発したことのある方は「あれ、I2Cの設定できる項目が少なすぎない…?」と感じたかもしれません。I2Cに使うピンの設定や、通信の周波数は、あらかじめプラットフォーム側で用意された(M5Stack固有の)デフォルト値を使っています。もちろん、この値は必要に応じてアプリ側で上書きできます。
プログラムを書き込むには、前回の記事通りmcconfig
コマンドを使います。
$ mcconfig -m -p esp32/m5stack
これでマスタ側の処理を記述できました。実際に通信するためにはSTM32側の実装も必要です。その方法は次回に紹介します。
解説:Moddableのソフトウェアスタック
今回、Moddableが提供するI2Cモジュールを使ってアプリケーションを書きました。ここでModdableで作ったプロジェクトのソースがどんな構造になっているか、簡単に紹介します。
Moddableのソフトウェアスタックは上記画像のようになります。
- アプリケーション(青の網掛け):ユーザが作成するアプリケーションのコードです
- Moddableが提供するモジュール(緑の網掛け):JavaScriptランタイムの
xs
およびその上で動作するモジュール群から構成されます。 - ESP-IDF(赤の網掛け):Moddableはマルチプラットフォーム対応です。同じコードがESP32やESP8266などのマイコンで動作します。これを実現するために、一部のモジュールは、マイコンによって異なるプラットフォームのAPIをラップする形で実装されています。pins(デジタル/アナログ通信)やi2cなどの通信用モジュールもその一つで、内部的にはESP-IDFを使っています。
Moddableのモジュールは非常に様々な種類があります。ユーザはModdableを使うことで、各プラットフォーム固有のAPIの使い方を都度調べることなく、必要な機能にアクセスできます。今回使ったのはI2Cだけでしたが、これから画像や音声、フォントを扱う描画ライブラリや、ネットワーク通信の使い方についても触れていきます。