基于STM32与红外对管的人流量检测系统:硬件设计、状态机算法与抗干扰实践
2026/9/2 5:59:06 网站建设 项目流程

简介:本资源是一套面向嵌入式初学者与STM32进阶实践者的综合性人流量检测系统开发包,聚焦于真实场景下的软硬件协同设计,解决从传感器数据采集、本地计数处理到多模态数据上传的完整技术链路问题。压缩包共含6个核心项目文件,涵盖C语言主控程序(基于HAL库)、Keil工程文件、传感器驱动模块、蓝牙与机智云通信接口代码、电机控制逻辑及风扇温控仿真模型,类型以.c/.h源码、.uvprojx工程、README.md说明文档为主,整体大小为10.47MB。目前已有201人学习下载,适合课程设计、毕业设计或电子竞赛备赛使用。读者可直接导入Keil环境编译运行,获取红外/超声波双模检测逻辑、中断计数防抖策略、PWM风扇调速实现、蓝牙串口透传协议封装等关键工程细节,并通过配套README快速掌握各子项目功能定位与集成方式。

1. 项目概述与核心需求解析

最近在做一个商场出入口的人流量统计项目,客户要求成本可控、部署灵活且数据准确。市面上现成的方案要么太贵,要么体积庞大安装不便。琢磨了一下,决定自己动手,用STM32单片机为核心,搭配一对红外对管,设计一个轻量级的人流量检测器。这个方案的核心思路很简单:通过判断人体是否遮挡了红外光束来触发计数,关键在于如何稳定、准确地识别“一个人”经过,而不是被误触发(比如小动物、飘过的塑料袋或者两个人紧挨着走过)。最终完成的成果,包括完整的程序代码、原理图、PCB设计文件等,我打包成了“STM32单片机人流量检测器设计程序代码等6个.zip”。今天就来详细拆解这个项目的设计思路、硬件选型、软件逻辑以及那些调试过程中踩过的坑,希望能给想做类似项目的朋友一些参考。

这个项目本质上是一个数字信号的事件检测与去抖系统。它不适合需要精确识别个体特征(如人脸)或行进方向的场景,但在统计单向通道、门口、闸机等位置的人员通过次数上,具有极高的性价比和可靠性。整个系统由STM32F103C8T6(俗称“蓝桥杯”或“最小系统板”那颗芯片)作为主控,一对红外发射接收管作为传感器,一个OLED屏幕用于本地显示,以及一个ESP-01S WiFi模块用于数据上传。程序代码基于STM32标准外设库开发,结构清晰,便于二次修改。

2. 硬件系统设计与关键器件选型

硬件是整个系统稳定性的基石。在预算有限的前提下,每一分钱都要花在刀刃上,同时要保证足够的抗干扰能力和长期运行的可靠性。

2.1 主控芯片:为什么是STM32F103C8T6?

在8位机和32位机之间,我毫不犹豫地选择了STM32。对于人流量检测这种需要实时处理传感器信号、管理外设(显示、通信)并可能涉及简单算法的应用,STM32的Cortex-M3内核提供了充足的性能余量。具体到型号,F103C8T6是经典型号,资源丰富:72MHz主频、64KB Flash、20KB RAM、多达37个GPIO、3个定时器、2个SPI、2个I2C、3个USART、1个USB和2个ADC。最关键的是,它的生态极其成熟,资料遍地都是,价格也打到了白菜价。

注意:市场上F103C8T6有国产替代(如GD32)和原装ST之分。对于本项目,国产芯片完全够用,但在采购时要注意Flash容量和功耗的细微差异,最好让卖家提供测试过的例程。

2.2 传感方案:红外对管 vs. 热释电 vs. 摄像头

