ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STM32与红外对射的人流量检测系统:硬件设计、状态机算法与工程实践

基于STM32与红外对射的人流量检测系统:硬件设计、状态机算法与工程实践 简介本资源是一套面向嵌入式初学者与STM32进阶实践者的综合性人流量检测系统开发包聚焦于真实场景下的软硬件协同设计解决从传感器数据采集、本地计数处理到多模态数据上传的完整技术链路问题。压缩包共含6个核心项目文件涵盖C语言主控程序基于HAL库、Keil工程文件、传感器驱动模块、蓝牙与机智云通信接口代码、电机控制逻辑及风扇温控仿真模型类型以.c/.h源码、.uvprojx工程、README.md说明文档为主整体大小为10.47MB。目前已有201人学习下载适合课程设计、毕业设计或智能硬件创客项目参考。读者可直接复用红外/超声波检测算法框架、移植物联网通信协议栈、调试图像化调试流程并通过结构清晰的模块划分快速理解人流量统计系统的分层架构与外设协同机制。1. 项目缘起从“数人头”到智能感知最近在整理硬盘翻出来一个老项目一个基于STM32的人流量检测器。这玩意儿现在看起来可能有点“复古”毕竟满大街都是AI摄像头了。但恰恰是这种“复古”让我觉得有必要拿出来聊聊。它不依赖复杂的神经网络不消耗大量算力只用最基础的传感器和单片机就能实现一个相当可靠的计数功能。对于很多成本敏感、对实时性要求不高或者仅仅需要统计“有/无”人经过的场景比如小型店铺的客流量统计、图书馆阅览室的人数监控、甚至家里玄关的“回家提醒”这种方案依然有它的生命力。这个项目打包文件里通常包含了STM32的完整工程代码、原理图、PCB设计文件可能还有上位机软件和设计文档。它的核心逻辑并不复杂通过一对红外对管或者一个热释电传感器检测是否有人经过通道然后由STM32进行去抖动和方向判断最终累加或递减计数器。但想把这事儿做“稳”里面门道可不少。比如如何防止两个人紧挨着走过被误判为一个人如何避免环境光干扰导致误触发计数数据怎么存储和上传这些问题都是我在实际调试中一个个踩坑填平的。接下来我就以这个“STM32单片机人流量检测器”为例拆解一下从硬件选型、电路设计、软件逻辑到调试优化的完整过程。无论你是刚接触STM32的新手想找个综合项目练手还是正在为某个简单计数需求寻找低成本方案的工程师希望这些“过时”但有效的经验能给你带来一些启发。2. 硬件架构设计与核心器件选型一个稳定的人流量检测器硬件是地基。方案选型直接决定了后续软件逻辑的复杂度和最终系统的可靠性。市面上主流的有三种思路红外对射、热释电红外PIR和区域型红外阵列。我们这个项目采用的是最经典、成本也最低的红外对射方案。2.1 传感器方案对比与定稿为什么最终选择红外对射而不是其他方案这需要从原理和场景说起。红外对射传感器通常由一个红外发射管和一个红外接收管组成面对面安装形成一个“光束”。当有人穿过时身体会遮挡光束接收管接收到的红外信号强度会发生变化从而产生一个脉冲信号。它的优点是原理简单、方向判断容易通过两对传感器错位安装即可、受环境温度影响小。缺点也很明显安装需要精确对位只能检测一个“点”或一条“线”如果两个人并排紧贴通过可能会漏检。热释电红外传感器PIR比如常用的HC-SR501模块。它能检测人体发出的特定波长的红外线变化。优点是检测范围是一个扇形面安装简单无需精确对光。但它的缺点对于人流量统计来说是致命的一是响应慢人有短暂停留就会持续输出高电平难以区分是“经过”还是“停留”二是无法直接判断运动方向需要非常复杂的算法分析信号波形三是容易受环境热源如暖气、阳光干扰。因此PIR更适合做人体存在感应比如自动灯而不适合做精确的通过性计数。红外阵列传感器比如MLX90640可以获取一个区域的红外温度分布图像理论上可以通过分析“热斑”的运动轨迹来实现计数和方向判断。这无疑是功能最强大的但成本高昂传感器本身就好几百而且需要大量的图像处理算法完全超出了STM32F103这类普通单片机的处理能力需要搭载Linux或更高级的MCU。所以综合成本、复杂度和可靠性对于通道宽度在1米以内的门、走廊等场景双路红外对射方案是性价比最高的选择。我们使用两对传感器一前一后间隔一定距离比如20cm安装。通过判断两路信号被遮挡的先后顺序就能准确判断人是“进入”还是“离开”。2.2 STM32主控选型与外围电路主控芯片我选择的是STM32F103C8T6也就是常说的“蓝桥杯”最小系统板核心。选择它理由很充分72MHz主频足够处理两路传感器信号和进行数据管理拥有足够的GPIO、定时器和串口成本极低社区资源丰富遇到问题容易找到解决方案。核心外围电路主要包括以下几部分传感器接口电路红外接收管输出的是模拟信号电压变化我们需要将其转换为单片机可以识别的数字信号。这里采用一个电压比较器电路如LM393是关键。将接收管的输出接到比较器的反相输入端同相输入端接一个由电位器调节的参考电压。这样当人体遮挡导致接收管输出电压低于参考电压时比较器输出高电平无遮挡时输出低电平。这个参考电压就是我们的“灵敏度”调节旋钮。特别注意比较器输出端到STM32 GPIO之间务必串联一个1k-10k的电阻并最好在GPIO引脚对地加一个100pF的电容用于滤除高频毛刺这是硬件去抖的第一道防线。电源管理整个系统可以由USB供电5V或者通过插座适配器。STM32需要3.3V传感器和比较器模块通常需要5V。所以需要一个稳压电路。我采用AMS1117-3.3将5V降压为3.3V给MCU。在电源入口处一个大电容如100uF和若干小电容0.1uF并联进行滤波是必须的能有效抑制电源噪声对传感器电路的干扰。数据显示与存储为了现场查看数据我添加了一个0.96寸的OLED显示屏SSD1306驱动I2C接口。同时为了断电不丢失数据增加了一个SPI接口的Flash芯片如W25Q16来存储历史人流数据。对于需要远程上报的场景可以预留一个ESP-01S WiFi模块的接口通过串口与STM32通信。调试接口SWD下载调试接口必不可少。同时引出一个USART串口到排针方便连接电脑进行调试信息打印。原理图设计时一个容易忽略的细节是传感器供电的隔离。红外发射管工作时电流较大50-100mA如果和单片机共用一条细的电源走线可能会引起电源波动导致单片机复位。好的做法是电源进来后先分两路一路经滤波后给数字部分MCU、OLED另一路单独给传感器和比较器供电。地线GND最后在电源入口处单点汇合。3. 软件逻辑核心状态机与抗干扰算法硬件搭好了就像有了健全的躯体软件则是赋予其灵魂的关键。人流检测的核心软件逻辑就是一个精心设计的有限状态机FSM再包裹上层层抗干扰算法。3.1 双路红外信号的状态机建模我们有两路传感器Sensor_A入口侧和Sensor_B出口侧。每一路传感器的信号在理想情况下人经过时会产生一个从低到高再到低的脉冲。但实际信号充满了抖动和毛刺。我们首先需要定义每一路传感器的状态typedef enum { SENSOR_CLEAR 0, // 光束未被遮挡 SENSOR_BLOCKED 1 // 光束被遮挡 } SensorState_t;但直接读取GPIO电平是不行的。我们需要一个软件消抖函数。我的做法是在定时器中断比如1ms一次里采样GPIO电平并用一个队列或计数器来判定稳定状态。// 简化的软件消抖示例 #define DEBOUNCE_THRESHOLD 30 // 持续30ms认为状态稳定 SensorState_t Debounce_Sensor(GPIO_PinState currentLevel) { static uint16_t count 0; static GPIO_PinState lastStableState GPIO_PIN_SET; // 假设初始为未遮挡 static GPIO_PinState lastRawState GPIO_PIN_SET; if (currentLevel lastRawState) { count; } else { count 0; lastRawState currentLevel; } if (count DEBOUNCE_THRESHOLD) { if (currentLevel ! lastStableState) { lastStableState currentLevel; // 状态发生稳定改变 return (lastStableState GPIO_PIN_RESET) ? SENSOR_BLOCKED : SENSOR_CLEAR; } } return (lastStableState GPIO_PIN_RESET) ? SENSOR_BLOCKED : SENSOR_CLEAR; }获取到稳定的两路传感器状态后就可以构建人流量检测的主状态机了。状态机的状态定义如下typedef enum { STATE_IDLE 0, // 空闲无人经过 STATE_A_BLOCKED, // A传感器被遮挡人可能进入或离开 STATE_B_BLOCKED, // B传感器被遮挡人可能进入或离开 STATE_BOTH_BLOCKED, // 两个传感器同时被遮挡人正在通道中间 STATE_PASS_IN, // 判定为进入 STATE_PASS_OUT // 判定为离开 } TrafficState_t;状态转移逻辑是这个算法的精髓IDLE - A_BLOCKEDA先被挡人可能从A侧进入。启动一个“有效时间窗口”定时器比如2秒如果在这个窗口内B也被挡则可能进入BOTH_BLOCKED或直接判定方向。A_BLOCKED - BOTH_BLOCKED在时间窗口内B也被遮挡。此时记录顺序是A先B后。BOTH_BLOCKED - B_BLOCKEDA先恢复B仍被挡。结合“A先于B被挡”的记录可以初步判定为进入。但为防止人后退需要等B也恢复。B_BLOCKED - IDLEB恢复。结合之前的记录确认一次“进入”事件计数器加一。反之如果是B先被挡A后被挡流程对称最终判定为“离开”。这个状态机有效处理了正常通过的情况。但现实场景更复杂。3.2 应对复杂场景的算法增强场景一两人紧贴通过。这是红外对射最大的挑战。如果两个人前后脚紧贴可能导致传感器只产生一个宽脉冲从而漏计一人。我的应对策略是“脉冲宽度分析”。在STATE_BOTH_BLOCKED状态下如果遮挡持续时间远长于单人正常通过时间比如单人通过约0.5秒而遮挡持续了1.2秒则有可能不止一人。我会在状态机中增加一个“疑似多人”子状态并结合传感器信号在“遮挡”期间是否有微弱的“恢复-遮挡”抖动因为人体间隙来辅助判断。虽然不能100%准确但可以设置一个“最大通过时间”超过后强制结束当前状态并计数1至少避免系统“卡死”。场景二人在通道内停留或折返。有人可能走到中间停下来或者进去后又退出来。这会导致状态机乱套。我的处理方法是引入“超时重置机制”。每一个非IDLE的状态都有一个独立的超时定时器如3秒。如果在该状态下超时未发生预期转移则强制重置状态机到IDLE并不进行计数。同时在STATE_PASS_IN或STATE_PASS_OUT这种判定状态增加一个“确认期”只有顺利回到IDLE计数才最终生效否则判定为无效动作丢弃。场景三环境光突变干扰。强烈的闪光灯或太阳光直射可能让红外接收管饱和产生类似被遮挡的信号。除了硬件上的滤光片和调制解调发射管用38kHz载波接收管只解调该频率信号外软件上可以增加“异常脉冲过滤”。统计单位时间内信号跳变的次数。正常人的遮挡是一个平滑的脉冲而光干扰产生的可能是高频抖动的毛刺。如果短时间内跳变次数超过阈值则忽略该段信号。将这些策略融入状态机后代码会变得复杂但鲁棒性大大提升。核心思想是宁可漏计不可错计。对于客流量统计偶尔漏掉一两个数据影响不大但频繁的误计数会导致数据完全不可信。4. 数据持久化、显示与通信模块实现计数只是第一步如何让数据变得可用同样重要。我们需要存储、显示和上传数据。4.1 基于SPI Flash的循环存储队列使用W25Q16这类SPI Flash存储历史数据。不要简单地顺序写入因为Flash有擦写寿命约10万次。我设计了一个循环队列存储结构。首先在Flash中固定一个区域例如从地址0x000000开始作为“数据区”。每条记录包含时间戳年月日时分秒和进出人数累计值或差值。假设每条记录占16字节。我们预留1000条记录的空间16KB。我们需要在Flash另一个固定地址例如末尾地址保存“队列头指针”和“队列尾指针”。每次上电先读取这两个指针。写入数据时将数据写入尾指针指向的地址。更新尾指针地址16。如果尾指针超出数据区末尾则绕回到开头。如果尾指针追上了头指针即队列满则更新头指针也向前移动一条记录实现“挤掉”最老的数据。将新的头尾指针写回Flash的指针保存区。注意Flash写入前必须先擦除通常按扇区擦除如4KB。频繁擦写指针区会损坏它。因此指针区最好单独占用一个最小擦除单元如一个4KB扇区并且采用“磨损均衡”的简单策略这次写扇区0下次写扇区1以此类推。或者使用FRAM或EEPROM这类无需擦除的存储器存指针会更简单但成本稍高。4.2 OLED显示与界面设计使用SSD1306 OLED通过I2C驱动。显示内容不宜过杂。我通常设计两屏通过按键切换第一屏主界面实时显示当前室内人数、今日累计进入人数、今日累计离开人数。以及一个简单的“信号强度”图标通过ADC读取比较器前的模拟电压值用格数表示方便安装时调试对光。第二屏历史数据显示最近10条的通过记录包括时间和方向进入/离开箭头。驱动OLED要注意刷新策略。不要在主循环里不断刷新全部内容这很耗时。使用一个“脏标记”机制。只有当数据如人数发生变化时才标记对应的显示区域为“需要刷新”然后在主循环中定时如100ms检查并执行局部刷新。这能大大降低CPU占用也让显示更稳定。4.3 通过串口与上位机或WiFi模块通信预留USART接口用于调试和数据导出。通信协议一定要简单、可靠。我自定义了一个简单的文本协议便于人眼阅读和PC端脚本解析。// 上报一条实时通过事件 EVT DIR:IN, TIME:2023-10-27 14:30:05, TOTAL_IN:123, TOTAL_OUT:117, CURRENT:6\n // 应答查询历史数据命令 HIS INDEX:1, DIR:OUT, TIME:2023-10-27 14:25:33\n HIS INDEX:2, DIR:IN, TIME:2023-10-27 14:28:01\n ...如果需要连接ESP-01S WiFi模块则STM32只需通过串口向ESP发送AT指令让ESP连接服务器并打包上报数据。STM32侧的逻辑就变成了简单的串口数据转发和命令解析。这里的关键是设计好重连机制和心跳包确保网络不稳定时数据不丢失。可以在STM32端开辟一个小缓存队列在网络断开时暂存事件数据等网络恢复后重发。5. 系统调试、校准与性能优化实战硬件焊接好代码烧录进去只是万里长征第一步。真正的挑战在于调试和优化让系统在实际环境中稳定工作。5.1 传感器安装校准与灵敏度调节安装位置至关重要。两对红外传感器必须平行且正对否则光束无法对齐接收端永远收不到足够信号。我的土办法是先用可见光的激光笔注意安全替代红外发射管调整位置直到光点打在接收管中心。然后再换回红外管。灵敏度调节需要耐心。通过旋转比较器上的电位器改变参考电压。首先在无人环境下调节电位器使比较器输出为低电平未遮挡。用万用表测量接收管输出电压记下这个值V_clear。然后用一张深色纸板完全遮挡光束测量此时的输出电压V_blocked。将参考电压V_ref设置在 (V_clear V_blocked) / 2 的位置附近。这样能获得最大的信噪比。实际测试让人以正常速度、不同姿势抱东西、低头玩手机通过观察STM32收到的脉冲信号是否干净、完整。一个重要的测试是让人在传感器前缓慢移动此时输出可能是一个缓慢变化的模拟信号如果参考电压设置不当比较器输出可能会产生多个抖动脉冲。这就需要适当调整V_ref或者增加软件消抖的时间阈值。5.2 利用STM32调试工具定位问题当计数不准时如何定位是硬件问题还是软件问题第一步信号可视化。不要盲目改代码。将两路传感器比较器后的数字信号即STM32 GPIO读取前的信号用杜邦线接到另外两个闲置的GPIO上编写一个简单程序让这两个GPIO跟随传感器信号变化。然后用逻辑分析仪或者示波器同时捕捉这两路信号和STM32判定为“进入/离开”时输出的一个提示信号比如另一个GPIO的短脉冲。通过对比波形你可以清晰地看到传感器信号是否干净遮挡顺序是否正确STM32的判定逻辑是否和波形对齐第二步打印调试信息。如果没逻辑分析仪串口打印是好朋友。在状态机每一个状态切换的关键点通过串口打印出当前状态、传感器A/B电平、定时器值等信息。[DBG] T:1002ms, State: IDLE-A_BLOCKED, A:1, B:0 [DBG] T:1205ms, State: A_BLOCKED-BOTH_BLOCKED, A:1, B:1 [DBG] T:1450ms, State: BOTH_BLOCKED-B_BLOCKED, A:0, B:1 [DBG] T:1655ms, State: B_BLOCKED-IDLE, A:0, B:0 PASS IN!把这些日志保存下来复现问题时对照分析能很快找到状态机在哪一步出了错。第三步模拟测试。直接用手遮挡传感器来测试太累。可以写一个简单的测试函数用定时器模拟产生理想的A、B传感器信号序列并注入到状态机中。这样可以在不依赖硬件的情况下验证状态机逻辑的正确性。5.3 低功耗设计与长期运行稳定性如果设备是电池供电低功耗设计就是必须的。STM32F103在低功耗方面不算最强但也能做不少优化睡眠模式在无人经过的长时间空闲期可以让STM32进入停止模式Stop Mode。此时外部中断EXTI可以唤醒它。我们可以把红外接收比较器的输出信号不直接给普通GPIO而是连接到支持外部中断的引脚上。当有人遮挡信号跳变产生边沿中断将MCU唤醒。唤醒后MCU快速完成计数处理然后如果没有后续触发再延时一段时间后重新进入停止模式。外设时钟管理在进入低功耗模式前关闭所有不必要的外设时钟如OLED的I2C、SPI Flash的SPI、ADC等。唤醒后再重新初始化。动态频率调整在活跃处理期间使用全速72MHz。在处理完任务等待下一次中断的间隙可以通过降低系统时钟HSI频率来减少功耗。长期运行稳定性除了代码逻辑健壮还要考虑“看门狗”。一定要开启独立看门狗IWDG防止程序跑飞。喂狗的位置要精心选择放在主循环和那些可能耗时较长的任务如Flash写入之后确保系统正常运行时能定期喂狗而一旦死锁就能复位重启。最后关于数据准确性没有任何单一传感器方案能做到100%。这个项目的意义在于在极低的成本下实现一个可靠性比如95%可用的解决方案。它教会我们的不仅仅是如何驱动一个STM32更是如何将一个简单的物理需求分解成硬件设计、信号处理、状态逻辑、数据管理、抗干扰等一系列工程问题并逐一找到平衡点去解决。这其中的思考过程比最终的代码和电路图更有价值。本文还有配套的精品资源点击获取
返回列表