Learning interrupt and exception mechanism of ARM Cortex-M0.
本来ならば BLE Nano の紹介を続けたいところなのですが、実は作業はずっと先を行っていて、しかし割込周りをよく理解しないまま作業を進めてきてしまいました。ようやくその辺りを勉強したので、今日は ARM Cortex-M0 の割込について備忘録を書いてみることにします。
私は ARM7TDMI(ARMv4T アーキテクチャ)時代も含めて、ARM のシステムコードを書いてきた経験がないので、実は Cortex-M0 が始めてのシステムプログラミングです。伝統的な ARM の割込は(基本的に)1つの割込 IRQ で受けて、割込要因はソフトウェアで分岐するというものでした。実は、Cortex-M シリーズには NVIC (Nested Vectored Interrupt Controller) というものが設けられ、割込プログラミングを比較的容易に扱えるような仕組になっています。
mbed で ARM Cortex-M0 のコードを書いていると、割込の禁止/許可に __disable_irq(), __enable_irq() というイントリンシクスが出てきます。TI の C6000 DSP などに慣れている私としては、禁止は良くても許可のほうが気になります。C6000 では、禁止前の状態に戻すのに restore というオペレーションをすることが多いからです。例えば C6000 では、割込発生時には(ハードウェア的に)ネスト割込が禁止されているので、仮に割込ベクタ #5 の割込処理中にネスト(多重)割込を受け付ける場合には、内部で enable 処理が必要です。そのため、あるコード(あるいは、そこから呼び出された関数)の実行中に割込が禁止なのか許可なのかは自明でないので、disable は良くても、その後は enable ではなくて restore オペレーションをアトミックに処理することで、割込禁止/許可状態が戻ることを保証している訳です。
しかし、Cortex-M0 のイントリンシクスにそのような restore オペレーションはありません。__disable_irq() した後に無条件に enable にしてしまって良いのでしょうか?
このような設計になっている理由が、ARM Cortex-M0 の NVIC という仕掛です。前述の TI C6000 DSP などですと、割込要因には番号が振られていて値の小さいほうが優先順位が高いことになっています。しかしこれは、複数の割込がきっかり同時に発生したとき、あるいはもっとよくあるケースとして、グローバル割込禁止が解除された場合などに複数の割込が同時にペンディングしている場合は意味がありますが、通常はあまり意味がありません。C6000 で多重割込を許す場合には、明示的に特定の割込要因だけマスクを外してグローバル割込許可をする必要があります。これはこれで理にかなった設計なのですが、多重割込の処理にオーバーヘッドがあることは否めません。(多重割込は、必要なければ使わないに越したことはないのですが。)
前置きが長くなりましたが、ARM Cortex-M0 の NVIC では、32種類の割込要因番号とは独立(直交)に 4段階の割込優先順位を付けることができる設計になっています。そして、割込処理中は、Interrupt PSR (IPSR) というレジスタに処理中の割込要因番号が納められているので、いつでもそこを見ればどの要因の割込を処理しているかが分かるようになっています。さらに、仮に割込要因 #5 を処理しているときに、その割込要因の優先度 p(5) よりも高い優先度 p(7) を持つ割込要因 #7 が発生した場合には、グローバル割込禁止(PRIMASK = 1)になっていない限り、その割込を即座に受け付けるという設計になっています。
このように、多重割込(nested interrupt)をハードウェアで実現しているために、グローバル割込禁止/許可を操作するケースは少なくなります。time critical な処理をしているときやシステム設定時などに、何が何でも割込が起きては困る場合は __disable_irq() を実行しますが、それが終わったら __enable_irq() を実行して問題ない、ということになります。逆に言うと、割込処理中にも通常はグローバル割込許可になっているので、もし他の割込をネストさせたくなければ、割込優先度を適切に設定しておけば良い、ということになります。小さな組込アプリケーションでは、必要十分かつ分かりやすい設計かと思いました。
おまけの CMSIS
ARM Cortex には CMSIS(シムシスと読むらしい)という考え方があって、他のアーキテクチャから来たひとには「なんだ?」と思わせますが、これは Cortex Microcontroller Software Interface Standard の略で、プログラミングモデルを統一するための仕様だそうです。
マイクロプロセッサのシステムコードを書こうとすると、上述のような割込関連イントリンシクスの名称に加えて、制御レジスタのアクセス方法とか、例外の名称とか、C言語のシステム API 関数とかを決めなくてはなりませんが、TI DSP などと違って ARM には様々な開発ベンダや開発ツールが存在します。そのため、それぞれが勝手に名称やアクセス方法を決めてしまうとプログラマの混乱の種になりますので、その辺りを統一しましょう、ということのようです。
つまり、先ほどの __disable_irq() などもアセンブリニーモニックの名称ではありませんが、CMSIS に従っていれば、どんな開発ツールでも同様に使うことができますよ、ということのようです。(__disable_irq() と __enable_irq() は、それぞれアセンブリニーモニック CPSID I と CPSIE I が一対一に対応しています。)
おしまい。