Raspberry Piでのシステムタイマ割り込みを試してみた

timer-irq2.png

Raspberry Piには「System Timer」と「ARM Timer」の2系統のタイマがあります。
前回は、このうちARM Timerのほうを使いましたが、今回は、同様にSystem Timerでの割り込みを使い、周期の異なる2つのタイマを並列に動かしてみました。
コードは以下に置いてあります。

bare_matal_rpi_zero/timer-irq2 at master · boochow/bare_matal_rpi_zero

System Timerの解説はマニュアルの172ページから記載があります。
ARM Timerは1つのタイマしか使えませんでしたが、System Timerは4つのタイマを使うことができます。

その代わり機能的にはARM Timerよりもさらにシンプルになっています。
要約すると

・値が1ずつ増えていく64bit カウンタが1つある。クロックは1MHz固定
・32bitのタイマが4つあり、上記のカウンタの下位32bitと値が一致したら割り込みがかかる

これだけです。
ちなみに、この64bitカウンタはベアメタルの最初のLチカのときに、時間計測用に使っています。

32bitのタイマは、それ自体の値は時間が経過しても変化しません。
タイマというより目覚まし時計に似ていて、設定した時刻に割り込みをかけられるアラームが4つある、みたいな感じです。

周期的に割り込みをかけたい場合は、割り込みがかかったときに、次に割り込みをかけたいタイミングを新たに設定しなおす必要があります。逆に、使い方によってはGPIOを複雑なリズムでオン・オフできますので、PWM的なこともできそうではあります。

使い方も単にレジスタにアラーム時刻を設定するだけです。
タイマ0~3の4つのタイマがありますが、0と2はGPUで使用しているそうです
そのため、実際に使えるのはタイマ1と3の二つです。

今回は、タイマ1を960000カウント、タイマ3を720000カウントの周期にしてみました。
前回同様、スタティック変数の値を上記の周期でインクリメントします。
割り込みハンドラは以下のようになります。

IRQはいろいろな要因で起こりますので、割り込みハンドラ内でどんな原因による割り込みなのかを調べています。
このために使うのが、割り込み制御レジスタの一つで割り込み要因を表す「ペンディングレジスタ」です。
このレジスタは「発生したが処理されていない割り込み」のフラグが立っています。

前回も書いたように、割り込み制御レジスタは3つ(Basic、IRQ1、IRQ2)あります。
どのレジスタのどのビットが、何の割り込み要因に対応するのかはマニュアルの113ページに一覧があります。
しかし、この表は不完全でシステムタイマ割り込みについての記載も抜けています。

irq-table1.png

ビットと割り込み要因の関係の完全なリストは、Linuxカーネルのソースコードにあります。

linux/platform.h at rpi-3.6.y · raspberrypi/linux

システムタイマ0~3は、割り込み制御レジスタのbit0~3に割り当てられていることが分かります。
割り込みを有効にする、割り込みハンドラの中でペンディングレジスタを確認する、等はこの情報に基づいて行います。

注意点としては、「同時に複数のIRQが起こった場合」、ペンディングレジスタにはそのうち1つの割り込み要因しか反映されないようです。
例えば、タイマー1を周期20、タイマー3を周期30で、初期値0から動かしたとします。
割り込みハンドラ内では、割り込みがかかる都度、タイマーの値を更新(+20または+30する)します。
すると、タイマー1の3回目およびタイマー3の2回目で、

タイマー1: 0  20  40  60
タイマー3: 0    30    60

どちらのタイマーも最小公倍数である60になってしまいます。
このとき、割り込みがかかるとペンディングレジスタには片方(私が試した限りでは、常にタイマー1)だけがフラグが立ちます。

従って、このフラグをチェックしてタイマーの値を更新していると、タイマー3の値が更新されなくなってしまいます。
初期値を少しだけずらせば、タイマーの値が一致する可能性を排除できます。
例えば、初期値を5にしておけば、

タイマー1: 0  20  40  60
タイマー3:  5    35    65

というようになり、値がぴったり一致することはありません。
ただし、初期値の差があまりに小さいと、割り込み処理が完了する前に次の割り込みが起こってしまうケースもあるかもしれません。

コメント