ESP32/M5Stack の開発環境構築(esp-idf と Arduino Core)

2020/1/19 更新

ESP32/M5Stack 開発環境構成の整理がてら構築手順をまとめてみました。Arduino IDE は使わずに VS Code でプログラムがかけるところまで書いてみます。

Windows 向けに書いていますが、ツールチェインの導入部分が異なるだけで他の OS でも基本的な考え方や手順は同じです。

ひとつずつ確認しながら進む手順になっていますので、フルコンパイルが何度かかかり少し時間がかかりますがご勘弁を。慣れてきたら飛ばしてやってください。

また、この記事は ESP32/M5Stack 系のツールチェインやライブラリー依存関係の参考にもなるかもしれません。(PlatformIO や Arduino IDE + VS Code Arduino extention などが行っている内部的な動作が分かりますので、コンパイルエラーなどが出たときに対応がしやすくなると思います)

参考リポジトリー

本手順を元につくられた M5Stack サンプルアプリとビルド手順は次の github リポジトリーから参照できます。手順とソースコードの構成を合わせて見ると分かりやすいと思います。

https://github.com/h1romas4/m5stack-synth-emulation

ESP32/M5Stack の開発系の構成

ESP32/M5Stack オフィシャル開発系の構成は次のようになっています。

  • xtensa-esp32 – ツールチェイン(gccクロスコンパイラ) Windows/Linux/macOS 用に分かれています。
  • esp-idf – ESP32 用のフレームワーク(FreeRTOS + ライブラリ+ 転送ツール等)
  • Arduino Core for the ESP32– ESP32 で Arduino 風の関数やコンポーネントを使うライブラリ(要 esp-idf)
  • M5Stack-IDF – M5Stack から提供される esp-idf + Arduino Core + M5用のライブラリー

Arduino Core for the ESP32 に関しては esp-idf の追加コンポーネントのような形となっていますので ESP32 開発で必須なのは xtensa-esp32 と esp-idf です。

拡張として Arduino Core は esp-idf に依存し、さらに M5Stack は Arduino Core に依存しているという構成になります。(といってもプロジェクトテンプレートがうまくできているので導入は簡単です)

バージョン依存

Arduino Core、M5Stack は上位モジュールを参照しているため、バージョンによる依存関係があります。この記事の解説では次のバージョンが導入されます。

esp-idfv3.1.3
Arduino Core1.0.0
M5Stack0.2.1

2020/1 現在の新しいバージョンの組み合わせは以下のようになっているようです。

esp-idfv3.2.3
Arduino Core1.0.4
M5Stack0.2.9

手順でも補足していますが、最新にする場合は esp-idf 及び Arduino Core の git clone 時のバージョンを変更してください。なお、Arduino Core のリリースに、依存する esp-idf のバージョンがかかれています。

本記事に対応した github の参考リポジトリーではビルド環境依存を防ぐため、リポジトリーに esp-idf も submodule で加え、後述する IDF_PATH 環境変数の切り替えをビルド手順に加え、数行のコマンドでビルドできるようにしています。

git clone --recursive https://github.com/h1romas4/m5stack-synth-emulation.git
cd m5stack-synth-emulation
# This repository includes eps-idf v3.2.3
export IDF_PATH=$(pwd)/esp-idf
make

また、Github Actions の機能を使ったスクリプトでビルドしてできたバイナリーを自動的にリリースできるようにしてみました。

ツールチェインの導入

まずコンパイラや周辺ツールを含むツールチェインを導入します。

Windows 上の任意の場所に開発系の作業フォルダーを作成し、Toolchain Setup からダウンロードしたツールチェインの .zip を展開します。

Toolchain Setup

The quick setup is to download the Windows all-in-one toolchain & MSYS2 zip file from dl.espressif.com:

執筆時点での安定版ツールチェインは

esp32_win32_msys2_environment_and_toolchain-20181001.zip

でした。

本手順では作業用ディレクトリを以下のパスにします。

