🎵

Seeed Studio XIAO RA4M1 でつくる電子オルゴール

2025/01/28に公開

変更履歴

  • rev.0 2025/01/28 新規作成

1. はじめに

Arduino UNO R4 Minima 用のブートローダーのソースコードを調べて記事にまとめています。

前回の記事:
Arduino UNO R4 Minima用のブートローダのソースコードを読む ( SystemInit 編 )

次回はクロック設定についてまとめる予定ですが、その前に少し休憩して、今回はブレッドボードを使った電子工作について紹介したいと思います。

MMLでメロディをつくる電子オルゴール

この記事では、電子工作の定番である電子オルゴールの作り方を紹介します。今回作成する電子オルゴールは、MML(Music Macro Language)を使用してメロディを作成できるようにします。

https://ja.wikipedia.org/wiki/Music_Macro_Language

メロディの制御にはマイコンボードを使用します。今回は、Arduino UNO R4 と同じマイコンを搭載した Seeed Studio XIAO RA4M1 を使用します。

https://jp.seeedstudio.com/Seeed-XIAO-RA4M1-p-5943.html

このマイコンボードには充電回路が組み込まれており、背面にあるバッテリー接続用のパッドに接続したバッテリーを USB 端子経由で充電することができます。今回はブレッドボードによる試作のため使用しませんでしたが、USB 端子を外に出しておけば簡単に充電できるため、小さく箱詰めしやすいと感じました。

PSG 音源

作成する電子オルゴールは、三和音矩形波 +ノイズ音を出力できる PSG 音源を使用します。

https://ja.wikipedia.org/wiki/Programmable_Sound_Generator

ただし、今回は実際の PSG 音源 IC は使用せず、次のエミュレーターを使用します。

https://github.com/digital-sound-antiques/emu2149

具体的には、この PSG エミュレーターを XIAO RA4M1 に組み込み、RA4M1 で演算した波形を DAC ピンから出力するようにします。

演奏デモ

https://youtu.be/49UWQPOqp3Q

2. 用意するもの

部品

下記の部品を使用して電子オルゴールを作成します。

No. 部品名 型番 備考
1 マイコンボード XIAO-RA4M1 スイッチサイエンスで購入。
2 アンプモジュール AE-PAM8012 秋月電子通商で購入。
3 ピンヘッダー PH-1x40SG 秋月電子通商で購入。
4 半固定ボリューム (10 kΩ) 3362P-1-103LF 秋月電子通商で購入。
5 スピーカー (8Ω) MSI28-12R 秋月電子通商で購入。
6 ブレッドボード - -
7 ジャンパーワイヤ - -

道具

No. 道具名 用途
1 はんだとはんだごて ピンヘッダーをマイコンボードとアンプモジュールにとりつけるために使用。
2 プラスドライバー 半固定ボリュームのワイパー調整で使用。
3 パソコン マイコンにプログラムを書き込むために使用。本記事では Windows 10 PC を使用。
4 USB Type-Cケーブル マイコンにプログラムを書き込みおよび電源供給で使用。

3. 開発環境の構築

Arduino IDE のセットアップ

電子オルゴールのプログラムは Arduino IDE を使用してマイコンボードに書き込みます。Seeed 社の下記サイトの Getting Started に沿って Arduino IDE のセットアップを行います。

https://wiki.seeedstudio.com/getting_started_xiao_ra4m1/

ソースコードの取得

以下の git コマンドを実行して、電子オルゴールのソースコードを取得します。

git clone --recursive https://github.com/nyannkov/electronic-music-box

https://github.com/nyannkov/electronic-music-box

この中にelectronic-music-box.ino という名前のスケッチがあります。このファイルを Aduino IDE で開きます。

次に PSG 音源用の MML デコーダーを取得します。まず、起動したArduino IDE のライブラリマネージャーを開きます。検索ボックスが表示されるので、そこに Psgino と打ち込み、インストールを実行します。

インストールが完了したら、XIAO RA4M1 と PC を接続して Arduino IDE で プログラムのビルドと書き込みを実行します。

完了したら、マイコンボードを PC から取り外し、電子オルゴールの組み立てに移ります。

