blog
← Blog一覧
M5stack / elixir / AtomVM 2026-06-13

M5stackでelixir(AtomVM)

M5stackでelixir(AtomVM)

atomvm_m5 の仕組みと環境構築

概要

atomvm_m5 は、M5Stack 公式ライブラリである M5Unified を AtomVM プラットフォーム向けにポートした拡張ライブラリです。これを使用することで、Elixir / Erlang のコードから M5Stack の LCD・ボタン・スピーカー・IMU などのハードウェアを統一的な API で制御できるようになります。

リポジトリ: https://github.com/pguyot/atomvm_m5


atomvm_m5 の仕組み

レイヤー構造

┌─────────────────────────────────────┐
│     Elixir / Erlang アプリ           │  ← あなたのコード
├─────────────────────────────────────┤
│     atomvm_m5 (Erlang NIF)          │  ← Elixir から C++ を呼ぶ橋
├─────────────────────────────────────┤
│     M5Unified (C++ ライブラリ)        │  ← M5Stack 公式ドライバ
├─────────────────────────────────────┤
│     M5GFX / LovyanGFX (C++)         │  ← 描画エンジン(内部で使用)
├─────────────────────────────────────┤
│     ESP-IDF (C)                     │  ← Espressif 公式フレームワーク
├─────────────────────────────────────┤
│     ESP32 ハードウェア                │
└─────────────────────────────────────┘

NIF とは

NIF(Native Implemented Function)とは、Erlang / Elixir から C / C++ の関数を直接呼び出す仕組みです。atomvm_m5 は M5Unified の C++ API を NIF としてラップすることで、Elixir コードから LCD やボタンなどを制御できるようにしています。

通常の AtomVM との違い

通常のプリビルド .img にはこの NIF が含まれていないため、AtomVM 本体をソースからビルドする必要があります。ビルド時に atomvm_m5 を ESP-IDF コンポーネントとして組み込むことで、NIF が VM に統合されます。

プリビルド版 atomvm_m5 込みカスタムビルド版
セットアップ 簡単 複雑
LCD 制御 ❌ 不可 ✅ 可能
ボタン(高レベル API)
スピーカー
IMU

フラッシュメモリのパーティション構成

カスタムビルド版では以下のパーティションレイアウトを使用します(partitions-elixir.csv)。

パーティション オフセット サイズ 内容
nvs 0x9000 24KB WiFi 設定などの不揮発性データ
phy_init 0xf000 4KB RF 初期化データ
factory 0x10000 1.75MB AtomVM VM 本体
boot.avm 0x1D0000 512KB Elixir 標準ライブラリ
main.avm 0x250000 1MB あなたの Elixir アプリ

重要: boot.avm には Elixir 標準ライブラリが入ります。idf.py flash では VM 本体しか書き込まれないため、boot.avm は別途書き込む必要があります。


環境構築の手順

必要なもの

ツール 用途
usbipd-win(Windows) WSL2 に USB デバイスを橋渡しする
ESP-IDF v5.4 ESP32 向けビルド環境
AtomVM ソースコード(v0.6.6) VM 本体のソース
atomvm_m5 ソースコード M5Unified NIF
Python3 + esptool フラッシュツール
Erlang / Elixir(mise 管理) アプリ開発

大まかなフロー

① usbipd-win のインストール(Windows)
        ↓
② ESP-IDF のインストール(WSL 内)
        ↓
③ AtomVM ソースコードのクローン
        ↓
④ atomvm_m5 を AtomVM の components ディレクトリに配置
        ↓
⑤ menuconfig で I2C 競合を解消・パーティション設定を変更
        ↓
⑥ idf.py build で AtomVM + atomvm_m5 を一緒にビルド
        ↓
⑦ M5Stack を USB 接続して WSL にアタッチする
        ↓
⑧ 生成されたバイナリを M5Stack に書き込む(VM 本体)
        ↓
⑨ boot.avm(Elixir 標準ライブラリ)を書き込む
        ↓
