過去に"YAMAHAの音源IC(YMZ294)の使い方の基礎"でPSG(SSG)音源のYMZ294の使い方の基礎を説明しましたが、今回はFM音源の中で一番簡単なYM2413(OPLL)について説明します。
この音源は音色データを内蔵しているので、複雑なパラメータを指定しなくてもすぐに音を鳴らすことができます。
今回もArduino Unoで制御しますが、理解すればmbedでもPICやAVRで直接制御するにせよ同じ方法で使うことができるはずです。
初期処理から基準のA(ラ,440Hz)の音を出力するまでの手順を説明します。
個々のレジスタの情報等の詳細な仕様はアプリケーションマニュアルを参照ください。
http://d4.princess.ne.jp/msx/datas/OPLL/YM2413AP.html
ハードウェア
下の回路図のようにArduinoとYMZ294を接続しています。
Arduino | 2:9 | - | YM2413 | D0:D7 |
Arduino | A0 | - | YM2413 | IC |
Arduino | A1 | - | YM2413 | CS |
Arduino | A2 | - | YM2413 | WE |
Arduino | A3 | - | YM2413 | A0 |
出力はメロディー出力のMOとリズム出力のROと別れてますが、今回はメロディー出力のみ使用します。
出力は小さいので、アンプを別に通すなり、アンプ回路を組むなりしてスピーカーやイヤホン等に接続してください。、
実際に動作させるまでの処理
IOの設定
まず、Arduinoに接続されたピンに対して便宜上名前を付けます。
const byte IC_PIN = A0; const byte CS_PIN = A1; const byte WE_PIN = A2; const byte A0_PIN = A3; const int DATA_PIN[] = { 2, 3, 4, 5, 6, 7, 8, 9 };
setup()内ですべて出力に設定します。
for (int i = 0; i < 8; i++) { pinMode(DATA_PIN[i], OUTPUT); } pinMode(CS_PIN, OUTPUT); pinMode(A0_PIN, OUTPUT); pinMode(IC_PIN, OUTPUT); pinMode(WE_PIN, OUTPUT);
レジスタ書き込みファンクション
音源ICの機能は全てレジスタにアクセスすることで制御できます。
これは同じYAMAHA製のFM音源ICも含めて基本的なことなので、この概念を理解すると他の音源ICもレジスタの仕様をチェックするだけで使えるようになります。
CS_PINがLOWの状態でDATA_PINにバイトデータをセットし、CS_PINをHIGHにするとバイトデータがYM2413に送られます。このときA0_PINがLOWの場合はレジスタのアドレス、A0_PINがHIGHの場合はレジスタに入れる値となります。
すぐにCS_PINピンの状態を変えても反応しないことがあるため、アドレス書き込み時には12us程度、データ書き込み時には84us程度ウェイトを入れます。
この方法でレジスタに値をセットするファンクションを定義します。
void set_register(byte addr, byte value) { // addr digitalWrite(A0_PIN, LOW); digitalWrite(CS_PIN, LOW); for (int i = 0; i < 8; i++) { digitalWrite(DATA_PIN[i], bitRead(addr, i)); } delayMicroseconds(12); digitalWrite(CS_PIN, HIGH); // value digitalWrite(A0_PIN, HIGH); digitalWrite(CS_PIN, LOW); for (int i = 0; i < 8; i++) { digitalWrite(DATA_PIN[i], bitRead(value, i)); } delayMicroseconds(84); digitalWrite(CS_PIN, LOW); }
YM2413の初期化
動作の初めにYM2413を初期化します。
ICをLOWにし、100us程度間をおいてからICをHIGHにするとすべてのレジスタが0でリセットされます。
また今回はレジスタに書き込む程度の操作しかしないため、WEをLOWにしておきます。
リセット後にすぐにレジストを操作しても動作しないことがあるので、最後に1秒程度のウェイトを入れてますが、もっと短くしても大丈夫です。適当に調整してください。
この方法で初期化するファンクションを定義します。
void reset() { digitalWrite(WE_PIN, LOW); digitalWrite(IC_PIN, LOW); delayMicroseconds(100); digitalWrite(IC_PIN, HIGH); delay(1000); }
このファンクションをsetup()内で実行します
reset();
F-Numberを指定して出力
音の出力はF-Numberという値を使用します。
音程とF-Numberの対応表はアプリケーションマニュアルに書いてあります。
レジスタ0x10(+チャンネル番号)にF-Numberの下位8ビットを指定します。
レジスタ0x20(+チャンネル番号)に音階(オクターブ)とF-Numberの上位1ビットを指定します。
チャンネルとF-Numberを設定するファンクションを定義
レジスタ0x20(+チャンネル番号)に音のオン/オフ(key)も指定するするのですが、今回はkey=1(オン)を固定で設定します
void SetFNumber(int ch, int oct, int f_number) { // F-Numberの下位8ビット set_register(0x10 + ch, f_number & 0xff); // key=1, oct, F-Numberの上位1ビット set_register(0x20 + ch, (1 << 4) | (oct << 1) | (f_number >> 8)); }
音色と音量
レジスタ0x30(+チャンネル番号)の上位4ビットは音色、下位4ビットは音量になっています。
チャンネルと音色と音量を設定するファンクションを定義
void SetInstVol(int ch, int inst, int vol) { set_register(0x30 + ch, (inst << 4) | vol); }
演奏
チャンネル0の音色を3(ピアノ)、音量を15にします。
チャンネル0に440HzのA(ラ)の音程のF-Number(288)とオクターブ4をセットします。
1秒後にチャンネル0の演奏を止めます。
この時0x20(+チャンネル番号)に0を入れます。こうすることによりkeyのビットも0になるので、演奏も止まります。
この処理は1回だけ実行するつもりだったので、setup()内の最後に追加しました。
SetInstVol(0, 3, 16); SetFNumber(0, 4, 288); delay(1000); set_register(0x20, 0);
これで起動後、1秒間だけ音が出力されるはずです。
今回はおまけでloop()無いでドレミファソラシドを演奏するサンプルも書きました。
void loop() { delay(1000); SetFNumber(0, 4, 172); delay(500); SetFNumber(0, 4, 192); delay(500); SetFNumber(0, 4, 216); delay(500); SetFNumber(0, 4, 229); delay(500); SetFNumber(0, 4, 257); delay(500); SetFNumber(0, 4, 288); delay(500); SetFNumber(0, 4, 323); delay(500); SetFNumber(0, 4, 343); delay(1000); set_register(0x20, 0); }
最後にソース全文を掲載するので、参考にどうぞ。
// Output Pins const byte IC_PIN = A0; const byte CS_PIN = A1; const byte WE_PIN = A2; const byte A0_PIN = A3; const int DATA_PIN[] = { 2, 3, 4, 5, 6, 7, 8, 9 }; void setup() { for (int i = 0; i < 8; i++) { pinMode(DATA_PIN[i], OUTPUT); } pinMode(CS_PIN, OUTPUT); pinMode(A0_PIN, OUTPUT); pinMode(IC_PIN, OUTPUT); pinMode(WE_PIN, OUTPUT); reset(); SetInstVol(0, 3, 16); SetFNumber(0, 4, 288); delay(1000); set_register(0x20, 0); } void loop() { delay(1000); SetFNumber(0, 4, 172); delay(500); SetFNumber(0, 4, 192); delay(500); SetFNumber(0, 4, 216); delay(500); SetFNumber(0, 4, 229); delay(500); SetFNumber(0, 4, 257); delay(500); SetFNumber(0, 4, 288); delay(500); SetFNumber(0, 4, 323); delay(500); SetFNumber(0, 4, 343); delay(1000); set_register(0x20, 0); } // 初期化 void reset() { digitalWrite(WE_PIN, LOW); digitalWrite(IC_PIN, LOW); delayMicroseconds(100); digitalWrite(IC_PIN, HIGH); delay(1000); } // F-Number指定 void SetFNumber(int ch, int oct, int f_number) { set_register(0x10 + ch, f_number & 0xff); set_register(0x20 + ch, 16 | (oct << 1) | (f_number >> 8)); } // 音色と音量指定 void SetInstVol(int ch, int inst, int vol) { set_register(0x30 + ch, (inst << 4) | vol); } // レジスタセット void set_register(byte addr, byte value) { // addr digitalWrite(A0_PIN, LOW); digitalWrite(CS_PIN, LOW); for (int i = 0; i < 8; i++) { digitalWrite(DATA_PIN[i], bitRead(addr, i)); } delayMicroseconds(12); digitalWrite(CS_PIN, HIGH); // value digitalWrite(A0_PIN, HIGH); digitalWrite(CS_PIN, LOW); for (int i = 0; i < 8; i++) { digitalWrite(DATA_PIN[i], bitRead(value, i)); } delayMicroseconds(84); digitalWrite(CS_PIN, LOW); }