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にダイエットしないとイケないのですが、多分無理。

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

 

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

 - 電子工作

  関連記事

FM音源YM2203をArduinoで鳴らしたい #1

お疲れ様です。高橋です。 きっかけ 4月から現在に掛けてのコロナな最近、会社のと …

満員電車内でのオナラと検知 #5

お疲れ様です。高橋です。 マイコンと5V給電 先日実験に成功した硫化水素センサー …

4桁7セグLEDを基板に実装 完成

お疲れ様です。高橋です。 大失敗 前回、実体配線図を書いたこれ。 こんな感じに頑 …

遠隔でRaspberryPi+DCモーターを制御 #2

お疲れ様です。高橋です。 WiMAXが上限オーバー 私の家の自宅回線はWiMAX …

Pro Mini互換機でFF2戦闘音楽を使ったYMZ動作試験

お疲れ様です。高橋です。 電子オルゴールの最終形をイメージしてブレッドボード上で …

汎用ロジックIC(NAND/NOR/AND/OR)試験機

お疲れ様です。高橋です。 単一の機能を持ったシンプルな、汎用ロジックICという物 …

音階LED実装 #3 – 秋葉原で買い物

お疲れ様です。高橋です。 先日、お小遣いがチャージされまして、音階LED実装に必 …

ArduinoIDE1.6.4+ATTiny13Aで赤外線リモコン(難航)

お疲れ様です。高橋です。 先日から引き続いて、ATTiny13Aで赤外線リモコン …

電子オルゴール 基板実装 #1

お疲れ様です。高橋です。 Pro Mini互換機を贅沢に使った電子オルゴールの仮 …

aitendoで買った375円のバックライト付きI2C LCDで文字を表示してみた。

お疲れ様です。高橋です。 ちょっと前にaitendoで買ったバックライト付きのL …