背景
上一讲 STM32 CubeMX 学习:GPIO的使用 介绍了如何配置以及操作GPIO引脚。
这一讲我们通过中断来控制按键。关于中断的概念不做介绍。
HOST-OS : Windows-10
STM32 Cube :v5.6
MCU : STM32F429
LIB : stm32cube_fw_f4_v1250
知识
cortex-M4支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。
NVIC
是嵌套向量中断控制器(Nested Vectored Interrupt Controller),控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对Cortex-M4
内核里面的NVIC
进行裁剪,把不需要的部分去掉,所以说STM32
的NVIC
是Cortex-M4
的NVIC
的一个子集。
NVIC
负责控制中断响应,主要有三个参数:中断使能(Interrupts Enabled),抢占优先级(Preemption Priority),次优先级(Sub Priority,也叫响应优先级)。(优先级数值越小,优先级别越高)
- 中断使能:表面是否开启中断,如果开启中断,则满足中断触发条件时程序会跳到中断服务程序运行,否则不响应中断主程序继续运行。
- 抢占优先级:用来判断一个中断是否可以打断另外一个中断的中断服务程序抢先运行。
例如A中断触发,正在运行A中断的服务程序,此时B中断也触发,如果B中断的抢占优先级比A的高,则程序会打断A的中断服务程序,去运行B的中断服务程序,即中断嵌套。等B的中断服务程序运行完后继续运行A的中断服务程序。如果B的抢占优先级没有高过A的抢占优先级,则程序不会打断A的中断服务程序,而是待定A的中断服务程序运行完成后才运行B的中断服务程序。
- 次优先级:用来判断抢占优先级相同的几个中断那个中断会优先响应。如果几个抢占优先相同的中断同时触发,那么次优先级高的最先运行。
判断中断的优先级,先看抢占优先级,抢占优先级高的中断优先级别高。抢占优先级相同的情况下,响应优先高的中断优先级别高。抢占优先级和次优先级相同的情况下,根据中断向量表确定。
其实把Sub Priority翻译成次优先级就很好理解了:先看主优先级,再看次优先级,都相同则根据中断向量表的顺序。
优先级分组(Priority Group)
STM32以4个比特位表示中断的抢占优先级和次优先级。中断优先级分组是为了给抢占式优先级
和次优先级
在中断优先级寄丛器的四个比特位分配各个优先级数字所占的位数。
例如3位用于抢占优先级(优先级有2^3=8种优先级),1位用于次优先级(优先级有2^1=2种优先级)。
STM32F4的每个IO都可以作为外部中断的中断输入口,这点也是STM32F4的强大之处。
有关的定义(位于文件stm32f4xx_hal_gpio.h
中)
下面的代码会引申出2个概念:中断/事件、触发方式。
#define GPIO_MODE_IT_RISING 0x10110000U /*!< External Interrupt Mode with Rising edge trigger detection */
#define GPIO_MODE_IT_FALLING 0x10210000U /*!< External Interrupt Mode with Falling edge trigger detection */
#define GPIO_MODE_IT_RISING_FALLING 0x10310000U /*!< External Interrupt Mode with Rising/Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING 0x10120000U /*!< External Event Mode with Rising edge trigger detection */
#define GPIO_MODE_EVT_FALLING 0x10220000U /*!< External Event Mode with Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING_FALLING 0x10320000U /*!< External Event Mode with Rising/Falling edge trigger detection */
我们会发现,GPIO_MODE_IT_xxx(中断, interrupt)
与 GPIO_MODE_EVT_xxx(事件, event)
2种定义。那么它们有什么不同呢?
GPIO_MODE_IT_RISING
等能够触发中断,用在中断方式编程。GPIO_MODE_EVT_RISING
等只设置中断标志位,不产生中断,可以用在轮询方式。(比较少用)
中断/事件 的触发方式(没有边沿触发)
RISING : 上升沿触发。
FALLING: 下降沿触发。
RISING_FALLING:上升沿和下降沿都触发。
CubeMx 对 中断 的配置
只要我们使用到外部中断,就必须打开SYSCFG时钟。
1)在Pinout& Confiurgation
页的Pinout view
中,点击引脚,设置为GPIO_EXTI..
。
EXTI后面跟着的数字n代表第n个中断线,这会代表在哪个中断处理函数来响应它
2)点击左栏的GPIO
,选择配置的引脚,在界面下方中部靠左的位置可以看到 类似PF4 Configuration
这一栏,有以下选项:
- GPIO mode(模式)
External Interrupt Mode with Rising edge trigger detection(上升沿式外部中断模式)
、External Interrupt Mode with Falling edge trigger detection(下降沿式外部中断模式)
、External Interrupt Mode with Rising/Falling edge trigger detection(上升下降沿式外部中断模式)
External Event Mode with Rising edge trigger detection(上升沿式外部置位模式)
、External Event Mode with Falling edge trigger detection(下降沿式外部置位模式)
、External Event Mode with Rising/Falling edge trigger detection(上升下降沿式外部置位模式)
GPIO_MODE_IT_RISING能够触发中断,用在中断方式编程。而GPIO_MODE_EVT_RISING只设置中断标志位,不产生中断,可以用在查询方式
- GPIO Pull-up/Pull-down(上下拉):默认设置为
No pull-up and no pull-down
- User Label(用户标签): 可以标记这个引脚是做什么用的,提高可读性。
3)点击左侧NVIC
- 根据需要设置
Priority Group
- 勾选对应的中断使能(Enabled)
- 根据需要填写抢占优先级(Preemption Priority),次优先级(Sub Priority)
4)点击右上角的GENERATE CODE
生成工程
5)使用外部的工具链编译工程,确保没有问题。
代码分析
通过配置以后,在GPIO的初始化函数中就有了 中断的配置与使能
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin : PF7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/*Configure GPIO pin : PB14 */
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 2);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
// 补充,关闭中断的函数为:
// void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
}
同时,CubeMx已经根据配置的情况在./Src/stm32f4xx_it.c
文件中。
虽然外部中断可以来自不同的中断线,但在HAL库的机制中,外部中断函数都会先在HAL_GPIO_EXTI_IRQHandler
清除中断标志位,再调用同一个中断回调函数:HAL_GPIO_EXTI_Callback
。
从
MX_GPIO_Init
不难看出,我已经配置了2个中断,在下文中的EXTI9_5_IRQHandler
与EXTI15_10_IRQHandler
都会调用HAL_GPIO_EXTI_Callback
,并给出对应的中断引脚作为参数;所以,我们只需要重写HAL_GPIO_EXTI_Callback
时,判断参数就可以正确地做出响应。
/**
* @brief This function handles EXTI line[9:5] interrupts.
*/
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
/**
* @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_14);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}
此时,根据startup_stm32f429xx.s中的描述,每个中断服务函数代表一个(0~4)或者一类([9:5]为一类;[15:10]为另一类)
即:只要是中断线在EXTI_Line9_5,或者 EXTI_Line在10_15 的,中断服务函数就只有一个。
你可能会想,那这么多中断,我同时启动2个中断,会不会造成冲突。
由于中断线只有唯一性,可以在中断服务函数里面判断到底是哪个中断线触发,这也保证不会误触发或者是占线的一系列的问题。
; 中断线与中断处理函数 来自 startup_stm32f429xx.s
DCD EXTI0_IRQHandler ; EXTI Line 0 //只是管脚的中断服务函数,还有串口等等中断服务函数
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
...
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
...
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
添加代码
通过上面的分析,我们对于HAL处理外部中断有了一个系统的认识,那么我们目前唯一欠缺的就是重写HAL_GPIO_EXTI_Callback
函数。注意,代码应该插在 USER CODE BEGIN
、USER CODE END
之间。
/* USER CODE BEGIN 4 */
/**
* @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)
{
switch(GPIO_Pin)
{
case GPIO_PIN_7:
// do_someting_A();
break;
case GPIO_PIN_14:
// do_someting_B();
break;
default : break;
}
return ;
}
/* USER CODE END 4 */