Arduino+EEPROM+YMZ294で曲の抜き差し
お疲れ様です。高橋です。
I2Cの256KBitなEEPROMに曲データを書き込み、書き込んだデータを読みこみながらYMZ294で演奏する実験が終了しました。
私が買ったI2C EEPROMはこれ。秋月で1個90円。こいつは中に256KBit(つまり32KByte)のデータを格納出来ます。
中は32KBit(4KByte)毎にブロックで区切られています。
今のArduino+YMZ294の構成だと、曲を差し替える度に毎回書き込み作業を行うのが面倒です。それなら最初からEEPROMに曲データだけ書き込んどいて、あたかもファミコンのカセットの如く抜き差しすれば曲が変更出来る、というタイプにしてみたかったのが主な理由です。
そして、ATmega328はプログラムFlash領域が32KByteもあるので、ほぼほぼ問題無いですが、それでも容量が足りなくなってくる事があると思います。その時のためにEEPROMの使い方を学んでおきたかった、っていうのもあります。
実体配線図
読み書きするため、そしてArduinoでEEPROMを使う上で参考にしたのが、きむ茶工房ガレージハウスさんのEEPROM(24LC256/1024)と接続して読み書きを行って見ますというページです。
実体配線図は下記のような感じです。書き込む際にYMZは不要です。
ライブラリは、きむ茶工房さんがページにアップロードしているライブラリ「skMC24xxx」を使います。
EEPROMへの書き込みフォーマット
私が曲をArduinoで演奏させる時は、こんな流れで作っています。
SMF2YMZで吐き出した「Arduino code」は、下記のような感じになっています。
set_ch(ADDR_FREQ_A, 74); delay(441); set_ch(ADDR_FREQ_A, 0); set_ch(ADDR_FREQ_A, 83); set_ch(ADDR_FREQ_C, 43); delay(249); set_ch(ADDR_FREQ_B, 62); set_ch(ADDR_FREQ_C, 0); set_ch(ADDR_FREQ_C, 55); delay(249); set_ch(ADDR_FREQ_A, 0);
要は、set_chで音階とチャンネルを設定し、delayで経過時間を設定する、というだけのものです。
これをEEPROMにByteで書き込むため、下記のような感じでbyteの配列にします。
命令 | 値1 | 値2 | |
set_ch | 1: 音階設定 | 1~3: チャンネル | 0~127: 音階 |
delay | 2: 待機時間設定 | 時間を100で割った商 | 時間を100で割った剰余 |
曲の終了 | 3: 終了 |
このルールを踏まえると、さっきの曲データは
0x01 0x01 0x4A 0x02 0x04 0x29 0x01 0x01 0x00 0x01 0x01 0x53 0x01 0x03 0x2B 0x02 0x02 0x31 0x01 0x02 0x3E 0x01 0x03 0x00 0x01 0x03 0x37 0x02 0x02 0x31
というように美しいバイト配列に変換されます。これをEEPROMにアドレスを指定しながら書き込めばOK。
書き込みスケッチ
下記がスケッチ。曲データの中のset_chメソッドとuser_delayメソッドの呼び出しはそのまま使い、メソッド内でバイト配列に変換して、3バイトずつEEPROMに書き込んでいます。
#include <Wire.h> #include <skMC24xxx.h> #define ADDR_FREQ_A 1 #define ADDR_FREQ_B 2 #define ADDR_FREQ_C 3 skMC24xxx MEM(0, 0, 0) ; int intCurrentAddress = 1; int response; void editCurrentAddress() { if(intCurrentAddress % 64 == 0) { intCurrentAddress = intCurrentAddress + 1; } } void set_ch(int ch, int note) { char aryWriteData[4]; aryWriteData[0] = 1; aryWriteData[1] = ch; aryWriteData[2] = note; response = MEM.Write(intCurrentAddress, aryWriteData, 3); showResponseMessage(response, aryWriteData); intCurrentAddress = intCurrentAddress + 3; editCurrentAddress(); delay(5); } void user_delay(int time) { char aryWriteData[4]; aryWriteData[0] = 2; aryWriteData[1] = time / 100; aryWriteData[2] = time % 100; response = MEM.Write(intCurrentAddress, aryWriteData, 3); showResponseMessage(response, aryWriteData); intCurrentAddress = intCurrentAddress + 3; editCurrentAddress(); delay(5); } void writeEndData() { char aryWriteData[4]; aryWriteData[0] = 3; aryWriteData[1] = 0; aryWriteData[2] = 0; response = MEM.Write(intCurrentAddress, aryWriteData, 3); showResponseMessage(response, aryWriteData); editCurrentAddress(); delay(5); } void showResponseMessage(int response, char data[4]) { if (response == 0) { Serial.print("EEPROM Write Success ") ; Serial.print("Address["); Serial.print(intCurrentAddress); Serial.print("] data["); Serial.print(data[0], HEX); Serial.print("/"); Serial.print(data[1], HEX); Serial.print("/"); Serial.print(data[2], HEX); Serial.println("]"); } else { Serial.print("EEPROM Write Error "); Serial.print("response [") ; Serial.print(response) ; Serial.print("] Address["); Serial.print(intCurrentAddress); Serial.print("] data["); Serial.print(data[0], HEX); Serial.print("/"); Serial.print(data[1], HEX); Serial.print("/"); Serial.print(data[2], HEX); Serial.println("]"); } } void setup() { Serial.begin(9600) ; delay(5000); writeSongData(); writeEndData(); } void loop() { } void writeSongData() { set_ch(ADDR_FREQ_A, 74); user_delay(441); set_ch(ADDR_FREQ_A, 0); set_ch(ADDR_FREQ_A, 83); set_ch(ADDR_FREQ_C, 43); user_delay(249); set_ch(ADDR_FREQ_B, 62); set_ch(ADDR_FREQ_C, 0); set_ch(ADDR_FREQ_C, 55); user_delay(249); <以下略>
ハマった事
- なぜか64byte毎に書き込みがおかしくなるアドレスがあった事。ここは、アドレスを64で割った余りが0の場合だけ、アドレスを強制的に++させる事でしのいでいます。原因は後ほど追求。。おそらくEEPROM内部のブロック跨ぎが原因な気がします。
- それと、アドレス0に書き込もうとしたら、エラーにはならなかった物の、データがおかしくなりました。原因は不明ですがアドレス1から書き込む事にしてます。
- EEPROMに3バイトずつ書き込んでいますが、次の3バイトを書き込む前にdelay(5)を仕込んでおかないと、Write時にエラーとなります。
write時のSerialデバッグ結果と、Read時のSerialデバッグ結果を突き合わせ、全件OKになるまで、実験を何度も繰り返しました。。
読み込みスケッチ
EEPROMに書き込んだ曲データを3バイトずつ読み込みながらYMZ294で演奏するスケッチがこちらです。
#include <YMZ294R.h> #include <Wire.h> #include <skMC24xxx.h> #define ADDR_FREQ_A CH_A #define ADDR_FREQ_B CH_B #define ADDR_FREQ_C CH_C skMC24xxx MEM(0, 0, 0) ; // Output Pins const byte WRCS_PIN = 8; const byte A0_PIN = 9; const byte RESET_PIN = 10; int intCurrentAddress = 1; int response; //----------------------------------------------- //-- for YMZ294 //----------------------------------------------- YMZ294R ymz(WRCS_PIN, A0_PIN, RESET_PIN); void set_ch(Channel ch, int note) { if ( note == 0 ) { ymz.SetVolume(ch, 0b00000000); ymz.SetFrequency(ch, noteFreq[0]); } else { ymz.SetVolume(ch, 0b00001110); ymz.SetFrequency(ch, noteFreq[note]); } } void user_delay(int time) { delay(time / 1.2); } void editCurrentAddress() { if (intCurrentAddress % 64 == 0) { intCurrentAddress = intCurrentAddress + 1; } } void setup() { //-- for YMZ294 DDRD = 0b11111111; pinMode(WRCS_PIN, OUTPUT); pinMode(A0_PIN, OUTPUT); pinMode(RESET_PIN, OUTPUT); ymz.Reset(); ymz.SetMixer(0b111, 0b000); ymz.SetVolume(CH_A, 0b00001110); ymz.SetFreqBit(CH_A, 0); ymz.SetVolume(CH_B, 0b00001110); ymz.SetFreqBit(CH_B, 0); ymz.SetVolume(CH_C, 0b00001110); ymz.SetFreqBit(CH_C, 0); ymz.SetEnvEnable(CH_A, false); ymz.SetEnvEnable(CH_B, false); ymz.SetEnvEnable(CH_C, false); } void loop() { char readData[4]; Channel ch; int note; int time; response = MEM.Read(intCurrentAddress, readData, 3) ; switch (readData[0]) { case 1: switch (readData[1]) { case 1: ch = CH_A; break; case 2: ch = CH_B; break; case 3: ch = CH_C; break; } note = readData[2]; set_ch(ch, note); intCurrentAddress = intCurrentAddress + 3; editCurrentAddress(); break; case 2: time = readData[1] * 100; time = time + readData[2]; user_delay(time); intCurrentAddress = intCurrentAddress + 3; editCurrentAddress(); break; case 3: intCurrentAddress = 1; break; } }
3バイトずつ読み込んで、
- 先頭バイトが1だったら音階設定なので、次の1バイトでチャンネル番号を取得し、その次の1バイトで音階を取得します。
- 2だったら待機時間なので、次の1バイトで時間を100で割った商を取得し、その次の1バイトで100で割った余りを取得し、時間を生成します。
- 3だったら演奏終了なので、読み込みアドレスを1に戻してループさせる、
という感じにしています。
本当はコレ、ATtiny13AとEEPROMの組み合わせで実現出来たら最高なんですが、ATtiny13Aのプログラム領域は1KByte。今の読み込み側のスケッチが4,912Byteなので、1/5にダイエットしないとイケないのですが、多分無理。
小さくて素敵なんだけどなあ。。
以上、よろしくお願い致します。
関連記事
-
-
YMZシールド 再作成
お疲れ様です。高橋です。 YMZのノイズが超気になる YMZ294用の音楽を聞く …
-
-
100均で買ったボリュームコントロール付きステレオ延長コードを、バブ型スピーカーに搭載してみた。
お疲れ様です。高橋です。 先日作ったバブ型スピーカーですが、実は裏側がとても汚い …
-
-
Arduino nano互換機でオナラ検知デバイス
お疲れ様です。高橋です。 今日のお昼、会社の大先輩から、綺麗にケーシングした自作 …
-
-
マウスを入力I/Fとして使うのを断念してみた。
お疲れ様です。高橋です。 先日の「マウスを分解してみた」でマウスを分解し、ケーブ …
-
-
Pro Mini互換機でFF2戦闘音楽を使ったYMZ動作試験
お疲れ様です。高橋です。 電子オルゴールの最終形をイメージしてブレッドボード上で …
-
-
【完成】ArduinoIDE1.6.4+ATTiny13Aで赤外線リモコン
お疲れ様です。高橋です。 苦戦していたArduinoIDE1.6.4を使ったAT …
-
-
YMZシールドの作成 #2
お疲れ様です。高橋です。 先日から作っていたYMZシールドですが、本日完成しまし …
-
-
電子オルゴール 回路図
お疲れ様です。高橋です。 電子オルゴールの回路図を書いてみました。 実体配線図よ …
-
-
aitendoで買った375円のバックライト付きI2C LCDで文字を表示してみた。
お疲れ様です。高橋です。 ちょっと前にaitendoで買ったバックライト付きのL …
-
-
YMZ294のMIDI音源化と、YMZのピン数圧縮
お疲れ様です。高橋です。 YMZで使うピン数を11本から6本に減らす事によって、 …
- PREV
- ファイナルファンタジー2 戦闘テーマ1
- NEXT
- PCとArduinoをJavaでシリアル通信