読み手を想定して書いているわけではありません。-- 実はプログラムを引用して説明を書く練習だったりします。
平タスク
平タスクともメインループとも呼ばれる典型的な処理です。いろいろ凝った仕掛けを作ろうかと思ったこともありましたが、最近はもっぱらこの形式で書いています。注意点としては、イベントの中で止まらないようにすること。特にこのプログラムでは、LED の連続点灯になってしまうとマズイので注意が必要です。具体的に書くと getc() は入力データがないとき待ちになってしまうので、 can_getc(1) で読み込めるかどうか確認するとか...
あと、イベントに変数を使うときは volatile を付けること。これを忘れるとレジスタにコピーした値をずっと参照して、イベントが上がらないケースがあります。
int main(void)
{
/* set system_clock pre scaler 1/8 => 1/1 */
CLKPR = (1<<CLKPCE);
CLKPR = 0;
各種初期化
sei();
for(;;) {
wdt_reset();
poll();
if (イベント1) {
イベント1の処理
}
if (イベント2) {
イベント2の処理
}
:
}
}
タイマー処理
一定間隔である処理をさせたいとき使う処理に以下の set_timeout / check_timeout ってのを作って使っています。時間の定義は 最小の時間と最大の時間が int16_t の正の範囲(0-32767)に収まるようにプログラム毎に調整しています。ちなみに、変数とかの型は、uint8_t とか使ってサイズと signed/unsigned を明示するようこころがけています。-- こう書いておくと、プログラムの一部を PC にもっていってテストする場合に便利なのです。
static inline uint16_t get_time() {
return TCNT1;
}
static void set_timeout(uint8_t chan, uint16_t t) {
to_time[chan] = t + get_time();
to_flag[chan] = 1;
}
static int8_t check_timeout(uint8_t chan) {
if (to_flag[chan] && (to_time[chan]
- (int16_t)(get_time()) <= 0)) {
to_flag[chan] = 0;
}
return !to_flag[chan];
}
使用例:
if(check_timeout(TO_DOT)) {
:
set_timeout(TO_DOT, 24);
}
詳しいコメント:
TCNT1を直接使えばよいものを get_time で ラップ(wrap)しているのは、ATtiny861 みたいな HIとLO が別々のレジスタになる拡張に対応できるようにしたため。
to_flagが冗長で無駄のように思えるかも知れません。以前は使っていなかったのですが、そうすると set_timeoutで最初の TIMEOUT を設定しないといけないので、フラグを使うようにしました。1bit の情報を扱うのに 1バイトも!使うのは無駄だと思う場合は、set_timeout/check_timeout を inline 化して to_flag を bitmap にする場合もあります。
timeoutの名前の由来は、UNIX。もともとは、timeout という関数で時間が来れば登録した関数を callするものですが、そこまでするとコードを小さくできないので set_/check_ という プレフィックスを付けた二組の関数にしました。もちろん 定期的な処理だけでなく(名前のとおり)一定の時間が立つのを待ち合わせるのにも使えます。
ボタンの処理
static uint8_t button_read() {
static uint8_t b1_state;
b1_state <<=1;
if (bit_is_clear(ROW2_PIN, ROW2)) {
b1_state |= 1;
}
if (b1_state == 0 || b1_state == 0xff) {
:
}
}
ざっくり簡略化すると上のような処理になっています。これはチャタリング除去の処理で、8回連続(120Hz なので、66ms の間)同じ値なら採用するというもの。