中断
中断简介
中断是一种事件处理机制,可以暂停主程序的运行,转而处理特定事件程序。
中断的作用和意义:
实时控制 在确定事件内对响应事件做出相应
故障处理 检测到故障需要第一时间处理
数据传输 如串口通信,不确定数据何时会来
意义:高效处理紧急程序,不会一直占用cpu资源
信号从外部进来,首先遇到的外设是GPIO
然后会走到SYSCFG系统配置控制器,将GPIOXy映射到EXTIy
SYSCFG系统配置控制器是一种常见外设,其作用是配置系统的各种控制和配置寄存器,用于管理系统的各种配置参数和功能。
接着信号进入到EXTI来判断是否触发响应
触发EXTI响应后会进入NVIC判断响应优先级
最后优先级高的先进入CPU去处理中断
NVIC
NVIC基本概念
Nested vectored interrupt controlelr,嵌套向量中断控制器,属于内核的一部分(M3/4/7)
NVIC支持256个中断(16个内核+240外部),支持256个优先级,允许裁剪
中断向量表
中断向量表是一块固定的内存,4字节对齐,存放各个中断对应的中断服务函数首地址
中断向量表定义在启动文件,也就是.s文件里,当发生中断,CPU会自动执行对应的中断服务函数
.s文件中__Vectors标注的代码块就是中断向量表开始的地方
DCD表示4字节对齐
NVIC相关寄存器介绍
中断使能寄存器 ISER,Interrupt Set-Enable Register
中断除能寄存器 ICER,Interrupt Clean-Enable Register
应用程序中断及复位控制寄存器 AIRCR,Application Interrupt and Reset Controller Register
中断优先级寄存器 IPR,Interrupt Priority Register
NVIC还有中断挂起、解挂、激活标志等不常用的功能,此处不做介绍
NVIC工作原理
经过EXTI判断后触发响应的外部中断会来到中断使能寄存器(ISER)和中断失能寄存器(ICER),
再走到中断优先级寄存器(IPR),经过优先级判断后交由CPU处理
内核中断直接到达SHPR
SHPR,System Handler Priority Registers 系统处理器优先级寄存器
STM32中断优先级基本概念
1、抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先级的中断
2、响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能互相打断
3、自然优先级:抢占和响应都相同的话,自然优先级高的先执行。自然优先级看向量表上到下
STM32中断优先级分组
NVIC中通过AIRCR的[10:8]位,可支持8种优先级分组
STM32只用到了其中5个。
如果优先级分组为0,
则IPR的[7:4]位全部用于响应优先级配置,也就是0个抢占优先级,2^4=16个响应优先级
如果分组为1,
则IPR的[7]位用于抢占优先级,[6,4]用于分配响应优先级,也就是2个抢占优先级,8个响应优先级
以此推类。
一个工程中一般只设置一次优先级分组。设置多次会以最后一次为准。
STM32 NVIC的使用
使用流程:
1、设置中断分组 AIRCR[10:8],HAL_NVIC_SetPriorityGrouping
2、设置中断优先级 IPRx bit[7:4],HAL_NVIC_SetPriority //STM32只用了IPR的高四位
3、使能中断 ISERx,HAL_NVIC_EnableIRQ
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); //设置中断优先级分组为2
/****************************************************************/
HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2); /* 抢占0,子优先级2 */
HAL_NVIC_EnableIRQ(KEY0_INT_IRQn); /* 使能中断线3 */
//设置中断优先级分组源代码
__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
uint32_t reg_value;
uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */
reg_value = SCB->AIRCR; /* read old register configuration */
reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change */
reg_value = (reg_value |
((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos) ); /* Insert write key and priority group */
SCB->AIRCR = reg_value;
}
//设置中断优先级源代码
__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if ((int32_t)(IRQn) >= 0)
{
NVIC->IP[((uint32_t)IRQn)] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
else
{
SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
设置中断优先级源代码中的
NVIC->IP[IRQn] 代表设置外部中断优先级的 IPR 寄存器小知识,IP[0]就是WWG窗口看门口
SCB->SHP[IRQn] 代表设置内核中断优先级的SHPR寄存器
EXTI
EXTI基本概念
External(Extended) interrupt/event Controlelr 外部(扩展)中断事件控制器
如F4系列包含23个产生事件/中断请求的边沿检测器,即总共:23条EXTI线
中断和事件的区别:
中断要进入NVIC,有响应的中断服务函数,需要CPU处理
事件不进入NVIC,仅用于内部硬件自动控制,如:TIM、DMA、ADC
EXTI主要特性
F1/F4/F7系列每条EXTI线都可以单独配置:
选择类型(中断或者事件)
触发方式(上升沿、下降沿、双边)
支持软件触发
开启/屏蔽
有挂起状态位
H7系列
由其他外设对EXTI产生的事件可分为可配置事件和直接事件
可配置事件:基本和F1/F4/F7类似
直接时间:固定上升沿触发、不支持软件触发、无挂起状态位(由其他外设提供)
EXTI工作原理(F4)
一个信号从EXTI线输入进来,首先到达 边沿检测线路
接着到达 软硬件触发选择线路
最后到达 中断屏蔽/清除(挂起)线路 或 事件屏蔽线路
边沿检测线路:通过EXTI_RTSR/EXTI_FTSR上升下降触发选择寄存器的配置来决定是否允许信号通过。比如上升下降两个选择寄存器对应位都置1,代表来了上升沿或者下降沿都允许通过
软硬件触发选择:然后信号经过或门,不管是选择软件触发还是硬件触发,只要触发了到左边就是1
中断屏蔽/清除(挂起):之后可以通过与门,也可以通过请求挂起寄存器,如果到达EXTI_PR请求挂起寄存器的信号是1,就会自动让EXTI_PR请求挂起寄存器置 1;如果EXTI_IMR屏蔽掉了,输出的就是0,与门得到的也是0,中断信号就无法到达NVIC也就无法产生中断
事件屏蔽线路:EXTI_EMR事件屏蔽寄存器如果屏蔽了信号,则与门会让信号无法到达脉冲发生器,也就无法使外设产生事件(触发外设功能)
上升/下降沿触发选择寄存器 EXTI_RTSR/EXTI_FTSR(Rising/Falling trigger selection register)
上升/下降沿触发选择器RTSR为32位,用到了20位,每一位TRx控制一个线x的上升沿中断/事件触发的允许和禁止
软件中断事件寄存器 EXTI_SWIER(Software Interrupt Event Register)
挂起寄存器 EXTI_PR(Pending Register)
请求挂起寄存器可以挂起/清除中断标志位。32位寄存器,20位有效,如果外部中断线上发生了选择的边沿事件,该位被置1,也就是硬件触发中断,清除时主动往该位写1,或改变边沿触发的极性(上升沿改下降沿)
中断屏蔽寄存器 EXTI_IMR(Interrupt Mask Register)
IMR事件屏蔽寄存器是32位,只用到了20位,想屏蔽或者开启某条线的中断置1获置0即可
事件屏蔽寄存器 EXTI_EMR(Event Mask Register)
EXTI_RTSR 和 EXTI_FTSR都是32位的,具体多少位有效就看有多少EXTI线,比如F1系列有20根EXTI线那么RTSR和FTSR就是20位有效,F4系列有23根EXTI线就是23位有效
EXTI和IO的映射关系
SYSCFG简介(F4/F7/H7)
System configuration controller,即系统配置寄存器,用于外部中断映射配置等功能(F1为AFIO模块)
SYSCFG模块的外部中断配置主要使用SYSCFG_EXTICR1~4,配置EXTI中断线0~15对应到哪个具体IO口
SYSCFG_EXTICR1~4 (Configuration Register)
特别注意:配置SYSCFG寄存器之前要使能SYSCFG时钟,方法如下:
__HAL_RCC_SYSCFG_CLK_ENABLE();
EXTI与IO对应关系
当Px0映射到EXTI0时,其他分组的Pin0就不能映射到EXTI0了
可以看到EXTI0与引脚号为0的IO口对应。SYSCFG有SYSCFG_EXTICR1~4共4个外部中断配置寄存器,即EXTI0~EXTI15,EXTIx寄存器的[3:0]位用来选择相同x引脚号的不同分组。以EXTICR0为例,外部中断配置寄存器EXTICR0为32位,高16位保留,低16位拆分为4个EXTI寄存器,控制不同分组外部中断引脚的映射。EXTI0可选择对应A0、B0~I0,EXTI1可选择A1、B1~I1,依次推类
如何使用中断
既然是GPIO外部中断,肯定首先是要使用GPIO的,
1、设置GPIO的输入模式,比如上拉、下拉、浮空
经过GPIO以后又和EXTI有一个映射关系,就需要
2、使用AFIO或SYSCFG配置IO引脚和EXTI线映射
3、之后针对EXTI可以设置中断的屏蔽、打开,上升下降双边沿触发方式
4、经过EXTI后需要使用NVIC来设置中断分组、中断优先级和中断使能
5、最后CPU会按照优先级顺序依次处理中断
需要到达EXTI中断的可以统称为EXTI中断
GPIO中断对应的是EXTI0~15这16根线,而EXTI16 ~23是来自其他外设,如RTC闹钟事件、USB唤醒事件等等,这些EXTI16之后的外部中断不经过SYSCFG直接到达EXTI
不经过EXTI直接到达NVIC的中断统称为外设中断
USART/TIM/SPI等外设中断则不需要经过EXTI,直接由外设自己的寄存器进行中断开启和触发方式选择,可以直接到达NVIC
EXTI的HAL库配置步骤(GPIO外部中断)
1、使能GPIO时钟 __HAL_RCC_GPIOx_CLK_ENABLE
/*******************************************HAL_GPIO_Init()一步到位**********************************/
2、设置GPIO输入模式 上/下拉/浮空输入
3、设置AFIO/SYSCFG时钟 设置AFIO/SYSCFG时钟开启寄存器
4、设置EXTI和IO对应关系 AFIO_EXTICR/SYSCFG_EXTICR
5、设置EXTI屏蔽,上下沿触发方式 IMR设置EXTI对应通道的屏蔽,、RTSR/FTSR设 置上升沿/下降沿触发方式
/*********************************************HAL_GPIO_Init()一步到位**********************************/
6、设置NVIC 设置优先级分组、设置优先级、中断使能 HAL_NVIC_SetPriorityGrouping
HAL_NVIC_EnableIRQ
7、设置中断服务函数 编写对应中断服务函数,清中断标志
EXTIx_IRQHandler
STM32仅有7个外部中断服务函数,EXTI 0~4有5个中断服务函数,EXTI 9_5共用一个中断服务函数,EXTI 15_10共用一个中断服务函数
通用外设驱动模型(四步法)
HAL库中断回调处理机制介绍
HAL库的中断回调处理异常复杂,知道大概原理即可,按需去看即可
大概流程就是从main()进入到中断服务函数、HAL库中断处理公用函数、HAL库数据处理回调函数,再依次往上回到上一级函数,最后回到main函数
通过外部中断控制一个灯亮灭
首先分析按键IO应该配置为什么模式
KEY_UP外接高电平,连接时应该给到一个上升沿触发,断开的时候是高阻态,高阻态电平不确定,因此为了确保稳定性初始状态应该配置为下拉输入
KEY0、KEY1、KEY2这三个按键是反过来的,按下时输入低电平,会给到一个下降沿触发,因此初始状态应该配置成上拉输入
当外部中断产生之后会调用中断服务函数,中断处理函数会调用中断处理公用函数,处理公用函数会调用数据处理回调函数
通常重写数据处理回调函数处理自己的业务逻辑
#define KEY0_INT_IRQHandler EXTI3_IRQHandler
#define __HAL_GPIO_EXTI_GET_IT(__EXTI_LINE__) (EXTI->PR & (__EXTI_LINE__))
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin Specifies the pins connected EXTI line
* @retval None
*/
//Pin口引脚与EXTI线由SYSCFG_EXTI1~3的EXTI0~3(共0~15)进行映射
//映射规则不可能有两条EXTI线映射到相同的Pin号引脚
//PR寄存器的20位有效每一位对应被映射到EXTI的引脚Pin号。因此Pin号确定PR寄存器的对应位也就可确定
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) //***中断处理公用函数***
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin); //***数据处理回调函数***
}
}
void KEY0_INT_IRQHandler(void) //***中断服务函数***
{
HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); /* 调用中断处理公用函数,清除中断标志位 */
__HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN); /* 退出时再清一次中断,避免按键抖动误触发 */
//不清除中断的话,长按就会持续进入数据处理回调函数里面
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(20); /* 消抖 */
switch (GPIO_Pin)
{
case KEY0_INT_GPIO_PIN:
if (KEY0 == 0)
{
LED1_TOGGLE(); /* LED1状态取反 */
LED0_TOGGLE(); /* LED0状态取反 */
}
break;
case KEY1_INT_GPIO_PIN:
if (KEY1 == 0)
{
LED1_TOGGLE(); /* LED1 状态取反 */
}
break;
case KEY2_INT_GPIO_PIN:
if (KEY2 == 0)
{
LED0_TOGGLE(); /* LED0 状态取反 */
}
break;
case WKUP_INT_GPIO_PIN:
if (WK_UP == 1)
{
LED1_TOGGLE(); /* LED1状态取反 */
if (HAL_GPIO_ReadPin(LED1_GPIO_PORT, LED1_GPIO_PIN) == 1)
{
LED0(0);
}
else
{
LED0(1);
}
}
break;
default : break;
}
}