拔出式开发之stm32外部中断的基本写法
拔出式开发之stm32外部中断的基本写法
实验目的
了解外部中断的原理、作用并熟悉其c语言实现。本次实验做一个简易计数器,当按下按键时计数器的值+1,并实时显示在oled屏上。
实验过程
硬件部分
如图接线:

由于没有按键,这里我使用一个碰撞开关代替。这个模块需要单独供电,不过用法和按键一样。
软件部分
首先开启时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
需要同时用到GPIOB
和AFIO
引脚,所以时钟也要一起开。 GPIO_EXTILineConfig
要写AFIO
的EXTICR
寄存器。如果AFIO
时钟没开,写操作无效或未定义行为。
PB1
接的是开关,初始化为上拉输入模式:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
后面外部中断需要用这个PB1
来触发,所以要把它映射到一个中断通道上,这里映射为EXTI1
:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
这个函数修改AFIO
的 EXTICR
寄存器组(AFIO->EXTICR[x]),将EXTI1
的端口来源设置为PORTB
。EXTI 有 0..15 条线,每条线对应引脚编号(0 对应 Px0,1 对应 Px1 …)。但“端口”可以是 A/B/C…中的任意一个,必须通过 AFIO 映射选择哪一个端口的引脚作为该 EXTI 线的源。
配置中断,关键在于EXTI_Trigger
属性,选择触发方式。如果希望在按下按键时触发,就配置为下降沿触发EXTI_Trigger_Falling
:
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
这段代码指定了要配置的EXTI中断线为EXTI1
,并把模式指定为中断模式(如果EXTI_Mode
填EXTI_Mode_Event
就是事件配置了)。最后,EXTI_LineCmd
负责使能(写入 IMR 掩码位),否则刚才的配置都无效。
下面是重点,配置NVIC
(嵌套向量中断控制器),需要指定这次中断的优先级属性。首先选择通道,EXTI1
中断线对应的是NVIC
中的EXTI1_IRQn
通道:
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
然后设置抢占优先级和响应优先级。注意,如果不手动指定优先级组,Cortex-M3
中的默认组是2,即2位的抢占和响应优先级,只能写0-3,不要超出了,我这里都设为1:
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
最后使能中断通道(等效于在 NVIC->ISER 寄存器写 1):
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
这样就可以写中断回调函数了,即中断时需要做什么:
void EXTI1_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line1) != RESET) {
Delay_ms(20);
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) {
counter++;
OLED_ShowNum(1, 1, counter, 5);
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
第一个判断条件EXTI_GetITStatus(EXTI_Line1) != RESET
检查 EXTI 线路的中断挂起标志(PR 寄存器)是否被置位。因为一个中断向量有时可能由多条EXTI线触发(例如 EXTI9_5_IRQHandler
),所以先判定是哪条线触发是好习惯。
用Delay
做简单的防抖空过安检抖动时间,之后再次读引脚状态看看开关是否被按下,如果按了就触发计数器和oled屏刷新。
最后,一定要清中断挂起位(写 1
到 PR 对应位)。如果不清,pending 位仍旧为 1,NVIC 会认为中断还未被处理,可能会马上重新进入 ISR(死循环)。所以清标志是必须的(通常在 ISR 末尾或开始都可以,但要确保清标志时机和逻辑正确)。
实验结果
试验成功,编译下载后可以实现实验目标。
改进与反思
对CPU来说,中断已属节外生枝,为了不耽误主程序和其它中断执行,在回调函数中应该尽量避免写I2C的操作。比较合适的做法是在中断中只更改状态,把写I2C的部分移到主程序:
volatile uint8_t key_flag = 0;
void EXTI1_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line1) != RESET) {
EXTI_ClearITPendingBit(EXTI_Line1); // 先清标志
key_flag = 1;
}
}
while (1) {
if (key_flag) {
Delay_ms(20);
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) {
counter++;
OLED_ShowNum(1, 1, counter, 5);
}
key_flag = 0;
}
}