はじめに
M5Atomシリーズ(M5AtomS3)とM5Atomic Echo Baseを組み合わせて、小さいスタックチャンを動かすまでの過程を備忘メモに残します。
Moddable SDKでターゲットデバイスを追加する時の一例として参考にしていただければ幸いです。

ゴール
Node-RED MCU版(Moddable SDK版)の小さいスタックチャンを動かすことが目的です。

サンプルコードを動かす
Arduinoのサンプルコード
Arduinoライブラリとサンプルコードが用意されています。
サンプルコードを動かす際に注意点がありました。
- ESP32のArduinoライブラリは2.0.x系を使用する(ESP-IDF 4.4系を使用する)
- 3.0.x系はESP-IDF 5.x系が使用されているため、ビルドでエラーが出る

- M5Atomic-EchoBaseのライブラリ(1.0.0)の内容の一部(PI4IOESV6408チップのI2Cアドレス)が間違っている
- GitHubの内容(最新)は修正されているが、リリースパッケージが更新されていない

UIFlow2のサンプルコード
UIFlow2(Micropython)のサンプルコードが用意されています。
実装はGitHubのソースコードを参考にしました。
Moddable SDKで実装する
過去にターゲットデバイスの追加や、液晶ディスプレイドライバの初期化処理のデバッグを行ったことがあったので、安直に考えていましたが間違いでした。
サンプルコードを参考に実装しましたが、デバイスから音が鳴りません。
ロジアナで解析する
まずはI2Cの実装が合っているか、ロジアナ(激安USBロジアナ、sigrok、PulseView)を使用して確認しました。
(参考)
https://qiita.com/nanbuwks/items/9069165bfb66d8836583

ES8311とPI4IOE5V6408のコマンドシーケンスを追いかけます。

I2Cのコマンドシーケンスは同じでも音が出ませんでした。
(たまに音が鳴っても、虫の音ほど微かに聞こえる程度)
次にI2Sの信号も確認しました。

デバッグ作業
ES8311はモノラルオーディオデコーダーなのでR(右)の音声データの有無は関係ないと先入観を持っていましたが、最終的に気がついたのはI2Sの信号で、送信される音声データに差分(LRのデータと、Lのみのデータ)がありました。
また、後になって気づいたことですが、起動(初期化)後の数秒(1〜2秒)は音が出ない仕様のようです。
動いた
L(左)の音声データをR(右)にも送信することで音が鳴るようになりました。

