CubeMXを使ったスケルトンで、割り込みを使ってみました。
Nucleo F722ボードにはユーザーボタンがあり、これを押すたびにLED3が反転するようなプログラムです。ユーザーボタンで割り込みを発生させ、そのハンドラでLEDをトグルしました。
また、プログラムにはCubeMXで定義したシンボルを使ってみます。
割り込みソース
割り込みのソースには、ユーザーボタンを使います。
Nucleoの回路図によれば、ユーザーボタンはPC13(GPIO Cのbit 13)に接続されています。Pinoutビューで見てみると、PC13にはUSER_Btnラベルが設定されています。
PC13にあらかじめUSER_Btnラベルが与えられているのは、CubeMXでプロジェクトを作る際、MPUではなくボードをターゲットとして作ったからです。ボードをターゲットにすると、このようにあらかじめGPIOが設定済みになります。
ConfigurationビューでGPIOを開いてみると、PC13がリストされています(下図)。
ここにはGPIOのピンをどのように初期化するかが指定されています。
前回LEDを点滅する際、何も考えずにGPIOをトグルしましたが、それはLEDの出力用GPIOがこのリストで出力として宣言されているからです。CubeMXはこのリストの宣言に基づいて初期化コードを出力します。
PC13の設定を見てみると、”External Interrupt Mode with Rising edge trigger detection”となっています。つまり、エッジトリガによる外部割り込みモードです。
先ほどPC13にはUSER_Btnというラベルが設定されていると説明しましたが、その設定はここで行われています。
次に割り込みの設定を見てみましょう。割り込みの設定はNVIC Configurationから見ることが出来ます(NVIC)
ここには、割り込みソースにすると設定したペリフェラルに関連する割り込みが列挙されています。GPIOC 13も外部割り込み入力として宣言しましたので、ここにEXTI (External Interrupt)が列挙されています。
デフォルトでは列挙されるだけでイネーブルになっていませんので、チェックボックスでイネーブルにします。
コードを生成するための準備はこれで終わりです。ボードの設定がデフォルトで割り込み受け付け可能でしたので、実質チェックボックスを1つチェックしただけで、割り込み対応スケルトンになりました。
割り込みハンドラを書く
ボタン・スイッチの動作を検出するために外部割込み(EXTI)を使います。
EXTI割り込みを起こすための設定はスケルトンが全部やっているため、ここでは発生した割り込みを受け取るハンドラの記述を行います。CubeMXが生成するスケルトンのEXTI処理手順は以下のとおりです。
- CPUが割り込みを受理し、CubeMXが生成したEXTI15_10_IRQHandler()が呼ばれる。
- EXTI15_10_IRQHandler()が、割り込み番号を引数としてHAL_GPIO_EXTI_IRQHandler()を呼ぶ。
- HAL_GPIO_EXTI_IRQHandler()が指定された割り込みが発生しているか確認し、発生していれば割り込みをクリアしてHAL_GPIO_EXTI_Callback()を呼ぶ。
ここで、HAL_GPIO_EXTI_IRQHandler()とHAL_GPIO_EXTI_Callback()は名前のとおりHALの関数であり、あらかじめ用意されています。
「コールバックがHALに存在するのはおかしくない?」
と思ってしまいますが、HAL_GPIO_EXTI_Callback()はweakと宣言されており、同名の関数があるとそちらがリンクされ、あらかじめHAL内部に用意されている実体は無視されます。うまい仕掛けではありますが、名前を間違えてコールバック関数を作ると、リンクエラーが起きないのに永遠に呼ばれませんのでご注意。
さて、実際のコードを見てみましょう。
stm32f7xx_it.c はCubeMXが生成するソース・ファイルです。EXTI15_10_IRQHandler()はここに生成されます。
/** * @brief This function handles EXTI line[15:10] interrupts. */ void EXTI15_10_IRQHandler(void) { /* USER CODE BEGIN EXTI15_10_IRQn 0 */ /* USER CODE END EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); /* USER CODE BEGIN EXTI15_10_IRQn 1 */ /* USER CODE END EXTI15_10_IRQn 1 */ }
名前からしてEXTI15からEXTI10がまとめられているようですが、これはSTM32F722のNVICがそのように配線されているためです。このへんはリファレンス・マニュアルと付きあわせて読んでいくことになります。
プログラムを覗いてみると、割り込み番号を引数としてHAL_GPIO_EXTI_IRQHandler()を呼び出しているだけです。そしてその呼び出しの前後にユーザーコードを書くよう指示があります。HAL_GPIO_EXTI_IRQHandler()は割り込みをクリアしますから、割り込みクリア前にしたいこと、割り込みクリア後にしたいことを書けというのでしょう。
この関数の中に割り込み処理を書く場合、「どの割り込みが発生したか」は自分で調べないといけません。その点は気をつけてください。
HAL_GPIO_EXTI_IRQHandler()とHAL_GPIO_EXTI_Callback()は、HALの中にあらかじめ用意されています。
/** * @brief This function handles EXTI interrupt request. * @param GPIO_Pin: Specifies the pins connected EXTI line * @retval None */ void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { /* EXTI line interrupt detected */ if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } } /** * @brief EXTI line detection callbacks. * @param GPIO_Pin: Specifies the pins connected EXTI line * @retval None */ __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { /* Prevent unused argument(s) compilation warning */ UNUSED(GPIO_Pin); /* NOTE: This function Should not be modified, when the callback is needed, the HAL_GPIO_EXTI_Callback could be implemented in the user file */ }
HAL_GPIO_EXTI_IRQHandler()は引数で渡された割り込みが発生しているか確認した後、発生している(!= RESET)ならばそれをクリアしてコールバックを呼びます。
コールバックはweak宣言されていることはすでに説明しました。コメントのノートを読むと、「この関数を変更するな。コールバック関数が必要なら、HAL_GPIO_EXTI_Callbackはユーザーファイルに書いていい」とあります。これに従えば、HALのHAL_GPIO_EXTI_Callback()はリンカによって無視され、ユーザーが新しく定義したHAL_GPIO_EXTI_Callback()がリンクされます。
今回はコールバックを上書きすることにしました。以下のコードをmain()に追加しています。
/** * @brief EXTI line detection callbacks. * @param GPIO_Pin: Specifies the pins connected EXTI line * @retval None */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if ( GPIO_Pin == USER_Btn_Pin ) // if EXTI13 HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin); // toggle LD3 }
weak宣言していないことに注目してください。
ここまで辿った呼び出し手順から、このコールバックが呼ばれた時点で、引数に指定されている割り込みが発生したことは確定しています。ですので、引数にでしめされる外部割り込みに必要な処理をここで行います。具体的には、PC13 (EXTI 13)割り込みの場合に、LEDをトグルします。
今回の試験プログラムは割込み源が一つだけですが、作法として処理すべき割り込みか否かを確認して、LEDの点滅を行っています。