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

Arduino で YAMAHA YM2151 を VGM ファイルで演奏させる

1990年代の多くのアーケードゲーム機に搭載されていた音源チップ YAMAHA YM2151 を Arduino からこの手でどうしても発声させてみたい。

電子工作を始めて Lチカもそこそこに製作を始めた”未来ガジェット”のひとつです。

結構前につくったものでしたが、どのように製作したか忘れぬよう github に Arduino スケッチと参考させていただいた資料・回路図のリンクを掲載してみました。

Play back the VGM format file with Arduino. (only YM2151)

https://github.com/h1romas4/arduino-vgmplayer

本スケッチでは VGM Format と呼ばれる、音源チップに流すデーターダンプ形式を、そのまま YM2151 に write して発声させる方式を採っています。

このことから Arduino のスケッチは 100行ほどの単純なものになり、まずは発声させてみたい方にはちょうど良い規模感のプログラムになっています。たとえば PSG を同時に鳴らしたい、なんて時も数行足してもらえれば実装できると思います。

演奏データとなる VGM ファイルは mml2vgm を使わせて頂き MML (Music Macro Language)で作成しました。MML の書き方は体が覚えていることでしょう 😀

https://github.com/kuma4649/mml2vgm

[概要]
このツールは、ユーザーが作成したMMLファイルを元にVGM/XGMファイルを作成します。

[機能、特徴]
[VGM]
・メガドライブ2台分の音源構成(YM2612 + SN76489 + RF5C164)*2にそったVGMを生成します。
(他にYM2151,YM2203,YM2608,YM2610B,SegaPCM,HuC6280に対応しています。)

github 上のサンプルは次のようにしてみました。(カエルの歌の3声輪唱です)

assets/vgmsaple.gwi

'@ M 110
   AR  DR  SR  RR  SL  TL  KS  ML  DT1 DT2 AME
'@ 022,005,000,004,005,041,000,001,005,000,000
'@ 016,008,008,008,002,000,001,002,005,000,000
'@ 031,018,000,004,010,044,000,008,009,000,000
'@ 031,009,007,008,002,003,001,001,009,000,000
   ALG FB
'@ 004,007

'X6 T160@110l4 r1r1r1r1 cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc
'X7 T160@110l4 r1r1     cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc
'X8 T160@110l4          cdefedcrefgagfercrcrcrcrc8c8d8d8e8e8f8f8edc

このテキストファイルを mml2vgm により .vgm 化して、Arduino のスケッチから PROGMEM で Flash メモリーに書き込んでいます。(music/vgmsample.h

曲の大きさによっては Arduino の 32KByte Flash に載りきらないため、作例では EEPROM を接続し書き込んだデーターを演奏させることもしています。(ちなみに EEPROM を単純に 1Byte 読みすると速度が追いつかなかったため、先読みのリングバッファのような実装を入れました)

ハードの方は Arduino UNO のシールドで実装しています。(まずはブレッドボードで動作を確認した方が良いと思います)

足の数に対して基板が結構小さく、おまけに全部ビニール線で繋いだため結構大変でした。。とはいえ、初心者の自分が、2日くらいハンダ付けをがんばれば完成しましたので、おそらくみなさまいけるかと思います。(部品は AliExpress で入手しています)

さて完成した機械から奏でられた音色は…

リーディングシュタイナー発動…!!!

苦労した甲斐もありまして、無事タイムリープに成功。 😀

子供の頃からゲームの音楽と言えば FM 音源で、なんとかいい音で聴こうと PC からラインを引っ張り出してステレオコンポに接続して、何度も何度も聴いていたことを思い出します。

この機械は、電源を入れればすぐ楽曲が奏でられますので「古の電子オルゴール」として現在も活躍中です。

Arduino Pro Micro を使ってセガサターンのコントローラーを USB に変換する

Arduino Pro Micro の勉強も兼ねまして、セガサターンのバーチャスティックを USB HID 化して PC に接続できるようにしてみました。

使いました Arduino Pro Micro 互換機はこちらです。

KEYESTUDIO Pro Micro Atmega32U4 5V、ピンヘッダーを交換Pro Micro for Arduino

この機械は SparkFun  社が設計した本家の Arduino には存在しないラインナップで、KeyeStudio のはさらにその互換機になります。 1000円くらいなり。

ということで、まずは Arduino のボードマネージャーに以下の URL を指定して、SpackFun Pro Micro をボードに追加します。

https://raw.githubusercontent.com/sparkfun/Arduino_Boards/master/IDE_Board_Manager/package_sparkfun_index.json

ボードを追加したら書き込み対象の Arduino に、 SpackFun Pro Micro の 5V/16MHz を指定します。 vscode-arduino を使っている場合は、arduino board configuration が次のようになります。

デフォルトが 3.3V/8Mhz になるようですので要注意です。 この指定でもすんなり書き込めてしまうようですが、どんなプログラムを実行しても不明な USB デバイスになってしまう悲しい機械になってしまいます。(ちょっとはまりました…

また、Pro Micro に USB HID になるプログラムを書き込み、実行が始まると USB HID と COM ポートが OS に認識されますが、この際に COM ポートが書き込み時のものと異なるポートに配置され、次回のプログラム書き込みができなくなるケースがあるようです。 Windows であればデバイスマネージャから、どのように認識されたかを確認して、書き込みできない場合は COM ポートの設定を合わせてみてください。

特殊な操作として、起動中の Pro Micro の RST 端子を GND にちょいちょいと 2回落とすとリセットがかかり初期状態の COM ポートが 8秒間だけ現れる機能があります。

8MHz で設定してしまったなど、どうしても書き込みができなくなった場合は、コンパイルから書き込みに入ったあたりでリセットをかけ、出現した COM ポートめがけて Lチカなどを転送すると元に戻ってほっとします。 😀

てなわけで若干癖があるボードですが、無事セガサターンのバーチャスティックを USB 化することができました。

結線やソースコードは github に公開してみました。

Convert Sega Saturn controller to USB HID joypad using Arduino Leonardo / Pro Micro.

https://github.com/h1romas4/arduino-saturn-joystick

ジョイスティックデバイスとして認識したら、Windows では次の画面で動作確認をすることができます。

ボタンキーアサインは手持ちしていました HORI のファイティングスティックmini を真似てつくってみました。

ソースコードの次の部分でボタン番号を指定できますので、好みに変えてみてください。 0,1,2… が画面上 1,2,3… ボタン相当です。(9は存在しないボタン)

uint8_t buttonMap[3][4] = {
    { 4, 9, 9, 9 },     // L  -  -  - 
    { 6, 0, 3, 5 },     // R  X  Y  Z
    { 8, 1, 7, 2 }      // S  A  C  B
};

なるべく入力断面を合わせたり、遅延しないようにつくったつもりですが果たして…

違和感なく動作しているようなので、ザック島に急ぎます。3D プリンターでケースをつくろう。 🙂