**开发平台** :
正点原子 探索者STM32F407ZET6
cube mx:V 5.1.0
keil uVision5
项目地址:https://github.com/liliang1918/STM32LL
注意:
本教程默认读者已经对stm32和cubemx软件有一定了解;
为节省篇幅,详细配置cubemx的步骤仅在第一篇中说明,以后仅会简略介绍配置相关内容。
STM32LL库系列教程(四)——外部中断
实验目的:学会GPIO_IDR寄存器操作;
学会EXIT_PR寄存器操作;
学会外部中断的原理(重点);
了解按键消抖
实验现象:按下按键KEY_UP LED1翻转一次(红灯);
按下KEY2 LED2翻转一次(黄灯)
注意:如下原理图
WK_UP(PA0)按下按键会接到3.3V,我们配置中断类型为上升沿中断,内部下拉,当按下按键后,GPIO电平由低变高,触发上升沿中断
KEY0(PE2)按下会接到地,我们配置中断类型为下降沿中断,内部上拉,当按下按键后,GPIO电平由高变低,触发下降沿中断
这里就选用系统默认的优先级了,不做修改,具体含义在后面再具体解释
因为只对中断进行响应和操作,在这里对main函数中的while(1)中是不需要编写代码的。
stm32f4xx_it.c是存放各类中断服务函数的文件。
首先,对EXIT0的(PA0),因为正点原子的开发板并没有涉及硬件防抖,因此我们需要软件进行按键防抖,否侧,会导致按一次按键,会进入多次中断的问题。读者也可以把按键防抖的程序去掉再看一看效果如何。
在EXTI0_IRQHandler(void)
函数中,LL库自身做了判断、清除标志位的操作,保证了程序不会一直卡在中断服务函数中
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
LL_mDelay(10);
if(LL_GPIO_IsInputPinSet(GPIOA,LL_GPIO_PIN_0)==SET)//按键消抖
LL_GPIO_TogglePin(GPIOF,LL_GPIO_PIN_9);//翻转LED1
/* USER CODE END EXTI0_IRQn 0 */
if (LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0) != RESET)//检验EXIT0的标志位
{
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0);//清除EXIT0的标志位
/* USER CODE BEGIN LL_EXTI_LINE_0 */
/* USER CODE END LL_EXTI_LINE_0 */
}
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
同理对 EXTI2_IRQHandler(void)
自此,程序编写完成。程序可以下到开发板,观察现象了。
在第二节中,我们介绍了GPIOx_ODR,这个是操作输出端口的,而GPIOx_IDR就是读取输入端口电平状态的
我们通过调用LL_GPIO_IsInputPinSet
这个函数来读取GPIO的输入,在按键消抖程序中,通过10ms后的按键状态,即可判断是真实按下,还是抖动。
__STATIC_INLINE uint32_t LL_GPIO_IsInputPinSet(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
return (READ_BIT(GPIOx->IDR, PinMask) == (PinMask));
}
在进入中断服务函数 EXTI2_IRQHandler(void)
后,LL库会自行提供函数LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)
判断是否是由外部中断线0引发的中断并清除标志位,对这两个函数,他们都是操作挂起寄存器 (EXTI_PR) 来实现对应的功能的。
首先是LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)
__STATIC_INLINE uint32_t LL_EXTI_IsActiveFlag_0_31(uint32_t ExtiLine)
{
return (READ_BIT(EXTI->PR, ExtiLine) == (ExtiLine));
}
若PR0 =1,则中断线0,发生了相应的边沿事件,进一步确认中断的发生。
对于LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0);
__STATIC_INLINE void LL_EXTI_ClearFlag_0_31(uint32_t ExtiLine)
{
WRITE_REG(EXTI->PR, ExtiLine);
}
在确认中断线发生中断后,再对相应位写1,即可清除。
因为 NVIC 的这些寄 存器都是写 1 有效的,写 0 是无效的。
写1清除不太符合正常逻辑,记住即可
本部分内容参考了正点原子F4开发指南的 5.2.6 节 中断管理函数
STM32F4 将中断分为 5 个组,组 0~4。该分组 的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表 5.2.6.1 所示:
抢占优先级的 级别高于响应优先级。而数值越小所代表的优先级就越高。
这里需要注意两点:
第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看 哪个中断先发生就先执行;
第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级 中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
对于文章一开始对NVIC的配置,在这里就可以解释一下了。CUBEMX默认选择4位抢占优先级,0位响应优先级,因此,抢占优先级的范围是0-15,数字越小,优先级越高,响应优先级可以配置为0-1;默认所有中断的抢占优先级都是0(最高),都不能互相打断。
为了严谨,在这里补充一个不常遇到的情况——抢占优先级和响应优先级相同,在这时中断顺序由中断向量表确定。
而中断向量表可以由stm32f4xx中文参考手册
中查得:
篇幅有限,后续就不添加了,详细见参考手册 P234
以本程序为例,正是PA0映射在EXIT0上,所以触发的是EXIT0中断
整体的过程就是:首先main函数主程序中一直在while循环里面执行。当按键(PA0引脚)按下时,边沿检测电路检测到下降沿,触发中断,设置中断标识位。NVIC中断控制器判断EXTI0中断优先是否为最高,若为最高优先级则执行EXIT0中断。
在执行中断服务函数之前,Contex-M4内核先将现在使用到的寄存器和主程序中断点的地址压入堆栈(保护现场)。然后程序在中断向量表中找到EXTI0中断对应的地址(0x0000 0058)。这个地址存储的为EXTI0中断服务函数的入口地址。然后程序转跳到中断服务函数执行。
如下,即为上文提到的中断向量表,EXTI0中断对应的地址为0x0000 0058
在stm32f407的启动文件startup_stm32f407xx.s
中,我们就可以找到对应的中断向量表
在上面这张表中我们可以看到地址0x0000 0000保存的为栈顶的地址。0x0000 0004地址保存复位中断服务函数的地址。第22个中断为EXTI0中断,对应的地址为 22x4,即0x0000 0058。
正是这个设定的存在,才让EXIT0的中断服务函数,直接对应上了EXTI0_IRQHandler
这个函数
执行完中断服务函数后。内核从堆栈去除压入的寄存器数据恢复现场,取出主程序中断点的地址,转到到主程序中断点的地址继续运行主程序。
到这里就完成一次中断服务。简单的来说,触发中断时,硬件先标识中断标识位
,然后NVIC中断控制器判断中断优先级是否可以执行此中断
。执行中断时,先保护现在程序的状态
,将数据保存进堆栈中,执行完中断服务函数后,再在堆栈中取数据回复现场,跳回主程序继续运行
。如果在中断服务函数中有更高优先级的中断触发,则也会将现在的数据保存去执行更高优先级的中断服务函数
,即中断嵌套。高优先级的中断执行完后,恢复现场,继续执行低优先级的服务函数。
按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
—— 百度百科
而在抖动状态时,如果不采取一定措施,就会被单片机读到,错误的以为是按键被反复按下抬起,从而引起错误
软件消抖的原理通常是,当检测到按键按下后,隔10ms再去读按键是否还是按下的状态,如果是,就可以确认按键确实被按下了。
缺点:占用单片机资源,如果是在中断服务函数中,10ms是一个很长的时间了。
LL_mDelay(10);
if(LL_GPIO_IsInputPinSet(GPIOA,LL_GPIO_PIN_0)==SET)//按键消抖
LL_GPIO_TogglePin(GPIOF,LL_GPIO_PIN_9);//