请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
(代码仓库地址:https://gitee.com/luobing4365/yie002-explorer
具体参考博客:YIE002开发探索-Gitee代码仓库说明)
上一篇中,实现了按键的控制功能。本篇准备使用外部中断的方式,来实现对按键的控制。
1 STM32的外部中断
在跑马灯的实验中,简要的介绍过GPIO的设置。对于本篇来说,使用GPIO作为外部输入中断,所需要关注的知识点有两块:
1) 如何配置外部中断,以及与之相关的中断程序的编写;
2) 中断管理分组以及中断的优先级。
1.1 外部中断/事件控制器(EXTI)
STM32的每个IO都可以作为外部中断的中断输入口。STM32F103C8T6有19个能产生事件/中断请求的边沿检测器,每个输入线可以独立配置成输入类型和对应的触发事件(上升沿、下降沿或双边沿触发)。
这19个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
也就是说,供IO口使用的中断线只有16个。而STM32的IO口数目是超过16个的,因此,必然产生共用的问题。
在设计上,STM32的管脚GPIOx.0GPIOx.15(x=A/B/C/D/E/F/G)分别对应中断线015,也即每个中断线最多对应7个IO口。比如,线1对应GPIOA.1、GPIOB.1、GPIOC.1…GPIOG.1。中断线每次只能连接到1个IO口上,在平常编程中,需要通过配置决定中断线连接到哪个GPIO上。
如图1,给出了GPIO与中断线的映射关系图。
![](http://yiiyee.cn/blog/wp-content/uploads/2021/07/YIE002EXPLORER04-001.png)
当然,由于我们使用Cube MX编程,这些配置工作,通过图形配置以及自动生成代码,就可以完成了。对于中断你配置的细节,可以参考相应的文档(RM0008)。
另外一个需要注意的问题是,STM32的IO外部中断函数只有6个(在启动文件startup_stm32f103xb.s中可以看到)。
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
从函数字面上也可以看出,中断线0-4分别对应一个中断函数;中断线5-9共用中断函数EXPORT EXTI9_5_IRQHandler;中断线10-15共用中断函数EXPORT EXTI15_10_IRQHandler。因此,对于共用函数的中断,在中断函数中还需要分别判断是哪个引脚引发的中断,然后再进行相应的动作。
1.2 中断管理和优先级
ARM Cortex-M3内核支持256个中断,包括16个内核中断和240个外部中断。STM32只是使用了其中一部分,它有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
对于STM32F103系列的单片机,又只有60个可屏蔽中断。中断的控制,在单片机内部提供了ICER(中断除能寄存器组)、ISPR(中断挂起控制寄存器组)、ICPR(中断解挂控制寄存器组)、IABR(中断激活标志位寄存器组)和IP(中断优先级控制寄存器组)等来进行操作。
对于中断相关寄存器的操作,可以参考《Cortex M3权威指南》,我们在编程时,主要关注的是中断优先级的处理。
优先级的分组是通过AIRCR(应用程序中断及复位控制寄存器)实现的,其bit 8~10用来定义分组。如表1所示。
表1 AIRCR的中断分组设置表
组 | AIRCR[10:8] | 分配结果 |
---|---|---|
0 | 111 | 0 位抢占优先级, 4 位子优先级 |
1 | 110 | 1 位抢占优先级, 3 位子优先级 |
2 | 101 | 2 位抢占优先级, 2 位子优先级 |
3 | 100 | 3 位抢占优先级, 1 位子优先级 |
4 | 011 | 4 位抢占优先级, 0 位子优先级 |
优先级分为抢占优先级和子优先级,抢占优先级在前,子优先级在后,数值越小优先级越高。每个外部中断都有一个优先级寄存器,占用8位来表示优先级。 STM32F103用了60个中断,其优先级相关的位也只用了4位。也就是表1中,抢占优先级和子优先级共用的4位。
举例来说,当抢占优先级占了3位时,它可以取值0至7,此时子优先级只能取值0和1;当抢占优先级占了4位,则可取值0至15,子优先级只能取值0了。
需要注意的是:如果抢占优先级和子优先级一样,哪个中断先发生就先执行;高抢占优先级可以打断正在进行的低抢占优先级的中断,而相同抢占优先级的中断,高子优先级中断不可以打断低子优先级中断。
总结来说,对于GPIO的外部中断,一般的步骤如下:
1) 初始化IO口,开启时钟;
2) 设置IO口与中断线的映射关系;
3) 设置触发条件;
4) 配置中断分组(NVIC),使能中断;
5) 编写中断服务函数;
实际上,使用Cube MX编程,除了第5步编写中断服务函数外,其他步骤都可以自动生成代码。下面进入实际的操作环节。
2 YIE002-STM32型的外部中断编程
编程步骤包括Cube MX的图形配置,生成代码,以及编写中断服务函数。
2.1 Cube MX的图形配置
使用上一篇的代码,在Cube MX上重新配置引脚参数。如图2所示。
![](http://yiiyee.cn/blog/wp-content/uploads/2021/07/YIE002EXPLORER04-002.png)
将按键相关的PA1、PA5、PA6都配置成外部中断,其他引脚的配置不变。另外,在GPIO的配置页面,将PA1、PA5和PA6均配置为下降沿触发中断(缺省是上升沿触发)。
对于中断优先级的配置,可在System Core中,选择NVIC,在配置界面中修改。如图3所示。
![](http://yiiyee.cn/blog/wp-content/uploads/2021/07/YIE002EXPLORER04-003.png)
首先,在Pritority Group的下拉菜单中,选择为抢占优先级分配2个位。也就是说,抢占优先级可以取值0至3;子优先级占2位,取值也是0至3。
然后,分别为中断函数设置相应的优先级,并使能中断就可以了。本例中,两个外部中断的抢占优先级都是2,子优先级分别设为了0和1。
完成上述设置后,自动生成代码,就完成了前期工作了。
剩下的工作,只需要编写中断服务程序就可以了。
2.2 中断服务程序的编写
在生成的代码中,MX_GPIO_Init()用来初始化GPIO,与LED灯和按键相关的GPIO,都在此完成配置。从这个函数中,可以看到外部中断的配置过程。
我们所关注的两个中断函数,均位于stm32f1xx_it.c中。分别为:
void EXTI1_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
}
这两个函数都调用了HAL_GPIO_EXTI_IRQHandler(),它位于stm32f1xx_hal_gpio.c中,内容为:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
注意其中的函数HAL_GPIO_EXTI_Callback(),这就是Cube Library提供的,供用户修改的中断函数。框架代码中,帮助我们处理了清中断的操作。编码时所要做的,是关注应用需求。
在main.c的 “USER CODE BEGIN 4”和“USER CODE END 4”之间,添加如下代码:
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_1)
{
HAL_Delay(10);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_12);
}
if (GPIO_Pin == GPIO_PIN_5)
{
HAL_Delay(10);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_13);
}
if (GPIO_Pin == GPIO_PIN_6)
{
HAL_Delay(10);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);
}
}
/* USER CODE END 4 */
就完成了类似上篇博客的按键功能。
在HAL_GPIO_EXTI_Callback()中,对引发中断的引脚进行了判断。不同的按键按下,将对不同的LED灯进行操作。
当然,由于使用了中断函数,主函数main()中什么代码都不用添加。
1,009 total views, 1 views today