本文へスキップ

code-d5.org

Knowledge of Ignorance

StackChan の M5版ファームウェアを書き込む環境を Linux で作る

物理 Linux(Ubuntu 26.04 LTS)マシン上で、 M5版StackChan の公式ファームウェアをビルド・書き込みできるよう環境を整える。

目標は「公式ファームウェアのソースを git で取得し、ビルドして、USB経由で書き込むこと」。
これができれば、自由自在に StackChan をカスタマイズできるようになるはず。

※ 本記事は、M5版StackChanファームウェアのバージョン 1.4.3 時点での情報です


Step 0:前提知識/M5版StackChan ファームウェアの全体像

0-1:OTA アップデートに注意

M5版StackChanは、起動時にサーバ側へアップデート確認しにいき、最新版に自動更新される。
必要に応じて、このあたりの挙動を無効化する設定を入れたファームを書き込むことを頭に入れておく。
これは別記事で触れる予定。

0-2:大方針

StackChan の中身は ESP32-S3 という Wi-Fi/BLE 内蔵のマイコンチップ。Windows/Linuxのような汎用的なOS は載っていない。
代わりにファームウェアが内蔵フラッシュメモリに焼かれている。

M5版のStackChan のファームウェアは C/C++ で書かれている。
今回は ESP-IDF という Espressif 公式の開発フレームワークを使い、ソースコードからクロスコンパイルして、ESP32-S3用のバイナリを生成して、USB(シリアル)経由で書き込む。

ソースコード (.c, .cpp)
  ↓ ESP-IDF (cmake + ninja + クロスコンパイラ)
バイナリ (stack-chan.bin)
  ↓ esptool.py (USB シリアル経由)
ESP32-S3 の Flash メモリに書き込み
  ↓ 電源ON
StackChan が起動

0-3:git リポジトリの構成

公式ファームウェアのリポジトリ (m5stack/StackChan) はこんな構造になっているっぽい。
firmware/ だけが ESP32 に焼くファームウェア。残りは別モノ。

m5stack/StackChan/
├── firmware/
│   ├── CMakeLists.txt         ← ビルドの設定(プロジェクト名、バージョン等)
│   ├── main/                   ← StackChan 本体のソースコード
│   ├── sdkconfig.defaults     ← デフォルトのビルド設定
│   ├── repos.json             ← 依存ライブラリの定義
│   ├── fetch_repos.py         ← 依存を git clone するスクリプト
│   ├── patches/               ← 依存ライブラリへのパッチ
│   └── partitions.csv         ← Flash メモリのパーティション定義
├── server/                     ← サーバー側のソース
├── remote_controller/         ← リモコンのソース
└── app/                       ← モバイルアプリ

0-4:依存ライブラリ (repos.json) の仕組み

StackChan のファームウェアは単独では動かない。AI対話エンジンや UI フレームワークなど、複数のライブラリに依存している。
これらは repos.json に定義されていて、fetch_repos.py を実行すると git clone で取得されるので、あんまり意識する必要はないかも。
xiaozhi-esp32 に独自のパッチを適用して使っているっぽいのだけ把握しておく。

[
  {"url": "https://github.com/Forairaaaaa/mooncake.git", "path": "components/mooncake", "branch": "v2.3.3"},
  {"url": "https://github.com/78/xiaozhi-esp32.git", "path": "xiaozhi-esp32", "branch": "v2.2.4", "patch": "patches/xiaozhi-esp32.patch"},
   ...
]

0-5:managed_components(ESP-IDF のコンポーネント管理)

repos.json とは別に、ESP-IDF には Component Manager という仕組みがある。
初回ビルド時に managed_components/ ディレクトリが作られて、追加依存ライブラリなどが自動ダウンロードされる。数百MB あるっぽい。

firmware/
├── components/             ← fetch_repos.py で取得するもの(gitリポジトリ)
│   ├── mooncake/
│   ├── smooth_ui_toolkit/
│   ├── ArduinoJson/
│   └── esp-now/
├── xiaozhi-esp32/           ← AI対話エンジン(パッチ適用済み)
└── managed_components/     ← idf.py build 時に自動ダウンロードされるもの
  ├── espressif__esp-sr/   ← 音声認識 (WakeNet, MultiNet)
  ├── lvgl__lvgl/         ← UI ライブラリ
  ├── espressif__esp32-camera/
  └── ...


