PICでタイマー:目的の時間になるべく正確に割り込む

ストップウォッチなど高精度なタイマーであれば、外部オシレータからクロックを供給して、RTCC(Real Time Clock and Calender)等の専用モジュールで。。ということなりそうですが、今回はそこまでは考えてないです。

  • 内部クロックで
  • 8bitタイマーで

を前提とした内容です。「なるべく」と言ったのは前提からして矛盾しているからです。

タイマーで割り込み処理をするときにどの程度の精度で割り込むのか気になったので、MPLABX IDEのStopWatch機能でクロックをカウントしてみます。

タイマー0(8bit)で1秒ごとに割り込みたい

今回もPIC12F1822を使います。

以下の投稿に記載していますが、まずはちゃんとSimulator設定します。

MPLABX IDEシミュレータの使い方( Stimulus その5 )

mplabx_timer_1sec_000

今回は4MHzのシステムクロックなので、Instruction Frequencyは1MHzに設定します。そうしないとStopWatchの値が正しく表示してくれない。

TMR0を使った設定が下図になる。プリスケーラは使用せずに250カウントさせることによって250us毎にオーバーフローさせる。そして、ソフトウェア側で1secで割り込むようにします。(図のSoftware Settings)

mplabx_timer_1sec_001

ソフトウェア側ってのが何やっているのかというと、以下のように250us毎に割り込みが発生するので、1secなら4000回割り込んだらコールバックを呼ぶようになってます。

#define TMR0_INTERRUPT_TICKER_FACTOR    4000

void TMR0_ISR(void)
{
    static volatile uint16_t CountCallBack = 0;

    // Clear the TMR0 interrupt flag
    INTCONbits.TMR0IF = 0;

    TMR0 = timer0ReloadVal;

    // callback function - called every 4000th pass
    if (++CountCallBack >= TMR0_INTERRUPT_TICKER_FACTOR)
    {
        // ticker function call
        TMR0_CallBack();

        // reset ticker counter
        CountCallBack = 0;
    }

    // add your TMR0 interrupt custom code
}

この状態で、StopWatch機能つかって、実際に何カウントしてコールされるのか確認してみます。

PICの割り込み処理 ( MCC編 )

の割り込みで使ったプログラムと同じです。コールバック関数に適当な処理を入れてブレークしてみたところ、以下の様な結果に。

mplabx_timer_1sec_002

96ms遅い結果になりました。96msは上記の余計な処理のせいです。下記URLでもタイマーの検証しています。

96ms遅いということは、ソフトウェア側の4000回カウンタを減らして調整してみます。

96000us / 250us = 384
4000 - 384 = 3616

mplabx_timer_1sec_003

再度StopWatch実行。今度は足りない。。この「ソフトウェア側の処理」が誤差に大きく影響してそう。

mplabx_timer_1sec_004

こっからは微調整の世界

mplabx_timer_1sec_006

誤差100us

mplabx_timer_1sec_007

1カウント減らしたところ、174us誤差に広がった。誤差100us(誤差1/10000sec)ってところかと。

mplabx_timer_1sec_008

2.7時間で1secずれてくる計算になる。微妙なのとちょっとメンドクサイ。。「なるべく」という趣旨ではこんなもんだと思いますが、ちょっと腑に落ちない。。

参考サイト

先人たちがどうしているのか、以下のサイトが参考になります。

PICのタイマー割り込み

http://www.easyaudiokit.com/bekkan2020/TeaTime2020-6/led4.html

タイマ制御機能付き時計ユニット

http://www.picfun.com/equipj39.html

  • プリスケーラ設定でもズレるのでなるべく使わない。
  • タイマー0はフリーランさせて、TMR0レジスタに値を再設定するとズレる。

TMR0レジスタに値を再設定するとズレる」MCCが出力するコードは再設定しているんですよね。

void TMR0_ISR(void)
{
    static volatile uint16_t CountCallBack = 0;

    // Clear the TMR0 interrupt flag
    INTCONbits.TMR0IF = 0;

    TMR0 = timer0ReloadVal;

    // callback function - called every 3907th pass
    if (++CountCallBack >= TMR0_INTERRUPT_TICKER_FACTOR)
    {
        // ticker function call
        TMR0_CallBack();

        // reset ticker counter
        CountCallBack = 0;
    }

    // add your TMR0 interrupt custom code
}

フリーランさせるということは常にTMR0=0なので、コメントアウトして誤差見てみます。タイマー0は256usでオーバーフローするはずです。

void TMR0_ISR(void)
{
    :
    TMR0 = timer0ReloadVal;
    :
}

mplabx_timer_1sec_009

24usほど誤差出てます。

void TMR0_ISR(void)
{
    :
    //TMR0 = timer0ReloadVal;
    :
}

mplabx_timer_1sec_010

コメントアウトすると誤差ほぼなし。

ごちゃごちゃ試行錯誤していたが、とりあえず言えることは__interruptを付与した割り込み関数は、TMR0のハードウェアカウンタの定刻通りに割り込みされているのは間違いない。

その後、ソフトウェア側でゴチャゴチャやればやるほどズレていくわけです。特にMCCが生成するタイマーのソースにはカウンタを再設定しており、再設定するタイミングまでにタイマーが10カウント(10us)ほどしていました。なので、きっちり計測したい人は無駄のないタイマープログラムを自前で書くか、誤差を考慮したタイマー設定する必要がありそうです。

タイマーの誤作動??

それと、処理能力を超えるような短いタイマーの設定も注意

半固定抵抗の分圧をADCで読み取って、それに応じてタイマーのインターバルを変更するプログラムを作成していたとき、ある一定以上の短い間隔になったときに全く操作を受け付けなくなる現象が発生。半固定抵抗が壊れている?他のプログラムのバグ??と色々試行錯誤して調査した結果、タイマーのインターバルが小さすぎて割り込み書しかできなくなってたことが原因でした。

冷静に考えるとアホな話でしたが、いい勉強になりました。