对射式红外传感器计次&旋转编码器计次
中断处理说明
- 对设式红外传感器采用的是外部中断,那么何种时候何种外设要采用外部中断的处理方式呢?
①对实时性要求极高的事件:有些事件需要系统在极短时间内(例如毫秒级甚至微秒级)做出反应。例如,工业控制中的急停按钮、安全系统中的异常报警信号
。如果使用轮询,可能会因为检测的微小延迟而错过最佳处理时机,造成严重后果。
②事件发生频率低且随机:像按键操作、无线遥控信号的接收这类事件,完全是随用户操作而定,大部分时间处于“等待”状态。如果让主程序不停地去检查(轮询)按键是否被按下,CPU 时间将被大量浪费在无意义的查询上。使用中断,CPU 可以在此期间处理其他任务,只在事件发生时被“唤醒”处理,极大提高了效率。
③系统需要低功耗运行:在电池供电的物联网设备中,MCU(微控制器单元) 大部分时间处于睡眠模式以节省电量。此时,任何轮询操作都无法进行。只有像外部中断这样的异步事件才能将 CPU 从低功耗模式中唤醒,使其处理任务后继续休眠。
④总结来说,当一个外部事件难以预测、要求快速响应、且不常发生时,外部中断就是最佳的解决方案。它能让你的系统更高效、更实时地运作。
- 采用外部中断的注意事项:
①保持中断服务函数简短高效:中断服务程序的黄金法则是快进快出。它不应该执行复杂的计算或包含延时操作。理想情况下,它只负责完成最紧急的任务,比如清除中断标志、设置一个事件标志位,或者将数据存入缓冲区,然后迅速退出。具体的处理逻辑应放到主循环中完成。
②妥善处理中断抖动:特别是机械触点(如按键、继电器)在通断瞬间会产生一系列抖动的电信号,可能导致一次动作触发多次中断。常用的解决方法包括硬件上并联电容进行滤波,或在软件中断服务函数中加入简单的延时消抖(Delay函数)逻辑。延时消抖的具体时间需要根据实际情况来确定。
③合理配置中断优先级:当一个系统有多个中断源时,你需要通过 NVIC 为它们设置合理的抢占优先级和子优先级。这确保了更紧急的事件(如系统故障报警)能够打断正在处理的普通事件(如按键扫描),保证系统的实时性和可靠性。
- 外部中断的参与设备:
进行外部中断需要对GPIO、AFIO、EXTI、NVIC等进行中断设置。外部中断流程图:

NVIC(内核设备)中断流程图:

对射式红外传感器计次
工程步骤及代码
-
接线图如图:

