小ネタ:フラグを8秒間保持

あるイベントが起きたら、フラグを最後のイベントから一定時間だけ(たとえば8秒)立てておきたいとき、下のようなコードだとちょっとだめです

----------
volatile uint8_t flag;

ISR(TIMER0_OVF_vect) // 割り込みは8秒おき
{
    flag = 0;
}

int main()
{
    while (1) {
        if (check_event()) {
            flag = 1;
        }
    }
}
----------

これだと、割り込みハンドラでクリアするフラグが、8秒前に立ったものか1秒前に立ったものか区別つかないからです。


それではこうする?

----------
volatile uint8_t flags[8];
volatile *uint8_t ptr;

ISR(TIMER0_OVF_vect) // 割り込みは1秒おきに発生する
{
    if (++ptr > &flags[7]) {
        ptr = &flags[0];
    }
    *ptr = 0x0;
}

int is_flag_up()
{
    int i;
    for (i=0; i<8; i++) {
        if (flags[i]) {
            return 1;
        }
    }
    return 0;
}

int main()
{
    ptr = &flags[0];

    while (1) {
        if (check_event()) {
            *ptr = 1;
        }
    }
}
----------

これだと、分解能一秒で8秒間保持してくれますが、処理が煩雑だしメモリ食いです。

保持するのが8秒以内だったら、こんな方法はどうでしょう。

----------
volatile uint8_t flag;

ISR(TIMER0_OVF_vect) // 割り込みは1秒おきに発生する
{
    flag <<= 1;
}

int main()
{
    flag = 0;

    while (1) {
        if (check_event()) {
            flag |= 0x1;
        }
    }
}
----------

これなら、メモリをあまり食わないし処理は簡単だし、フラグ条件は flag が non-zero かどうかだけ見ればいいので扱いやすいしで、よさそうです。(実は動作確認してませんが)

組み込みさんの間では常識なのかもしれませんが、素人の私には、案外とこういうのが新鮮で楽しいです :-)

6 Comments

  1. 8秒 ONの状態をレベルで取りたいときには便利ですね。変化点エッジで取りたいときにはどうやったらスマートでしょうか?

    イベントがひとつだったら、イベントがきたときにタイマをスタートするというやり方もあります。

    イベントが複数でタイマリソースがひとつのときに次のような手を使うことがあります。常時短めの時間間隔で割り込んで、16bitとか32bitのカウンタをカウントアップするようにしておきます。イベントがきたらそのときのカウント値をホールドします。別の場所でホールドした値と現在カウント値を比較して所定時間(8秒)経ったかどうかを判定します。ラップアラウンド と カウンタ値のアトミックアクセスに気をつける必要がありますが、割り込みサービスルーチン内の処理時間がイベントの数に依存しないので、場合によっては便利です。

  2. Gan

    Chuck さんコメントありがとうございます。

    > イベントがひとつだったら、イベントがきたときにタイマをスタートするというやり方もあります。
    タイマを占有できる場合は、これはとてもいいですね。イベントが来るたびにタイマを巻き戻すだけでOKで、すごく安定して動きそうです :-)

    二番目の方法は、タイムスタンプを打つようなイメージなのかな?これはフラグをビットシフトさせる方法に似ていますが、フラグは8ステップで終わってしまうのと比べて、タイムスタンプならステップ数を断然増やせるわけで、ずっと高い精度が確保できそうですね。

    そういえば、AVR にも、イベントでタイムスタンプを打つ Input Capture 機能がありますね。周波数カウンタで使ってみましたがまだ使い慣れていなくてうまく動きませんでした。習得したい機能の一つであります。まだまだ修行は続きます。

  3. そうですね>タイムスタンプ。times(2) みたいな感覚で :) ( %man 2 times )

    また、単純に飽和処理を施したダウンカウントもよく使うと思います。数個のイベント管理の程度だったらISR内でダウンカウントさせてしまいます。

    MIDI-Trigger変換で良く使うことになると思います。MIDI NOTE ONイベントを受けてポートをアサートして一定時間経ったらネゲートする用途です(モノマルチを置かずに)。ポートに繋がるリズム音源ごとに最適なゲート時間が異なる(あるいはパラメトリックにしたい)ので。

  4. Gan

    おお、確かにトリガ幅を決めるのに使えますね。

  5. 先日、大将の課題で書いたMIDIクロックカウンタは、同時には1つの待ちしかないという前提だったけど、MIDI-Gateで使おうと思うと、ホボ同時、というか、アタマはピシっと合わなくても、あるタイミングでは複数のイベントが時間終わり待ちしてることがあるんですよねー。
    (そいや、あれって、実際はゲームのBGM発生器だったらしい。なにやってんだろーね、おもしろそーだけど。本当、ロジックでやる処理をAVR化しただけらしいです)

    昨日拝見して自分なりのソフトを書いてみようかと色々考えたんだけど、結局、8と決まってるなら(8で決まるようにベースのタイミングを作っちゃえば、か)シフトしちゃうのが楽でいいカナーとも思いましたunsigned charで宣言しそこなうと、アハハっすね。
    8以外はつかえないのがちとあれっすね。今試してるドラムシンセの入力回路、入力のレベルじゃなくて、オンにした時間でベロシティーが表現できないかなーとか思ってます。
    そーだ、イミディエイトで0x01を入れるより、0x01をオアするほうが早いんだ?ちゃんと調べてないけど。

  6. Gan

    マイコンでプログラミングしているときには、データのビット数をいつも意識するので、変数は、unsigned char とかでなく、uint8_t というような、明示的な宣言を使っています。確かC/C++ の新しい規格だったかな?

    フラグをシフトさせる方法のメリットは、精度よりも、プログラムのコンパクトさだと思います。データは1バイトしか使わないし、状態の更新もフラグのチェックもステップ数が少ないです。

    フラグを立てるときに flag = 0x01 としないで flag |= 0x01 とするのは、既存で立っているフラグをつぶさないようにするためです。どっちでも想定どおりに動くとは思いますが、まあ、好みの問題でしょうか。速さもどっちも同じだと思います。コンパイラが適切なオペレーションを選んでくれるはずです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください