

响应优先级:即当有中断正在执行,执行完毕后,响应优先级高的,先执行。(排队排第一)
抢占优先级:即当有中断正在执行,无论有没有执行完,抢占优先级高的立马执行。(插队)

AFIO中断引脚选择: 它可以在前面的GPIO外设的16个引脚中选择其中一个连接到后面的EXTI的通道里,所以相同的Pin不可以同时触发中断。


对于STM32来说,想要获取的信号是外部驱动的很快的突发信号,就应该用到外部中断。
比如旋转编码器的输出信号,当我们不使用旋转编码器时,这时我们不需要STM32做出任何反应,但是一旦我们拧动了旋转编码器,就会发出很多脉冲波形需要被接收,这个信号是突发的,STM32是不知道什么时候会来的,且它是外部驱动的,STM32只能被动读取,信号也非常快,当STM32稍微晚接收一点点时间都会造成数据的丢失。这种情况就应该使用外部中断。




首先先编写初始化函数
这里我们需要搞懂需要配置哪些函数

由上图可知:
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
当不知道配置GPIO为什么输入模式时,可以查看参考手册中的GPIO介绍中的外设GPIO配置


这里我们选择上拉输入
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure);
这和配置对射式红外传感器的GPIO口时不一样,需要用到新的函数,在GPIO的头文件中可以找到
首先是
void GPIO_AFIODeInit(void);
会把AFIO外设的配置全部清除
第二个:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
这个函数可以配置AFIO的数据选择器,来选择我们想要的中断引脚。
打开函数定义就可得知参数应该填什么
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
打开EXTI头文件,查看EXTI有什么函数
a、清除EXTI的配置
void EXTI_DeInit(void);
b、 初始化EXTI
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
c、 调用这个函数,可以把参数传递的结构体变量附一个默认值
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
d、软件触发外部中断,调用这个函数,参数给一个指定的中断线,就能外部软件触发一次中断
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
在外设运行过程中,会产生一些状态标志位(比如外部中断来了,会置标志位;串口受到数据,会置标志位;定时器时间到,也会置标志位;这些标志位都是存放在标志寄存器上的,当我们想要阅读这些标志位时就可以使用到以下四个函数)
e、第一个:可以获取指定标志位是否被置1,;第二个:可以对置1的标志位进行清除(倾向于在主程序中使用)
- FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
- void EXTI_ClearFlag(uint32_t EXTI_Line);
f、在中断函数中,使用以下两个函数
第一个:获取中断标志位是否被置1,;第二个:清除中断挂起标志位(倾向于在中断函数中使用)
- ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
- void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
初始化
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
需要用到结构体,右键查看结构体成员
- EXTI_InitTypeDef EXTI_InitStructure;
-
-
- EXTI_InitStructure.EXTI_Line = ;
- EXTI_InitStructure.EXTI_Mode = ;
- EXTI_InitStructure.EXTI_Trigger = ;
- EXTI_InitStructure.EXTI_LineCmd = ;
-
- EXTI_Init(&EXTI_InitStructure);
然后依次右键查看各成员的定义

在注释中搜索关键词的位置,例如EXTI_Lines;

- //配置EXTI
- EXTI_InitTypeDef EXTI_InitStructure;
-
- //指定要配置的中断线
- EXTI_InitStructure.EXTI_Line = EXTI_Line14;
- //指定外部中断线的模式(枚举类型的中断模式和事件模式)
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- //指定触发信号的有效边缘(上升沿,下降沿,双向沿)
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
- //指定选择中断线的新状态(ENABLE/DISABLE)
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure);
NVIC的函数被发配到了杂项头文件中misc.h
1、用于中断分组,参数是中断分组的方式
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
2、初始化
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
在配置中断之前,先指定中断的分组,再初始化NVIC即可。
中断分组:

在中断不多的情况下,不用太在意如何分组,这里我们选择比较平均的第二个分组,两位抢占(pre-emption),两位响应(subpriority)。
- //配置NVIC
- //分组方式整个芯片只能用一种,则这个分组的代码只要执行一次即可
- //如果是放在模块内使用该分组函数,则一定要保证每个模块的分组方式相同
- //或许可以直接放在main函数中
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC初始化
- NVIC_InitTypeDef NVIC_InitStructure;
- NVIC_InitStructure.NVIC_IRQChannel = ;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = ;
-
- NVIC_Init(&NVIC_InitStructure);
再一个个仔细的查找该填入的值
第一个是选择中断通道,我们芯片时MD中等密度的,所以只需要展开MD的条件编译即可


STM32的EXTI10到EXTI15都合并到了这个通道里。
再根据下图赋值

