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にダイエットしないとイケないのですが、多分無理。
小さくて素敵なんだけどなあ。。
以上、よろしくお願い致します。
関連記事
-
-
マウスを分解してみた。
お疲れ様です。高橋です。 壊れたマウスを貰った 先日、壊れたマウスを貰いました。 …
-
-
音階LED実装 #1
お疲れ様です。高橋です。 今日の進捗 音階LEDの半田付け 1日目です。 ICソ …
-
-
YMZシールドの基板設計
お疲れ様です。 前に基板化したYMZ294をさらにシールド化したい事をPOSTし …
-
-
Arduino Pro MiniでLチカしてみた
お疲れ様です。高橋です。 最近ずっと忙しく、平日はもちろん、土日も疲労のため何も …
-
-
満員電車内でのオナラと検知 #4
お疲れ様です。高橋です。 硫化水素センサーが到着しました。結構前に到着していたの …
-
-
音階LED仮組み
お疲れ様です。高橋です。 先日設計し直した音階LEDを、B型基板上に部品を載せて …
-
-
音階LED実装 #3 – 秋葉原で買い物
お疲れ様です。高橋です。 先日、お小遣いがチャージされまして、音階LED実装に必 …
-
-
YMZシールドの基板設計 #2
お疲れ様です。高橋です。 先日行ったYMZシールドの基板設計の設計図を元に、部品 …
-
-
100均で買ったボリュームコントロール付きステレオ延長コードを、バブ型スピーカーに搭載してみた。
お疲れ様です。高橋です。 先日作ったバブ型スピーカーですが、実は裏側がとても汚い …
-
-
【完成】PCからシリアル通信でEEPROM書き込み
お疲れ様です。高橋です。 PCからシリアル通信を介して楽曲データをEEPROMに …
- PREV
- ファイナルファンタジー2 戦闘テーマ1
- NEXT
- PCとArduinoをJavaでシリアル通信