STM32シリーズのEXTIは、GPIOラインを割り込みに使える便利な機能ですが、CubeIDE とHALを通して使う場合、危険な落とし穴があります。
EXTI ( EXTernal Interrupt )は、GPIOを使った汎用割込みラインです。実際には内部回路の割込み源ともつながっているという、なかなか込み入った設計ですが、単純なシステムの場合には16本の外部入力ラインと考えてよいでしょう。EXTIは任意のポートを利用することができ、例えば、PA0, PB1, PC2, PA3,.. といった具合に、数字が重ならない限り好きなポートの好きなピンを割込み源とすることができます。
便利なEXTIですが、CubeIDEとHALを使う場合には注意が必要です。
CubeIDEはDevice Configuration ToolでEXTIの構成を行った場合、有無を言わさず割込み可能の状態でEXTIを初期化するというかなり困ったコードを生成します。当然GPIOのEXTIにはビットごとに割込みをマスクする機能がありますが、この機能を使うAPIがHALにありません(Githubで要求したが蹴られた)。
じゃぁ、仕方ないので(ものすごく嫌ですが)Device Configuration Toolで対応するNVICの割込みラインをディセーブルにしておき、あとでプログラム中にイネーブルにすれば…と思うのですが、その場合はCubeIDEが割込みエントリを生成しないため、割込みはデフォルト割込みハンドラに捨てられてしまいます。
私は割込み線というものはディセーブル状態で初期化し、準備ができたときにイネーブルするものだと思っているのですが、STはそうは考えていないようです。
ということでCubeIDEとHALを使う限り、こちらのタイミングとは無関係にEXTIが割込み可能になります。これに対応するため、EXTIの割込みコールバックでは必ずシステムが割込み受付可能かを確認するようにしてください。
紫ライブラリではEXTIを以下のように処理しています。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (murasaki::Exti::isReady()) { // Obtain the responding object. murasaki::InterruptStrategy *exti = murasaki::ExtiCallbackRepositorySingleton::GetInstance()->GetExtiObject(GPIO_Pin); // Handle the callback by the object. exti->Release(GPIO_Pin); } }
詳細はともかく、割込み処理が可能かどうか調べてから割込みを受け付けていることがわかります。
本来こういった処理はハードウェアが行うべきですし、EXTIにはそのハードウェアがあるのですが、現状、ほかに方法がありません。もしよい方法があれば教えてください。