- //配置NVIC
- //NVIC的响应优先级和抢断优先级分组
- //分组方式整个芯片只能用一种,则这个分组的代码只要执行一次即可
- //如果是放在模块内使用该分组函数,则一定要保证每个模块的分组方式相同
- //或者可以直接放在main函数中
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- NVIC_InitTypeDef NVIC_InitStructure;
- //中断通道
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
- //指定中断通道是使能还是失能
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- //抢断优先级和响应优先级
- //根据之前NVIC的分组函数,可以确定一下两个元素的范围
- //因为选择了分组2,则范围是0~3,且该工程只有这一个中断,所以随意设置为1
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
-
- NVIC_Init(&NVIC_InitStructure);
名字是固定的,可以在启动文件中参考

这些以IRQHandler结尾的都是中断函数的名字
其中
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
就是我们EXTI的中断函数
在我们编写的中断函数的下面写下中断函数的基本框架
- void EXTI15_10_IRQHandler(void)
- {
-
-
- }
一定是无返回值和输入值的!
到这步我们还需要添加一个判断语句——是否是PB14口引起的中断,这就需要用到我们之前所解释过的EXTI函数,读取中断标志位的函数
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
同时还需要在判断后加上清除中断标志位的函数
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
把中断状态清除,以防一直卡在中断函数中
整体
- void EXTI15_10_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line14) == SET)//判断是否是指定端口产生的中断
- {
- //内容写在这
-
-
- //清除中断标志位,回归主函数,以防一直卡在中断函数中
- EXTI_ClearITPendingBit(EXTI_Line14);
- }
- }
记得一定要在中断函数执行完后清除中断标志位,否则中断标志位一直为SET,程序就会一直响应中断,一直执行中断函数。
这样一来,再在main函数中初始化中断函数就写好代码了,可以用Keil自带的调试模式查看能不能跳到中断函数中。
在中断函数中设置一个停止点,然后全速运行,再试着使PB14产生中断(实验中是挡住红外传感器的光)

当出现
时,就说明中断函数写得没错
我们就可以定义一个数来计算中断触发的次数,即挡光片挡住光线的次数。
- uint16_t Counts;
-
- void EXTI15_10_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line14) == SET)//判断是否是指定端口产生的中断
- {
- //内容写在这
- Counts++;
-
- //清除中断标志位,回归主函数,以防一直卡在中断函数中
- EXTI_ClearITPendingBit(EXTI_Line14);
- }
- }
-
- uint16_t GetCounts(void)
- {
- return Counts;
- }
在主函数中:
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "CountSenor.h"
-
- uint16_t Sensor_Counts;
-
- int main(void)
- {
- OLED_Init();
- CountSensor_Init();
-
- while(1)
- {
- Sensor_Counts = GetCounts();
- OLED_ShowString(1,1,"Counts:");
- OLED_ShowNum(1,8,Sensor_Counts,2);
- }
- }
就可以实现我们想要的功能了
总体:
CountSensor.c
- #include "stm32f10x.h" // Device header
-
- uint16_t Counts;
-
- void CountSensor_Init(void)
- {
- //配置时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- //EXTI和NVIC的时钟是一直开着的,不用使用函数配置
-
-
- //配置对射式红外传感器的GPIO口
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure);
-
- //配置AFIO
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
-
- //配置EXTI
- EXTI_InitTypeDef EXTI_InitStructure;
-
- //指定要配置的中断线
- EXTI_InitStructure.EXTI_Line = EXTI_Line14;
- //指定外部中断线的模式(枚举类型的中断模式和事件模式)
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- //指定触发信号的有效边缘(上升沿,下降沿,双向沿)
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
- //指定选择中断线的新状态(ENABLE/DISABLE)
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure);
-
- //配置NVIC
- //NVIC的响应优先级和抢断优先级分组
- //分组方式整个芯片只能用一种,则这个分组的代码只要执行一次即可
- //如果是放在模块内使用该分组函数,则一定要保证每个模块的分组方式相同
- //或者可以直接放在main函数中
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
-
- NVIC_InitTypeDef NVIC_InitStructure;
- //中断通道
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
- //指定中断通道是使能还是失能
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- //抢断优先级和响应优先级
- //根据之前NVIC的分组函数,可以确定一下两个元素的范围
- //因为选择了分组2,则范围是0~3,且该工程只有这一个中断,所以随意设置为1
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
-
- NVIC_Init(&NVIC_InitStructure);
- }
-
- void EXTI15_10_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line14) == SET)//判断是否是指定端口产生的中断
- {
- //内容写在这
- Counts++;
-
- //清除中断标志位,回归主函数,以防一直卡在中断函数中
- EXTI_ClearITPendingBit(EXTI_Line14);
- }
- }
-
- uint16_t GetCounts(void)
- {
- return Counts;
- }
main.c
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "CountSenor.h"
-
- uint16_t Sensor_Counts;
-
- int main(void)
- {
- OLED_Init();
- CountSensor_Init();
-
- while(1)
- {
- Sensor_Counts = GetCounts();
- OLED_ShowString(1,1,"Counts:");
- OLED_ShowNum(1,8,Sensor_Counts,2);
- }
- }
还用到了OLED模块
这里是移开挡光片+1,只需更改
- //指定触发信号的有效边缘(上升沿,下降沿,双向沿)
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
即可改为挡住+1,或者挡住+1和移开+1
在之前的代码上稍作修改即可,把向左转和向右转改为两个EXTI中断。
配置时钟不用改,配置GPIO口时
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
配置APIO需要分开配置
- //配置AFIO
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
EXTI中断可以一起配置
EXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line0;
配置NVIC时需要分开配置

- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- //EXTI0
- NVIC_InitTypeDef NVIC_InitStructure;
- //中断通道
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
- //指定中断通道是使能还是失能
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- //抢断优先级和响应优先级
- //根据之前NVIC的分组函数,可以确定一下两个元素的范围
- //因为选择了分组2,则范围是0~3,且该工程只有这一个中断,所以随意设置为1
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
-
- NVIC_Init(&NVIC_InitStructure);
-
- //EXTI1
- NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
- NVIC_Init(&NVIC_InitStructure);
这里需要稍稍把EXTI1的中断优先级调后
再分别写出两个EXTI的中断函数

- void EXTI0_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line0) == SET)
- {
- if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
- {
- Counts++;
- }
- EXTI_ClearITPendingBit(EXTI_Line0);
- }
-
- }
-
- void EXTI1_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line1) == SET)
- {
- if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
- {
- Counts--;
- }
- EXTI_ClearITPendingBit(EXTI_Line1);
- }
-
- }
这样再把原先显示数字的Counts改为有符号的就行了
主体
CountSensor.c
- #include "stm32f10x.h" // Device header
-
- int16_t Counts;
-
- void CountSensor_Init(void)
- {
- //配置时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- //EXTI和NVIC的时钟是一直开着的,不用使用函数配置
-
-
- //配置旋转编码器的GPIO口
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure);
-
- //配置AFIO
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
-
- //配置EXTI
- EXTI_InitTypeDef EXTI_InitStructure;
-
- //指定要配置的中断线
- EXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line0;
- //指定外部中断线的模式(枚举类型的中断模式和事件模式)
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- //指定触发信号的有效边缘(上升沿,下降沿,双向沿)
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
- //指定选择中断线的新状态(ENABLE/DISABLE)
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure);
-
- //配置NVIC
- //NVIC的响应优先级和抢断优先级分组
- //分组方式整个芯片只能用一种,则这个分组的代码只要执行一次即可
- //如果是放在模块内使用该分组函数,则一定要保证每个模块的分组方式相同
- //或者可以直接放在main函数中
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- //EXTI0
- NVIC_InitTypeDef NVIC_InitStructure;
- //中断通道
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
- //指定中断通道是使能还是失能
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- //抢断优先级和响应优先级
- //根据之前NVIC的分组函数,可以确定一下两个元素的范围
- //因为选择了分组2,则范围是0~3,且该工程只有这一个中断,所以随意设置为1
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
-
- NVIC_Init(&NVIC_InitStructure);
-
- //EXTI1
- NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
- NVIC_Init(&NVIC_InitStructure);
- }
-
- //旋转编码器旋转时会产生两道波形,设为A和B,
- //正转时,B相比A,慢90°,反转则快90°
- //以此特性,只有A处于低电平,B处于下降沿时,判断为正转
- //A为下降沿,B为低电平时判断为反转
- 所以以判断旋转编码器的两个输出端的值,来设置两个旋转方向的中断函数
-
- void EXTI0_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line0) == SET)
- {
- if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
- {
- Counts++;
- }
- EXTI_ClearITPendingBit(EXTI_Line0);
- }
-
- }
-
- void EXTI1_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line1) == SET)
- {
- if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
- {
- Counts--;
- }
- EXTI_ClearITPendingBit(EXTI_Line1);
- }
-
- }
-
- int16_t GetCounts(void)
- {
- return Counts;
- }
main.c
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "CountSenor.h"
-
- int16_t Sensor_Counts;
-
- int main(void)
- {
- OLED_Init();
- CountSensor_Init();
-
- while(1)
- {
- Sensor_Counts = GetCounts();
- OLED_ShowString(1,1,"Counts:");
- OLED_ShowSignedNum(1,8,Sensor_Counts,2);
- }
- }