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

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

 

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

 - 電子工作

  関連記事

マウスを分解してみた。

お疲れ様です。高橋です。 壊れたマウスを貰った 先日、壊れたマウスを貰いました。 …

音階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に …