这是项目的核心决策点。

  1. 红外对管(最终选择):由红外发射管和红外接收管(通常是光电三极管)组成。发射管持续发出红外光,接收管接收。当有人经过遮挡光束时,接收管输出电平变化。优点是电路简单、成本极低(一对几毛钱)、响应速度快、功耗可控(可调制发射)。缺点是易受环境光干扰,特别是太阳光中含有丰富的红外成分;检测区域是一条细线,安装需要精确对齐。
  2. 热释电红外传感器(PIR):检测人体发出的特定波长红外线。优点是检测面广、不易受可见光干扰。缺点是响应速度慢(适合检测静止或慢速移动)、对温度变化敏感、无法区分物体移动方向(除非用双元探头加菲涅尔透镜做方向判断,电路复杂),且成本相对较高。
  3. 摄像头图像识别:最强大也最复杂。优点可获取丰富信息(人数、方向、甚至属性)。缺点是成本高昂(需要摄像头模组和较强的处理芯片)、算法复杂、功耗大、涉及隐私问题。

综合比较,红外对管方案在成本、响应速度和实现难度上取得了最佳平衡。为了解决环境光干扰,我们采用调制解调技术:让STM32的PWM驱动红外发射管发出特定频率(例如38kHz)的红外光,接收端使用一体化红外接收头(如HS0038B)或由运放搭建的解调电路。这样,只有该频率的信号才会被识别为有效信号,自然光等直流或低频干扰被极大抑制。

2.3 外围电路与电源设计

  1. 红外发射电路:用一个NPN三极管(如S8050)驱动红外发射管。STM32的PWM信号通过一个限流电阻连接到三极管基极,控制其通断。发射管串联一个电阻限流,防止过流烧毁。计算限流电阻时,要确保发射管工作在额定电流下(通常20-50mA),以获得足够的发射功率。
  2. 红外接收电路:如果使用一体化接收头(如HS0038B),则电路非常简单,VCC、GND、OUT三根线,OUT直接接STM32的GPIO。如果为了成本使用光电三极管自行解调,则需要搭建一个运放电路,包含带通滤波以筛选出调制频率,再经过比较器整形成数字信号。后者调试更复杂,但BOM成本更低。
  3. 电源模块:整个系统预计由5V USB或电池供电。STM32需要3.3V,OLED屏和ESP-01S也多是3.3V。因此选用一颗AMS1117-3.3稳压芯片。关键点:ESP-01S在发射WiFi信号时瞬时电流可能超过200mA,必须确保AMS1117和前端电源能提供至少500mA的连续电流,并在电源入口和芯片电源脚放置足够的滤波电容(如100uF电解电容并联0.1uF陶瓷电容)。
  4. 显示与通信:选用0.96寸OLED(SSD1306驱动,I2C接口),功耗低、显示清晰。WiFi模块选用ESP-01S,通过串口与STM32通信,实现数据上传到服务器或云平台。

3. 软件架构与核心逻辑实现

软件部分采用前后台(超级循环)架构,对于本应用足够清晰高效。程序主要包含以下几个模块:系统初始化、红外信号采集与处理、人数统计逻辑、OLED显示驱动、WiFi通信协议。

3.1 红外信号采集与数字滤波

这是确保计数准确的第一道关卡。我们使用STM32的一个外部中断引脚(如PA0)来接收红外接收头的输出信号。当无人遮挡时,接收头输出高电平;遮挡时,输出低电平。

// 伪代码示例:外部中断服务函数 void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { // 读取当前引脚电平 uint8_t current_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0); // 将事件放入一个队列或设置标志位,供主循环处理 ir_event_queue[queue_tail++] = current_state; EXTI_ClearITPendingBit(EXTI_Line0); } }

然而,机械振动、瞬间的遮挡物(如飞虫)会导致信号的抖动,产生多次快速的高低电平变化。必须在软件中进行去抖(Debounce)。我采用“延时确认法”在中断中处理:当检测到电平变化(如下降沿)时,不立即认为有人通过,而是启动一个定时器(如10ms)。10ms后再次检测该引脚电平,如果仍然是低电平,才确认是一次有效的遮挡开始。

