目录
一.前言
什么是中断?中断就是在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。为什么能够在原来位置继续运行的原因就在于我们的单片机具有还原现场的能力。值得注意的是:最好不要在主程序和中断程序里,进行可能产生冲突的硬件配置。例如主程序中正在使用OLED,突然收到中断,从而转到中断函数中,而这个时候中断函数再进行OLED相关的操作,那么在执行完中断程序后,继续执行主程序就会出错。
在知道了什么是中断之后,大家应该就不难理解EXTI中断了。EXTI中断,可以检测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
看到这里,想必大家还是有点问题的,NVIC又是什么呢?它的名字叫做嵌套中断向量控制器,在内核中帮助CPU处理中断选择,这样CPU就只需要执行中断,而不再需要自己进行筛选了。如下所示:
讲到这里,肯定有不少小伙伴对这个NVIC是怎么筛选中断的过程感兴趣吧。这里我们讲下NVIC的中断优先级。它是由优先寄存器的4位决定的,这4位又可以进行切分,分为高n位的抢占优先级和4-n位的响应优先级。其中,抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,如果这两个都相同,那么就得按照中断号进行排队。这个中断号就在我们的引脚定义图当中。
其实,NVIC能够完成它的工作还得多亏了前面的两个“大功臣”----AFIO和EXTI边沿检测及控制。
二. 红外对射式传感器
在了解了上述知识后,我们就可以开始进行实际中断操作了。首先就是我们的红外对射式传感器。将它连在任意的GPIO口上,这里我选择GPIOB口的14号。
首先肯定开启外设的时钟,不然是没法进行工作的。这里需要打开GPIOB和AFIO的时钟,NVIC和EXTI无需打开。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
接着就是对GPIO,AFIO和EXTI进行初始化了,如下所示:
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU ;
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//AFIO选择中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//EXTI初始化
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line=EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
这些东西的配置我们是都可以在对应的库函数中查找到的,我们一定要学会查看手册及使用库函数。对GPIO初始化的库函数就在gpio.h当中。AFIO相关的库函数也包括在了gpio.h当中。
对EXTI初始化的库函数包含在了exti.h当中。
下面我们进行最后一步的配置----NVIC的配置,如下所示:
//NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
由于NVIC属于内核当中,所以和它有关的库函数就放在了杂项当中,也就是misc.h。
这里我们设好NVIC的中断分组为2,因为这个小项目不会产生过多的中断冲突,我们就可以随意点,如果后面在做项目的时候,就需要严谨一点了。中断分组的原理如下:
所以这里我们选择了中断分组为2之后,我们的抢占优先级和响应优先级的取值范围就都为0~3了。PreemptionPriority为抢占优先级,subpriority为响应优先级。 所以我们这里都设置为1。
并且由于我们这里选择的是GPIOB的14号引脚中断,而10号到15号是合并在一起的,所以也就是EXTI15_10_IRQN。
在初始化完成之后,我们就可以编写中断函数了,这个中断函数是自动执行的,因此我们不要在头文件当中再进行声明了。关于中断函数的名称,我们可以从启动文件中进行复制。
如下所示:
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14)==SET) //判断是否是外部中断14号线触发的中断
{
//如果出现数据乱跳的情况,可再次判断引脚电平,以避免抖动
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0)
{
CountSensor_Count++;
}
//清除外部中断14号线的中断标志位
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
值得注意的是:这里的中断标志位必须清除,否则就会导致中断连续不断地触发,从而导致主程序卡死。
到这里,整个配置就已经完成了,如果我们想要检测红外对射式传感器的效果,就可以将其中CountSensor_Count结果显示在OLED上面。这个比较简单,我这里就不再介绍了。如果有任何疑问,欢迎到评论区留言讨论。
三.旋转编码器
旋转编码器就是用来测量位置,速度或者旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向。
主要类型有:机械触电式/霍尔传感器式/光栅式
相关中断函数配置如下所示,这里它的一些初始化代码和上面的相差不大,只需更换GPIO相关引脚配置即可。
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}