STM32マイコンにおける割り込みの概要
STM32マイコンにおいて、割り込みは実行中のメインプログラムを一時停止し、特定のイベントに応じた処理を実行する仕組みです。割り込みにはいくつかのカテゴリがあります。
- 外部割り込み (EXTI): GPIOピンの状態変化により発生します。
- 内部割り込み: タイマー、USARTなどの周辺機能に起因します。
- システム割り込み: SysTickなどのコア機能に関連します。
割り込み処理には、NVIC(Nested Vectored Interrupt Controller)が優先度や有効化を制御します。
ハードウェア環境とソフトウェア準備
この実践では以下の環境を使用します。
- マイコンボード: STM32F103C8T6 最小システム基板
- 周辺部品: LED(PB0ピンに接続)、タクトスイッチ(PA0ピンに接続)
- 開発環境: Keil MDK (バージョン5.36)
- ファームウェアライブラリ: STM32 Standard Peripheral Library 3.5.0
目的は、PA0ピンのスイッチ押下をトリガーとしてPB0のLED状態を反転させることです。
外部割り込みの設定フロー
標準ライブラリを使用した外部割り込みの設定は、以下の手順で行います。
1. 関連周辺回路のクロックを有効化
GPIOポート、AFIO(Alternate Function I/O)のクロックを有効にします。AFIOは外部割り込みのピン割り当てに必須です。
// クロック有効化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
2. GPIOピンの動作モード設定
PA0ピンを入力(内部プルアップ)、PB0ピンを出力(プッシュプル)に設定します。
GPIO_InitTypeDef gpioConfig;
// PA0設定: 入力(プルアップ)
gpioConfig.GPIO_Pin = GPIO_Pin_0;
gpioConfig.GPIO_Mode = GPIO_Mode_IPU;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpioConfig);
// PB0設定: 出力(プッシュプル)
gpioConfig.GPIO_Pin = GPIO_Pin_0;
gpioConfig.GPIO_Mode = GPIO_Mode_Out_PP;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpioConfig);
// LED初期状態: 消灯
GPIO_SetBits(GPIOB, GPIO_Pin_0);
3. AFIOを用いた外部割り込みラインの割り当て
GPIOAのピン0を外部割り込みライン0に接続します。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
4. EXTIの動作パラメータ設定
割り込みトリガー条件(下降縁)を指定します。
EXTI_InitTypeDef extiConfig;
extiConfig.EXTI_Line = EXTI_Line0;
extiConfig.EXTI_Mode = EXTI_Mode_Interrupt;
extiConfig.EXTI_Trigger = EXTI_Trigger_Falling;
extiConfig.EXTI_LineCmd = ENABLE;
EXTI_Init(&extiConfig);
5. NVICによる割り込み優先度の設定
割り込みチャネルの有効化と優先度を指定します。優先度グループは一度だけ設定します。
NVIC_InitTypeDef nvicConfig;
// 優先度グループ設定 (全システムで1回のみ)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// EXTI0割り込み設定
nvicConfig.NVIC_IRQChannel = EXTI0_IRQn;
nvicConfig.NVIC_IRQChannelPreemptionPriority =:
nvicConfig.NVIC_IRQChannelSubPriority = 1;
nvicConfig.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicConfig);
6. 割り込みサービスルーチンの実装
割り込み発生時に呼ばれる関数です。割り込みフラグのクリアを必ず実行してください。
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
// チャタリング対策 (オプション)
// Delay_ms(10);
// LED状態反転
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0)) {
GPIO_ResetBits(GPIOB, GPIO_Pin_0); // LED点灯
} else {
GPIO_SetBits(GPIOB, GPIO_Pin_0); // LED消灯
}
// 割り込みフラグクリア
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
7. メイン関数での統合
上記設定をメイン関数で統合します。メインループでは特に処理を行わず、割り込みを待機します。
#include "stm32f10x.h"
int main(void) {
SystemInit();
// 上記1〜5の設定をここに記述
while(1) {
// バックグラウンドタスクがあればここに記述
// 割り込みは割り込みハンドラで処理される
}
}
実装時の注意点
- AFIOクロックの有効化: 外部割り込みを使用する場合はAFIOクロックを必ず有効にします。
- 割り込みフラグのクリア: 割り込みハンドラ内でフラグをクリアしないと、連続して割り込みが発生します。
- GPIO入力モードの選択: 回路に応じて適切な入力モード(プルアップ/プルダウン)を選択します。
- 優先度グループの設定: NVIC_PriorityGroupConfigはプログラム全体で一度だけ呼び出します。
応用例: 回転エンコーダの読み取り
2相式エンコーダのカウント例を示します。PB0とPB1を入力として使用します。
// エンコーダカウント変数
int16_t encoderCount = 0;
void Encoder_Init(void) {
// GPIO, AFIOクロック有効化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// PB0, PB1を入力(プルアップ)に設定
GPIO_InitTypeDef gpioConfig;
gpioConfig.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
gpioConfig.GPIO_Mode = GPIO_Mode_IPU;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpioConfig);
// 外部割り込みライン割り当て
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
// EXTI設定 (両ラインを下降縁トリガーに)
EXTI_InitTypeDef extiConfig;
extiConfig.EXTI_Line = EXTI_Line0 | EXTI_Line1;
extiConfig.EXTI_Mode = EXTI_Mode_Interrupt;
extiConfig.EXTI_Trigger = EXTI_Trigger_Falling;
extiConfig.EXTI_LineCmd = ENABLE;
EXTI_Init(&extiConfig);
// NVIC設定 (EXTI0, EXTI1)
NVIC_InitTypeDef nvicConfig;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
nvicConfig.NVIC_IRQChannel = EXTI0_IRQn;
nvicConfig.NVIC_IRQChannelPreemptionPriority = 1;
nvicConfig.NVIC_IRQChannelSubPriority = 1;
nvicConfig.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicConfig);
nvicConfig.NVIC_IRQChannel = EXTI1_IRQn;
nvicConfig.NVIC_IRQChannelPreemptionPriority = 1;
nvicConfig.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&nvicConfig);
}
// EXTI0割り込みハンドラ (PB0)
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) == SET) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) {
// PB0の下降縁でPB1の状態を確認し回転方向を判定
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) {
encoderCount--; // 反時計回り
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
// EXTI1割り込みハンドラ (PB1)
void EXTI1_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line1) == SET) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) {
// PB1の下降縁でPB0の状態を確認
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) {
encoderCount++; // 時計回り
}
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}