C:\develop\esp

ツールチェインの .zip が展開できたら

C:\develop\esp\msys32

の形にフォルダーを移動後、以下のファイルをダブルクリックしてビルド操作用のターミナルを起動します。(この後はこの窓で作業していきます)

C:\develop\esp\msys32\mingw32.exe

mingw32 が起動したらホームディレクトリに作業用のディレクトリを作成してカレントディレクトリを移しておきます

$ mkdir ~/esp
$ cd ~/esp

なおこれらの Windows 的なファイルの実態は

C:\develop\esp\msys32\home\[ユーザー名]\esp

になります。またツールチェイン(コンパイラ)は

C:\develop\esp\msys32\opt\xtensa-esp32-elf\bin

にあり事前にパスが通っています。

それぞれをエクスプローラーで確認しておきましょう。

esp-idf の導入

引き続き mingw シェル上で git を使い esp-idf を導入します。

Get ESP-IDF

$ cd ~/esp
$ git clone -b v3.1.3 --recursive https://github.com/espressif/esp-idf.git

ディレクトリ esp-idf が作成され esp-idf が格納されます。

git clone で指定している v3.1.3 が esp-idf のバージョンになります。より新しいバージョンを指定するには v3.2.3 などとします。

前述の「バージョン依存」の通り Arduino Core や M5Stack は依存する esp-idf のバージョンが決まっていますので注意します。これらを利用しない場合は任意の最新バージョンの esp-idf を使うことができます。

なお、esp-idf は v3.3 系が LTS(長期サポート版)になっているようです。

ESP-IDF Release v3.3 (LTS)

Version 3.3 is the first ESP-IDF Long Term Support release. ESP-IDF v3.3.x will be supported for 2.5 years, until March 2022.

次に OS の IDF_PATH 環境変数に上記で得られた esp-idf のパスを設定します。(この機能により複数の esp-idf のバージョンを切り替えることができます)

公式では mingw 上のシェル環境に設定することになっていますが、Windows 側で設定した方が後述の VS Code からも見ることができるので、システム詳細設定でユーザ環境設定で IDF_PATH を追加します。

新規ボタンで IDF_PATH を追加し

C:\develop\esp\msys32\home\[ユーザー名]\esp\esp-idf

を設定します。できたらいったん mingw32 ターミナルを exit やウインドウの☒で落として再び、

C:\develop\esp\msys32\mingw32.exe

をダブルクリックして再起動し設定を反映させます。

$ echo $IDF_PATH
# 設定した値が取れれば OK!
C:\develop\esp\msys32\home\hirom\esp\esp-idf

M5Stack の場合はこの後は飛ばし、手順「M5Stack テンプレート のコピー」まで進んでください。

esp-idf 開発の テンプレートとなる hello world ソースファイルをコピー

引き続き mingw32 シェル上で、テンプレートとなるソースファイルをコピーして開発の準備をします。

$ cd ~/esp
$ cp -r $IDF_PATH/examples/get-started/hello_world .
$ cd hello_world
$ make menuconfig

make 設定のメニューが表示されますのであれば必要な設定をします。(ここでは Component Config > ESP32-specific > CPU frequrncy で CPU クロックを 160MHz から 240MHz に変えてみました)

<Exit> で終了させるとプロジェクトフォルダーの sdkconfig ファイルが更新されますのでいったんここでコンパイルしてみます。 sdkconfig 更新後のコンパイルはフルコンパイルになりますので少し時間がかかります。

$ make -j4
# エラーがでなければコンパイル成功
To flash all build output, run 'make flash' or:
python /mnt/c/develop/esp32/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyS3 --baud 115200 --before default_r
eset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 /mnt/c/develop/esp32/hello_world/build/b
ootloader/bootloader.bin 0x10000 /mnt/c/develop/esp32/hello_world/build/hello-world.bin 0x8000 /mnt/c/develop/esp32/hello_world/build/partit
ions_singleapp.bin

