curious4dev

中国旅行、Arduinoなどを使った電子工作、その他色々。

*

Arduino+EEPROM+YMZ294で曲の抜き差し

 

お疲れ様です。高橋です。

I2Cの256KBitなEEPROMに曲データを書き込み、書き込んだデータを読みこみながらYMZ294で演奏する実験が終了しました。

IMG_5483

私が買ったI2C EEPROMはこれ。秋月で1個90円。こいつは中に256KBit(つまり32KByte)のデータを格納出来ます。

中は32KBit(4KByte)毎にブロックで区切られています。

今のArduino+YMZ294の構成だと、曲を差し替える度に毎回書き込み作業を行うのが面倒です。それなら最初からEEPROMに曲データだけ書き込んどいて、あたかもファミコンのカセットの如く抜き差しすれば曲が変更出来る、というタイプにしてみたかったのが主な理由です。

042901

そして、ATmega328はプログラムFlash領域が32KByteもあるので、ほぼほぼ問題無いですが、それでも容量が足りなくなってくる事があると思います。その時のためにEEPROMの使い方を学んでおきたかった、っていうのもあります。

実体配線図

読み書きするため、そしてArduinoでEEPROMを使う上で参考にしたのが、きむ茶工房ガレージハウスさんのEEPROM(24LC256/1024)と接続して読み書きを行って見ますというページです。

実体配線図は下記のような感じです。書き込む際にYMZは不要です。

042902

ライブラリは、きむ茶工房さんがページにアップロードしているライブラリ「skMC24xxx」を使います。

EEPROMへの書き込みフォーマット

私が曲をArduinoで演奏させる時は、こんな流れで作っています。

042903

 

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になるまで、実験を何度も繰り返しました。。

042904

読み込みスケッチ

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バイトでチャンネル番号を取得し、その次の1バイトで音階を取得します。
  2. 2だったら待機時間なので、次の1バイトで時間を100で割った商を取得し、その次の1バイトで100で割った余りを取得し、時間を生成します。
  3. 3だったら演奏終了なので、読み込みアドレスを1に戻してループさせる、

という感じにしています。

本当はコレ、ATtiny13AとEEPROMの組み合わせで実現出来たら最高なんですが、ATtiny13Aのプログラム領域は1KByte。今の読み込み側のスケッチが4,912Byteなので、1/5にダイエットしないとイケないのですが、多分無理。

小さくて素敵なんだけどなあ。。

 

以上、よろしくお願い致します。

 - 電子工作

  関連記事

遠隔でRaspberryPi+DCモーターを制御してaitendoの名刺をクルクルさせる。

お疲れ様です。高橋です。 「猫×おもちゃ×動画×アプリ」のための要素技術として必 …

マウスを入力I/Fとして使うのを断念してみた。

お疲れ様です。高橋です。 先日の「マウスを分解してみた」でマウスを分解し、ケーブ …

赤外線リモコンをケースに格納

お疲れ様です。高橋です。 先日作ったATtiny13Aの赤外線リモコンを、ケース …

PCとArduinoをJavaでシリアル通信

お疲れ様です。高橋です。 先日作ったコレ。 よくよく考えると、書き込む際にいちい …

電子オルゴール設計

お疲れ様です。高橋です。 電子オルゴールがどんな形状になるのか、実体配線図を作っ …

抵抗が焼けた – Arduinoで赤外線リモコン

お疲れ様です。高橋です。 今日の実験中、抵抗が焼けてしまいました。 なんか異臭が …

【完成】 電子オルゴール

お疲れ様です。高橋です。 電子オルゴールの制作が佳境に入り、とうとうカルトナージ …

PCとArduino間でのシリアル通信を介したEEPROM書き込み(難航)

お疲れ様です。高橋です。 Arduino Pro MiniでI2C EEPROM …

音階LED実装 #2

お疲れ様です。高橋です。 本日も昨日に引き続き半田付け。 今日の進捗 昨日の残り …

マウスの位置座標を使った電子楽器を作ってみた。

お疲れ様です。高橋です。 先日分解したマウスを使って、位置座標の取り出しと、取り …