
简介这是一份基于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度误报最少。再用一块纸板挡住透镜做干扰测试如果仍有触发问题多半在电源干扰而不是红外光路。整套动作跑完人体红外检测程序才算真正具备交付条件。本文还有配套的精品资源点击获取