// 主循环中处理去抖逻辑 if(ir_event_flag) { // 中断设置了标志 delay_ms(10); // 阻塞延时,在实际项目中用定时器非阻塞方式更好 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) { // 确认是有效的遮挡开始 person_detected_start(); } ir_event_flag = 0; }

3.2 核心算法:如何区分“一个人”?

这是本项目的灵魂。简单的“遮挡-恢复”就计数,会导致人挥手、侧身、携带大件行李等情况多次计数。我设计了一个状态机,来更智能地判断一次完整的通过。

我们定义两个红外对管:管A和管B,一前一后安装,间距约20-30cm(根据人通过速度调整)。通过判断A、B被遮挡的先后顺序,不仅可以计数,还能判断方向(进/出)。

状态机有以下几个状态:

  • IDLE:空闲状态,A、B均无遮挡。
  • A_TRIGGERED:A管首先被遮挡。启动一个计时器T1(例如,正常人走过20cm距离的时间,假设为0.2-1秒)。
  • A_B_TRIGGERED:在T1超时前,B管也被遮挡。这很可能是一个人正常通过。启动另一个计时器T2,等待B管恢复(人离开B)。
  • B_TRIGGERED:B管首先被遮挡(与A_TRIGGERED逻辑对称,用于反方向)。
  • COUNT_PENDING:当处于A_B_TRIGGERED状态,且B管恢复(人完全通过)时,进入此状态。这里加入一个“确认时间”T3(如100ms),如果在此期间A、B都保持未遮挡,则确认一次有效计数,方向为A->B(进入),并将计数加1。然后状态回到IDLE。

这个状态机有效解决了以下问题:

  1. 在A处徘徊:触发A后,一直不触发B,计时器T1超时后,状态重置为IDLE,不计数。
  2. 反向通过(B->A):由状态B_TRIGGERED和对应的逻辑处理,实现反向计数。
  3. 两人紧贴:第一个人触发A、B并计数后,状态回到IDLE。此时第二个人如果已经触发了A,系统会重新从A_TRIGGERED开始识别,只要时间间隔大于状态机处理时间,就能区分。
  4. 遮挡物未完全离开:通过“确认时间”T3,确保光束完全恢复后再进行计数,避免同一人被重复分割计数。

实操心得:计时器T1、T2、T3的参数需要现场实地调试。在办公室用步行速度调好的参数,放到人流量大、行走速度多变的商场门口,可能就不准了。最好的办法是留出通过串口或按键在线调整这些参数的功能,方便现场校准。

3.3 数据展示与上传

本地显示使用OLED,实时刷新当前总人数、进出人数、系统状态等。使用硬件I2C驱动,注意初始化时的速率设置,过高的速率可能导致显示异常。

WiFi通信采用ESP-01S,STM32通过串口(USART)以AT指令与其交互。为了通信稳定,我设计了简单的应用层协议:

  1. 心跳包:每30秒发送一次,维持TCP连接,格式如[HEARTBEAT]\n
  2. 数据上报包:当人数发生变化时(或定时,如每1分钟)上报,格式如[DATA]IN:123,OUT:122,TOTAL:245\n
  3. 指令响应:ESP-01S收到服务器指令(如重置计数器)后,转发给STM32,STM32执行后回复确认。

串口通信必须做好数据帧解析和超时重发。我使用一个环形缓冲区接收ESP-01S返回的数据,然后逐字节解析,寻找换行符\n作为帧结束标志。每个AT指令发送后,等待特定时间(如2秒)内的响应,如果超时或响应错误,则重试(最多3次)。重试多次失败后,系统应尝试重启ESP-01S(通过控制其EN或RST引脚)或切换到离线模式,仅本地计数。

