简介:这是一份基于STM32F103C8T6的HC-SR501人体红外检测工程源码,面向物联网、嵌入式入门开发者与高校单片机实验课学员,可直接体验“人体感应触发-指示灯亮灭”的完整闭环。压缩包共163个文件,以C源码、头文件、启动文件及Keil工程配置为主,并附hex烧写文件、编译映射表等辅助输出,整体仅3.34MB,便于下载后直接编译与烧录。代码采用标准外设库编写,通过杜邦线将传感器VCC/GND/OUT分别连接开发板5V/GND/PB6,检测到移动人体后自动拉高电平,驱动PB9指示灯规律亮灭,离开后延时恢复低电平,逻辑清晰适合二次修改。资源已在STM32F103C8T6验证,若换用同系列其他芯片,仅需调整Keil中的芯片型号与Flash容量,代码无需大改。目前已有5305人学习下载,可作为物联网人体感应、GPIO输入输出及中断控制项目的快速参考。
1. 从一块HC_SR501开始的STM32物联网入门项目,坑在哪
很多人的第一个物联网项目,是从给楼道做一盏人体感应灯开始的。拿到HC_SR501和一块STM32最小系统板,最常见的做法是开个串口把引脚读数打出来,看到高电平就点灯。这样做的结果是:人在灯前站着不动,灯反而灭了;对着传感器挥手,灯又闪个不停。这类基于STM32的HC_SR501人体红外检测程序,真正要处理的不是“读到1还是0”,而是HC_SR501的输出窗口:它检测的是人体红外辐射变化,不是位置开关。对新手它是最容易上手的传感器之一,对做了几年嵌入式的工程师而言,难点也往往不在GPIO配置,而是怎么把一次触发变成可靠、可复用的动作事件,这也是物联网毕设和实际项目中反复出现的基本功。
2. HC_SR501的时序行为,是人体红外检测程序的关键
2.1 HC_SR501到底在输出什么:高电平窗口与边沿
HC_SR501的核心是一颗热释电红外传感器,感应的是人体发出的10微米左右的红外辐射。人一旦在感应范围内移动,传感器内部的热释电元件会输出一个微弱的电荷变化,再经BISS0001芯片整形放大,最终从OUT脚输出一个高电平。这个高电平不会像按钮那样只弹一下,而是会维持一段时间,正是这段维持时间,决定了程序里所有时间参数怎么设计。
用一个LED直接并联在OUT脚和GND之间,就能直观看到这个窗口现象:人走过时LED亮起,停留片刻后LED熄灭。在默认的可重复触发模式下,只要人在感应区内持续有动作,OUT就会被不断重新拉高,LED会一直保持点亮。这个“高电平窗口”是硬件行为,MCU代码只能在窗口之上做二次判断,不能靠软件把窗口本身变长。
2.2 三个可调参数,决定了MCU侧代码怎么写
模块上有两个电位器和一个跳线帽,出厂默认值并不一定适合你的现场环境,动手写程序前要先把它们确认一遍。下面这张表是三个调节点对代码行为的直接影响,调完参数代码逻辑也要跟着改。
| 调节点 | 默认行为 | 调节范围 | 对程序的直接影响 |
|---|---|---|---|
| 延时电位器 | 出厂约3-5秒 | 约3秒-300秒 | 决定OUT高电平的持续时间,影响MCU侧超时计时 |
| 距离电位器 | 出厂约7米 | 约3-7米 | 决定感应半径,调太大会缩小近处盲区 |
| 重复触发跳线帽 | H(可重复触发) | H/L两档 | L模式下输出为固定宽度高电平,MCU应按单次事件处理 |
程序层面最容易被忽略的是模块上电自检。HC_SR501通电后内部会进入预热状态,前1-2秒OUT会主动输出一次高电平,之后才进入正常检测状态。如果MCU和模块同时上电,这次自检高电平极容易被当成人员活动。把启动阶段的无效窗口过滤掉,是人体红外检测程序的第一道消抖。
2.3 读GPIO前,先分清“检测”和“存在”
另一个常见误读是把HC_SR501当成存在传感器。人坐在办公位或教室里长时间相对静止,传感器可能隔几分钟就掉回低电平,这不是硬件坏了,而是没有足够强的红外辐射变化可检测。模块能感知的是“动态变化”,不是“静态占用”。
所以这里说的“人体红外检测”应当理解成“活动检测”。MCU侧要把“一次活动事件”翻译成“一段可用状态”,而不是只读电平。理解了这个前提,下面配置STM32外部中断、写事件回调、做消抖才有意义。
3. 用STM32CubeMX配好GPIO,再写HC_SR501事件处理
3.1 引脚配置:把PB0设为EXTI输入并开启内部上拉
我一般会先在STM32CubeMX里把PB0设为GPIO_Input,并打开内部上拉。HC_SR501的OUT脚是推挽输出,高电平约3.3V,和STM32 GPIO电平匹配,不需要额外分压。内部上拉的意义在于:如果OUT悬空或杜邦线松脱,引脚默认读到高电平,问题能在调试时马上暴露出来,而不是让系统默认把人判定为不在场。
在GPIO配置页里把PB0的GPIO mode选为External Interrupt Mode with Rising/Falling edge trigger detection,然后在NVIC Setting里使能EXTI0中断。抢占优先级给一个中间值即可,我习惯设成2,避免和SysTick的优先级判据发生混淆。生成代码后,硬件中断入口由CubeMX自动接通,我们只需要在用户代码文件里补充HAL_GPIO_EXTI_Callback的处理逻辑。
// user_gpio.h #define PIR_GPIO_Port GPIOB #define PIR_PIN GPIO_PIN_0 // user_gpio.c volatile uint8_t pir_event_flag = 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == PIR_PIN) { pir_event_flag = 1; // 只做标记,不在中断里做耗时操作 } }这段代码的逻辑很清楚:中断回调只负责把事件采集下来,真正的时间判断放到主循环里。不要在中断回调里加HAL_Delay,也不要直接调用printf,中断服务函数追求的是占用时间最短,否则多个中断嵌套时容易丢事件。
3.2 30ms消抖,让人体红外检测程序不误触发
HC_SR501的正常输出变化周期远大于30ms,但电源毛刺或继电器动作产生的干扰会在引脚上制造几十毫秒的窄脉冲。在主循环里对事件做一次持续宽度判断,是简单有效的滤波方式。
uint8_t pir_action_trigger = 0; uint8_t pir_level_now = 0; void PIR_Process_Event(void) { static uint32_t rise_tick = 0; uint32_t now = HAL_GetTick(); pir_level_now = HAL_GPIO_ReadPin(PIR_GPIO_Port, PIR_PIN); if (pir_event_flag) { pir_event_flag = 0; if (pir_level_now == GPIO_PIN_SET) { rise_tick = now; // 记录上升沿到达时间 } else { if (now - rise_tick > 30) // 高电平持续超过30ms才有效 { pir_action_trigger = 1; } } } }这段消抖代码的逻辑基于“上升沿记录时间,下降沿判断宽度”的边沿配合。如果系统运行在电机或继电器旁边,干扰更重,可以把30ms提到100ms。阈值如果再往上调,就需要确认HC_SR501本身在距离调节电位器调大后,输出高电平宽度是否仍然满足条件。
3.3 主循环状态机:把一次检测变成可复用的动作
把读GPIO升级成事件驱动之后,业务代码只需要关心动作状态。一个简单的两态机就够用:空闲态和活动态。检测到有效触发后进入活动态,在活动态里如果再次检测到触发就重置计时,计时结束后回到空闲态。
#define PIR_STATE_IDLE 0 #define PIR_STATE_ACTIVE 1 #define PIR_HOLD_TIME_MS 5000 static uint8_t pir_state = PIR_STATE_IDLE; static uint32_t hold_tick = 0; void PIR_Task_Handler(void) { switch (pir_state) { case PIR_STATE_IDLE: if (pir_action_trigger == 1) { pir_action_trigger = 0; pir_state = PIR_STATE_ACTIVE; hold_tick = HAL_GetTick(); LED_On(); // 进入活动状态,打开照明或录像 } break; case PIR_STATE_ACTIVE: if (pir_action_trigger == 1) { pir_action_trigger = 0; hold_tick = HAL_GetTick(); // 活动持续则刷新保活时间 } if (HAL_GetTick() - hold_tick > PIR_HOLD_TIME_MS) { pir_state = PIR_STATE_IDLE; LED_Off(); } break; default: pir_state = PIR_STATE_IDLE; break; } }状态机的意义在于把“检测到活动”变成“维持一段可配置的动作时间”。人坐在工位不动,传感器输出掉回低电平,但灯不会立刻熄灭,因为5秒的PIR_HOLD_TIME_MS在起作用。走廊灯可以把保持时间调到10秒,仓库照明调到30秒,这个时间要和HC_SR501本身的延时电位器拉开差距,软件侧要保持自己的保护窗口,不能依赖传感器参数兜底。
4. 烧录与调试:从stm32芯片包安装到no target found排查
4.1 先装对芯片包和驱动,再连接调试器
用Keil 5打开项目报错时,先检查Pack是否装了对的系列包。用STM32F103C8T6时,需要在Pack Installer里安装STM32F1xx_DFP,缺失时设备列表会变灰,或者编译正常但下载时提示找不到算法文件。设备管理器里如果ST-LINK/V2显示为带叹号的未知设备,要先把驱动装好,再回Keil的Options for Target进入Debug页选择ST-Link Debugger。Settings里能看到SWDIO、SWCLK两个引脚,并显示出IDCODE,才算连接成功。
有一类提示是驱动的虚拟串口感叹号,和下载无关,但会让你误判为烧录器坏了。ST-LINK的驱动和虚拟串口驱动要分开安装,装完驱动后如果IDCODE还是读不到,就要检查调试口的连接线。
4.2 error: no stm32 target found! 的几个常见原因
烧录过程中出现error: no stm32 target found!时,后一句常跟着if your product embeds debug authentication。这个报错信息看着吓人,但多数情况不是芯片被锁,而是下载器和目标板之间的链路断了。
| 报错现场 | 常见原因 | 处理办法 |
|---|---|---|
| 连接时找不到目标 | SWDIO/SWCLK杜邦线太长 | 缩短到20cm以内,或用排线连接 |
| 擦除后重新连接失败 | 目标板供电不足,3.3V被拉低 | 用独立稳压电源给板卡供电 |
| IDCODE时有时无 | SWDIO和SWCLK接反 | 对调两根数据线再试 |
| 始终报debug authentication | 芯片读保护已开启 | 用ST-LINK Utility整片擦除 |
| 能连上但Flash Verify失败 | Flash Download里没有勾选复位并运行 | 勾选Reset and Run并确认算法文件 |
注意:某些最小系统板会把SWDIO附近的引脚复用成普通GPIO,程序一运行就占住调试口。这时按住板上复位键不放,在复位状态下点击下载,Keil连接成功后松开复位即可,这个办法能解决大部分连接失败问题。
4.3 上电自检高电平,最容易漏掉的启动阶段坑
代码烧进去之后,常见现象是一上电就触发一次动作,然后才恢复正常。这不是传感器坏了,而是HC_SR501在上电后1-2秒内输出了一次自检高电平,加上STM32刚启动时GPIO电平还没配好,这次无效输出被当成了有效输入。
处理方式是在程序启动后加一段屏蔽时间,期间收到的所有事件都丢弃。
// main函数初始化完成后记录启动时刻 uint32_t boot_ms = HAL_GetTick(); void PIR_Process_Event(void) { // 启动后前2秒内的事件全部丢弃,用于过滤HC_SR501上电自检输出 if (HAL_GetTick() - boot_ms < 2000) { pir_event_flag = 0; return; } // 原本的事件处理逻辑 }启动屏蔽时间根据模块的实际预热表现调整,2秒够用就保持,环境温度低时可以加到3秒。调试阶段建议在串口里把每次触发的时间戳打出来,对照传感器前的动作来确认事件来源,减少反复猜测。
5. 把人体红外检测程序升级成低功耗事件源
5.1 短触发和长占用的模式区分
把跳线帽切到L模式时,HC_SR501输出的是固定宽度的高电平,就算人一直在动,到达延时时间后输出也会掉回低电平,然后再触发。这种模式适合做单次事件计数,比如统计进出人数,不适合做占用检测。
我在项目里通常保留H模式,用代码里的PIR_HOLD_TIME_MS做二次保持。这样传感器的机械参数保持不变,软件可以随时调整行为,改需求时不必拆开外壳去拧电位器。
5.2 用EXTI把STM32从睡眠中唤醒
低功耗场景下,把PB0的中断配置成上升沿和下降沿都触发,模块检测到活动时输出高电平,STM32就从停止模式被唤醒。进入停止模式前调用HAL_SuspendTick(),唤醒后必须重新调用SystemClock_Config(),否则系统节拍不会恢复。
后面如果接WiFi模块或NB-IoT模组做完整的物联网项目,不要把网络重连逻辑写进中断回调。把pir_action_trigger事件置一个report_flag,通信任务只消费这个标志,检测环节和上报环节解耦。模块掉线重连时,不会拖累人体检测的正常工作。
5.3 现场验证的三种动作测试
最后用秒表记录三种场景来验证:人在3米外横向走过,记录第一次触发时刻;人坐下来保持安静,观察输出多久复位;人在0.5米内来回靠近,注意菲涅尔透镜近处有盲区,离得太近反而检测不到。安装高度建议放到2.2米,向下倾斜15度,误报最少。再用一块纸板挡住透镜做干扰测试,如果仍有触发,问题多半在电源干扰而不是红外光路。整套动作跑完,人体红外检测程序才算真正具备交付条件。
本文还有配套的精品资源,点击获取