また、menuconfig では使うコンポーネントの選択を Component config 項から行うことができます。もしコンパイル時にヘッダーファイルが足りないといったエラーがでた場合は、プログラムで使っているコンポーネントを有効にします。

これらの設定も sdkconfig ファイルに保存されます。

Arduino Core を使わずに esp-idf のみで開発する方はこの手順で終了です。この後は飛ばし、手順「プログラムの転送」まで進みます。

Arduino Core コンポーネントの追加

ESP32 で Arduino Core を使う場合は、プロジェクトに components フォルダーを作成し Arduino Core コンポーネントを追加します。

To use as a component of ESP-IDF

$ cd ~/esp/hello_world
$ mkdir -p components
$ cd components
$ git clone -b 1.0.0 https://github.com/espressif/arduino-esp32.git arduino
$ cd arduino
$ git submodule update --init --recursive
$ cd ../..
$ make menuconfig

git clone で指定している 1.0.0 が Arduino Core for the ESP32 のバージョンになります。例えば esp-idf の導入で v3.2.3 を指定した場合は 1.0.4 などとなります。

もし後述の make が通らないようであれば esp-idf と Arduino Core の依存バージョンが不整合していますので最新の情報を確認してみてください。

Arduino Core コンポーネントを追加すると menuconfig に「Arduino Configuration」が追加されますので「Autostart Arduino setup and loop on boot」を enable にします。プログラムを loop/setup の Arduino 構成でかくことができるようになります。

また、デフォルトの sdkconfig であると Flash の容量が 2M 設定になっているので「Serial flasher config」から「Flash Size」を 4M に変更します

最後に Arduino Core で C++ の例外ハンドリングに依存しているソースがありますので設定を有効化します。「Compiler Options」から「Enable C++ Exceptions」

exit して menuconfig を終了させ sdkconfig を保存します。

Arduino Core を使った場合の main/hello_world_main.cpp は次のようになります。

ESP32 のテンプレートは hello_world_main.c と C になっていますので .cpp にリネームします。実ファイルが

C:\develop\esp\msys32\home\[ユーザ名]\esp\hello_world

にありますので任意の Windows 操作でリネーム、ファイルの編集を行います。

#include "Arduino.h"

void setup() {
    Serial.begin(115200);
}

void loop() {
    Serial.println("loop");
    delay(1000);
}

ソースファイルを変更後 make してみてコンパイルが通れば成功です。(Arduino Core が大きいので初回コンパイルには結構時間がかかります)

$ make clean # 手順の流れで ESP32 でコンパイルしているのでいったんクリーン 
$ make -j4
# エラーがでなければ OK!
To flash all build output, run 'make flash' or:
python /home/hirom/esp/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0xe000 /home/hirom/esp/hello_world/components/arduino/tools/partitions/boot_app0.bin 0x1000 /home/hirom/esp/hello_world/build/bootloader/bootloader.bin 0x10000 /home/hirom/esp/hello_world/build/hello-world.bin 0x8000 /home/hirom/esp/hello_world/build/default.bin

Arduino Code のセットアップはこの手順で終了です。「プログラムの転送 」まで進みます。

M5Stack テンプレートのコピー

M5Stack の場合は、M5 が事前に準備してくれている git リポジトリをクローンすることで Arduino Core と M5Stack-IDF コンポーネントが入った hello world テンプレートプロジェクトを得ることができます。

$ cd ~/esp
$ git clone --recursive https://github.com/m5stack/M5Stack-IDF.git
$ mv M5Stack-IDF/ hello_m5stack # 分りやすい名前にリネーム
$ cd hello_m5stack
$ make -j4
# ウイザードでいろいろ質問がありますが全て ENTER で OK
# コンパイル完了後、エラーがでなければ OK!
To flash all build output, run 'make flash' or:
python /home/hirom/esp/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 115200 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 /home/hirom/esp/hello_m5stack/components/arduino/tools/partitions/boot_app0.bin 0x1000 /home/hirom/esp/hello_m5stack/build/bootloader/bootloader.bin 0x10000 /home/hirom/esp/hello_m5stack/build/app-template.bin 0x8000 /home/hirom/esp/hello_m5stack/build/default.bin

