ちょっと Tea Time!? PICのタイマー割り込みの憂鬱(4桁LEDバージョンアップ) 2020.5.17

PICで一定時間の待ち時間がほしいときがよくある。たとえば周波数の計測カウンタを一定時間だけ動かしたい場合に
正確な1秒の待ち時間が必要だったりします。これを実現するのに、一番簡単なのは無駄ループを回すこと。
10MHzで動いているPICなら、ちょうど2500000サイクルの命令を消費させればいいわけです。一番簡単で一番正確
なのですが、その間はCPUは何もできません。
#仕事でメーカへの製品発注後に、納期まで寝て待っているようなものですね(笑。

そういう使い方ができるなら、問題ないですが、多くの場合は他の処理も多いいわけで・・・・
#待ってる時間があるなら働け!といわれるのと同じでしょうか・・・。

方法方としては他の処理もこなすため、一定間隔でタイマー割り込みをかけるのが一般的でしょう。
たとえば、タイマー周期を1msに設定して、1秒を計時するのに、割り込み1000回を待つというやり方です。
でも、それを使って周波数カウンタを組んだときに、どうも期待した計測結果が得られずにすこし悩んでしまいました。
というとで、タイマー割り込みの誤差について調べてみました。

PICのタイマー割り込み
 PICにはタイマー割り込み専用のダウンカウンタ(8Bit)があり、これがゼロになると割り込みが発生します。
設定時間Tは
  T(s)=1/(動作クロック÷4÷プリスケーラ÷ダウンカウンタ
となります。割り込み周期Fはその逆数なので
  F(Hz)=動作クロック÷4÷プリスケーラ÷ダウンカウンタ
になります。

プログラムの記述としては、割り込みルーチンの中にタイマーを再設定する一行を入れます。というのは、割り込みがかかった時点では
タイマーがゼロになっているので、再度設定することで次の割り込み時間を設定するためです。このやり方は、いわゆるPICの解説書
にあったやり方で、おそらく教科書的なものだと思います(実際には違っていた)。

実際に測ってみると・・・・

(1)タイマーを割り込み毎に設定しなおす・・・誤差大
PICに10MHzの水晶を搭載して、割り込み周期を測定してみました。
タイマーの設定値は7で250回数えたら割り込みがかかるようにしています。
で、その結果は下記の通りです。
 微妙というか大きな誤差がでています。1%以下なので、あまり時間精度を追求する必要のないプログラムなら問題ないでしょうが、
周波数カウンタのような応用では1%近い誤差は致命的です。

分周比 カウンタ設定値
(実際の設定値は7)
256+1-7=250
割り込み周期
実測
期待される周期
誤差(%)
8 250 1.2395kHz 1.250kHz -0.84%
16 250 623.62Hz 625Hz -0.22%
32 250 312.78Hz 312.5Hz +0.090%
256 250 39.205Hz 39.0625Hz +0.3648%

この誤差の原因を考えてみましたが、よくわかりません。プリスケーラの分周比に応じて誤差の方向が変わっているので、これが鍵にはなりそうです。
ひょっとプリスケーラがタイマー設定と同時にリセットされるのでは?と考えましたが、それならばプリスケーラの値によらず誤差はマイナス(周期が
長くなる方向)ですが、そうではありません。割り込み処理に移行する間に、プリスケーラからのパルスを取りこぼすのかな?と考えましたが、
そうすればプリスケーラの値が大きい(例えば256)のときに、誤差がプラス(周期が短くなる)になるのが説明できません。
よくわかりませ〜ん!!

(2)タイマーの再設定はしない・・・・誤差ゼロの様子

今度は、タイマーの値を設定せずに割り込みをかけます。すなわちタイマーは8ビットなのでプリスケーラからのパルスを256カウント毎に割り込みをかけます。
さきほどの設定値250に比べると、定数が256になるので周期がとても中途半端な値になります。
で、その結果は下記の通りで、誤差はゼロの様子です。誤差があったとしてもオシロのカウンタの誤差か、PICに搭載した水晶の誤差と思われます。

分周比 カウンタ設定値
(実際の設定なし)
割り込み周期
実測
期待される周期
誤差(%)
8 256 1.2207kHz 1.2207kH 0%
16 256 610.38Hz 610.35Hz +0.0049%
32 256 305.19Hz 305.18Hz -0.0033%
256 256 38.148Hz 38.147Hz -0.0026%

これからわかった結果として、インターバルタイマーを使うときは、正確な計時をするにはタイマーは設定せずにフリーに動かすことが必要
ということのようです。

この場合、中途半端な割り込み周期になってしまいますので、たとえば1秒という間隔を得ようとする場合は次のようになるでしょう。
例えば、分周比16とした場合は610.35Hzの割り込み周期なので、まずは610回のループをカウントする。
610回のカウントでは時間は0.9994266秒なので、1秒にはあと573us足りません。そこで、610回ループカウントの後に無駄ループで573usを消費する、
という方法になるでしょう。
 573usの無駄時間は結構長いのですが、割り込み周期自体が1.64ms(610Hz)なので、割り込み処理の中で十分に消化できます。
そのためタイマによる多重割り込みがかかる心配はありません。
 こうすれば、かなり正確な1秒という時間が計時できそうです。

具体的な応用として

 オーディオカウンターとして4桁LED基板をリリースしていますが、これは既定の周波数の1〜2%以内の周波数なら、既定の周波数を
表示するものです。すなわち、入力が44.0kHzや44.3kHzであれば表示は44.1kHzとしています。といのも、オーディオでつかうクロックに
44.0や44.3kHzはないわけです。実際の周波数がそれであるならば、それは装置側の誤差でしょう。
 そこで、今回の応用としてはこの4桁のオーディオカウンターに追加して、周波数カウンターとしての機能も持たせることにしました。

この基板に周波数カウンターとしての機能を追加です。

なぜ、周波数カウンターの機能の追加において割り込みでの計時をするかといえば、使用するLEDがダイナミック点灯なので
のんびりと1秒間のパルス数だけを計測することはできません。オーディオカウンターとしてつかうなら、周波数の測定精度はさほど
必要ないので計時する時間は数msで十分です。そのため1つのプログラムループの中で周波数カウントとLED表示を入れ込むことができます。
しかしながら周波数カウンタでは、できるだけ長い時間でのパルスをカウントしないと、低い周波数での精度がでません。
そのため割り込み回数での計時を行い、同時に割り込み処理の中でダイナミック点灯の処理をこなす必要があります。


たとえば4500Hzの測定値です。

作成したプログラムは、1秒間隔での測定なので表示は1秒毎の更新になりますが、一応下限の測定は1Hzになります。
ただし、低周波数では誤差がでる可能性がありますが、まあオーディオでそんな低周波数を扱うこともないでしょう。
本当は10Hz以下の周波数だと、こんどは入力パルスの時間間隔を測ることで周波数を逆算することをすればいいのですが、
なんせこの基板につかっているPICの容量が小さいので、そこまではいりませんでした。上位のPICに変更すればいいのですが・・・・。

4桁LEDをバージョンアップ

プログラムのVer2へのバージョンアップで従来のオーディオカウンターと周波数カウンタがジャンパ一つで切り替えらるようにしました。

あたらしい4桁LED基板のマニュアルです。 → LED4Manual.pdf

今後はv2のリリースになります。

(おしまい)