在stm32中姑且可以认为,异常就是中断
单片机上电之后,首先执行启动文件,开辟堆栈之后,开始初始化中断向量表。
NVIC是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核
里面的一个外设。
####三个寄存器ISER
、ICER
和IP
NVIC 结构体定义,来自固件库头文件:core_cm3.h
在配置中断的时候我们一般只用 ISER
、ICER
和IP
这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。
ISER[8]
:Interrupt Set Enable Registers,这是一个中断使能寄存器组。是个u32类型的数组,成员有8个。 CM3、4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。根据单片机生产厂商的设计,不一定会全部使用到。
ICER[8]
:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ISER 一样。这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。
IP [240]
:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32F407 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32F407只用到了其中的 82 个。IP[81]-IP[0]分别对应中断 81~0。而每个可屏蔽中断占用的 8bit 并没有
全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
//----------以上是常用的NVIC寄存器,对NVIC的配置实际上就是配置这几个寄存器----------//
ISPR[8]
:全称是:Interrupt Set Pending Registers,是一个中断使能挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
ICPR[8]
:全称是:Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。
IABR[8]
:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
1.优先级只有主优先级和子优先级
2.所谓的优先级分组是决定了IP寄存器的高4bit,其中哪几位代表主优先级,几位代表子优先级
3.数值越小所代表的优先级就越高
4.抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
5.响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
还有一种情况就是当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循
自然优先级,看中断向量表的中断排序,数值越小,优先级越高。
先比较主优先级,再比较sub优先级,再按中断向量表的优先级序号比较
中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高
。但是绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数减少,在F103中,只使用了高 4bit。而这高4bit也会根据CB->AIRCR 寄存器的 bit10~8 的配置来决定主优先级占几位,子优先级占几位。
优先级的分组由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的PRIGROUP[10:8]位决定,也就是说用于表达优先级的这4bit还需要根据优先级分组的配置,再进行主优先级和子优先级的配置。
优先级分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的.
设置优先级:设置优先级分组之后,再设置主优先级和子优先级
1.设置优先级分组
2.设置主优先级
3.设置sub优先级
例如SCB->AIRCR 寄存器的 bit10~8将优先级分组设置为3,那么此时所有的 82 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
假定设置中断优先级分组为 2,然后设置中断 3(RTC_WKUP 中断)的抢占优先级为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:
中断 7>中断 3>中断 6。
上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!
NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中
一般情况下不使用库函数封装好的函数
编程时主要进行三步
使能外设某个中断,比如USART的传输完成中断,接受完成中断等,这些由相关外设的控制寄存器进行控制,配置控制寄存器进行使能。这边是允许外设的具体某个中断,还需要在NVIC操作 NVIC_ISER 和 NVIC_ICER 这两个寄存器,进行中断的使能
初始化 NVIC_InitTypeDef 结构体
2.1 设置中断源
2.3中断使能(ENABLE)或者失能(DISABLE),操作的是 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
启动文件 startup_stm32f10x_hd.s 中预先定义了中断服务函数,可以在里面进行编写。
NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中(stm32f1系列)
把 core_cm4.h 文件的 NVIC 相关函数封装到 stm32f4xx_hal_cortex.c 文件中(stm32f4系列)
代码差不多就是这个意思,具体使用时需要修改
配置GPIO PA0引脚作为外部中断的流程
使用 GPIO 之前必须开启 GPIO 端口的时钟;用到 EXTI 必须开启 AFIO 时钟。
GPIO_InitTypeDef GPIO_InitStructure;//
EXTI_InitTypeDef EXTI_InitStructure;//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);//打开GPIO的时钟,打开AFIO的时钟,
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置 NVIC 为优先级组 1 所有82个中断源都属于优先级组1,主优先级0-1,子优先级0-7*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:exti1 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
/* 配置抢占优先级:1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级:1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
# 设定GPIO模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;#浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
#设定EXTI触发条件
/* 选择 EXTI 的信号源 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
/* EXTI 为中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 上升沿中断 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
/* 使能中断 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
void EXTI0_IRQHandler(void){
//确保是否产生了 EXTI Line 中断
if (EXTI_GetITStatus(EXTI_Line0) != RESET){
//Todo
EXTI_ClearITPendingBit(EXTI_Line0);
}
}