目录
简介
什么是NVIC
中断优先级
EXTI 简介
总结
hal库初始化代码
标准库初始化代码
简介
什么是中断?正常情况下,微处理器根据代码内容,按顺序执行指令。执行过程中,如果遇到其它紧急的事件需要处理,则先暂停当前任务,执行紧急事件,待紧急事件处理完后,再恢复到刚才暂停的地方继续执行。这个产生的紧急事件就叫做中断或异常
通常把CPU内部产生的紧急事件叫做异常,来自CPU外部的片上外设产生的紧急事件叫做中断,异常和中断的效果基本一致,都是暂停当前任务,优先执行紧急事件,因此一般将中断和异常统称为中断
什么是NVIC
如果两个中断同时发生,应该先执行哪个中断任务?又比如一个中断发生了,又来了一个更紧急的中断,这是中断嵌套,这是继续执行原来的中断,还是执行新的紧急中断?
针对问题,Cortex-M3内核有一个专门管理中断的外设NVIC( Nested Vectored Interrupt Controller,嵌套向量中断控制器) , 通过优先级控制中断的嵌套和调度。 NVIC是一个总的中断控制器, 无论是来在内核的异常还是外设的外部中断, 都由NVIC统一进行管理
如图一,Reset( 复位) 、 NMI( Non Maskable Interrupt,不可屏蔽中断)、 HardFault( 硬件异常) 的优先级是固定的, 且优先级是负数,也就是最高的(优先级数字越小,优先级越高)。 剩下的异常或中断,都可以通过修改NVIC的寄存器调整优先级( 但不能设置为负数) 。
中断优先级
中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先级。每个中断源都需要被指定这两种优先级,具有高优先级的中断可以打断低优先级的中断,实现中断嵌套,只有抢占优先级才可能出现中断嵌套。两个或者多个中断优先级和子优先级相同时,自然优先级,看中断向量表的中断排序(图一),数值越小,优先级越高
NVCI 的中断优先级分组: STM32F103 将中断分为 5 个组,组 0~4,分组的设置是
由 SCB->AIRCR 寄存器的 bit10~8 来定义。M3 芯片为了精简设计,只使用了PRI_n的Bits[7:0]中的Bits[7:4]设置优先级,低四位取零,这样以至于最多只有 16 级中断嵌套,即 2^4=16
通常中断优先级分组只会设置一次, 它针对的是系统中所有的中断。 后续设置某个中断的中断优先级时, 只需要在这个组规定的抢占优先级数和子优先级级数范围内分配优先级级数。 后续代码中,不应该再修改中断优先级分组,否则导致中断顺序不按预期触发。
EXTI 简介
EXTI 即是外部中断和事件控制器,STM32F103系列的EXTI支持19个外部中断/事件请求,它是由产生事件/中断请求的边沿检测器组成.每个中断/事件都有独立的触发和屏蔽设置,支持中断模式和事件模式。这些都是信息输入端,也就是输入线具体如下:
EXTI 线 0~15:对应外部 IO 口的输入中断
EXTI 线 16:连接到 PVD 输出
EXTI 线 17:连接到 RTC 闹钟事件
EXTI 线 18:连接到 USB 唤醒事件
EXTI 线 19:连接到以太网唤醒事件
中断模式是指外部信号产生电平变化时, EXTI将该信号给NVIC处理, 从而触发中断,执行中断服务函数,完成对应操作。
事件模式是指外部信号产生电平变化时, EXTI根据配置,联动ADC或TIM执行相关操作
中断和事件的产生源是一样的,中断需要软件实现相应功能,而事件是由硬件触发后执行相应操作。前者需要CPU参与功能实现,可以实现的功能更多,后者无需CPU参与,具有更高的响应速度
AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择(也就是选择引脚接入到EXTI)
STM32F103的GPIO挂载APB总线上,如果要使用GPIO引脚作为外部中断/事件功能,则必须使能APB总线上该引脚对应端口的时钟和AFIO复用功能,而且GPIO引脚众多,将引脚数字相同的作为一组,共享一个中断线,如EXTI0组, PA0作为了中断源,则PB0~PG0不能作为中断源
总结
假设中断A的抢占优先级比中断B的抢占优先级高,两个中断同时发生,那中断A优先执行
假设中断A的抢占优先级和中断B的抢占优先级一样,两个中断同时发生,那么子优先级高的中断优先执行。
假设中断A的抢占优先级比中断B的抢占优先级高,中断B先发生,随后A也发生,那么将暂停中断B,先执行中断A, A执行完后,再回来执行中断B,最后执行主程序,这种效果即中断嵌套。
假设中断A的抢占优先级比中断B的抢占优先级一样,中断A的子优先级比中断B的子优先级高,中断B先发生,随后A也发生,那么中断A将等待中断B执行完后,才会执行中断A,即子优先级不能中断嵌套。
假设中断A的抢占优先级和中断B的抢占优先级一样,且子优先级也一样,两个中断同时发生,那么根据前面图一顺序,排在前面的先执行。
hal库初始化代码
void exti_init(void)
{
/*定义GPIO结构体*/
GPIO_InitTypeDef gpio_init_exti_struct = {0};
/*使能时钟*/
__HAL_RCC_GPIOA_CLK_ENABLE();
/*配置io*/
gpio_init_exti_struct.Mode = GPIO_MODE_IT_FALLING;/*下降沿触发模式*/
gpio_init_exti_struct.Pull = GPIO_PULLUP; /*上拉*/
gpio_init_exti_struct.Speed = GPIO_SPEED_FREQ_HIGH; /*引脚反转速度快*/
gpio_init_exti_struct.Pin = EXTI_GPIO_PIN; /*选择引脚*/
HAL_GPIO_Init(EXTI_GPIO_PORT,&gpio_init_exti_struct);/*初始化引脚(AFIO\EXTI)*/
/*这已经默认设置中断分组,有需要开启下面*/
//HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
HAL_NVIC_SetPriority(EXTI0_IRQn,0, 0);/*设置中断优先级*/
HAL_NVIC_EnableIRQ(EXTI0_IRQn);/*使能中断*/
}
/*中断服务函数,由引脚号确定,公共处理函数*/
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(EXTI_GPIO_PIN);
}
/*回调函数*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/*需要分开是那个引脚引起的中断*/
switch(GPIO_Pin)
{
case EXTI_GPIO_PIN:
{
/*执行处理的任务*/
}
default:break;
}
}
标准库初始化代码
void exit_init(void)
{
/*使能GPIOA时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*使能AFIO时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/*初始化GPIO*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;/*上拉*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*配置中断源*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
/*中断初始化*/
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line0;/*设置中断输入线*/
EXTI_InitStruct.EXTI_LineCmd = ENABLE;/*使能对应的外部中断线*/
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;/*设置外部中断的模式为中断模式*/
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;/*设置外部中断的触发方式为下降沿触发*/
EXTI_Init(&EXTI_InitStruct);
/*配置中断优先级分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;/*配置中断的通道*/
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;/*使能中断的通道*/
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;/*抢占优先级*/
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;/*响应优先级*/
NVIC_Init(&NVIC_InitStruct);
}
/*中断服务函数*/
void EXTI0_IRQHandler(void)
{
/*检查 EXTI 对应引脚的中断是否已经被触发*/
if(EXTI_GetITStatus(EXTI_Line0) == SET)
{
/*处理代码*/
}
EXTI_ClearITPendingBit(EXTI_Line0);/*清除中断标志位*/
}