3. 電子オルゴールの組み立て

ピンヘッダーの取り付け (アンプモジュール)

まず、アンプモジュールにピンヘッダーを取り付けます。下図の左の部品がアンプモジュールです。ピンヘッダは右のように4ピンがつながった状態にカットしたものを二つ作ります。

下図のようにピンヘッダをアンプモジュールに取り付けて、はんだで固定します。

ピンヘッダーの取り付け (マイコンボード)

アンプモジュールの場合と同様に、マイコンボードとピンヘッダーをはんだ付けします。ピンヘッダーは付属のものを使用します。

ブレッドボードの配線

全体

マイコンボード

ブレッドボードの + レーンには 3V3 端子 を、- レーンには GND 端子を接続します。また、DAC出力ピンの P014 (A0) は、半固定ボリュームの 3 番ピンに接続します。

半固定ボリューム

1 番ピンを - レーン に接続し、2 番ピンを アンプモジュールの IN+ 端子に接続します。また、電源を入れた時にメロディが大音量で鳴らないように、プラスドライバーでワイパーを反時計回りに止まるまで回します。

アンプモジュールとスピーカー

IN- 端子と GND 端子を - レーンに接続します。また、+ V 端子は + レーンに接続します。 スピーカーは SPK + , - 端子に接続します。

4. 動作確認

PC と XIAO RA4M1 を USB ケーブルで接続し、半固定ボリュームのワイパーを少しずつ時計回りに回します。スピーカーから桃太郎のメロディが流れたら完成です。

5. 遊び方

メロディの作成と変更

MML は char 型配列として コード内で記述できます。メロディの変更は、記述した MML へのポインタを Psgino クラスオブジェクトの SetMML メソッドで登録することで行えます。

例えば、以下のMMLを SetMML メソッドで登録すると、単音でドレミファソラシドが鳴ります。

const char mml_1[] ="CDEFGAB>C";

また、次のように記述するとCメジャーコードが鳴ります。

const char mml_2[] ="C,E,G";

少し複雑ですが、以下のように記述すると演奏デモ動画の冒頭で流れたドラム音が鳴ります。

const char mml_3[] = 
    "T120 V15"
    "$E1$A0$H40$D100$S65$F500$R100"
    "$P-720 I15"
    "[2" "O2 A8 R8 H8 A8 A8 | R8 H8 R8" "] $P-1080 O5 A16A16 O4A16A16 O3A16R16"
    "$F2000 I3 H1"
;

MML の文法についてはこちらのページにまとめています。

MML の演奏と外部機器の同期

この MML デコーダーでは、MML 内にユーザー定義のコールバック処理を登録することができます。これにより、MML の中で外部機器の制御タイミングや内容を記述し、演奏と外部機器の動作を同期させることができます。
ただし、メロディによっては MML に処理を直接記述するのが難しい場合があります。その場合は、以下のように Psgino クラスのオブジェクトをもう 1 つ作成し、外部機器制御専用のチャンネルを作成することができます。

...

const char mml[] = "T120L4[0 O4 CDEFGAB>C]";
const char mml_ext[] = "T120L4[0 @C(0x01) R4]";

void user_callback(uint8_t ch, int32_t param) {
    // ch: 呼び出し元の PSG チャンネル ( 0-2 )
    // param: @Cコマンドのパラメータ ( 今回の場合は 0x01 固定 )
}

void setup() {
...

    psgino.Initialize(psg_write, PSG_EMU_CLOCK, MML_PROC_RATE);
    psgino.SetMML(mml);
    psgino.Play();

    // このチャンネルではPSG音源を操作しないため、psg_write は nullptr を指定。
    psgino_2.Initialize(nullptr, PSG_EMU_CLOCK, MML_PROC_RATE);
    psgino_2.SetMML(mml_ext);
    psgino_2.SetUserCallback(user_callback);
    psgino_2.Play();
...
}

void loop() {
    unsigned long time_now = micros();

    if ( (time_now - time0_mml) >= EXEC_CYCLE_MML ) {
        time0_mml = time_now;
        psgino.Proc();
        psgino_2.Proc();
    }
}

Discussion