中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
NVIC:嵌套向量中断控制器,控制着整个芯片中断相关的功能,是Cortex-M3内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3内核里面的 NVIC进行裁剪,把不需要的部分去掉,即 STM32F103系列的 NVIC 是阉割过得。
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级 。
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。(STM32中断向量表,值越小优先级越高)
EXTI(Extern Interrupt)外部中断
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
支持的触发方式:上升沿/下降沿/双边沿/软件触发。
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒。
触发响应方式:中断响应/事件响应。
外部中断/事件控制器由20个产生事件/中断请求的边沿检测器组成,但是一个GPIO口就有16个引脚,所以需要使用外部中断配置寄存器AFIO_EXTICR来配置GPIO的外部中断,这是不同的GPIO口相同的引脚不能同时触发中断的原因。
给外部中断使用的硬件外部中断线有16个,但是GPIO端口却远不止16个,所以为了解决这个问题,采用了每一个外部中断线控制一组GPIO端口,即尾号相同的引脚作为一组被一条外部中断线控制。
当使用外部中断的时候,通过配置,我们可以将某一组中的某一个引脚连接到外部中断线上。所以一组引脚只能有一个作为外部中断引脚。例如,PA0、PB0、PC0、PD0、PE0、PF0、PG0和PH0这些引脚作为一组,如果我们使用PA0引脚作为外部中断引脚,那么该组的其余引脚就不能作为外部中断引脚使用。因此,从本质上讲,可供用户同时使用的外部中断引脚最多只有16个。
而且虽然外部中断线有16个,但是NVIC只为EXTI提供了7个中断通道,其中EXTI0_IRQ、EXTI1_IRQ、EXTI2_IRQ、EXTI3_IRQ、EXTI4_IRQ分别控制一条外部中断线,EXTI9_5_IRQ控制EXTI5-EXTI9,并且这几条外部中断线共享一个外部中断服务程序,EXTI5_10_IRQ同理。
外部中断设置方法
中断优先级:
当同时有两个及以上的中断时,就必须设置中断优先级了,在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx, 用来配置外部中断的优先级,其宽度为 8bit,每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数减少,在F103中,只使用了高 4bit。
分成抢占优先级(主优先级)和子优先级。而且为了管理这用于表达优先级的这高4bit,于是又封装了一个优先级组,通过选择不同的组可以得到不同的优先级数目的比例,参看库文件misc.h:
============================================================================================================================
NVIC_PriorityGroup | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority | Description
============================================================================================================================
NVIC_PriorityGroup_0 | 0 | 0-15 | 0 bits for pre-emption priority
| | | 4 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 1 bits for pre-emption priority
| | | 3 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 2 bits for pre-emption priority
| | | 2 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 3 bits for pre-emption priority
| | | 1 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_4 | 0-15 | 0 | 4 bits for pre-emption priority
| | | 0 bits for subpriority
============================================================================================================================
显然,大多数时候,
人们都喜欢选择折中的,即 NVIC_PriorityGroup_2。
当有多个中断同时请求(注意是还没执行中断服务函数)时,抢占优先级(主优先级)高的就会先得到执行,如果抢占优先级(主优先级)相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,就越先响应。
中断嵌套:
假定在选定了优先级组后,在组里添加了两个中断,当单片机正在执行中断1的服务函数过程中,突然,又来一个中断2的请求,那么:
若中断2的主优先级比中断1的主优先级高,那么单片机暂停中断1的服务函数,进入中断2的服务函数,执行完之后,才返回中断1的服务函数。形成了所谓的嵌套。
若中断2的主优先级比中断1的主优先级低,那么就必须等待中断1的服务函数执行完毕才能响应中断2。不能形成嵌套。
在配置每个中断的时候一般有3个步骤:
1、结合当前使用的外设,确定自己需要的中断源(单片机外设)。比如单片机用于通信的固件外设(I2C,SPI,USART等)和GPIO等。
以GPIOB的14引脚外部中断为例
配置AFIO
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
2、配置EXTI
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
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外设
3、配置NVIC
1). NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
2). 初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择通道(要中断的对象)
//选择配置NVIC的EXTI15_10线
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外设
4、 编写中断服务函数,在固件库启动文件 startup_stm32f10x_Hd.s 中预先为每个中断都写了一个中断服务函数,只是这些中断函数都是为空,为的只是初始化中断向量表:
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
实际的中断服务函数都需要我们重新编写, 放在 stm32f10x_it.c 这个库文件中,可以看到那里面已经有了一些写好的系统中断的服务函数,我们只需跟着在文件末尾继续写就行了。中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数,实现不了中断。 注意:中断服务函数里不应该处理耗时的操作!
接下来这个中断服务函数需要放在stm32f10x_it.c里
/**
* @brief 中断服务函数
* @param 无
* @retval 无
*/
void EXTI0_IRQHandler(void)
{
//确保是否产生了中断
if(EXTI_GetITStatus(EXTI_Line14) != RESET) //判断是否是外部中断14号线触发的中断
{
//TODO:
/*别放置耗时间的操作!!!!!!!!*/
EXTI_ClearITPendingBit(EXTI_Line14); //清除中断标志位
}
}