⑩ Elixir アプリを mix atomvm.packbeam でビルドして書き込む

Step 0: Windows 側の USB 設定(usbipd-win)

WSL2 は標準では USB デバイスを認識できません。usbipd-win を使用して Windows に接続した M5Stack を WSL2 から使えるようにします。この設定は フラッシュ・シリアルモニタのたびに必要です。

usbipd-win のインストール(初回のみ)

PowerShell(管理者)で実行します。

winget install --interactive --exact dorssel.usbipd-win

Tips: インストール後はターミナルを再起動してください。

M5Stack を接続して BUSID を確認する

M5Stack を USB で PC に接続してから PowerShell(管理者)で実行します。

usbipd list
# 例:
# 1-2  1a86:55d3  USB-Enhanced-SERIAL CH343 (COM5)  Not shared

表示された BUSID(例: 1-2)を控えておきます。

Tips: M5Stack のチップによってデバイス名が異なります。CH340/CH343 系は USB-Enhanced-SERIAL、ESP32-S3 内蔵 USB は USB JTAG/serial debug unit と表示されます。

デバイスを bind する(初回のみ・管理者権限が必要)

usbipd bind --busid 1-2

Tips: bind は一度実行すれば再起動後も設定が維持されます。

WSL に attach する(接続のたびに必要)

usbipd attach --wsl --busid 1-2

⚠️ 重要: M5Stack を抜き差しするたびに attach の再実行が必要です。bind と異なり attach は永続しません。attach は管理者権限がなくても実行できます。

WSL 側でデバイスを確認する

ls /dev/ttyUSB* /dev/ttyACM*
# → /dev/ttyUSB0 が見えれば OK

デバイスが見えない場合は以下を確認します。

sudo dmesg | grep tty

シリアルポートの権限エラーが出る場合は以下を実行します(初回のみ・再ログインが必要)。

sudo usermod -aG dialout $USER

Step 1: ESP-IDF のインストール(WSL 内)

AtomVM v0.6.x のビルドには ESP-IDF v5.4 が必要です。

# 依存パッケージのインストール
sudo apt 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

# ESP-IDF のクローン(v5.4 を指定)
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout v5.4
git submodule update --init --recursive

# ツールチェーンのインストール(ESP32 向け)
./install.sh esp32

インストール後、~/.bashrc にエイリアスを追加しておくと便利です。

echo "alias get_idf='. ~/esp/esp-idf/export.sh'" >> ~/.bashrc
source ~/.bashrc

# 動作確認
get_idf
idf.py --version
# → ESP-IDF v5.4 と表示されれば OK

Tips: get_idf はターミナルを開くたびに実行が必要です。idf.py: command not found が出たらまず get_idf を実行してください。


Step 2: AtomVM ソースコードのクローン

cd ~
git clone https://github.com/atomvm/AtomVM.git
cd AtomVM
git checkout v0.6.6
git submodule update --init --recursive

Step 3: atomvm_m5 を components ディレクトリに配置

cd ~/AtomVM/src/platforms/esp32/components
git clone https://github.com/pguyot/atomvm_m5.git

# 確認(atomvm_m5 が並んで見えれば OK)
ls
# → atomvm_m5  avm_builtins  avm_sys  libatomvm

Tips: components ディレクトリに配置するだけで自動的にビルドに組み込まれます。


Step 4: ビルド設定(menuconfig)

ターゲットの設定と初期化

get_idf
cd ~/AtomVM/src/platforms/esp32
idf.py set-target esp32

menuconfig で設定を変更する

idf.py menuconfig

以下の2つを同じ menuconfig セッション内で設定して保存します。

① I2C 競合を解消する

M5Unified が使う新しい I2C ドライバ(driver_ng)と AtomVM の古い I2C ドライバが競合します。AtomVM 側の I2C ドライバを無効化して解消します。

/ キーで検索して以下を両方無効化します。

AVM_ENABLE_I2C_PORT_DRIVER  → n(無効)
AVM_M5_I2C_ENABLE           → n(無効)