M5Stack-IDF のリポジトリの Arduino Core 及び M5Stack ライブラリーは最新に追従していないようです。 esp-idf 3.1 より新しい esp-idf を使う場合は、make する前に以下のように依存コンポーネント(Arduino Core 及び M5Stack ライブラリー)をアップデートします。

例えば esp-idf v3.2.3 を指定した場合はそれぞれ 1.0.4 / 0.2.9 となります。

$ cd components/arduino
$ rm -Rf libraries/BLE/
$ git checkout 1.0.4 --recurse-submodules
$ cd ../m5stack/
$ git checkout 0.2.9 --recurse-submodules
$ cd ../..
$ make menuconfig # 質問は全てデフォルト(enter)で設定
# menuconfig から以下を有効化
Component Config -> mbedTLS -> TLS Key Exchange Methods -> 
  [*] Enable pre-shared-key ciphersuits
    [*] Enable PSK based ciphersuite modes
Component Config -> Bluetooth
  [*] Bluetooth
# menuconfig を終了させビルド
$ make -j4

初回コンパイルは少し時間がかかります。

プログラムの転送

コンパイルしたプログラムをマイコンへ転送してみます。

ESP32 Devkit もしくは M5stack を USB で PC に接続すると Windows 10 であれば自動的に認識して COM ポートが増えます。

COM ポート番号を覚えて make menuconfig から Serial flasher config でポートを設定します。

保存したら準備完了です。次のコマンドでプログラムの転送後リセットがかかりプログラムが動作します。 ハローハッピーワールド!

$ make -j4 flash monitor
# フラッシュ後起動すれば OK!
Flashing binaries to serial port COM3 (app at offset 0x10000 )...
esptool.py v2.6
Serial port COM3
Connecting....
Chip is ESP32D0WDQ6 (revision 1)

なお、起動後のシリアルモニターは ctrl + ] で停止することができます。

VS Code によるソースの編集とコンパイル

ここまでの手順でコンパイルとフラッシュができるようになりましたので、これを Visual Studio Code でソースの編集とともにできるようにします。

VS Code インストール後、拡張機能「ms-vscode.cpptools」を導入します。

次に編集したいプロジェクトをフォルダー指定で VS Code で開きます。この手順では M5Stack の手順つくったフォルダーを指定しています。

C:\develop\esp\msys32\home\[ユーザ名]\esp\hello_m5stack

プロジェクトフォルダーに上のように .vscode/settings.json を新規作成しワークスペース設定を次のようにします。

.vscode/settings.json
{
    "terminal.integrated.shell.windows": "C:\\develop\\esp\\msys32\\usr\\bin\\bash.exe",
    "terminal.integrated.env.windows": {
        "MSYSTEM": "MINGW32",
        "CHERE_INVOKING": "1"
    },
    "terminal.integrated.shellArgs.windows": [
        "--login"
    ],
    "C_Cpp.intelliSenseEngine": "Default"
}

設定後、統合ターミナルを開くと mingw32 がターミナルで使えるようになります。初回起動時は変更の可否を聞いてきますので「許可」して、再度統合ターミナルを起動し直してください。

mingw32 窓と同じ操作ができるようになりますので、make flash monitor してコンパイルからのフラッシュが VS Code 上からできるようになります。

次に C/C++ のコード補完ができるように 拡張機能「ms-vscode.cpptools」を設定します。先ほどつくった settings.json と同じ階層に c_cpp_properties.json を新規作成し次のように設定します。

