响应优先级:即当有中断正在执行,执行完毕后,响应优先级高的,先执行。(排队排第一)
抢占优先级:即当有中断正在执行,无论有没有执行完,抢占优先级高的立马执行。(插队)
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);
}
}