注意: I2C を Elixir から直接使う機能は失われますが、M5Unified 経由で LCD やボタンを使う分には問題ありません。M5Unified が内部で I2C を管理してくれます。

② パーティションレイアウトを Elixir 用に変更する

AtomVM のソースには Elixir 用のパーティション定義ファイル partitions-elixir.csv が最初から用意されています。

Partition TableCustom partition CSV file で以下を入力します。

partitions-elixir.csv

設定後に保存して menuconfig を終了します。

設定が反映されているか確認します。

grep -i "partition_table_custom_filename" ~/AtomVM/src/platforms/esp32/sdkconfig
# → CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-elixir.csv" と表示されれば OK

Step 5: ビルド実行

cd ~/AtomVM/src/platforms/esp32
idf.py build

成功すると以下のファイルが生成されます。

ls build/atomvm-esp32.bin

Tips: 初回ビルドは数十分かかります。WARNING は無視して構いません。


Step 6: VM 本体を M5Stack に書き込む

M5Stack を USB 接続し、PowerShell で usbipd attach が済んでいることを確認してから実行します。

idf.py -p /dev/ttyUSB0 flash

Step 7: boot.avm(Elixir 標準ライブラリ)を書き込む

idf.py flash では VM 本体しか書き込まれません。boot.avm パーティションには Elixir 標準ライブラリが必要です。プリビルド版 Elixir イメージから boot.avm を取り出して書き込みます。

cd ~

# プリビルド版 Elixir イメージをダウンロード
wget https://github.com/atomvm/AtomVM/releases/download/v0.6.6/AtomVM-esp32-elixir-v0.6.6.img

# dd で boot.avm 部分(0x1d0000 から 512KB)を取り出す
# ※ img ファイルは 0x1000 から始まるため、skip でオフセットを調整する
dd if=AtomVM-esp32-elixir-v0.6.6.img \
   of=boot.avm \
   bs=1 \
   skip=$((0x1d0000 - 0x1000)) \
   count=$((0x80000))

# boot.avm を書き込む
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
    write_flash 0x1d0000 boot.avm

書き込み後にシリアルモニタで確認します。

picocom -b 115200 /dev/ttyUSB0

以下のログが出れば成功です。

I (885) AtomVM: Starting AtomVM revision 0.6.6
I (895) sys: Loaded BEAM partition boot.avm at address 0x1d0000 (size=524288 bytes)
E (935) AtomVM: Invalid startup avmpack. size=524288

確認ポイントは以下の通りです。

  • boot.avm のサイズが 524288(512KB)になっている
  • I2C CONFLICT エラーが出ていない
  • Invalid startup avmpack はアプリ未書き込みによる正常なメッセージ

Step 8: Elixir アプリの開発とフラッシュ

VM と boot.avm の書き込みが完了したら、通常の Elixir 開発フローと同じです。アプリのフラッシュオフセットは 0x250000 を使用します。

# Elixir アプリのビルド
mix atomvm.packbeam

# アプリのフラッシュ
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
    write_flash 0x250000 your_app.avm

サンプルプログラムを動かす

atomvm_m5 にはサンプルプログラムが付属しています。LCD・ボタン・スピーカー・IMU・バッテリー監視などの基本的な使い方を網羅したサンプルです。

サンプルの場所

ls ~/AtomVM/src/platforms/esp32/components/atomvm_m5/examples/basic/
# → ex_how_to_use  how_to_use  rtc

Elixir 版のサンプルは ex_how_to_use です。

サンプルのビルドとフラッシュ

# サンプルディレクトリに移動
cd ~/AtomVM/src/platforms/esp32/components/atomvm_m5/examples/basic/ex_how_to_use

# 依存パッケージを取得
mix deps.get

# .avm ファイルを生成
mix atomvm.packbeam

# 生成された .avm ファイルを確認
find . -name "*.avm" -not -path "*/deps/*"
# → ./HowToUse.avm  ./deps.avm  ./priv.avm