// 简化的AT指令发送与等待响应函数 int8_t send_at_command_and_wait(const char* cmd, const char* expected_resp, uint32_t timeout_ms) { clear_uart_buffer(); uart_send_string(cmd); uint32_t start_tick = get_tick(); while((get_tick() - start_tick) < timeout_ms) { if(uart_receive_line(buffer)) { // 接收到一行数据 if(strstr(buffer, expected_resp) != NULL) { return 0; // 成功 } else if(strstr(buffer, "ERROR") != NULL) { return -1; // AT指令错误 } } } return -2; // 超时 }

4. PCB设计要点与抗干扰考量

将原理图转化为可靠的PCB,是产品化的关键一步。本项目PCB为双层板,布局布线遵循以下原则:

  1. 分区布局

    • 电源区:电源接口、稳压芯片、滤波电容集中放置在板子入口处。
    • 数字区:STM32、晶振、下载接口放在板子中部,远离模拟部分。
    • 模拟/传感区:红外发射接收电路、运放(如果用的话)放在板子一侧,特别是接收端的前级放大电路,要特别敏感。
    • 射频区:ESP-01S模块尽量放置在板边,其天线部分下方和周围尽量净空(不走线、不铺铜),以减少对数字信号的干扰。
  2. 电源与地处理

    • 采用“星型”或“单点”接地策略,特别是模拟地(AGND)和数字地(DGND)在一点相连(通常在电源芯片附近)。
    • 为关键芯片(STM32、运放)的电源引脚就近放置去耦电容(0.1uF陶瓷电容),并确保回流路径最短。
    • 电源走线尽量宽,减少压降。
  3. 信号完整性

    • 红外接收头的输出信号线,尽量短,并远离时钟线、PWM线等高速数字信号线。必要时可以包地处理。
    • 晶振电路靠近STM32的OSC_IN/OSC_OUT引脚,下方禁止走线,并用地线包围。
  4. 红外对管接口:发射和接收管通过排针或导线外接。在PCB接口处,为接收管信号线预留一个RC低通滤波电路的位置(如1K电阻串联,对地100pF电容),如果现场干扰大,可以焊上,进一步滤除高频噪声。

5. 系统调试与现场部署实录

硬件焊接完成、程序烧录后,真正的挑战才刚刚开始。实验室环境和实际部署环境天差地别。

5.1 实验室调试步骤

  1. 电源与最小系统:先不焊传感器和外设,只焊接STM32最小系统、电源和下载口。测试3.3V电压是否稳定,能否正常烧录和运行一个简单的LED闪烁程序。
  2. 红外传感电路单独测试:用示波器观察红外发射管两端的电压,确认PWM调制信号(38kHz)正常。用物体遮挡,观察接收头输出引脚的电平变化是否干净利落,没有毛刺。如果使用自搭建接收电路,需要用示波器仔细调整运放的放大倍数和比较器阈值,直到输出理想的方波。
  3. 集成测试:将所有模块焊接。编写测试程序,让OLED显示当前A、B传感器的实时状态(0/1),并通过串口打印出来。用手模拟人通过,观察状态变化和打印信息是否符合状态机设计。
  4. WiFi联调:使用USB转TTL工具直接连接ESP-01S,测试其AT指令是否正常,能否连接指定的WiFi和服务器。然后再集成到系统中,测试STM32与它的串口通信。

5.2 现场部署常见问题与解决