Step 1:必要パッケージのインストール

とりあえず必要なものを apt install でひたすらいれる。
公式ページにコマンドが載っている ので、これでいいんじゃないかなと。
私の場合は cmake, dfu-util が入っていなかった。

Ubuntuの場合

$ sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0


Step 2:ESP-IDF v5.5.4 の git クローン

私は、ホームディレクトリ配下に stackchan-m5 ディレクトリを作り、そこに環境を整えていくことにした。

ESP-IDF は Espressif(ESP32 の製造元)が提供する公式開発フレームワーク。
クロスコンパイラ、ビルドシステム、各種ライブラリ、書き込みツール(esptool)がセットになっている。

私がこの作業を行ったときは、StackChan 公式ファームウェアが ESP-IDF v5.5.4 でビルドされていたので、念のため同じバージョンを持ってくることにした。
サブモジュールも --recursive で一括で持ってくる。全体で約 5 分。

$ cd ~/stackchan-m5
$ git clone -b v5.5.4 --recursive https://github.com/espressif/esp-idf.git esp-idf-v5.5.4
Cloning into 'esp-idf-v5.5.4'...
Receiving objects: 100% (1074981/1074981), 436.94 MiB | 14.85 MiB/s, done.
Resolving deltas: 100% (783212/783212), done.


Step 3:ツールチェーンのインストール

ESP32-S3 用のツールをインストールする。
すべて ~/.espressif/ 以下に配置されるようだ。

$ cd ~/stackchan-m5/esp-idf-v5.5.4
$ ./install.sh esp32s3
Installing tools: xtensa-esp-elf-gdb, xtensa-esp-elf, riscv32-esp-elf, esp32ulp-elf, openocd-esp32, esp-rom-elfs
...
Creating a virtual environment ... done
Installing Python packages ... done
All done! You can now run:
. ./export.sh


Step 4: 環境変数の有効化と確認

ESP-IDF のツールを使うには、毎回 export.sh で環境変数(PATH 等)を設定する必要がある。

$ source ~/stackchan-m5/esp-idf-v5.5.4/export.sh
Activating ESP-IDF 5.5
Setting IDF_PATH to '/home/youruser/stackchan-m5/esp-idf-v5.5.4'.
Done! You can now compile ESP-IDF projects.
$ idf.py --version
ESP-IDF v5.5.4


Step 5:USBシリアル書き込みの設定/dialout グループへの追加

Linux で ESP32 に USB 書き込みするには /dev/ttyACM0 へのアクセス権が必要。
このデバイスファイルの所有グループは dialout なので、そこに自分のユーザーを入れてあげる。

$ sudo usermod -aG dialout youruser

確認:groups youruser を叩いて、dialout に所属していればOK

$ groups youruser

現在のシェルセッションには反映されないようなので、一度ログアウトして、再ログインする。


Step 6:USB 認識確認

Linux マシンと StackChan を USB で接続し、StackChan の電源を入れて、認識確認をする。

$ ls /dev/ttyACM*
/dev/ttyACM0

$ lsusb | grep -i espressif
Bus 003 Device 004: ID 303a:1001 Espressif USB JTAG/serial debug unit

303a:1001 は Espressif の USB ベンダーID:プロダクトID。
ESP32-S3 の内蔵 USB-Serial/JTAG コントローラとして認識された。


Step 7:公式ファームウェアの取得とビルド

ようやく環境が整ったので、ここからが本番。
公式リポジトリのソースを取得して、手元でビルドする。コンパイルさえ通ればこちらのもの!

7-1:ソースの取得

公式リポジトリ (m5stack/StackChan) を ~/stackchan-m5/ 配下に git clone する。

$ cd ~/stackchan-m5/
$ git clone https://github.com/m5stack/StackChan.git stackchan

クローンしたバージョンを確認しておく。

$ cd ~/stackchan-m5/stackchan
$ git log --oneline -1
b72b3ed Merge pull request #100 from m5stack/firmware-dev

