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

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

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

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

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

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

WSL から Windows のツールチェインに変更してもどうにも Linux と比較してコンパイルが遅いので、さくさく開発を求める方は生か仮想の Linux 上で作業するといいかもです。なお、現在開発版(v3.2)の esp-idf にはプレビューとして CMake ベースのWindows のツールチェインが提供されており、将来的にネイティブの cmake/make/gcc 構成になるようなのでコンパイル速度は恐らく今後改善されるのではないかと思います。

本記事では以下の文書を参考に 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.

ESP32/M5Stack の開発系の構成

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

  • xtensa-esp32 – ツールチェイン(gccクロスコンパイラ+転送ツール等) Windows/Linux/macOS 用に分かれています。
  • esp-idf – ESP32 用のフレームワーク(ライブラリ)
  • 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 開発で必須なのは上の2つです。

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

ツールチェインの導入

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

Windows 上の任意の場所に開発系の作業フォルダー(ここでは C:\develop\esp)を作成しダウンロードしたツールチェインの .zip を展開します。執筆時点では esp32_win32_msys2_environment_and_toolchain-20180110.zip でした。

Toolchain Setup

展開できたら 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 にあり事前にパスが通っています。

esp-idf の導入

引き続き mingw シェル上で次のコマンドから esp-idf を導入します。(こちらも最新情報は次のリンクを確認してください)

Get ESP-IDF

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

git clone で指定している v3.1.3 が esp-idf のバージョンになります。ディレクトリ esp-idf が作成され esp-idf が格納されます。

次にソースファイルの Makefile で指定されている IDF_PATH 環境変数に上記で得られた 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

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

Arduino Core テンプレートのコピー

ESP32 で Arduino Core を使う場合は、次のコマンドでプロジェクトに Arduino Core コンポーネントを追加することができます。上記で配置した ESP32 テンプレートプロジェクトに 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 のバージョンになります。もし後述の make が通らないようであれば esp-idf と Arduino Core の依存バージョンが不整合していますので最新の情報を確認してみてください。

この手順では esp-idf v3.1.3 と Arduino Core 1.0.0 の組み合わせを使っています。 Arduino Core 1.0.1 は esp-idf v3.2(現在rc) に依存するため v3.1.3 だとコンパイルエラーとなります。将来の手順は esp-idf v3.2 と Arduino Core 1.0.1 以降の組み合わせになるでしょう。

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

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

プログラムの転送

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

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 で開きます。(ここでは先ほどつくった C:\develop\esp\msys32\home\[ユーザ名]\esp\hello_m5stack フォルダを開いています)

プロジェクトフォルダーに上のように .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 を新規作成し次のように設定します。

{
    "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!

関連

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 は次のようなイメージで鳴ると思います。

static const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number

i2s_config_t i2s_config = {
    .mode = static_cast&amp;lt;i2s_mode_t&amp;gt;(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
    .sample_rate = SAMPLING_RATE, // #define SAMPLING_RATE 44100
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = static_cast&amp;lt;i2s_comm_format_t&amp;gt;(I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = 0,
    .dma_buf_count = 16,
    .dma_buf_len = 512,
    .use_apll = false,
    .fixed_mclk = 0
};

i2s_driver_install(i2s_num, &amp;amp;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, &amp;amp;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 で一通りの困りそうなことは踏んだ気がしますので、引き続き何か作っていきたいと思います。 🙂

関連