中断:
含义:
在主程序运行时,出现了特定的中断触发条件,需要CPU暂停当前工作(当前运行的程序),转向去处理中断信号对应的程序(中断程序),在处理完中断程序之后回到原先的工作位置并继续运行。
中断的流程:
保存现场——>确定中断源——>执行中断函数——>恢复现场——>继续执行产生中断时的工作
1.保存现场:
CPU会改变其工作模式至中断模式,并保存当前程序的执行状态。这通常包括保存程序计数器(PC)的值,即当前指令的地址,以及通用寄存器(GPR)的值、控制与状态寄存器(CSR)等敏感数据。这些数据通常会被压入堆栈中,以便在中断处理完成后能够恢复之前的执行状态。2
2.确定中断源:
CPU会检查中断向量表,以确定触发中断的源头。中断向量表是一个数据结构,它包含了每个可能的中断源对应的中断处理程序的入口地址。当发生中断时,CPU会根据中断向量表跳转到相应的中断处理程序。
3.行中断处理程序:
一旦确定了中断源,CPU就会跳转到对应的中断处理程序并执行它。中断处理程序是一段特定的代码,用于处理特定类型的中断。在处理过程中,中断处理程序可能会执行一些必要的操作,如清中断标志、读/写数据、寄存器操作等。
4.恢复现场:
当中断处理程序执行完毕后,CPU会恢复之前的执行状态。这通常包括从堆栈中弹出之前保存的PC值、GPR值和CSR值等,以恢复之前的程序执行环境。
5.继续执行之前的任务:
在恢复了之前的执行状态后,CPU会继续执行之前被中断的程序或任务。
中断源:
EXTI(外部中断)、
TIM(定时器)、
ADC(模数转换)、
USART(串口通信)、
SPI通信、
I2C通信、
RTC(实时时钟)
中断通道:
不同的中断源有对应的中断通道进行传输中断信号。
NVIC:
Nested Vectored Interrupt Controller,嵌套向量中断控制器
①控制中断通道的分级:响应优先级、抢占优先级;共同由4位二进制数控制。
也就是将16个值(四位二进制数 0000 ~ 1111)分配给这两个模式,分到多少,模式就能把对应通道分多少个级别。如响应优先级被分到了4个值( 0000 ~ 0100,不含0100),那么响应优先级就可以分为1、2、3、4级。那么剩下12个(0100 ~ 1011)就都是用来划分抢占优先级的等级。
(图片来源于江协)
②控制通道的开关(使能开启/关闭)
中断优先级(响应优先级——排队):
多个相同抢占优先级的中断同时发生时,用来排序哪个中断程序先被CPU执行。
响应优先级高的先排队/插队,哪怕在此之前已经有了一个低级响应的中断。
中断嵌套(抢占优先级——打断):
多个中断依次发生时,如果目前执行的中断的抢占优先级低于后面产生的中断,就会优先执行抢占优先级更高的中断,执行完了高等级的再执行低等级。
相同抢占优先级时,不同响应优先级的中断排序为先高后低;
不同抢占优先级时,高级的会打断目前的直接被执行(嵌套)。
中断函数、中断号、中断向量表:
①中断向量表:中断源的标志位。用于存放中断函数的入口地址。
②中断号:分配给每个中断源的代号,以此标识不同的中断源。当中断发生时,CPU会根据中断号在中断向量表中查找对应的中断服务程序的入口地址。
③中断函数:函数名+函数体。函数名是固定的,内容应该是短小精悍、耗时短的。
1.为什么需要用官方的中断函数(号)来标识不同中断信号对应的中断函数?
因为STM32的中断函数的名称是预先定义好的,如果给中断函数定义了与名命标准不符合的名字,那么中断将会无法正常运行。
2.什么道理?
中断号在这里起到了一个索引或标识符的作用,它告诉CPU应该跳转到中断向量表中的哪个位置来找到中断服务程序的入口地址。
可以理解为中断向量表的地址单元是中断号,中断号对应的地址单元存储着中断函数的地址。
3.意思是中断向量表的地址是固定的?那中断函数和中断号的地址呢?
中断向量表的地址是固定的;
中断函数由于编译流程,所以不同程序的中断函数的地址是不同的;
中断号并不是个地址,而是用于标识中断函数在中断向量表位置的一个变量。
4.但是Stm32F103C8T6的中断函数并不像STC89C52的中断函数一样由后主中断号,是否意味着Stm32没有中断号?
Stm32没有显式中断号,但中断号隐含在中断函数名字。
而STC89C52的函数名是可以自定义的,只需要在后补充中断号可以。
因为中断号是为了标识中断函数在中断向量表,但是stm32的函数名是固定的,就可以规定其地址所在,而不用显式中断号。
EXTI:
含义:
External Interrupt/Event Controller,外部中断/事件控制器。
微控制器检测并响应来自外部引脚(GPIO口)的信号,从而执行相应的中断服务程序或触发特定的事件处理。
工作流程:
外部中断信号——>NVIC(抢占优先、响应优先)——>触发对应中断函数
外部中断信号的产生与传输通道:
引脚:任意GPIO口都可以配置为外部中断信号的输入引脚,但是相同的Pin脚不能同时工作(有GPIOA_Pin_0工作,就不能有GPIOB_Pin_0工作)。因为配置GPIO引脚为外设的功能引脚需要用到引脚复用,而复用只能接一个GPIO_Pin的同号引脚,其余引脚都无法复用,所以才说相同Pin脚不能同时工作
触发:上升沿触发、下降沿触发、双边沿触发、软件触发(代码)
通道:
16个GPIO口(GPIOA、GPIOB、…………)
PVD输出
RTC闹钟
USB唤醒
以太网唤醒
图片来自江科大,用于说明Stm32把外部中断通道的5~9通道,10~15通道的规划成两个通道,也就是响应通道5,6,7,8,9通道触发信号的函数是同一个函数…………
GPIO口通道的说明:
GPIO口通过复用功能,作为外部中断的输入引脚
同一个Pin的不同选择,可以让不同组的GPIO口被复用。
除GPIO口之外的其他通道说明:
外部中断可以唤醒低功耗模式的STM32,从而进入正常工作模式。
PVD是电压检测,当电压回升到正常工作电压是,就可以通过外部中断退出低功耗模式;
RTC可以定时让STM32进入低功耗模式,而低功耗模式的唤醒需要外部中断;
…………
外部中断的触发方式:
中断响应——中断信号通向CPU,申请中断,让CPU执行中断函数(耗费软件资源)
事件响应 ——中断信号通向外设,触发外设工作。(硬件实现)
路线:
输入线——>边沿检测电路(上升沿、下降沿)——>①+事件屏蔽触发器——>脉冲发生器——>其他外设
——>②请求挂起寄存器——>+中断屏蔽存储器——>NVIC中断控制器
其中
软件中断事件寄存器就是软件触发中断开(1)关(0)的使能寄存器。
请求挂起寄存器就是中断触发标志位。有(1)无(0)中断触发。
中断屏蔽寄存器是控制软硬件中断触发开(1)关(2)中断的使能寄存器
外部中断的函数:(代码参考自江科大)
流程:外设时钟——>GPIO参数选择——>GPIO的外部中断引脚复用(AFIO)——>EXTI的参数配置——>NVIC的参数配置
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设