STM32F7学习笔记13:中断详解
1. 中断类型
F767在内核水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断。 其中系统异常有10个,F767外部中断有110个,其余F7系列外部中断有150个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。
有关具体的系统异常和外部中断可在标准库文件举例为F767的stm32f767xx.h这个头文件查询到,在IRQn_Type这个结构体里面包含了F7系列全部的异常声明。
系统异常如下表所示
编号 | 优先级 | 优先级类型 | 名称 | 说明 | 地址 |
---|---|---|---|---|---|
保留(实际存的是 MSP地址) | 0X0000 0000 | ||||
-3 | 固定 | Reset | 复位 | 0X0000 0004 | |
-2 | 固定 | NMI | 不可屏蔽中断。RCC 时钟安全系统(CSS) 连接到 NMI 向量 | 0X0000 0008 | |
-1 | 固定 | HardFaul t | 所有类型的错误 | 0X0000 000C | |
0 | 可编程 | MemManag e | 存储器管理 | 0X0000 0010 | |
1 | 可编程 | BusFault | 预取指失败,存储 器访问失败 | 0X0000 0014 | |
2 | 可编程 | UsageFau lt | 未定义的指令或非 法状态 | 0X0000 0018 | |
保留 | 0X0000 001C-0X0000 002B | ||||
3 | 可编程 | SVCall | 通过 SWI 指令调用的系统服 务 | 0X0000 002C | |
4 | 可编程 | Debug Monitor | 调试监控器 | 0X0000 0030 | |
保留 | 0X0000 0034 | ||||
5 | 可编程 | PendSV | 可挂起的系统服务 | 0X0000 0038 | |
6 | 可编程 | SysTick | 系统嘀嗒定时器 | 0X0000 003C |
外部中断如下表:
编号 | 优先级 | 优先级类型 | 名称 | 说明 | 地址 |
---|---|---|---|---|---|
0 | 7 | 可编程 | 窗口看门狗中断 | 0X0000 0040 | |
1 | 8 | 可编程 | PVD | 连接EXTI 线的可编程电压检 测中断 | 0X0000 0044 |
2 | 9 | 可编程 | TAMP_STA MP | 连接EXTI 线的入侵和时间戳 中断 | 0X0000 0048 |
2. NVIC
NVIC(Nested Vectored Interrupt Controller)是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对Cortex-M7内核里面的NVIC进行裁剪,把不需要的部分去掉,所以说STM32的NVIC是Cortex-M7的NVIC的一个子集。
NVIC结构体定义,在 core_cM7.h
文件中定义如下:
typedef struct {
__IO uint32_t ISER[8U]; // 中断使能寄存器
uint32_t RESERVED0[24U];
__IO uint32_t ICER[8U]; // 中断清除寄存器
uint32_t RSERVED1[24U];
__IO uint32_t ISPR[8U]; // 中断使能悬起寄存器
uint32_t RESERVED2[24U];
__IO uint32_t ICPR[8U]; // 中断清除悬起寄存器
uint32_t RESERVED3[24U];
__IO uint32_t IABR[8U]; // 中断有效位寄存器
uint32_t RESERVED4[56U];
__IO uint8_t IP[240U]; // 中断优先级寄存器(8Bit wide)
uint32_t RESERVED5[644U];
__O uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;
在配置中断的时候我们一般只用ISER、ICER和IP这三个寄存器,ISER用来使能中断,ICER用来失能中断,IP用来设置中断优先级。
3. 优先级
3.1优先级定义
在NVIC 有一个专门的寄存器:中断优先级寄存器NVIC_IPRx(在F7中,x=0…100)用来配置外部中断的优先级,IPR宽度为8bit,原则上每个外部中断可配置的优先级为0~255,数值越小,优先级越高。但是绝大多数CM7芯片都会精简设计,以致实际上支持的优先级数减少,在F767中,只使用了高4bit,
用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占,抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
3.2 优先级分组
优先级的分组由内核外设SCB的应用程序中断及复位控制寄存器AIRCR的PRIGROUP[10:8]位决定,F7分为了5组,具体如下:主优先级=抢占优先级
4. 中断编程
在配置每个中断的时候一般有3个编程要点:
- 使用
HAL\_NVIC\_SetPriorityGrouping(uint32\_t PriorityGroup)
函数配置中断优先级分组。一般默认是NVIC\_PRIORITYGROUP\_4
分组4。 - 使用
HAL\_NVIC\_SetPriority(IRQn\_Type IRQn, uint32\_t PreemptPriority, uint32\_t SubPriority)
函数配置具体外设中断通道的抢占优先级和子优先级。 - 使用
HAL\_NVIC\_EnableIRQ
函数使能中断请求。
IRQn\_Type
中断源结构体代码如下:
typedef enum IRQn {
//Cortex-M7 处理器异常编号
NonMaskableInt_IRQn = -14,
MemoryManagement_IRQn = -12,
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
//STM32 外部中断编号
WWDG_IRQn = 0,
PVD_IRQn = 1,
TAMP_STAMP_IRQn = 2,
// 限于篇幅,中间部分代码省略,具体的可查看库文件
} IRQn_Type;
- PreemptPriority:抢占优先级,具体的值要根据优先级分组来确定
- SubPriority:子优先级,具体的值要根据优先级分组来确定
5. 外部中断
外部中断/事件控制器(EXTI)管理了控制器的25个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
EXTI功能框图如下:
EXTI可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
5.1 产生中断流程
红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到NVIC控制器内。
编号1是输入线,EXTI控制器有25个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个GPIO,也可以是一些外设的事件。输入线一般是存在电平变化的信号。
编号2是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号1给编号3电路,否则输出无效信号0。
编号3电路实际就是一个或门电路,它一个输入来自编号2电路,另外一输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER允许通过程序控制就可以启动中断/事件线,这在某些地方非常有用。
编号4电路是一个与门电路,它一个输入编号3电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为1才输出1。
编号5是将EXTI_PR寄存器内容输出到NVIC内,从而实现系统中断事件控制。
5.2 产生事件流程
绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。
产生事件线路是在编号3电路之后与中断线路有所不同,之前电路都是共用的。
编号6电路是一个与门,它一个输入编号3电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。
编号7是一个脉冲发生器电路,当它的输入端,即编号6电路的输出端,是一个有效信号1时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号8是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器TIM、模拟数字转换器ADC等等,这样的脉冲信号一般用来触发TIM或者ADC开始转换。
5.3 区别
- 产生中断线路目的是把输入信号输入到NVIC,进一步会运行中断服务函数,实现功能,这是软件级的。
- 产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,这是硬件级的。
6. 中断/事件线
EXTI有25个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0至EXTI15,还有另外七根用于特定的外设事件。
EXTI中断/事件线如下表所示:
中断/事件线 | 输入源 |
---|---|
EXTI0 | PX0(X可为A,B,C,D,E,F,G,H,I) |
EXTI1 | PX1(X可为A,B,C,D,E,F,G,H,I) |
EXTI2 | PX2(X可为A,B,C,D,E,F,G,H,I) |
EXTI3 | PX3(X可为A,B,C,D,E,F,G,H,I) |
EXTI4 | PX4(X可为A,B,C,D,E,F,G,H,I) |
EXTI5 | PX5(X可为A,B,C,D,E,F,G,H,I) |
EXTI6 | PX6(X可为A,B,C,D,E,F,G,H,I) |
EXTI7 | PX7(X可为A,B,C,D,E,F,G,H,I) |
EXTI8 | PX8(X可为A,B,C,D,E,F,G,H,I) |
EXTI9 | PX9(X可为A,B,C,D,E,F,G,H,I) |
EXTI10 | PX10(X可为A,B,C,D,E,F,G,H,I) |
EXTI11 | PX11(X可为A,B,C,D,E,F,G,H,I) |
EXTI12 | PX12(X可为A,B,C,D,E,F,G,H,I) |
EXTI13 | PX13(X可为A,B,C,D,E,F,G,H,I) |
EXTI14 | PX14(X可为A,B,C,D,E,F,G,H,I) |
EXTI15 | PX15(X可为A,B,C,D,E,F,G,H) |
EXTI0至EXTI15用于GPIO,通过编程控制可以实现任意一个GPIO作为EXTI的输入源。由EXTI有25个中断/事件线,每个GPIO都可以被设置为输入线, 占用EXTI0至EXTI15,还有另外七根用于特定的外设事件。
EXTI0可以通过SYSCFG外部中断配置寄存器1(SYSCFG_EXTICR1)的EXTI0[3:0]位选择配置为PA0、PB0、PC0、PD0、 PE0、PF0、PG0、PH0或者PI0
7. HAL中断编程
7.1 EXTI初始化详解
HAL库函数的EXIT初始化非常简单,只需配置好IO口的模式,然后配置中断源、中断优先级、使能中断。
- HAL_NVIC_SetPriority:该函数负责EXTI中断/事件线选择,可选EXTI0至EXTI25,可参考表 17-1选择,配置优先级。
- HAL_NVIC_EnableIRQ:该函数负责控制使能中断。
7.2 中断编程示例
使用外部按键1作为中断源
其他配置不再赘述
1. 使能外部中断
2. 使能中断
然后生成代码。
3. 添加中断代码
在 stm32f7xx_it.c
中断设置代码如下:
/**
* @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(KEY2_PIN_Pin);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}
在该代码下添加Callback方法:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin== KEY2_PIN_Pin) {
HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
}
}
该回调函数的原型为:
/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
#if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) /* ARM Compiler V6 */
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#ifndef __packed
#define __packed __attribute__((packed))
#endif
#elif defined ( __GNUC__ ) && !defined (__CC_ARM) /* GNU Compiler */
#ifndef __weak
#define __weak __attribute__((weak))
#endif /* __weak */
#ifndef __packed
#define __packed __attribute__((__packed__))
#endif /* __packed */
#endif /* __GNUC__ */
使用 __attribute__((weak)) 修饰函数,告诉编译器此函数为同名函数的备选,如果定义了其他同名函数,让编译器优先使用其他同名函数,如果未定义其他同名函数,那么就使用此函数。
7.3 测试
略