M5StackのGrove端子に接続するユニットには、I2Cで接続するもの、UARTで接続するもの、アナログ入出力で行うものがあります。今回は複数のユニットを1つのGrove端子に接続したかったのですが、Unit-SynthのI/FはUARTなので、他のI2Cのものと一緒に接続することができません。そこでI2CからUARTを制御できる変換デバイスを使用することにしました。これならM5Stackからは全てI2Cに統一することができます。
I2C-UART変換 SC16IS740
I2CとUARTの変換には SC16IS740 というICが乗っている I2C-Uart変換基板 を使用しました。このICは文字通りI2CからUARTに、UARTからI2Cにデータを送受信することができます。
M5Stack用MIDIシンセサイザユニット Unit-Synth
Unit-SynthはSAM2695という音源チップが乗ったM5Stack用MIDIシンセサイザユニットです。大きさのわりにしっかりした音が出ます。
Unit-Synthから使用するプログラム
今回作成したプログラムは GitHub にアップしました。
デフォルトではI2C-UART変換基板経由で使用するようになっていますが、Unit-Synth関係の処理はM5Unit-Synthライブラリをしているので、I2CかUARTかを意識しなくても使うことができます。一部を変えるだけでUART接続にできます。
準備:ライブラリの修正
Arduino IDEのライブラリマネージャーからインストールできるSC16IS7X0というライブラリを使うと変数名が競合してしまって、コンパイルエラーになってしまいます。管理が複雑になるので本当はやりたくなかったのですが、SC16IS7X0ライブラリのSerialConfigという変数名を別の名前で置き換えました。
使い方
#define SC16IS750_ADDR 0x9A // I2C to UART変換基板のI2Cアドレス
#define SC16IS750_XTAL_FREQ 7372800UL // I2C to UART基板のクリスタルの周波数 (https://www.switch-science.com/products/6027)
#define UNIT_SYNTH_BAUD_I2C 30720 // Unit-Syhthのボーレートは本来は31250だが出せないので30720にしている
#include <SC16IS7X0.h> // このライブラリはSerialConfigをSCSerialConfigに直接書き換えている(ESP32と競合するため)
SC16IS7X0 sc16is750(SC16IS750_XTAL_FREQ);
#include "SC16IS7X0Serial.h" // SC16IS7X0のラッパークラス
SC16IS7X0Serial i2cuart(sc16is750);
I2C-UART変換基板には7.3728MHzのクリスタルが乗っているので、SC16IS750_XTAL_FREQに指定します。Unit-Synthが受け付けるボーレートは本来31250ボーなのですが、このクリスタルの組み合わせでは31250ボーを出力する事ができません。31250を与えても近似値になってしまい正常に動作しませんでした。今回はUnit-Synthが認識できた30720ボーを採用することにしました。
sc16is750がI2C-UART変換基板のインスタンス、i2cuartがシリアルとして扱えるインスタンスです。M5Unit-Synthライブラリからはi2cuartに対してアクセスします。
以下でI2C-UART変換基板の初期化とボーレートを設定します。
if (sc16is750.begin_I2C(SC16IS750_ADDR >> 1)) {
}
sc16is750.begin_UART(UNIT_SYNTH_BAUD_I2C);
以下はUnit-Synthの初期化をしています。begin()の第一引数は、UART接続の場合は&Serial2を使用しますが、今回はI2C-UART変換をするので&i2cuartになっているのが異なる点です。
#include <M5UnitSynth.h>
M5UnitSynth synth;
synth.begin(&i2cuart, UNIT_SYNTH_BAUD, 13, 14);
逆にこれ以外は同じなので、他のサンプルプログラムとかもそのまま使えるかと思います。音色の設定などの説明は割愛します。
音楽の再生
曲データのフォーマットは独自のものですが、ソースを見れば構造はだいたいわかるかと思います。1つ目の和音の音階と長さ、2つ目…、3つ目…、で1セットで、一応3和音まで再生できます。本当は複雑な曲に対応できるようにしたかったのですが、面倒だったので簡単なものしか再生できません。
uint16_t tempo = 160;
const uint8_t melody[][6] PROGMEM = {
{ NOTE_FS5,8, 0,0, 0,0 },
:
};
SynthUtil music(&synth);
size_t len = sizeof(melody) / sizeof(melody[0]);
music.setMusic(melody, len, tempo); // 楽譜データをセットする
music.playBackground(); // 再生開始
while (music.isPlaying()) { // 再生中なら待機する
}
playBackground()でバックグラウンド再生できるので、再生中に別の処理もできるはずです。一方、play()は再生が終わるまで待ちます。
実はM5GFXでアニメーションを再生しながらUnit-Synthでサウンドを再生するプログラムを作っていたのですが、UART接続の場合、バックグラウンド再生にすると画面が乱れたり、リセットがかかってしまうことがありました。なのでどちらでも対応できる形にしてみました。
動かしてみた
とりあえずこれがやりたかっただけです😅
AliExpressで売っていた安い変換基板で試してみる
スイッチサイエンスさんで販売されているI2C-UART変換基板は高いので、AliExpressでもっと安いのがないか探してみたところ、SC16IS750が乗った変換基板が4ドル前後で販売されていました。さっそく購入して試してみました。昔のAliExpressって注文してから届くまで2か月くらい普通にかかっていたのに、今は1~2週間くらいで来るんですよね。
この変換基板はクリスタルが14.7456MHzなので、以下のように設定しました。
#define SC16IS750_XTAL_FREQ 14745600UL
しかしなぜかUnit-Synthから音は鳴らず…。Teratermに繋げてバイナリモードにして比較したところ、ちゃんと値は取れてるのに認識してくれません。オシロで見ると微妙に波形の幅に違いがあり、ボーレートを調整しましたがだめでした。じゃあいっそのことクリスタルを交換して、ぴったり31250ボーを出せばいいじゃないか!
12MHzのクリスタルに交換してみました。計算上31250ボーが出せるはずです。M5Stackから出力した波形と比べて同じスピードのように見えました。
が、しかし、Unit-Synthは鳴りません。Teratermで見てみるとデータの一部が正常に出力されておらず、おかしな値に化けてしまっていました。リフローで熱を加えすぎて壊してしまったのだろうか…、それならそもそも中途半端に動かないだろうし、他にも変えないといけないところがあったのか…、うーんわからん!まいっか。
とりあえず目的は果たしたのでこれでヨシ!とします