2014年6月11日水曜日

7セグメントLEDの制御

こちらもネットワークとはあまり関係ない話です。

NTP時計では6桁分の7セグメントLEDを制御しています。
複数桁の7セグを制御するときは、通常、ダイナミック点灯制御と呼ばれる制御が使われます。この制御では制御のリアルタイム性が非常に重要になってくるので、タイマ2を使った割り込みで制御を行っています。

ところで、NTP時計ではこの7セグの明るさを変えたいというニーズが出てきます。
時計は24時間動き続けるもの。夜、部屋を消灯しても煌々と光り続けていたらとても目障りです。

なので、ダイナミック点灯制御に加えてPWM制御も行い、明るさを変えられるようにしました。さらに、CdSを使い、環境の明るさを取得し、設定したしきい値で自動的に7セグの明るさを変えるようにしています。

ところで、この話題をTwitterでしていたら、興味深い情報を教えてくれた人がいました。
実は昔、同じように7セグで時計を作ったことがあって、それにも同様にPWMによる明るさ制御はしていました。しかし、PWMのデューティー比が小さいうちはデューティー比を変えると目に見えて明るさが変わるのですが、デューティー比が大きくなるとまあ変わってはいるのでしょうが、そんなに大きく明るさが変わっているようには見えません。

3乗にどういう意味があるのかはわかりませんが、そのような経験から、明るさの設定値が大きくなればなるほどデューティー比を大きく変えるという相関は理に適っていると言えます。


次に、ダイナミック点灯制御の実装を考えます。
もともとこのPWMの明るさ制御をする前はタイマ2の割り込みで点灯させる桁を切り替えていましたが、PWMするとなるとさらに例えば256段階制御するならこの1/256の周期で割り込みを入れなければなりません(と思い込んでいた)。

割り込み周期は0x2000サイクルだったので、1/256にすると0x0020サイクル…32サイクル…。
当然、割り込み処理をしている間に次の割り込みが入り、永遠に割り込みが終わらない事態になってしまいました。

はい、お分かりの方も多いとは思いますが、割り込みが掛かったときにTMR2の値を適当に設定してやれば、(0xFFFF-TMR2の設定値) サイクル後に割り込みが入ることになりますね。
PIC24Fシリーズの割り込みは割り込みが発生する値をPRxレジスタに設定すればそこで割り込みが起こるので、こういう使い方をすっかり忘れていましたね。PIC18まではよくやっていたのに…。


というわけで、こんな感じになっています。

#define PWM_PERIOD ((uint16_t)0x2000)

void LED7SEG_SetBrightness(uint8_t value)
{
    uint32_t buf = value + 1ul;

    buf = (uint64_t)buf * buf * buf * PWM_PERIOD / (256ul * 256 * 256);

    PWMOnStart = PWM_PERIOD - buf;
    PWMOffStart = buf;

    PWMOnStart = min(PWMOnStart, PWM_PERIOD);
    PWMOffStart = min(PWMOffStart, PWM_PERIOD);

    Brightness = value;
}

消灯はしたくないので、0~255の設定値を1足して1~256にしてあります。そして、それを3乗して256の3乗で規格化したうえで、PWM_PERIODをかけています。PWMの周期です。
そして、TMR2のLEDがONになるときのスタート値とOFFになるときのスタート値を計算し、保存しておいています。

void _ISR __attribute__((__auto_psv__)) _T2Interrupt(void)
{
    static uint8_t cnt;
    IFS0bits.T2IF = 0;

    if(cnt++ & 0x01) {
        TMR2 = PWMOnStart;

        Show7SEG();
        LED_COLON = Colon;
    } else {
        TMR2 = PWMOffStart;

        LATA &= ~(MASK_7SEG_RA | MASK_7SEGTR_RA);
        LATB &= ~(MASK_7SEG_RB | MASK_7SEGTR_RB);
        LATC &= ~(MASK_7SEG_RC | MASK_7SEGTR_RC);
        LED_COLON = 0;
    }
}

割り込みはこのようにして、カウンタをずっとインクリメントしていき、下1bitが1のときか0のときかでONにする処理、OFFにする処理を使い分けています。


余談ですが、これ、設定値が小さい時は全然明るさ変わって見えないんですよね…。
3乗を導入したから仕方ないって言えばそうなんですが、 例えば、明るさ設定値+1が32以下のときのPWMのONサイクル数を示したのが以下の図です。

はい。12以下に至っては、ONサイクル数がゼロでずーっときちゃってますね…。
多分、13以降もある一定の値までは、割り込みルーチンを抜けるのにこれ以上時間が掛かってしまい、実質的に明るさがほとんど変わらないゾーンが出てくるはずです(それでもOFFサイクル数は若干変化するので少しは変わって見える?)。
うーんこの微妙な感じ。
PWMの周期を大きくすればここらへん明確に出てくるんでしょうが、そうするとダイナミック点灯制御がちらついて見えてしまいます。 となると解決策はマイコンを高速で動かすこと…もうこれ以上速度上がらないよ!

というわけで、まあ、結局3乗導入したけどそれはそれ残念なことが起こっちゃったテヘペロッ☆というオチでした。

0 件のコメント:

コメントを投稿