$ grep PROJECT_VER firmware/CMakeLists.txt
set(PROJECT_VER "1.4.3")

7-2:依存ライブラリの取得

fetch_repos.py を走らせて、依存ライブラリの取得を行う。パッチ適用もしてくれる。

$ cd ~/stackchan-m5/stackchan/firmware
$ python3 ./fetch_repos.py
Cloning into '.../components/mooncake'...
HEAD is now at 572a7e4 bump version                   ← mooncake v2.3.3

Cloning into '.../components/mooncake_log'...
HEAD is now at 554d55c Update README.md               ← mooncake_log v1.5.0

Cloning into '.../components/smooth_ui_toolkit'...
HEAD is now at 2b20e5c bump version                   ← smooth_ui_toolkit v2.12.0

Cloning into '.../xiaozhi-esp32'...
HEAD is now at 24f9a09 ...                             ← xiaozhi-esp32 v2.2.4
Applied patch patches/xiaozhi-esp32.patch             ← パッチ適用

Cloning into '.../components/ArduinoJson'...
HEAD is now at e0850a2 v7.4.2                         ← ArduinoJson v7.4.2

Cloning into '.../components/esp-now'...
HEAD is now at c33383d Merge branch 'fix/wireless-debug' into 'master'

7-3:ビルド (idf.py build)

いよいよビルドする。
export.sh で環境変数を与えてから、idf.py build でビルドする。

$ source ~/stackchan-m5/esp-idf-v5.5.4/export.sh
$ idf.py build

初回ビルド時のログ(抜粋):

-- IDF_TARGET is not set, guessed 'esp32s3' from sdkconfig
-- App "stack-chan" version: 1.4.3
NOTICE: Processing 60 dependencies:
NOTICE: [1/60] 78/esp-ml307 (3.6.5)
NOTICE: [14/60] espressif/esp-sr (2.3.1)        
NOTICE: [53/60] lvgl/lvgl (9.4.0)          
...
[2489/2491] Linking CXX executable stack-chan.elf
[2490/2491] Generating binary image from built executable
Successfully created esp32s3 image.
Generated .../build/stack-chan.bin

stack-chan.bin binary size 0x39c4e0 bytes. Smallest app partition is 0x4f0000 bytes. 0x153b20 bytes (27%) free.
Project build complete.

最終的なバイナリサイズは大体 3.6 MiB だった。
初回のビルド時間は managed_components のダウンロード含めて約 5〜8 分。
2回目以降は差分コンパイルになるので 1分くらいで収まる。


Step 8:ビルドしたファームウェアの書き込み

StackChan を Linux マシンに USB 接続した状態として、ビルドしたファームウェアを、USBシリアル経由で書き込む。
所要時間は約 30 秒。

$ idf.py -p /dev/ttyACM0 flash

書き込みログ(抜粋):

esptool.py --chip esp32s3 -p /dev/ttyACM0 -b 460800 write_flash ...
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3 (QFN56) (revision v0.2)
Features: WiFi, BLE
Crystal is 40MHz
USB mode: USB-Serial/JTAG
MAC: XX:XX:XX:XX:XX:XX
Uploading stub...
Running stub...
Changing baud rate to 460800
Changed.

Compressed 23792 bytes to 15336...
Writing at 0x00000000... (100 %)       ← bootloader
Wrote 23792 bytes (15336 compressed) at 0x00000000 in 0.2 seconds

Compressed 3785952 bytes to 2127784...
Writing at 0x00020000... (0 %)         ← stack-chan.bin
Writing at 0x003b6dbb... (100 %)
Wrote 3785952 bytes (2127784 compressed) at 0x00020000 in 19.3 seconds (effective 1566.2 kbit/s)

Compressed 2298032 bytes to 1028976...
Writing at 0x00a00000... (1 %)         ← generated_assets.bin
Writing at 0x00c2dc58... (100 %)
Wrote 2298032 bytes (1028976 compressed) at 0x00a00000 in 10.4 seconds

Hard resetting via RTS pin...
Done

書き込み完了後、StackChan が自動リセットされて、起動してきたら書き込み完了!
おつかれさまでした。