资讯动态

STM32人流量检测系统设计:从红外传感器到状态机算法的嵌入式实战

发布时间:2026/9/1 2:36:10 来源:尧图企业网站定制
简介本资源是一套面向嵌入式初学者与STM32进阶开发者的综合性实践项目包聚焦人流量检测这一典型物联网应用场景覆盖传感器驱动、实时计数、无线通信及外设控制等核心能力训练。压缩包共含6个子项目含程序代码、仿真工程、硬件设计说明及README文档主要文件类型包括Keil工程源码C语言、Proteus或STM32CubeMX仿真配置、蓝牙与云平台通信模块、电机PID控制逻辑及风扇/温度等扩展功能实现整体大小为10.47MB结构清晰、模块解耦便于分步学习与功能复用。已有201人下载学习资源特别提供完整的人流计数中断处理逻辑、机智云接入示例、红外/超声波双模检测参考方案以及配套的硬件接口定义与调试要点说明可直接用于课程设计、毕业设计或小型智能安防项目原型开发。1. 项目概述从“压缩包”到完整的人流量检测系统最近在整理资料时翻到了一个名为“STM32单片机人流量检测器设计程序代码等6个.zip”的老项目文件。这名字一看就很有故事典型的“毕设风”或“课程设计风”一个压缩包里塞满了源码、原理图、报告可能还有一堆没注释的库文件。对于刚接触STM32和嵌入式开发的朋友来说拿到这样的“大礼包”往往是既兴奋又头疼东西很全但不知从何下手更不清楚如何把它变成一个真正能跑起来的、稳定可靠的作品。这个项目的核心目标很明确利用STM32单片机配合传感器实现对特定区域比如门口、通道人员进出数量的自动检测与统计。它听起来简单但麻雀虽小五脏俱全几乎涵盖了嵌入式开发的几个核心环节传感器选型与信号处理、单片机外设驱动、算法逻辑实现、数据存储与显示甚至可能涉及简单的无线通信。今天我就以这个项目为引子结合我多年的踩坑经验从头到尾拆解一下如何设计并实现一个稳定、实用的STM32人流量检测器。我会重点讲清楚设计思路、关键代码的实现逻辑、以及那些在标准教程里不会写的调试技巧和避坑指南让你不仅能看懂这个“压缩包”更能自己动手搭建一个。2. 核心方案选型与设计思路拆解拿到一个项目第一步不是急着写代码而是先想清楚“怎么做”和“为什么这么做”。人流量检测本质上是一个“事件检测方向判断计数”的问题。2.1 传感器方案对比与选择市面上常见的方案主要有以下几种各有优劣红外对射/反射式传感器这是最经典、成本最低的方案。通常使用一对红外发射管和接收管对射式或者一个红外反射模块如E18-D80NK。当人体穿过光束时会遮挡或反射红外信号产生电平变化。优点电路简单成本极低响应速度快。缺点无法直接判断方向。单对传感器只能检测“有无遮挡”要判断进出方向至少需要两对传感器通过判断两路信号被触发的先后顺序即相位差来推断方向。此外容易受环境光干扰多人并行通过可能漏检。热释电红外传感器PIR如常见的HC-SR501模块。它检测人体发出的特定波长的红外线。优点本身可检测运动有一定方向性功耗相对较低。缺点检测距离和范围有局限响应速度较慢环境温度变化可能影响灵敏度同样存在方向判断模糊的问题通常也需要多个配合或结合其他传感器。超声波/毫米波雷达模块如RCWL-0516微波雷达模块或更专业的毫米波雷达。优点可以检测静止存在穿透力强可穿透非金属材料不受光线影响一些模块能直接输出距离、速度信息。缺点成本较高电路和信号处理相对复杂容易受金属物体干扰算法难度大。摄像头视觉方案使用OV系列摄像头图像处理算法如OpenMV、K210甚至STM32AI加速核。优点信息最丰富可识别更多特征。缺点复杂度陡增对单片机算力要求高开发难度大成本高涉及隐私问题。我的选择与理由 对于学习、课程设计或低成本应用双路红外对射传感器方案是平衡复杂度、成本和可靠性的最佳选择。它硬件简单逻辑清晰非常适合用来理解嵌入式系统的事件检测、中断处理和状态机编程。因此后续的讨论将基于“两路红外对射传感器”这一核心检测方案展开。2.2 系统整体架构设计确定了传感器整个系统的框架就清晰了[红外传感器A] -- [信号调理电路] -- [STM32 GPIO (外部中断)] [红外传感器B] -- [信号调理电路] -- [STM32 GPIO (外部中断)] | v [STM32F103C8T6 核心] | ------------------------------|------------------------------- | | | v v v [状态机逻辑判断] [计数器累加/存储] [显示/输出接口] (判断A/B触发顺序) (EEPROM/I2C/Flash) (OLED/LCD/串口) | v [电源管理模块]设计思路输入层两路传感器信号接入STM32的GPIO并配置为外部中断模式确保人员经过时能即时响应不占用主循环资源。处理层在中断服务函数或主循环中通过一个“状态机”来分析A、B两路信号的触发序列从而判定是“进入”还是“离开”。输出层根据判定结果对内部计数器进行加减。同时将当前人数通过OLED屏幕实时显示并可通过串口发送到上位机。存储层考虑断电数据不丢失需要将累计人数存储到STM32内部的Flash或外挂的EEPROM芯片中。抗干扰层这是关键需要软件去抖动、设置有效触发时间窗、防止在门口徘徊误触发等。3. 硬件电路设计关键与避坑指南即使有现成的原理图理解背后的设计原因也同样重要。3.1 传感器接口电路设计红外对射传感器的输出通常是集电极开路OC或电压输出。直接连接单片机可能会因为电平不匹配、电流倒灌或干扰导致误触发。推荐电路传感器输出 ---[10K上拉电阻]--- VCC (3.3V) | |---[1kΩ串联电阻]--- GPIO引脚 (配置为上拉输入) | GND上拉电阻确保传感器未触发时GPIO引脚处于确定的高电平状态避免悬空引入噪声。串联电阻限流保护单片机GPIO引脚。虽然很多IO口内部有钳位二极管但外部串联电阻是良好的习惯。并联电容在GPIO引脚对地并联一个10-100pF的小电容可以滤除高频毛刺。注意一定要查阅你所用的具体传感器模块的数据手册。有些模块输出高有效有人遮挡输出高电平有些低有效有人遮挡输出低电平。这决定了你中断是配置为上升沿触发还是下降沿触发以及状态机里的逻辑判断。3.2 电源设计STM32和传感器最好由独立的LDO如AMS1117-3.3供电而不是直接来自USB或开发板的3.3V。如果使用电池供电需要考虑低功耗设计本项目实时检测功耗不会太低但可以优化。避坑点数字噪声单片机GPIO快速翻转、LCD背光等会在电源上产生噪声可能干扰传感器模拟电路部分如果传感器有模拟输出或导致单片机复位。在电源入口处加一个大的电解电容如100uF和一个小的陶瓷电容0.1uF进行退耦。传感器供电红外发射管需要一定电流通常20-50mA确保你的电源或LDO能提供足够电流。最好为发射管部分单独供电或使用三极管驱动。3.3 显示与存储模块选型显示0.96寸OLEDSSD1306驱动I2C接口是绝佳选择。它功耗低、显示清晰、接口简单比LCD1602更适合便携设备。注意I2C总线上需要接上拉电阻通常4.7K。存储STM32F103C8T6内部有64KB Flash可以划出一部分如最后一页来存储数据。但Flash擦写次数有限约1万次频繁写入会损坏。如果人数更新频繁建议外挂一片AT24Cxx系列的EEPROMI2C接口它有百万次擦写寿命。4. 软件程序设计从外部中断到状态机这是整个项目的核心。我们目标是写出清晰、健壮、易于维护的代码。4.1 工程结构与外设初始化建议使用STM32CubeMX初始化代码生成HAL库工程结构清晰。人流量检测器/ ├── Core/ │ ├── Src/ │ │ ├── main.c │ │ ├── gpio.c (中断配置) │ │ ├── i2c.c (OLED EEPROM) │ │ └── ... │ └── Inc/ (头文件) ├── Drivers/ ├── OLED/ (OLED驱动代码) ├── EEPROM/ (EEPROM驱动代码) └── ...在main.c的初始化部分// 1. 初始化系统时钟、GPIO、I2C等 // 2. 初始化OLED显示欢迎界面 // 3. 从EEPROM中读取历史人数计数值 // 4. 配置传感器GPIO为外部中断模式 void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 传感器A引脚 (PA0) GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; // 假设遮挡时产生下降沿 GPIO_InitStruct.Pull GPIO_PULLUP; // 内部上拉 HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 传感器B引脚 (PA1) 配置类似 // ... // 设置中断优先级 HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); }4.2 核心基于状态机的方向判断算法这是项目的灵魂。我们不能在中断里直接判断方向因为抖动和触发顺序可能很乱。我们需要一个状态机来跟踪传感器的触发序列。定义状态typedef enum { STATE_IDLE 0, // 空闲状态无人经过 STATE_A_TRIGGERED, // A传感器先被触发 STATE_B_TRIGGERED, // B传感器先被触发 STATE_WAIT_RESET // 等待系统复位两人次检测完成或超时 } DetectState_t; volatile DetectState_t g_detect_state STATE_IDLE; // 全局状态变量用volatile修饰 volatile uint32_t g_last_trigger_time 0; // 最后一次触发的时间戳 uint32_t g_people_count 0; // 当前人数正数为室内人数中断服务函数与主循环逻辑// 在stm32f1xx_it.c中 void EXTI0_IRQHandler(void) { // 传感器A中断 if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); sensor_a_trigger_callback(); // 调用回调函数避免在中断里做复杂处理 } } // 类似地实现传感器B的中断 // 在main.c或单独模块中 void sensor_a_trigger_callback(void) { uint32_t current_time HAL_GetTick(); // 软件去抖检查两次触发是否间隔太近如50ms if((current_time - g_last_trigger_time) 50) { return; // 认为是抖动忽略 } g_last_trigger_time current_time; switch(g_detect_state) { case STATE_IDLE: // 第一个触发的是A可能有人进入A在外B在内 g_detect_state STATE_A_TRIGGERED; break; case STATE_B_TRIGGERED: // 之前B先触发现在A触发序列是 B-A判定为有人离开 g_detect_state STATE_WAIT_RESET; people_leave(); // 人数减一 break; case STATE_A_TRIGGERED: case STATE_WAIT_RESET: default: // 其他状态下的A触发可能是误触或无效序列直接复位状态 g_detect_state STATE_IDLE; break; } } void sensor_b_trigger_callback(void) { // 去抖逻辑同上... switch(g_detect_state) { case STATE_IDLE: g_detect_state STATE_B_TRIGGERED; // B先触发可能有人离开 break; case STATE_A_TRIGGERED: // 序列是 A-B判定为有人进入 g_detect_state STATE_WAIT_RESET; people_enter(); // 人数加一 break; // ... 其他情况处理类似 } } void people_enter(void) { if(g_people_count MAX_COUNT) { // 防止溢出 g_people_count; update_display(); // 更新OLED显示 save_count_to_eeprom(); // 可选存储到EEPROM } // 可以添加蜂鸣器提示音 } void people_leave(void) { if(g_people_count 0) { // 人数不为负 g_people_count--; update_display(); save_count_to_eeprom(); } }主循环中的超时复位while (1) { // 状态机超时处理如果进入某个中间状态如A_TRIGGERED后 // 超过一定时间如2秒另一个传感器没触发则认为是一次无效触发复位状态。 if((g_detect_state STATE_A_TRIGGERED || g_detect_state STATE_B_TRIGGERED) (HAL_GetTick() - g_last_trigger_time 2000)) { g_detect_state STATE_IDLE; } // 其他任务如按键扫描、定期存储等 HAL_Delay(10); }4.3 显示与数据存储实现OLED显示void update_display(void) { char str_buf[20]; OLED_Clear(); OLED_ShowString(0, 0, People Counter, 16); OLED_ShowString(0, 2, Count:, 16); sprintf(str_buf, %d, g_people_count); OLED_ShowString(50, 2, str_buf, 16); OLED_ShowString(0, 4, Status:, 16); switch(g_detect_state) { case STATE_IDLE: OLED_ShowString(50, 4, IDLE , 16); break; case STATE_A_TRIGGERED: OLED_ShowString(50, 4, A TRIG, 16); break; // ... 其他状态 } OLED_Refresh(); }EEPROM存储 为了避免频繁写入可以每分钟或在人数变化时、系统断电前写入一次。#define EEPROM_ADDR_COUNT 0x00 // 存储人数的EEPROM地址 void save_count_to_eeprom(void) { uint8_t data[4]; // 将32位人数拆分为4个字节 data[0] (g_people_count 24) 0xFF; data[1] (g_people_count 16) 0xFF; data[2] (g_people_count 8) 0xFF; data[3] g_people_count 0xFF; // 使用HAL_I2C_Mem_Write写入AT24Cxx HAL_I2C_Mem_Write(hi2c1, 0xA0, EEPROM_ADDR_COUNT, I2C_MEMADD_SIZE_8BIT, data, 4, 100); } void load_count_from_eeprom(void) { uint8_t data[4] {0}; if(HAL_I2C_Mem_Read(hi2c1, 0xA0, EEPROM_ADDR_COUNT, I2C_MEMADD_SIZE_8BIT, data, 4, 100) HAL_OK) { g_people_count (data[0] 24) | (data[1] 16) | (data[2] 8) | data[3]; } }5. 调试技巧与常见问题排查实录这部分是教科书里没有的“实战精华”。5.1 传感器信号调试问题1传感器一直触发或无触发。排查用万用表测量传感器输出引脚电压。遮挡和未遮挡时电压应有明显跳变如从3.3V跳到0V或反之。如果没有检查传感器供电、接线以及发射管和接收管是否对准。技巧可以在中断回调函数里翻转一个LED灯或者通过串口打印信息直观看到触发情况。void sensor_a_trigger_callback(void) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 翻转LED printf([INT] Sensor A Triggered!\r\n); // 串口打印 // ... 状态机逻辑 }问题2方向判断错误进和出反了。原因传感器A和B的安装顺序与代码中的逻辑假设相反。代码里默认“A在外B在内”如果实际装反了方向就反了。解决最简单的方法不是改代码而是交换一下两根传感器的信号线。或者在代码里交换sensor_a_trigger_callback和sensor_b_trigger_callback中的逻辑。问题3有人在传感器前徘徊导致重复计数。原因状态机没有超时复位或者超时时间设置太长。解决像前面代码所示在主循环中加入超时复位逻辑。超时时间如2秒需要根据人正常通过的速度来调整。也可以加入“有效计数后屏蔽一段时间如3秒”的机制。5.2 软件逻辑调试问题4计数偶尔多一个或少一个。排查这是最典型的问题。首先确保去抖参数有效。50ms只是一个起点需要用示波器看传感器实际输出的抖动情况或者通过串口打印时间戳来调整。printf(A Trig %lu ms\r\n, HAL_GetTick());深入分析可能是两人紧挨着通过。当第一人触发A-B序列后状态机刚进入WAIT_RESET第二人又触发了A。此时状态机可能无法正确处理。这需要更复杂的状态机或队列来处理密集人流对于学习项目可以暂时忽略或延长屏蔽时间。问题5EEPROM数据读写出错。排查检查I2C线路连接、上拉电阻通常4.7KΩ。检查设备地址是否正确AT24C02地址是0xA0注意最后一位是读写位。注意写入周期EEPROM写入一个字节需要几毫秒时间连续写入时必须等待这个时间。HAL_I2C_Mem_Write函数本身不包含延时快速连续写入会导致失败。最简单的办法是在每次写操作后加一个HAL_Delay(5)。写入前先读出来对比一下确认读写函数本身无误。5.3 系统稳定性优化看门狗务必启用独立看门狗IWDG防止程序跑飞。在main循环中定期喂狗。// 初始化 IWDG_HandleTypeDef hiwdg; hiwdg.Instance IWDG; hiwdg.Init.Prescaler IWDG_PRESCALER_64; // 约1ms每 tick hiwdg.Init.Reload 4095; // 约4秒超时 HAL_IWDG_Init(hiwdg); // 主循环中 while (1) { HAL_IWDG_Refresh(hiwdg); // ... 其他任务 }中断优先级传感器中断优先级可以设为较高如0但中断服务函数一定要短平快只做标记复杂逻辑放到回调函数或主循环中处理。电源监控如果使用电池可以检测电压电量低时报警并尝试将关键数据存入EEPROM。6. 项目扩展与进阶思考一个基础的人流量计数器完成后你可以考虑以下方向进行扩展这会让你的项目从“作业”升级为“作品”无线传输与云端统计加入ESP8266/ESP32 WiFi模块将实时人数数据上传到云平台如阿里云、腾讯云IoT实现远程监控和数据可视化。电池供电与低功耗优化使用STM32的低功耗模式Stop模式让传感器中断唤醒MCU。平时OLED关闭只有检测到人或按键时才唤醒显示。这样可以大大延长电池寿命。多区域与双向统计用两套传感器组实现一个“十字路口”式的检测能统计不同方向的人流。本地数据记录与分析加入SD卡模块按时间戳记录人流数据生成每日、每周的客流报表。增加用户交互加入按键和菜单可以手动校正人数、设置上下限报警、查看历史数据等。尝试更复杂的传感器用RCWL-0516微波雷达模块替代红外对管它自带运动检测且能穿透塑料外壳安装更隐蔽。你需要处理它的数字输出信号同样可以用状态机逻辑。回过头看那个“STM32单片机人流量检测器设计程序代码等6个.zip”它可能包含了以上部分或全部内容。但通过今天的拆解我希望你收获的不只是读懂其中代码而是掌握从需求分析、方案选型、硬件设计、软件架构到调试优化的完整闭环思维。嵌入式开发就是这样每一个小项目都是对硬件、软件、算法和调试能力的综合锻炼。当你亲手焊好电路板看着OLED上的数字随着你的走动而准确变化时那种成就感是无可替代的。最后一个小建议把你自己的代码和注释写好说不定下一个“XXX等6个.zip”的创造者就是你而你的版本将因为清晰的逻辑和详实的注释成为后来者真正感激的“宝藏”。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价