基于STM32的对射式红外传感器计次实现方法
基于STM32的对射式红外传感器计次实现方法
在一条高速运转的包装流水线上,每秒都有数十个产品经过检测点。如果系统漏掉一次计数,不仅会影响产量统计,还可能导致后续分拣、装箱逻辑出错——这种“看似简单”的任务背后,其实藏着嵌入式开发中一个经典难题:如何用最低的资源开销,精准捕捉每一个瞬时事件?
这正是对射式红外传感器 + STM32 外部中断组合大显身手的场景 💡。
设想这样一个小装置:两个小小的塑料壳体分别装着红外发射和接收头,中间划过一道看不见的光束。每当有物体穿过,MCU 就能立刻感知并加一。听起来很简单?但如果你试过轮询 GPIO 的电平变化,就会知道——当物体快速通过或震动干扰时,那种“好像计了又好像没计”的感觉有多令人抓狂 😣。
而解决这一切的关键,就是 硬件级的外部中断(EXTI)机制。它让 STM32 不再“主动去看”,而是“被动被叫醒”——只有真正发生信号跳变时才响应,CPU 其余时间可以安心做别的事。
下面我们就来拆解这个“叫醒服务”到底是怎么搭建起来的。整个过程就像搭积木,每一块都不能少,而且顺序还得对 ⚙️。
先从最底层说起:GPIO 配置。你想啊,如果引脚浮空,稍微有点电磁干扰就可能误触发中断,那不是天天“狼来了”?所以我们得给 PB14 装个“默认状态”。
由于对射传感器在无遮挡时输出高电平,挡光后拉低,我们要检测的是 下降沿,所以自然选择 上拉输入模式(IPU):
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,防干扰
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
这样,即使线路暂时断开或者没信号,引脚也会稳稳保持高电平,不会乱飘 🛡️。
但光配置了 GPIO 还不够!你得告诉芯片:“嘿,PB14 这个脚我要拿它当外部中断用。”这就轮到 AFIO(复用功能 IO) 出场了。
很多人容易忽略这一点:必须开启 AFIO 时钟,并将 GPIO 引脚映射到对应的 EXTI 线路。不然 EXTI 根本不知道你要用哪个引脚!
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 必须!否则映射无效
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
这一步相当于在芯片内部接了一根“硬连线”:把 PB14 和 EXTI14 物理连通。没有这步,后面的中断就是空中楼阁 🏗️。
接下来是真正的主角登场 —— EXTI 自身的配置。
我们希望的是:一旦光线被挡住,电平从高变低,立刻触发中断。因此设置为 下降沿触发:
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式,不是事件模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
注意这里 EXTI_Mode_Interrupt 很关键。如果是 Event 模式,只会产生一个脉冲用于触发其他外设(比如启动 ADC),但不会引起 CPU 中断,也就进不了你的 ISR 函数 ❌。
最后一步,也是最容易出错的一环:NVIC 配置。
Cortex-M 内核的中断调度全靠 NVIC 来管理。EXTI 虽然发出了请求,但如果 NVIC 没开闸,照样没人理你。
而且要注意,STM32 把多个 EXTI 线合并到了同一个 IRQ 通道里。比如 EXTI15_10_IRQn 能响应从 EXTI10 到 EXTI15 的所有中断。所以你得提前分好优先级,避免被别的中断堵住门 🚪。
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置优先级分组:2位抢占 + 2位子优先级
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
到这里,整个“感知链路”才算打通:
传感器 → GPIO → AFIO → EXTI → NVIC → ISR
一旦光路被切断,不到几个微秒,CPU 就会跳进中断函数:
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line14) != RESET) {
count++; // 计数+1
EXTI_ClearITPendingBit(EXTI_Line14); // ⚠️ 务必清除标志位!
}
}
⚠️ 千万别忘了最后一句 EXTI_ClearITPendingBit()!如果不手动清标志,中断会一直挂起,导致系统卡死在中断里,俗称“中断风暴”🌀。
主循环则轻松得多,只需要不断刷新 OLED 显示当前 count 值即可:
while (1) {
OLED_ShowNum(1, 1, count, 5); // 第一行第一列显示5位数字
Delay_ms(100); // 小延迟避免频繁刷屏
}
整个架构简洁高效,几乎不占用主程序资源,真正做到“事件驱动” ✅。
实际应用中,有几个坑值得特别提醒 💣:
-
误触发问题:虽然用了上拉,但在工业现场强干扰环境下仍可能出现毛刺。此时可考虑加入 软件滤波,例如在中断中读取后延时几毫秒再确认是否真的为低电平。不过对于机械运动较慢的场景(如人走过、箱子掉落),通常不需要额外处理。
-
方向识别扩展:如果想判断物体是“进”还是“出”,单个传感器显然不够。这时候可以用两个对射传感器组成“前后阵列”,根据先后触发顺序判断方向。类似编码器的A/B相信号判向原理,稍加逻辑就能实现双向计数 🔁。
-
电源共地与滤波:务必确保传感器与 MCU 使用同一地线,最好在 VCC 引脚加一颗 0.1μF 陶瓷电容,吸收电源波动。曾经有人因为忘了接地,结果传感器输出一直在抖动,调试半天才发现是“虚焊”🙃。
-
引脚选择限制:并不是所有 GPIO 都能随便接到任意 EXTI 线。STM32 规定每个 EXTI 线只能映射到某一组端口的一个引脚(比如 EXTI0 可以选 PA0/PB0/PC0…但同一时间只能启用一个)。布局 PCB 时要提前规划好,别到最后发现引脚冲突 😤。
说到这儿,你会发现这套方案的核心思想其实是:把“等待”交给硬件,把“决策”留给人。
相比传统轮询方式(每隔几毫秒查一次电平),中断机制的优势太明显了:
- ✅ 零漏检:哪怕脉冲只持续 1μs,也能被捕获;
- ✅ 低功耗:CPU 大部分时间可以休眠,适合电池供电设备;
- ✅ 高效率:主循环自由执行显示、通信等任务,互不干扰。
更妙的是,这套“五步法”——
RCC → GPIO → AFIO → EXTI → NVIC
几乎是所有外部中断项目的通用模板,学会了就能复用到按键检测、限位开关、光电编码器等各种场景,堪称嵌入式入门的“必杀技”之一 🔑。
未来如果想进一步升级,还可以结合 DMA 实现无感数据搬运,或是接入 ESP32 模块将计数结果无线上传至服务器,打造物联网化的智能计数终端 🌐。甚至进入低功耗待机模式,靠中断唤醒,实现超长待机的远程传感节点。
总之,别小看这一个小小的红外对管和一根中断线。它们组合起来,不只是“数了个数”,更是理解实时系统设计思维的绝佳入口 👣。
下次当你面对一个新的传感器接入需求时,不妨先问自己一句:
“我能用中断吗?”
也许答案就是:能,而且应该用 ✨。
更多推荐
所有评论(0)