■例の鍵かけ忘れ監視器は順調に制作中。今回は音や光をつかう派手目のデバイスということもあって、これまでなんとなくスルーしてきたATmegaの機能とか割と使うことになっている。PWMは赤外リモコンでよく使っていたけど、今回は音を出すために利用している。そして光を明滅させるためにタイマ割り込みで、中間処理のステップ数にかかわらず、タイミングを合わせられるようにウェイトさせるルーチンを使ったり。
写真で明るく光っているLEDは赤・青2色発光で、カソードコモン。ステータスに合わせて発行色を変化させるため、p-Ch FETを使っている。鳴りものは圧電サウンダをPWMで駆動するけど、これもATmegaが直接ではなく、p-Ch FETを通して鳴らしている。
圧電サウンダではPWMの周波数で音程を調整し、デューティー比を1/2~1/16に変化させることで音量を調整している。デバイスではタイマー/カウンター1のBチャンネルを使っている。
明滅サイクルはサウンドのアラーム音と同期させ、かつ、なるべく処理に関わらずサイクル長が変化しないようにしたかったので、タイマー/カウンター0の8ビットタイマーを使った。タイマ割込みで(タイマー用カウンタとは別に)プログラムロジック内部のカウンタを使い、プログラム側で指定した期間がすぎるまでウェイトできるようにした。条件によって明滅サイクル内の処理ステップ数が変化し、そのことでサイクル長が左右されないようにしたかったので。
このウェイト処理ではまったのは、割り込みルーチン内部でグローバル変数として用意されたカウンタ値を変化させても、ウェイト処理側ではカウンタ値の変更を検出できないことだった。
これは不勉強だったのだけど、ウェイト処理とは別のロジックでカウンタが変更されるため、こういう変数についてはvolatile修飾子を付けてやらないと、コンパイラの最適化によってうまく動けなくなる。これはATmegaに限らず一般的な作法らしい。
これはモジュール内部ではレジスタと変数の格納先アドレスは別ものとして扱われているために発生する。レジスタは言うなれば(実際には違うのだけど)キャッシュのようなものであり、volatileを使うとキャッシュではなく、実際の格納先アドレスを見に行くようになる。
実際にはこのようにコーディングした。
unsigned int tm0_ct = 0; volatile unsigned int tm0_size = 60; unsigned int ftm = 0; ISR(TIMER0_COMPA_vect) { tm0_ct++; if(tm0_ct > 62){ tm0_ct = 0; if(tm0_size > 0) { tm0_size--; } } } void timer_set(unsigned int unit) { tm0_size = unit; tm0_ct = 0; } void wait_timer() { unsigned int wk; do{ wk = tm0_size; }while(wk != 0); }
timer_set関数でタイミング合わせをするスパンを指定し、wait_timerでスパンが終わるまで待つ。処理ステップ数が変動するロジックの前でtimer_setを呼び出し、変動ステップのロジックが終わった後でwait_timerを呼ぶことで、タイミング合わせができるようになる。