# M5Stack にフラッシュ
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
    write_flash 0x250000 HowToUse.avm

ビルド時の警告について

mix atomvm.packbeam 実行時に以下の警告が出ますが、いずれも無視して構いません。

警告 理由
warning: :console.puts/1 is undefined PC 側の Elixir 環境にないだけで AtomVM 上では動作する
No avm_deps directory found 標準ライブラリは boot.avm に含まれているため不要
Req.get is undefined ExAtomVM の install タスク用で実行時には使われない

動作確認

シリアルモニタで起動ログを確認します。

picocom -b 115200 /dev/ttyUSB0

正常に起動すると LCD に以下の情報が表示されます。

  • ボード名(Core など)
  • IMU の種類
  • ボタンの状態
  • バッテリー残量
  • IMU の加速度・ジャイロのグラフ

オリジナルプロジェクトの作成

Step 1: プロジェクトを作成する

cd ~/atom-project
mix new my_m5_app
cd my_m5_app

Step 2: mix.exs を編集する

defmodule MyM5App.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_m5_app,
      version: "0.1.0",
      elixir: "~> 1.18",
      start_permanent: Mix.env() == :prod,
      # AtomVM 用の設定
      # start: エントリーポイントのモジュール名
      # flash_offset: Elixir パーティションのアプリ開始アドレス
      atomvm: [
        start: MyM5App,
        flash_offset: 0x250000
      ],
      deps: deps()
    ]
  end

  def application do
    [
      # mod: は通常の OTP アプリ用なので AtomVM では不要
      extra_applications: []
    ]
  end

  defp deps do
    [
      # ExAtomVM は Hex に存在しないので GitHub から取得する
      {:exatomvm, github: "atomvm/ExAtomVM", runtime: false},
      # atomvm_m5 はローカルのソースを参照する(~ は展開される)
      {:atomvm_m5, path: "~/AtomVM/src/platforms/esp32/components/atomvm_m5"}
    ]
  end
end

Step 3: lib/my_m5_app.ex を編集する

defmodule MyM5App do
  # start/0(引数なし)で定義する
  # start/2 は通常の OTP アプリ用なので AtomVM では使わない
  def start do
    # M5Stack を初期化する(ディスプレイをクリアする)
    :m5.begin_(clear_display: true)

    # テキストサイズを設定する
    :m5_display.set_text_size(2)
    # カーソル位置を設定する(x, y)
    :m5_display.set_cursor(0, 0)
    # 文字列を表示して改行する
    :m5_display.println("Hello M5Stack!")

    loop()
  end

  defp loop do
    # M5Stack の状態を更新する(ボタン状態の取得などに必要)
    :m5.update()
    :timer.sleep(10)
    loop()
  end
end

Step 4: 依存取得・ビルド・フラッシュ

# 依存パッケージを取得する
mix deps.get

# .avm ファイルを生成する
mix atomvm.packbeam

# M5Stack にフラッシュする
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \
    write_flash 0x250000 my_m5_app.avm

注意事項

項目 内容
usbipd attach M5Stack を抜き差しするたびに PowerShell で再実行が必要
usbipd bind 初回のみ・管理者権限が必要。再起動後も設定は維持される
ESP-IDF バージョン AtomVM v0.6.x は ESP-IDF v5.4 が必要
ビルド時間 初回は数十分かかる
WSL のディスク容量 ESP-IDF は約 5GB 以上必要
get_idf ターミナルを開くたびに実行が必要
I2C 競合 AVM_ENABLE_I2C_PORT_DRIVERAVM_M5_I2C_ENABLE を無効化する
boot.avm idf.py flash では書き込まれないため別途 dd + esptool で書き込む
パーティション設定 partitions.csv を直接編集せず menuconfig で partitions-elixir.csv を指定する
エントリーポイント start/0(引数なし)で定義する。start/2 は AtomVM では使わない
アプリのオフセット 0x250000(Elixir パーティションレイアウト)