-
外部中断涉及很多元件,故设置也比常规的复杂很多,如图:
- 在Hanrware文件夹下新建一个CountSensor.h文件和CountSensor.c文件,CountSensor.c代码如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
uint8_t CountNum;
//初始化外部中断
void CountSensor_Init(void)
{
//开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//开启AFIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO_Mode_IPU为上拉输入,将GPIO没有信号输入的时候默认置为高电平
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//指定GPIOB的第14号引脚有权进入AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//初始化EXTI函数,EXTI只有一个结构体参数
EXTI_InitTypeDef EXTI_InitStructure;
//GPIO的14号引脚和EXTI的14号对应
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
//使用下降沿(EXTI_Trigger_Falling,电平由1到0触发)的触发方式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
//EXTI_Init只有一个参数
EXTI_Init(&EXTI_InitStructure);
//配置NVIC,因为NVIC是内核函数,应在杂项文件(misc.h)中寻找
//NVIC分组:确定几位为抢占优先级,几位为响应优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure;
//选择的引脚为GPIO14,对应EXTI14,EXTI14包含于EXTI15_10_IRQn,IRQ:中断请求
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//在NVIC_PriorityGroupConfig函数中选用的组为2组,对应抢占优先级PreemptionPriority和响应优先级SubPriority的范围均为0~3
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
//NVIC_Init只有一个参数
NVIC_Init(&NVIC_InitStructure);
}
//中断计数函数
uint8_t Count(void)
{
return CountNum;
}
//中断函数,在10到15这个分组内判断是不是要求的14号引脚信号传入
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
//第二个if嵌套是为了消除抖动
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountNum++;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
值得注意的是:
①要开启GPIO和AFIO两个时钟,EXTI和NVIC时钟不用管.
②在配置AFIO时只需要GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);一句代码即可,指定好那个GPIO的哪个引脚信号有权进入AFIO.
③配置EXTI函数(只有一个形参)也是配置一个结构体,该结构体有四个参数,其中一个参数要选择触发方式,有上升沿触发、下降沿触发、上升和下降沿触发,上升沿触发是指从0电平跳变为1电平时触发,其余两种类似。具体如图所示:
④在配置NVIC时要先确定NVIC的分组,具体分组如图所示, 抢占优先级是需要立即执行的程序,子优先级也就是响应优先级,是排队等待的程序
⑤继续配置NVIC,NVIC也是一个结构体函数,该结构体有四个参数,也是只含有一个形参,不过NVIC属于内核程序,官方给的函数文件在misc.h中查看.
⑥在中断函数中,第一个if语句的判断条件EXTI_GetITStatus(EXTI_Line14) == SET是为了检查EXTI14号线是否有信号通过,因为我外接设备选用的是GPIO14号引脚,对应EXTI的14号信号通道,而EXTI的14号信号通道对应NVIC的中断请求分组里(NVIC_IRQChannel )的EXTI15_10_IRQn分组(包含第10号信道到15号信道),故引用此if语句判断进来的信号是不是从我规定的第14号信道进来的.
⑦在中断函数中,第二个if嵌套是为了消除抖动.
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
OLED_Init();
CountSensor_Init();
OLED_ShowString(1,1,"Count:");
OLED_ShowString(2,1,"Level:");
while(1)
{
OLED_ShowNum(1,7,Count(),5);
OLED_ShowNum(2,7,GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14),1);
}
}
旋转编码器计次
工程步骤及代码
- 接线图如图:

与 对设式红外传感器计次相似,不再过多赘述.代码如下:
Encoder.c代码:
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
//初始化外部中断
void Encoder_Init(void)
{
//开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//开启AFIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO_Mode_IPU为上拉输入,将GPIO没有信号输入的时候默认置为高电平
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//指定GPIOB的第0号和第1号引脚有权进入AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
//初始化EXTI函数,EXTI只有一个结构体参数
EXTI_InitTypeDef EXTI_InitStructure;
//GPIO的14号引脚和EXTI的14号对应
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
//使用下降沿(EXTI_Trigger_Falling,电平由1到0触发)的触发方式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//配置NVIC,因为NVIC是内核函数,应在杂项文件(misc.h)中寻找
//NVIC分组:确定几位为抢占优先级,几位为响应优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//选择的引脚为GPIO0,对应EXTI0,IRQ:中断请求
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//选择的引脚为GPIO1,对应EXTI0,IRQ:中断请求
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
//编码器反转
void EXTI0_IRQHandler (void)
{
if(EXTI_GetFlagStatus(EXTI_Line0) == SET)
{
//第二个if语句是为了防止抖动现象干扰程序正常运行
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
//编码器正转
void EXTI1_IRQHandler (void)
{
if(EXTI_GetFlagStatus(EXTI_Line1) == SET)
{
//第二个if语句是为了防止抖动现象干扰程序正常运行
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
//计数器实现
int16_t Count_Encoder(void)
{
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num;
int main(void)
{
OLED_Init();
Encoder_Init();
OLED_ShowString(1,1,"Count:");
while(1)
{
Num += Count_Encoder();
OLED_ShowSignedNum(1,7,Num,5);
}
}
值得注意的是:
①在计数器实现函数int16_t Count_Encoder(void);中可以就写一句return Encoder_Count;在main.c直接将此数字打印到OLED屏幕上,但是为什么不这么做呢?
答:可能是因为Encoder_Count是一个全局变量,可能在任何时候被中断服务程序修改,使用Temp返回,在main.c函数中作累加和保存,防止因为中断崩溃Encoder_Count丢失.
效果图
- 正转屏幕显示正数次数:
- 反转显示负数次数:


1357

被折叠的 条评论
为什么被折叠?



