Arduino 周波数/周期カウンタ (7)

以降の記事では、記述を簡略化するために、WinAVR (AVR 用 GCC) の割り込みベクトルの表記にしたがって、

  • 「インプット・チャプチャ」を「CAPT」
  • 「タイマ・オーバーフロー」を「OVF」

と略記します。
たとえば、「インプット・キャプチャ割り込み」は「CAPT 割り込み」となります。
16 MHz クロックを 16 ビットカウンタである Timer1 でカウントすると、一回りで約 4 ms となりますが、もっと長いゲートタイムに対応するため、16 ビット・カウント・レジスタ「TCNT1」の上位ワード拡張となる「icr1x」をソフトウェア的に実現しています。
Timer1 の OVF 割り込みのサービス・ルーチン内で「icr1x」をインクリメントします。
16 ビット・カウンタ TCNT1 の値、CAPT 入力、およびゲートタイムの関係の図を下に示します。

上側で、のこぎり波状の波形となっているのが TCNT1 のカウント値で、下限 (BOTTOM 値) は 0x0000、上限 (TOP 値) は 0xFFFF です。
図では OVF を 8 回数えて、つまり 4 [ms] * 8 = 32 [ms] 程度がゲートタイムとなっています。
下側の波形が入力信号で、例として ICP1 端子に入力されるものとしています。 実際は、入力信号が直接使われるのではなく、カウンタのクロックに同期化する処理を施された後にキャプチャのトリガとなります。 ここでの説明では、立ち上がりエッジが有効とします。
この例では、ゲートタイム中に ICP1 の立ち上がりエッジが 3 回ありますから、周期としては 2 周期分ということになります。
TCNT1 の上位拡張レジスタ「icr1x」のインクリメントは OVF 割り込みによってソフトウェア的に実行されますから、CAPT 割り込みと OVF 割り込みの時間関係によっては (対策を施さなければ) インクリメント処理が完了する前にキャプチャされて、カウント値に 0x10000 だけの誤差が生じる可能性があります。
下に示すタイミング・チャートは、ATmega48/88/168/328 のデータシートの「Figure 15-12 Timer/Counter Timing Diagram, no Prescaling」に、後に示すような実験により得られた結果の推測を加えて書いたものです。

各信号は、上から順に、

  • clk I/O -- Timer1 の入力クロック (Pakurino/Arduino では 16 MHz)
  • TCNT1 -- Timer1 の 16 ビット・カウンタ本体の値
  • TOV1 -- タイマ/カウンタ 1 割り込み要求フラグレジスタ TIFR1 中のオーバーフロー・フラグ
  • ICF1 -- 同 TIFR1 中のインプット・キャプチャ・フラグ
  • ICR1 -- インプット・キャプチャ・レジスタの値
  • CPU -- 実行中の命令および割り込みシーケンスの状態

を示しています。
上の図は、CAPT されて ICR1 に入る値が「0xFFFF」となるようなタイミングでインプット・キャプチャが発生したものとしています。
そのような条件では、OVF 割り込み要求の TOV1 と、CAPT 割り込み要求の ICF1 とが「同時」に発生します。
割り込みの優先順位は、割り込みベクタテーブルのアドレスの若い方が優先されるような順位付けになっています。 したがって、同時に発生した CAPT 割り込みの方が、OVF 割り込みより優先的にサービスされます。
割り込み要求が生じた時点で実行中の CPU 命令を OP_n とし、それが 1 サイクル命令であったとすると、上の図のように OP_n の命令の実行後に CAPT 割り込みをサービスするシーケンスが開始されます。
したがって、まず、CAPT 割り込みがサービスされ、ついで OVF 割り込みがサービスされます。
キャプチャされたカウント値が 0xFFFF ということは、オーバーフローにより上位桁に桁上がりが生じる直前ということですから、CAPT → OVF という順番でサービスされるのは正しいということになります。
次の図は、CAPT のタイミングが 1 クロック遅れて、ICR1 に「0x0000」がキャプチャされる場合です。 OVF 割り込み要求が発生する時点で 1 サイクル命令を実行中であるとします。

この場合、OVF 割り込みと CAPT 割り込みは同時ではなくなり、時間的に 1 クロック早く発生する OVF 割り込みが先にサービスされます。
OVF 割り込みサービス・ルーチンでは、ソフト的に実現した上位桁のカウンタをカウント・アップして終了します。
その後にサービスされる CAPT 割り込みサービス・ルーチンでは、すでに正しくインクリメントされたカウンタ上位桁を参照するので、この場合も問題は生じません。
問題は、次の図のように、OVF 割り込み要求が発生する時点で実行中の命令が 2 サイクル以上の命令となる場合です。

確かに OVF 割り込み要求の方が先に発生するのですが、実行中の命令が 2 サイクル (以上) かかるので、命令実行中に優先度の高い CAPT 割り込み要求に「追いつかれて」しまい、命令終了時の割り込みシーケンス実行の判断では、優先度の高い CAPT 割り込みの方が先にサービスされることになります。
この場合には、オーバーフローが生じているので、ソフト的に設けたカウンタ上位桁のインクリメントをしなければならないのですが、CAPT 割り込みの方が先にサービスされるので、まだインクリメントされていない上位桁を読み込んでしまい、正しくない結果となります。
次回に続きます。