.vscode/c_cpp_properties.json
{
    "configurations": [
        {
            "name": "ESP32",
            "includePath": [
                "${workspaceRoot}/**",
                "${IDF_PATH}/components/**",
                "${IDF_PATH}/../../../../opt/xtensa-esp32-elf/**"
            ],
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

設定後ソースファイル(main/main.cpp)を開くとソースコード補完などができるようになります。

portTICK_PERIOD_MS などのビルド時に生成される一部の define が vscode にエラー申告される場合はソースコード上部に以下のコードをいれると良いでしょう。(__INTELLISENSE__ 定数は vscode のインテリセンス実行時のみ設定されます)

#ifdef __INTELLISENSE__
#include "build/include/sdkconfig.h"
#endif

ここまでで環境構築はおしまいです。

さて、便宜上プロジェクトフォルダーを mingw32 配下

C:\develop\esp\msys32\home\[ユーザー名]\esp

において作業を進めましたが、設定完了後は Windows 上の任意の位置に配置できますのでお好みの場所に移したりコピーしたりしてください。(なお、フォルダーを移した場合はいったん make clean した後 make してください)

ESP32/M5Stack の資料

開発系のオフィシャル資料は次から参照することが出来ます。

  • ESP-IDF Programming Guide – esp-idf プログラミングガイド。ページトップが lasted バージョンのドキュメントになっていますので、必ず画面左下の選択が stable(お使いのバージョン) になっていることを確認してください。(結構 API 変わっています)
  • ESP32 Technical Reference Manual – ESP32 ハードウェアに直接アクセスする場合など、EPS32 そのものについて記載があるテクニカルマニュアルです。
  • Arduino Core for the ESP32 – ESP32 特有のドキュメントは見つけられなかったのですが、component/arduino の中にソースといくつかの example がありますのでなんとかなるかと思います。 🙂
  • M5Stack Arduino API – M5Stack 特有の LCD やスピーカーなど拡張された便利な API の記載があります。

ESP32 はドキュメントがしっかりしていて使いやすいですね。

てなわけで hello happy coding!


Windows でビルドの速度が遅い件

当初 Windows 上の WSL(Windows Service for Linux)を使い Linux 用のツールチェインを使っていたのですが、コンパイルがちょっと待てないくらい遅いので普通の Windows 向けツールチェインを使うように変更しました。

また、WSL から Windows のツールチェインに変更してもどうにも Linux と比較してコンパイルが遅いので、さくさく開発を求める方は生か仮想の Linux 上で作業するといいかもです。なお、現在開発版(v4.0)の esp-idf には新 Windows のツールチェインが提供されており、将来的にコンパイル速度は恐らく今後改善されるのではないかと思います。

現時点、本記事では以下の文書を参考に make 時に -j4 オプションをつけて CPU コアを活用してコンパイル時間を改善した手順としました。(8コアの方は -j8 にしてください) Windows Defender をお使いの場合、プロジェクトフォルダを監視から外すのも有効なようです。

[Advice] Boost ESP-IDF Compile time on Windows 10

I have been building ESP-IDF projects for a while now, and as many of you I think have noticed, compile time on Windows 10 is VERY slow.

I just want to share some steps i have done to boost ESP-IDF project compile time on Windows 10.

関連

YM2612 + SN76489 メガドライブ音源を ESP32 で鳴らす (SPI接続編)

メガドライブのゲームミュージックをどうしても今再び実チップで鳴らしてみたい…。DNA がそうしろと言っているのだからしょうがない。

あれこれ部品を集めてがんばっておりましたが、なんとか PCM 付きで YM2612(FM音源) と SN76489(PSG音源)を発声させることができましたので、顛末を少しずつブログに書いていきたいと思います。

ハードウェアは次のように構成しています。

  • YM2612 + SN76489(メガドライブ搭載音源)
  • ESP32 DevkitC(制御用マイコン)
  • MCP23S17(SPI I/Oエクスパンダー)
  • LTC6904 * 2(I2C クロックジェネレーター)

作成中のものでソースコードも未整理、まだ回路図も書いていませんが(結線だけ README.md にメモしてあります) github に一式コミットしてあります。一応、現段階のものでフルスピードで PCM 付き .vgm が再生できています。:)

https://github.com/h1romas4/esp32-genesis-player

構成ですが、ESP32 マイコンは GPIO が少ないのと、将来的に音源を増やしたり、PC からも音源制御をしたいと考え、SPI 制御の I/Oエクスパンダー MCP23S17 を使ってシリアルパレラル変換を試みています。

音源制御のマイコン部分は、実際にゲームから送信された音源制御コマンドを44100Hz でサンプリングして保存した VGM Format 形式を事前に準備して、マイコンからデーターを読み取り音源に送信する形式をとっています。

さて、このシリーズの最初の難関は SPI MCP23S17 を使って 44100Hz(22us) に間に合う形で音源にコマンドを送りつけることで、最初はどうやっても間に合わず途方にくれてしまいした。。

esp-idf SPI Master Driver の spi_device_transmit でコマンドを送信すると、50us 程度次のコマンドを送るまでに間隔があいてしまいます。いきなり企画倒れか!がびーん。

これはどうやら SPI Master Driver が FreeRTOS マルチタスクに対応しており、処理に mutex と割り込みを使っていてその時間が乗っかってきているという事情のようです。

このため杓子定規に音源制御シーケンスを、YM2612 データーバスをセットして、CS と WR を落として…というふうに MCP23S17 に送ると、音楽がものすごいゆっくりに…。かつ YM2612 は 6ch 目を PCM に割り当てることができ、PCM の大きいデーターをひっきりなしに送信しますので、PCM を活用した楽曲ではまったく太刀打ちできず。

ということで、まずは音源制御のコマンドをなるべくまとめて送信すべく、MCP23S17 のデーターシートを読んでいたところ、良さそうなモードを発見。

特殊なモード(IOCON.BANK = 0のバイトモード)にすると、関連付けられたA/Bレジスタペア間でアドレスポインタが切り換わります。例えばBANKビットをクリアし、アドレスポインタをアドレス12h (GPIOA)またはアドレス13h (GPIOB)に初期設定した場合、アドレスポインタがGPIOAとGPIOBとの間で切り換わります。アドレスポインタは最初レジスタペアのどちらのアドレスを指していてもかまいません。

MCP23S17 の I/O は GPIOA と GPIOB の 8本ずつに分かれていますが、これらに ひとつのSPI シーケンス中にコマンドを連続で送ると A->B->A->B… とくるくる回ってくれます。

MCP23S17 と YM2612 の接続を、GPIOB に YM2612 データバスに、GPIOA に制御系の足を接続として、次のようにコマンドをおくる用に修正。赤い線の中がひとつのSPI シーケンスのイメージです。

(下の図はプロトでつくった I2C 版 MCP23012 で行ったものなので速度は無視してください… I2C 版でも同じ動きになります)

最初(0xB1)にアドレスバス(GPIOB)をセットし、次に CS/WR を落として同時にA0/A1 を設定(0x04)、次は GPIOB に戻ってしまうので最初と同じデーターをセットし、最後に CS/WR を上げて完了です。ひとつの音源コマンドを、ひとつのSPI シーケンスで送れるようになりました。

また、YM2612 より SN76489 は遅く、このような最短シーケンスだと音源が反応できなかったので(WR を下げてから上げるまで 12us くらいいるみたいです)ダミーの同一データーをコマンドに挿入して時間待ちし、こちらも同じようにひとつの SPI シーケンスで送れるようにしています。

ということで、これにて 4つの SPI シーケンスがいるところをひとつにまとめられシーケンス間隔問題を 1/4 削減できましたが、依然としてまとめられないシーケンス間に 50us 程度間隔があいてしまう問題は残りました。

いくつか考えて、ダミーデーターを送り続けてシーケンスを繋ぎっぱなしにすればいいじゃないかと思いつきましたがちょっと普通にやるとコマンドのタイミングを取るのが難しそうです。 一応鳴るにはなりましたがテンポがずこっとなりがちでした。。

再び途方に暮れましたが、いろいろ調べていたところ ESP32 本家のサンプルで SPI の液晶コントローラーをつかった NES(ファミコン)エミュレーターがあることを思い出す。

既存のエミュレーター移植なので、最終的にメモリーにレンダリングされた 320x240x8bit という大量データーを 60fps で SPI で転送しているとすれば何かあるハズ…とソースを確認。

https://github.com/espressif/esp32-nesemu/blob/master/components/nofrendo-esp32/spi_lcd.c#L402

かくしてそこで見つけたのは ESP32 の SPI ペリフェラルを直接たたくソースでした。

ESP32 Technical Reference Manual とにらめっこで命令を理解しつつ、はたして…。

1コマンド 7us キター! 😀

YM2612 は実際には 2コマンドで1セットなので 14us になりますが 1サンプルには十分間に合っていますのでこれで大丈夫そうです。(ここもまとめようと思えばできるハズです)

そんなこんなでメガドライブ楽曲が実チップで鳴るところまでごきつけました。嬉しい…。ベアナックル2が奏でられてしばし放心…。

タイムリーなことに、ぼくのゲームミュージック好きの原点にして頂点である古代祐三さんが MUCOM88 音源ドライバーをベアナックル2を含むサンプル曲付きで CC 公開されており、mucom MML を VGM に変換するプログラム mucomMD2vgm を kumatan さんが公開されています。

現時点残念ながら mucomMD2vgm で変換した .vgm はぼくの vgm パーサーだと unknown のコマンドがでてしまってまだ完全に発声できないのですが、おそらく vgmpos ずれだと思うのでちょっと直してみたいと思います。

他にも回路図をかいたり、基板をおこしたり、クロックを安定させたりまだ仕事が残っていますので、引き続きこの未来ガジェット製作をやっていきたいと思います。:D

関連

M5Stack/ESP32 でメガドライブ音源をエミュレート

勉強会で見せていただいて気になっていた M5Stack を買ってみました。  🙂

内蔵スピーカーもついていますので hello world がてら、 PSG 音源をエミュレーションしてみようと始めたところ、なかなか鳴ってくれなくて苦労しました…

「エンディングI/SORCERIAN/Copyright© Nihon Falcom Corporation」

追記。FM音源 + YM2612 の DAC もエミュレートできて無事メガドライブ(GENESIS/MEGADRIVE)音源になりました 😀

勢いがあるうちに忘れないようメモしておきます。

ソースは github で公開しています。(力尽きたので不出来、手抜きの部分は大目に見てください)

https://github.com/h1romas4/m5stack-synth-emulation

GENESIS/MEGADRIVE(YM2612+SN76496) VGM player on ESP32/M5Stack

開発環境

PSG エミュレーターを移植するのに gcc ツールチェインを使いたかったので開発環境は(Arduino ではなく) ESP-IDF を使いました。 リンク先の通りに SDK ファイルを展開して、 $IDF_PATH を設定してあげれば OK です。

今回は Linux を使ってやりましたが、Windows 10 の場合は WSL を使うと楽かもしれません。(未確認)

M5Stack ESP-IDF 用の雛形が github に提供されていますので、手順の通り git clone して make menuconfig してシリアルを設定して、make flash monitor すれば hello world できると思います。

https://github.com/m5stack/M5Stack-IDF
To use as a M5Stack component of ESP-IDF

ちなみに monitor の抜け方は ctrl + [ です。

自分がやったときは Makefile にバグがありいきなり、make flash できないという不具合に遭遇しています。

Detected overlap at address: 0xe000 for file:

これは Makefile にパッチする次のワークアラウンドで解消できました。(m5stack リポジトリは既に修正済みだと思います)

(esp-idf) make flash fails due to the overlapping of offset addresses #1724

https://github.com/espressif/arduino-esp32/issues/1724

static heap の上限

static heap をたくさん使おうとすると

region `dram0_0_seg' overflowed by 67900 bytes

とリンクエラーになります。 がびーん。

どうやら static heap にサイズ上限が決められているようなので、static で取得するのをやめて動的に heap_caps_malloc() して回避しました。

build/ 下に app-template.map というファイルができていて、static heap のサイズが分かります。(この場合 DECAY_TO_ATTACK 変数に 0x4000 取られています)

.bss._ZL15DECAY_TO_ATTACK
0x000000003ffbd1cc     0x4000 /home/..../libsynth.a(ym2612.o)

また、make size すると次のように現在のメモリーサイズが表示できます。

Total sizes:
 DRAM .data size:    6488 bytes
 DRAM .bss  size:  118632 bytes
Used static DRAM:  125120 bytes (  55616 available, 69.2% used)
Used static IRAM:   36140 bytes (  94932 available, 27.6% used)
      Flash code:  181978 bytes
    Flash rodata:   64104 bytes
Total image size:~ 288710 bytes (.bin may be padded larger)

static heap を回避しても malloc で失敗することもありますので、あちこち移動させてうまいことメモリーに載せていきます。 なお、heap_caps_get_free_size() で動作中の残りメモリーを知ることができるようです。

内蔵 DAC の鳴らし方

なかなか鳴らなくて PSG エミュレーションが悪いのか DAC の操作が悪いのかで苦労しました…。

I2S 経由の DAC は次のようなイメージで鳴ると思います。

i2s_config_t i2s_config = {
     .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
     .sample_rate = SAMPLING_RATE,
     .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
     .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
     .communication_format = static_cast(I2S_COMM_FORMAT_I2S_MSB),
     .intr_alloc_flags = 0,
     .dma_buf_count = 16,
     .dma_buf_len = 512,
     .use_apll = false,
     .tx_desc_auto_clear = true, // esp-idf 3.2 で追加
     .fixed_mclk = 0
};

i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, NULL);

size_t bytes_written = 0;
uint16_t buf[2];
buf[0] = 0x0000; // right sample
buf[1] = 0x0000; // left  sample
i2s_write((i2s_port_t)i2s_num, buf, sizeof(uint16_t) * 2, &bytes_written, portMAX_DELAY);

buf には符号付き符号なしリトルエンディアンのステレオサンプリングを渡します。(内蔵 DAC の場合は符号なしでした… どうにも音が割れると思ったら…)上のサンプルソースは 1フレームですが、この config で 4096 フレーム * 2(ステレオ)が一気に渡せました。

flash への任意データーの書き込み

partitions.csv ファイルをつくり make menuconfig (sdkconfig) で指定してあげることにより、内蔵の flash メモリーの任意の位置に esptool.py でバイナリを書き込みプログラムから read できます。

esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 115200 write_flash -fs 4MB 0x211000 "バイナリファイル"

プログラムから読み込むときは、

esp_partition_find_first() して esp_partition_mmap() でポインターがもらえます。便利。 🙂

その他

  • 普通に printf すると make flash monitor するだけで実行してコンソール出力されるので嬉しい。
  • プログラムが大きくなったせいか途中から “Make error of dangerous relocation” と怒られてリンクできなくなりました。 CFLAGS に -mlongcalls をつけることで解消しました。
  • M5STACK の内蔵スピーカーはそこそこ音が割れます。 PSG くらいがちょうど良さそうです。
  • 実はがんばって FM音源も移植したのですが、いい音で鳴りませんでした… さすが ESP32 だけあって処理速度は十分です。 ソースに残骸がありますので、気になる方は鳴らしてみてください…)
  • FM音源(YM2612)も鳴らすことができました!
  • YM2612 + SN76489 + YM2612 の内蔵 PCM をエミュレートしたら CPU の 1コアを使い切っているようです。

以前から充電できるポケステがあったらいいのになぁと思っていたのですが、ちょうどでてきてくれた M5Stack さん。 久しぶりに夢中になってしまいました。

今回の hello world で一通りの困りそうなことは踏んだ気がしますので、引き続き何か作っていきたいと思います。 🙂

関連