现场部署时,我遇到了几个典型问题:

  1. 问题:阳光直射导致误触发。

    • 现象:在室内工作正常,放到室外门口,即使没人,计数器也会偶尔跳动。
    • 排查:用示波器看接收头输出,发现即使未遮挡,输出基线也不在稳定的高电平,而是在轻微波动。强阳光包含了红外成分,被接收头部分接收。
    • 解决
      • 物理屏蔽:为红外对管制作一个遮光筒,只允许正前方的光进入,大幅减少环境光干扰。
      • 软件优化:提高判断阈值。原来判断遮挡是电平变低,现在改为“电平低于某个阈值(如0.8 * VCC)”才算。可以通过ADC读取接收头输出(如果是模拟输出),或者选用带模拟输出的接收器件。
      • 调制深度:增加红外发射管的驱动电流(在安全范围内),提高发射信号强度,提升信噪比。
  2. 问题:两人并排或推车通过,只计一次数。

    • 现象:两个人紧挨着同时通过,系统只计为1人。
    • 分析:这是红外对管方案的物理限制。光束是一条线,如果两个人体在垂直于光束的方向上重叠,系统无法区分。
    • 解决:这属于方案局限性,很难从根本上解决。可以尝试安装两套独立且水平方向有一定间隔的红外对管,通过逻辑判断(两套传感器触发的时间差极小)来推测可能是多人,但这会增加成本和复杂度。对于大多数统计场景,允许一定的误差(如95%准确率)是可以接受的。需要在项目需求阶段明确这一点。
  3. 问题:WiFi频繁断线重连。

    • 现象:设备运行一段时间后,数据上传中断。
    • 排查:查看ESP-01S的串口日志,发现经常有“断开连接”或“连接失败”的信息。现场WiFi信号强度可能不稳定。
    • 解决
      • 增强软件鲁棒性:在代码中增加更完善的重连机制。不仅仅是重启模块,而是先尝试断开旧连接,重新扫描WiFi,选择信号最强的AP(如果配置了多个)进行连接。
      • 增加看门狗:启用STM32的独立看门狗(IWDG),在WiFi通信卡死时复位整个系统。
      • 硬件优化:检查ESP-01S的电源,确保在发射瞬间电压跌落不超过0.3V。可以在其VCC引脚就近增加一个大容量钽电容(如100uF)。
      • 备用方案:增加SD卡模块,在网络异常时将数据暂存到SD卡,网络恢复后补传。
  4. 问题:长期运行后计数漂移。

    • 现象:设备运行一周后,统计的总人数与人工记录或视频回放核对,有少量累积误差。
    • 排查:可能是状态机的计时参数(T1, T2, T3)不完全适应该地点所有人流速度。例如,老人小孩通过慢,可能导致T1超时被漏计;快速跑过可能导致系统来不及反应。
    • 解决:进行长时间的数据采集和对比分析。录制一段高峰期视频,人工计数,与设备计数对比。根据误差情况,动态调整状态机的超时参数。最好能将参数做成可配置项,通过串口或服务器下发。

6. 程序代码结构详解与关键函数

“程序代码等6个.zip”中的工程结构清晰,基于STM32标准库。这里挑几个核心文件讲解:

Project/ ├── CMSIS/ /* ARM Cortex-M核心支持文件 */ ├── STM32F10x_StdPeriph_Driver/ /* STM32标准外设库 */ ├── User/ │ ├── main.c /* 主函数,初始化与主循环 */ │ ├── stm32f10x_it.c /* 中断服务函数 */ │ ├── ir_sensor.c /* 红外传感采集与状态机处理 */ │ ├── oled.c /* OLED显示驱动 */ │ ├── wifi_esp01s.c /* ESP-01S通信模块驱动 */ │ ├── timer.c /* 定时器配置,用于去抖、状态机计时 */ │ └── utils.c /* 工具函数,如延时、字符串处理 */ └── … (其他配置文件)

ir_sensor.c中的状态机实现(简化版)

