AVR の EEPROM データが壊れるのを防ぐには

909クローンのファームウェアの開発中、eeprom に書き込んだリズムパタンが書き込んだ後に崩れる現象がまあまあ頻繁に発生して、最初はファームウェアのバグのせいかと思っていたのですが、バグにしてはどうにも発生場所がランダムだったり発生の契機が読めなかったりで、これはプロセッサの誤動作なのでは?と考え始め、発生条件をもっとよく調べてみたら、どうも電源を落として立ち上げると何の書き込みをしていなくてもちょいちょい壊れるらしい。そういうわけで何の問題でどう対策すれば良いのか調べました。

怪しい通販サイトからトランジスタを買おうとしたらやっぱり怪しかった話

製作に使う部品にはあまりこだわらない方で普通は手に入るもので済ますので、部品の調達先はだいたい決まっているのですが、909クローンでは、どこを代替品にしてよいかというところまで部品表に指定してあって、そういったことを楽しむために頑張ってできるだけオリジナル部品を調達しました。そのためトランジスタは廃番を中心に集めることになり、トランジスタはほとんどが初めての販売者さんからの調達になりました。こういう時は eBay を利用するのが、詐欺防止の仕組みが充実していて安心なのですが、一点だけ eBay を通さず直接注文を出しました。ところ見事に問題が起きたので顛末を簡単に記録しておきます。

AVR の EEPROM 非同期書き込み関数

TR-909 クローンのシーケンスパタンは eeprom に書き込みますが、avr-libc ライブラリが提供している eeprom 関数には同期書き込みしかありません。

https://www.nongnu.org/avr-libc/user-manual/group__avr__eeprom.html

しかし、TR-909 クローンのファームウェアでは割り込みをほとんど使わずリアルタイム処理がなされていて、eeprom を同期書き込みすると書き込み中処理が止まってしまいます。これが様々な誤動作の原因になってしまうので、非同期書き込み関数を書きました。

シーケンサの基本構造とリズムパタンの打ち込み

909 の製作はハードウェア部分はほぼ終わり、ファームウェア書きが中心になってきました。ソースコードは GitHub に上げていて文章で書くことがだんだんなくなってきて、ブログ記事の間が広がってまいりました。その中で、シーケンサの基本構造とそこへリズムパタンを打ち込む部分はソースコードを読むだけではわかりにくい、自分でも忘れてしまうかもしれないので大枠を記事にしておきます。

バスドラム・スネアドラム・ハイハットのチューン機能実装

入力

バスドラム・スネアドラム・ハイハットの三つのチューン入力は、ボリューム基板から CV としてマイクロプロセッサへ ADC 入力として入っています。チューン制御をするには、まず ADC を使って CV を読み取る必要があります。

出力

バスドラム・スネアドラムのチューンは CV としてマイクロプロセッサの PWM 出力から回路に入っています。オリジナル回路でも同じなのですが、制御電圧の範囲について注意が必要です。ハイハットのチューンはオリジナルになかった機能ですが、PCM のクロック周波数を変えることで制御します。

バスドラム・部品の差し替え・スネアドラム

TR-909 クローン、製作が進みだんだんコツをつかんできてあまりつっかえるところがなくなってきました。できれば一月中にハードウェアの組み立てを終わってしまいたいのでブログに記録を残すのを端折って製作作業を急いでいました。スネアドラムまで組み終わったので忘れないうちにやったことをざっと記録しておきます。

トランジスタの選別

ファームウェア側の二つの気がかりだった PCM とノイズジェネレータのめどが立ったので、アナログ回路の組み立てをどんどん進めようと思います。組み進める前に、アナログ回路側の気がかり、トランジスタの選別に手を付けました。組…

AVR のファームウェアを C++ で書くのはどう?

TR-909 クローンのファームウェア、あまり深く考えずに C で書き始めましたけど、どうもちょいちょい「これは、C++ のほうがよくない?」という場面にあたります。

例えばこんなところ

void TriggerRimShot(int8_t velocity) {
  SET_BIT(PORT_TRIG_RIM_SHOT, BIT_TRIG_RIM_SHOT);
  SET_BIT(PORT_LED_RIM_SHOT, BIT_LED_RIM_SHOT);
  g_rim_shot.status = 255;
}

この関数にはこんなマクロが参照されています

#define SET_BIT(port, bit) (port) |= _BV(bit)
#define PORT_TRIG_RIM_SHOT PORTD
#define BIT_TRIG_RIM_SHOT  PD0

なんというか。マクロだらけです。けっこう危ないコードだしメンテも大変そうです。他にもこんなところ

void CheckSwitches(uint8_t prev_switches, uint8_t new_switches) {
  if ((prev_switches ^ new_switches) == 0) {
    return;
  }
  if (IS_RIM_SHOT_ON(new_switches)) {
    TriggerRimShort(127);
  }
  if (IS_OPEN_HI_HAT_ON(new_switches)) {
    TriggerOpenHiHat(127);
  }
  if (IS_CLOSED_HI_HAT_ON(new_switches)) {
    TriggerClosedHiHat(127);
  }
}

スイッチが押されるのを検知する関数ですが、マクロを使うわ微妙に違う似たようなパタンの繰り返しになるわ。実行速度を考えるとループを回したり関数ポインタを使った汎用ルーチンを使ったりは避けたいですけど、すぐにメンテが大変なことになりそうです。これ、テンプレートが使えないかな?

てな風に、C++ で書いた方がいいんじゃね?感がどんどん増してきました。8bit プロセッサのコードを C++ で書くのはあまりやったことがありませんがどんな感じなりそうか見てみました。