クリスマスツリー電飾の製作:ソフトウェア編

 一部繰り返しになるが、ソフトウェアは次の諸点を注意して組む。

明るさの時間変化

 下の実線のように変化させる。MAX は明るさの最大値、d は単位時間あたりの変化量。実装の際は、場合分けが多いとプログラムが長くなるので、点線の値を計算したあと 0 と MAX で切るようにする。

 ただ、このまま LED に出力すると、「少しずつ暗くなって、あるところから急に暗くなって消える」ように見える。下のようにカーブをかけると、「だんだん暗くなってふっと消える」感じになる(ような気がしたが、気のせいかもしれない)。このカーブは定数のテーブルとして実装するので、気に入らなければ作り直せばよい。

色の時間変化

 なるべく処理を単純にしつつ、様々な色が連続的に登場するようにする。このため、RGB成分のうち一つを最大値に固定して、残り2つを直線的に変化させる。どれかの成分が最大値に達したら、今度はそれを固定して、残り2つを直線的に変化させる。この処理を繰り返す。下の図の破線矢印のように色が変化することになる。(手前に向かって飛び出している立方体の表面を矢印に沿って進んでいる、と見てください)

ソフトウェアPWMの実装

 ATtiny2313 には PWM 機能が内蔵されているが、今回は8つ(3系統とも3色フルに使えば9つ)の出力を独立に制御しないといけないので、内蔵の PWM 機能では対応できない。タイマ割り込みを使って自前で制御する。

 まず、動作クロックを決める。回路を単純にするため、内蔵 RC クロックを 8 MHz で使う。これは、ヒューズビットで指定する。Low byte の下位4ビットを 0100 にすればよい。

# ATTiny2313 fuse bits # Low byte: # 0xe4 = 1 1 1 0 0 1 0 0 <-- Select Clock source # ^ ^ ^ ^ ^ ^ ^------ Select Clock source # | | | | | +-------- Select Clock source # | | | | +---------- Select Clock source (0b0100 = internal oscillator 8 MHz) # | | | +-------------- Select start-up time # | | +---------------- Select start-up time (0b10 = default) # | +------------------ Output clock on CKOUT pin # +-------------------- Divide clock by 8

 次に、タイマーを設定する。プリスケールなしで、単純なオーバーフローで割り込みがかかるようにする。

void setup(void) { cli(); // 割り込みを禁止 DDRB = 0b11111111; // PB0-PB7 は出力 DDRD = 0b01111000; // PD3-PD6 は出力 // タイマーを設定 // タイマー割り込みをすべて禁止 TIMSK = 0b00000000; // WGM0/1/2 = 0 ... ノーマルタイマーモード(CTC でも PWM でもない) // クロックソース = IO クロック、プリスケールなし TCCR0A = 0b00000000; TCCR0B = 0b00000001; // タイマーをリセットして、オーバーフロー割り込みを許可する TCNT0 = 0; TIMSK = (1<<TOIE0); sei(); // 割り込みを許可 }

 割り込みルーチンはこんな風になる。割り込みがかかるごとにグローバル変数の timer をインクリメントし、pwm[i] よりも小さければ "1", 大きければ "0" をポートの対応するビットに出力する。forループとか組むと遅くなりそうなので、書き下した。

// ソフトウェア PWM 9チャンネル ISR(TIMER0_OVF_vect) { unsigned char m1; timer++; m1 = 0; if (pwm[0] > timer) m1 |= 128; if (pwm[1] > timer) m1 |= 64; if (pwm[2] > timer) m1 |= 32; if (pwm[3] > timer) m1 |= 16; if (pwm[4] > timer) m1 |= 8; if (pwm[5] > timer) m1 |= 4; if (pwm[6] > timer) m1 |= 2; if (pwm[7] > timer) m1 |= 1; PORTB = m1; m1 = 0; if (pwm[8] > timer) m1 = 64; PORTD = m1; }

 あとは、最初に書いた明るさと色の変化を計算して、メインルーチンで回せばよい。できあがったソースコードはこちら。コンパイルすると 1040 bytes で、十分 2 KBytes の制約に収まった。

 1つハマった落とし穴がここ。

#include <avr/pgmspace.h> PROGMEM prog_uchar table[256] = { ...(省略)... }; ... pwm[i * 3 + j] = pgm_read_byte(table + ((int)colors[i * 3 + j] * c) / 128); ...

 ATtiny2313 はプログラム領域とデータ領域が分かれているので、単に const unsigned char table[256] = {...} とすると、実行時にデータ領域に配列データをコピーしようとするため、メモリが足りなくなってしまう。ATtiny2313 のメモリは 128 bytes しかない! プログラム領域にデータを置いてアクセスするために、PROGMEM prog_uchar 宣言と pgm_read_byte 関数を使う必要がある。