typedef enum { S_IDLE, S_A_FIRST, S_B_FIRST, S_BOTH_COVERED_A_FIRST, S_BOTH_COVERED_B_FIRST, S_PENDING_COUNT } DetectState_t; volatile DetectState_t g_detect_state = S_IDLE; uint32_t g_timer_start_ticks = 0; uint16_t g_count_in = 0, g_count_out = 0; void ir_sensor_state_machine_handler(uint8_t sensor_a, uint8_t sensor_b) { switch(g_detect_state) { case S_IDLE: if(sensor_a == 0 && sensor_b == 1) { // A被遮挡,B空闲 g_detect_state = S_A_FIRST; g_timer_start_ticks = get_system_tick(); } else if(sensor_a == 1 && sensor_b == 0) { // B被遮挡,A空闲 g_detect_state = S_B_FIRST; g_timer_start_ticks = get_system_tick(); } break; case S_A_FIRST: if(sensor_b == 0) { // 在超时前,B也被遮挡 g_detect_state = S_BOTH_COVERED_A_FIRST; // 可以重置一个计时器,等待B恢复 } else if(is_timeout(g_timer_start_ticks, T1_TIMEOUT)) { // A处遮挡物离开或不是人,复位 g_detect_state = S_IDLE; } break; case S_BOTH_COVERED_A_FIRST: if(sensor_b == 1) { // B恢复(人离开B) g_detect_state = S_PENDING_COUNT; g_timer_start_ticks = get_system_tick(); } break; case S_PENDING_COUNT: if(sensor_a == 1 && sensor_b == 1) { // A也恢复了 if(is_timeout(g_timer_start_ticks, T3_CONFIRM)) { g_count_in++; // 完成一次进入计数 g_detect_state = S_IDLE; // 触发数据更新和上传 data_updated = 1; } } else { // 在确认期间又有遮挡,可能不是一次完整通过,复位到合适状态 g_detect_state = S_IDLE; } break; // ... 其他状态(S_B_FIRST, S_BOTH_COVERED_B_FIRST)逻辑对称 default: g_detect_state = S_IDLE; break; } }

wifi_esp01s.c中的数据处理与发送: 数据上报不宜过于频繁。我设置了一个标志位data_updated,当计数变化时置位。在主循环中检查该标志,并启动一个“上报延时计时器”。例如,延时5秒后,如果data_updated仍为1,则打包数据并发送。这样可以避免网络拥堵,也防止因一个人通过时光束抖动导致短时间内多次上报。

7. 项目总结与扩展思考

这个基于STM32的人流量检测器项目,从构思到稳定运行,花费了不少调试和优化的时间。它让我深刻体会到,嵌入式开发不仅仅是写代码,更是硬件、软件、环境、需求之间反复权衡和妥协的艺术。

几个关键的体会:

  1. 传感器是系统的眼睛,但眼睛也会“看错”。红外对管方案成本低,但对环境非常敏感。调制解调是必须的,物理遮光同样重要。在预算允许下,可以考虑使用超声波测距模块激光对射传感器,它们的抗干扰能力更强,检测距离更远,但成本也更高。
  2. 状态机是逻辑控制的利器。对于这种顺序事件的处理,状态机比一堆if-else语句清晰、健壮得多。画好状态转移图再编码,事半功倍。
  3. 现场调试时间可能远超开发时间。实验室里一切完美,到现场可能问题百出。务必留出充足的现场调试和参数校准时间,并且最好在软件上预留调试接口(如通过串口实时修改参数、查看状态)。
  4. 电源稳定性是一切的前提。尤其是使用WiFi、4G等无线模块时,其发射瞬间的电流冲击必须考虑在内,电源滤波和PCB布局布线必须认真对待。

这个项目还可以从多个方向扩展:

  • 低功耗设计:如果采用电池供电,可以使用STM32的低功耗模式,让红外发射管间歇工作(如发射100ms,休眠900ms),并选用低功耗的LoRa或NB-IoT模块替代WiFi。
  • 多区域组网:在大型场所部署多个检测点,通过ZigBee或LoRa自组网,将数据汇总到一个网关再上传。
  • 数据融合:结合简单的压电传感器或地磁传感器,提高计数准确率,尤其是在推车、行李箱干扰大的场景。
  • 本地存储与备份:增加SPI Flash或SD卡,存储长时间的历史数据,即使网络中断数据也不丢失。

最后,所有的设计文件、原理图、PCB、源代码都打包在分享的文件里了。硬件设计使用Altium Designer,软件是Keil MDK。希望这份详尽的拆解,能帮你避开我踩过的那些坑,更顺利地完成你自己的项目。嵌入式开发,动手去做,遇到问题解决问题,就是最好的学习方式。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询