ESP-IDFのソースコードでは、I2SのslotはI2S_STD_SLOT_LEFTが指定されていることを確認しました。
これをI2S_STD_SLOT_BOTHに変更すればLR(左右)のデータとして出力されそうです。
Moddable SDKではmanifest.jsonで設定できることを確認しました。
"i2s": {
"slot": "I2S_STD_SLOT_BOTH"
}
コードの整理
起動(初期化)後の数秒(1〜2秒)は音が出ない仕様については、最初に1秒間の無音データ(16000サンプルデータ)を挿入することで問題を回避しました。
M5AtomS3 + M5Atomic Echo Baseのターゲットファイル(m5atom_s3_echo_base)を例に差分コードを示します。
% diff manifest.json ../m5atom_s3/manifest.json
19,21d18
< "setup/target": "./setup-target",
< "pins/audioout": "$(MODULES)/pins/i2s/*",
< "pins/smbus": "$(MODULES)/pins/smbus/smbus",
24,25d20
< "$(MODULES)/pins/audioin/*",
< "$(MODULES)/pins/audioin/esp32/*",
27c22,23
< ]
---
> ],
> "setup/target": "./setup-target"
30,31d25
< "pins/audioout",
< "audioin",
38,42c32
< "touch": "",
< "startupSound": "bflatmajor.maud",
< "es8311": {
< "volume": 160
< }
---
> "touch": ""
44,46d33
< "resources": {
< "*": "$(MODDABLE)/examples/assets/sounds/bflatmajor"
< },
111,135d97
< },
< "audioOut": {
< "bitsPerSample": 16,
< "numChannels": 1,
< "sampleRate": 11025,
< "volume_divider": 1,
< "i2s": {
< "num": 1,
< "slot": "I2S_STD_SLOT_BOTH",
< "bitsPerSample": 16,
< "bck_pin": 8,
< "lr_pin": 6,
< "dataout_pin": 5
< }
< },
< "audioIn": {
< "sampleRate": 11025,
< "bitsPerSample": 16,
< "i2s": {
< "num": 0,
< "bck_pin": 8,
< "lr_pin": 6,
< "datain": 7,
< "pdm": 1
< }
1c1
< //import Digital from "pins/digital";
---
> import Digital from "pins/digital";
9,11d8
< import AudioOut from "pins/audioout";
< import Resource from "Resource";
< import SMBus from "pins/smbus";
13,18d9
< const INTERNAL_I2C = Object.freeze({
< sda: 38,
< scl: 39,
< hz: 100000
< });
<
41,64d31
< // start-up sound
< if (config.startupSound) {
< const speaker = new AudioOut({streams: 1});
<
< speaker.callback = function () {
< this.stop();
< this.close();
< Timer.set(this.done);
< };
< speaker.done = done;
< done = undefined;
<
< new ES8311();
< new PI4IOE5V6408();
<
< speaker.enqueue(0, AudioOut.Silence, 16000); // enqueue silence for no audio output (1 sec) of ES8311 I2S initial setup
< speaker.enqueue(0, AudioOut.Samples, new Resource(config.startupSound), 1);
< speaker.enqueue(0, AudioOut.Callback, 0);
< speaker.start();
<
< }
<
<
< // accelerometer and gyrometer
69c36
< done?.();
---
> done();
72,149d38
< class ES8311 {
< // initialize ES8311(0x18)
< // https://github.com/m5stack/uiflow-micropython/blob/master/m5stack/libs/driver/es8311/__init__.py
< constructor(options) {
< const es = new SMBus({
< ...INTERNAL_I2C,
< address: 0x18,
< });
< es.writeByte(0x00, 0x1F); // ES8311_RESET_REG00
< Timer.delay(20);
< es.writeByte(0x00, 0x00); // ES8311_RESET_REG00
< es.writeByte(0x00, 0x80); // ES8311_RESET_REG00
<
< // clock config
< es.writeByte(0x01, 0xBF); // ES8311_CLK_MANAGER_REG01
< es.readByte(0x06); // ES8311_CLK_MANAGER_REG06
< es.writeByte(0x06, 0x03); // ES8311_CLK_MANAGER_REG06
< es.readByte(0x02); // ES8311_CLK_MANAGER_REG02
< es.writeByte(0x02, 0x10); // ES8311_CLK_MANAGER_REG02
< es.writeByte(0x03, 0x10); // ES8311_CLK_MANAGER_REG03
< es.writeByte(0x04, 0x10); // ES8311_CLK_MANAGER_REG04
< es.writeByte(0x05, 0x00); // ES8311_CLK_MANAGER_REG05
< es.readByte(0x06); // ES8311_CLK_MANAGER_REG06
< es.writeByte(0x06, 0x03); // ES8311_CLK_MANAGER_REG06
< es.readByte(0x07); // ES8311_CLK_MANAGER_REG06
< es.writeByte(0x07, 0x00); // ES8311_CLK_MANAGER_REG07
< es.writeByte(0x08, 0xFF); // ES8311_CLK_MANAGER_REG08
<
< // format config
< es.readByte(0x00); // ES8311_RESET_REG00
< es.writeByte(0x00, 0x80); // ES8311_RESET_REG00
< es.writeByte(0x09, 0x10); // ES8311_SDPIN_REG09
< es.writeByte(0x0A, 0x10); // ES8311_SDPOUT_REG0A
<
< //
< es.writeByte(0x0D, 0x01); // ES8311_SYSTEM_REG0D
< es.writeByte(0x0E, 0x02); // ES8311_SYSTEM_REG0E
< es.writeByte(0x12, 0x00); // ES8311_SYSTEM_REG12
< es.writeByte(0x13, 0x10); // ES8311_SYSTEM_REG13
< es.writeByte(0x1C, 0x6A); // ES8311_ADC_REG1C
< es.writeByte(0x37, 0x08); // ES8311_DAC_REG37
<
< // set volume (0 - 256)
< let volume = config.es8311.volume ?? 128;
< if (volume < 0) volume = 0;
< if (volume > 255) volume = 255;
< es.writeByte(0x32, volume); // ES8311_DAC_REG32
<
< // microphone
< es.writeByte(0x17, 0xFF); // ES8311_ADC_REG17
< es.writeByte(0x14, 0x1A); // ES8311_SYSTEM_REG14
<
< es.close();
< }
< }
<
< class PI4IOE5V6408 {
< // initialize PI4IOE5V6408(0x43)
< // https://github.com/m5stack/uiflow-micropython/blob/master/m5stack/libs/base/echo.py
< constructor(options) {
< const pi = new SMBus({
< ...INTERNAL_I2C,
< address: 0x43,
< });
< pi.readByte(0x00); // PI4IOE_REG_CTRL
< pi.writeByte(0x07, 0x00); // PI4IOE_REG_IO_PP
< pi.readByte(0x07);
< pi.writeByte(0x0D, 0xFF); // PI4IOE_REG_IO_PULLUP
< pi.writeByte(0x03, 0x6E); // PI4IOE_REG_IO_DIR
< pi.readByte(0x03);
< pi.writeByte(0x05, 0xFF); // PI4IOE_REG_IO_OUT
< pi.readByte(0x05);
<
< pi.close();
< }
< }
プルリク
2025年5月11日、プルリク(Pull Request)を送りました。
次回のリリース(5.7.0)にマージされると普通に利用できるようになります。
それまで待てないという方は
$MODDABLE/build/devices/esp32/targets
ディレクトリにターゲットファイルを配置(ディレクトリを作成)してください。
最後に
Node-RED MCUでも動くことを確認しました。
node-red-mcu-pluginの修正(ターゲットデバイスの定義追加)が必要です。
プルリクがマージされるまでお待ちください!
...
let platform_identifiers = [
(以下に行を追加)
'esp32/m5atom_s3_echo_base',