ESP32入门实战:HC-SR501人体感应传感器从原理到小夜灯实现
2026/9/14 6:09:18 网站建设 项目流程

最近后台总有人问我:ESP32 买了之后到底能玩点什么?我每次都建议从 HC-SR501 人体感应传感器开始。原因是这东西太适合入门了,一共三根线、一个 GPIO 就能跑通,不需要接电阻电容,也不需要懂什么协议。你把它往门口一放,写几十行代码,人走过去 LED 自动亮,过几秒自动灭,那种“我写的程序真的感知到了周围环境”的成就感,比单纯点个灯爽太多。

这篇文章就是给零基础朋友准备的。默认你手头有一块 ESP32 开发板、一个 HC-SR501 模块、几根杜邦线和一个 LED,就可以跟完整个流程。我会把传感器原理、接线、Arduino 环境配置、代码、常见坑全部讲一遍,全程不跳步。如果你之前完全没碰过硬件,也完全可以照着做。

1. 先搞懂:HC-SR501 凭什么能“看见人”

1.1 热释电传感器的原理,说人话版

HC-SR501 的本质是一个热释电红外传感器,英文缩写叫 PIR(Passive Infrared)。核心关键词是被动,它自己不会发射任何探测信号,只是在被动接收周围物体发出的红外线。所有高于绝对零度的物体都会向外辐射红外能量,人体的体温大约在 36 到 37 摄氏度,辐射出来的红外线波长正好落在 8 到 14 微米这个范围,而 PIR 传感器的探头就是为这个波段设计的。

探头内部一般有两片热释电元件,当人在传感器前面移动时,人体辐射的红外线会先后照到这两片元件上,两片元件感受到的温度差就会出现变化,内部会因此产生一个极其微弱的电荷信号。这个信号经过模块里的运算放大器和比较器处理之后,会从 OUT 引脚输出一个干净的高电平。所以你可以把 HC-SR501 理解成一个“人体有没有在动”的数字开关,MCU 只需要读一个引脚的电平就够了。

注意,它感应的是移动中的人体。如果你站在传感器面前一动不动,它很可能不触发,因为两片热释电元件接收到的红外辐射慢慢趋于均衡,输出就恢复平静。这不是模块坏了,而是红外感应原理的天然局限。

1.2 和超声波、摄像头方案比,为什么选它

新手经常问,用超声波模块测距不也能判断有人吗?用摄像头做人体检测不是更准吗?确实都能,但 HC-SR501 在绝大多数入门场景里是最省事的。

超声波只能测距离,你得自己设定一个阈值得出“有没有物体”,而且椅子、墙壁、走动的宠物都会造成误判。摄像头方案更麻烦,识别模型、帧率、存储、功耗样样都是坎,对零基础来说开局就是地狱难度。HC-SR501 不一样,它直接给你一个布尔信号,人动了一下,OUT 就变高,没人了,过一会儿就变低。MCU 不需要做任何算法判断,代码非常简单。

价格也是一个重要因素。一个 HC-SR501 模块通常只要几块钱到十几块钱,静态功耗低到可以忽略,非常适合做电池供电的小装置。当然它也有限制:区分不了人和宠物,隔不了墙,还容易受热气流干扰。真要判断“是不是人、人在哪里”,得上毫米波雷达或摄像头,但那是后话。入门阶段,先把这个小模块用明白,收益很高。

1.3 拿到手先看硬件:模块上的三个关键部位

模块买回来后先别急着接电,把它翻来覆去看一遍。正面是一个乳白色半球形的菲涅尔透镜,这个透镜负责扩大探测视角,把热释电探头原本很窄的感光范围变成大约 100 到 120 度的扇形区域。透镜后面藏着真正的传感器探头,注意不要用手长时间摸它,手指温度会让模块误判。

第二个关键部位是模块上的跳线帽。HC-SR501 一般有两个触发模式,跳线帽插在标记为 H 的一端表示可重复触发,插在 L 端表示单次触发。H 模式下,只要人一直在传感器前面活动,OUT 就会一直保持高电平;L 模式则不管人怎么活动,输出只维持一个固定的延时时间然后自动变低。后面代码部分我会告诉你实际调试时推荐用哪个模式。

第三个就是那两个小十字电位器。它们分别负责调节输出延时和感应灵敏度。不同批次的丝印、旋钮方向和调节范围可能不太一样,所以拿到模块后,先不接线,用小螺丝刀轻轻拧几下,观察模块上有没有明显的“咔嗒”限位感,大致记住方向再说。很多新手调乱之后忘了原始位置,后面排查起来相当痛苦。

2. 接线之前必读:ESP32 引脚和电平常识

2.1 ESP32 开发板引脚怎么看

ESP32 开发板不像 Arduino Uno 那样引脚排孔整齐,很多新手拿到 DevKit V1 看背面密密麻麻的字就懵了。不用怕,你只需要记住几件事:板上一定有 3V3、GND、5V、EN 这些基本引脚,以及一大堆 GPIO 引脚。GPIO 就是通用输入输出引脚,既可以做数字输入,也可以做数字输出,还能复用成 ADC、PWM、串口、I2C 等。

对 HC-SR501 这种数字输出传感器来说,任意一个空闲 GPIO 都可以用。但我习惯用 GPIO4,因为它在板子边缘,插杜邦线方便,而且不是 ESP32 启动流程里的敏感引脚。像 GPIO0 这种特殊引脚,作为普通输入虽然也能读,但它在上电时会被用于判断是否进入下载模式,如果外部电路把它拉低了,会导致芯片启动异常或烧录失败,所以新手最好避开。

还需要注意,GPIO 编号不是物理位置编号。板子上可能印着 D4、G4 或直接印 4,这些实际上指的都是 GPIO 编号。接线时不要想当然地挨着数字接,先确认板子丝印和原理图上的对应关系。如果你买的是一块没有丝印的裸板,那就先把引脚图搜出来核对一遍再插线。

2.2 HC-SR501 和 ESP32 到底怎么接

接线只有三根线,对着表格接就完事。

HC-SR501 引脚ESP32 开发板引脚
VCC3V3
GNDGND
OUTGPIO4(示例)

这里我特别建议先把 VCC 接到 3V3,而不是 5V。原因下一小节展开说。GND 接 GND,OUT 接 GPIO4,你后面完全可以把 GPIO4 改成任何其他可用引脚。

接好后上电,模块上的红色指示灯可能会亮,也可能会在有人经过时闪烁。第一次上电尤其要注意,HC-SR501 需要一点时间“预热”,通常十几秒到一分钟,期间模块在自动校准环境红外背景,输出可能会乱跳,这是正常现象。你刚上电就打开串口监视器,看到一堆“有人”“没人”乱跳,别慌,等半分钟再看。

2.3 供电和处理电平的两种稳妥方案

我在一些帖子里看到让人把 VCC 接 5V,然后把 OUT 直接怼到 ESP32 的 GPIO 上。这么做很多情况下也没烧板子,因为不少新版 HC-SR501 模块内部做了稳压,输出高电平大约在 3.3V,但这不代表所有模块都安全。ESP32 是实打实的 3.3V 逻辑芯片,GPIO 上出现超过 VDD 太多的高电平,长期看是有风险的。

方案一是给模块用 3V3 供电,OUT 直接接 GPIO。我手头好几个不同批次的 HC-SR501 在 3.3V 下都能正常工作,检测距离大概缩短到 3 到 5 米,用在室内小场景完全够了。这是我最推荐给新手的接法,简单安全。

方案二是如果你的模块在 3V3 下明显不灵敏,或者你就是想跑满 7 米的标称距离,那就把 VCC 接到 5V,然后在 OUT 和 GPIO 之间加分压电阻。具体做法是:OUT 先串联一个 10k 电阻,接到 GPIO;再从 GPIO 对地接一个 20k 电阻。5V 高电平经过这个分压网络后大约变成 3.33V,既给模块供足了电,又保护了 ESP32。分压会降低一些信号驱动能力,但 HC-SR501 的输出只接一个 GPIO,影响基本可以忽略。

3. 动手第一步:在 Arduino IDE 里把 ESP32 跑起来

3.1 添加 ESP32 开发板支持包

硬件接好了,下一步是让电脑认识 ESP32。我用的是 Arduino IDE,官方支持 ESP32,网上资料也最多,零基础选它最省心。

打开 Arduino IDE,进入文件菜单里的首选项(Arduino IDE 2.x 叫 Preferences),在“附加开发板管理器地址”这个输入框里填上 Espressif 官方提供的 JSON 地址:https://dl.espressif.com/dl/package_esp32_index.json,然后点确定。填好之后,再打开工具菜单里的开发板管理器,搜索 esp32,找到 Espressif Systems 出的那一个包,点击安装。

安装过程需要下载不少文件,耐心等一会儿。如果下载卡住或者一直失败,也别死磕,可以去 Espressif 官方仓库的 Releases 页面下载离线安装包,按说明手动放到 Arduino15 目录下。装完之后重新打开 Arduino IDE,就能在开发板列表里看到 ESP32 系列了。环境配置是很多新手的第一个坎,过了这一关,后面就是复制粘贴代码的事了。

3.2 选择开发板和串口

开发板支持包装好之后,在工具 -> 开发板菜单里找到 esp32,然后选择ESP32 Dev Module。市面上大多数 ESP32-WROOM-32 系列芯片的开发板都可以选这一项。如果你的板子是 ESP32-S3 或者 ESP32-C3,那就要选择对应的型号,比如 ESP32S3 Dev Module、ESP32C3 Dev Module。选错型号有时也能编译,但可能会出现引脚定义不一致、烧录后行为怪异的怪问题。

选完开发板,再用 USB 线把 ESP32 连接到电脑,在工具 -> 端口里选择对应的串口。Windows 用户可以在设备管理器里的“端口(COM 和 LPT)”下面看到设备,常见的是USB-SERIAL CH340 (COM3)或者Silicon Labs CP210x USB to UART Bridge (COM3)

如果插上 USB 线后电脑完全没反应,先换根线试试。很多看起来差不多的 USB 线只能充电,不能传数据。还有一种情况是开发板上的 USB 转串口芯片驱动没有装,CH340 和 CP2102 这两种芯片的驱动装好之后,端口才会正常出现在系统里。驱动问题解决后,烧录基本就不会卡在“端口找不到”这一步。

3.3 烧录前检查清单

写传感器代码之前,我强烈建议你先烧一个最简程序,把环境彻底跑通。Arduino IDE 自带的 Blink 示例就是很好的测试程序,打开文件 -> 示例 -> 01.Basics -> Blink,直接点烧录,如果板载 LED 开始闪烁,说明开发板、驱动、串口、编译链路全都正常。

在烧录之前做一次快速检查:

  • 开发板型号选的是不是你的板子,最常见选ESP32 Dev Module
  • 端口选的是不是那个带 CH340 或 CP210x 字样的 COM 口
  • USB 线能不能传数据,设备管理器里有没有出现端口
  • 串口监视器是否已经关闭,烧录时会占用串口,开着会导致烧录失败

很多新手一上来就写传感器代码,烧不进去就怀疑模块坏了、板子坏了,结果最后发现只是串口没选对。先用最简单的 Blink 排除掉环境问题,再往下做,后面调传感器时才不会一头雾水。

4. 核心代码实现:让“感知人体”变成可用的信号

4.1 最简读取:串口看输出

下面这段代码是 HC-SR501 的最小读取程序,功能只有一个:把 GPIO4 上的电平读出来,然后打印到串口监视器。波特率选择 115200,打开串口监视器后,你走到传感器前面动一下,应该能看到Motion detected!的输出。

#define PIR_PIN 4 void setup() { Serial.begin(115200); pinMode(PIR_PIN, INPUT); Serial.println("HC-SR501 ready!"); } void loop() { int val = digitalRead(PIR_PIN); if (val == HIGH) { Serial.println("Motion detected!"); } delay(100); }

这里要特别注意,代码里用的是pinMode(PIR_PIN, INPUT),不是INPUT_PULLUP。HC-SR501 的 OUT 引脚本身会拉高或拉低,属于推挽输出,不需要内部上拉。如果误开了内部上拉,高电平还好,低电平时相当于用内部上拉电阻和传感器输出端较劲,虽然一般不会损坏硬件,但可能让低电平读起来不干净,甚至引起误判。

delay(100)的意义是让循环每 100 毫秒读取一次。人体动作本身是毫秒级以上的变化,100 毫秒轮询足够及时,也能防止串口打印刷屏。实际项目里,这个轮询间隔可以根据需求调整,比如想省电就放大到 200 毫秒,想更灵敏就压到 50 毫秒。

4.2 加入状态变化和 LED 指示

只打印还不够直观,加一个 LED 就更有感觉了。LED 正极(长脚)接 GPIO2,负极串联一个 220 欧姆电阻再接地。GPIO2 是很多 ESP32 开发板上板载 LED 所在的引脚,如果你用的板子板载 LED 已经占了 GPIO2,也可以换其他引脚,代码里同步改一下就行。

这里的代码加入了状态变化检测,只在输出发生跳变时打印一次,同时用 LED 跟着亮灭。

#define PIR_PIN 4 #define LED_PIN 2 int pirState = LOW; int val = 0; void setup() { Serial.begin(115200); pinMode(PIR_PIN, INPUT); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); Serial.println("PIR test with LED"); } void loop() { val = digitalRead(PIR_PIN); if (val == HIGH) { digitalWrite(LED_PIN, HIGH); if (pirState == LOW) { Serial.println("Someone is here!"); pirState = HIGH; } } else { digitalWrite(LED_PIN, LOW); if (pirState == HIGH) { Serial.println("No one here..."); pirState = LOW; } } delay(100); }

核心逻辑其实很简单:每次循环读传感器,高电平时点亮 LED,并且只在pirState从低变高时打印“有人”;低电平时熄灭 LED,只在从高变低时打印“没人”。这样即使人一直在传感器前面来回走动,串口也不会刷屏。

如果你发现 LED 在人已经离开后还亮着好几秒才灭,很可能不是程序问题,而是 HC-SR501 模块上的输出延时电位器调得太大了。模块在检测到活动后,OUT 引脚会保持一段时间的高电平,这段时间由硬件决定,程序只是被动跟随。

4.3 参数调节的经验:灵敏度、延时、重复触发

模块上那两个小电位器,一个调延时,一个调灵敏度。我的建议是先调延时,再调灵敏度,顺序千万别反过来。

延时电位器控制 OUT 输出高电平的保持时间。把它调小,人走之后信号很快变低,适合做感应开关灯;调大,输出可以保持几十秒甚至更久,适合做报警联动。灵敏度电位器控制探测距离和范围,拧到最大时探测距离可以达到五六米,但也很容易被环境温度变化、空调气流干扰,产生误触发。

跳线帽我建议放在 H(可重复触发)模式。这个模式下,只要延时还没结束,传感器又探测到人体活动,OUT 就会重新保持高电平,相当于模块会一直输出“有人”。L 模式更像是计数模式,每次触发只输出固定延时的一次高电平,适合统计人流进出之类的场景。但如果你希望 ESP32 根据“有人在/没人”做持续判断,H 模式写起来逻辑更顺。

不同批次模块的电位器方向可能不一样,拧的时候别太用力,一个是避免拧坏元件,另一个是方便你自己感知到调节范围。模块上电位器没有刻度,最好贴一个小标签记住初始位置。

4.4 做一个人体感应小夜灯的完整逻辑

想实现“人来灯亮,人走延时关灯”,不一定非要把硬件延时调到很大,完全可以在 ESP32 里做软件计时。这样做的好处是,以后想改时间,直接改代码里的一个数字就行,不用再拆下来拧螺丝。

思路是:检测到高电平时打开 LED,并记录当前时间;检测到低电平后不马上关灯,而是等到超过预设的保持时间再关。下面是完整代码,默认人走之后 LED 继续亮 5 秒。

#define PIR_PIN 4 #define LED_PIN 2 unsigned long lastMotionTime = 0; bool ledOn = false; const unsigned long keepAliveMs = 5000; void setup() { Serial.begin(115200); pinMode(PIR_PIN, INPUT); pinMode(LED_PIN, OUTPUT); } void loop() { int val = digitalRead(PIR_PIN); if (val == HIGH) { lastMotionTime = millis(); if (!ledOn) { digitalWrite(LED_PIN, HIGH); ledOn = true; Serial.println("LED ON"); } } else { if (ledOn && millis() - lastMotionTime > keepAliveMs) { digitalWrite(LED_PIN, LOW); ledOn = false; Serial.println("LED OFF"); } } delay(50); }

这里的关键是millis()的用法。它返回 ESP32 从开机到现在经过的毫秒数,我们把它记下来,然后不断计算差值,判断是否超过了 5 秒。一定不要图省事用delay(5000),因为 delay 会让程序原地卡住 5 秒,这期间你无法再读取传感器。要是有人在 delay 期间又走进来,灯已经被灭了,体验很差。用时间戳方式,既能延时关灯,又不会阻塞循环,这是嵌入式开发里非常基础也非常重要的习惯。

5. 实战中常见的坑和排查方法

5.1 完全不触发怎么办

这个问题排查顺序很重要,别一上来就怀疑模块坏了。

第一步看模块有没有电。HC-SR501 上电后,模块上的红色指示灯通常会在触发时亮,如果上电后连灯都不亮,检查 VCC 和 GND 是否插反,杜邦线是否插紧。第二步看 OUT 和 GPIO 是否接触良好。很多“不触发”其实就是杜邦线松了,或者插到了不同的 GPIO 上,代码里写的和实际接的引脚对不上。

第三步把模块换个环境再试。避开空调风口、窗户旁边、暖炉附近这些温度变化明显的地方,因为 HC-SR501 对热气流太敏感,反而会乱触发。最后可以用万用表量一下 OUT 对 GND 的电压,人在模块前面活动时,正常情况下应该能从 0V 跳到高电平。如果完全没有变化,再判断模块是否损坏。

还有一点,HC-SR501 的探测区域是一个扇形,感应范围大约 3 到 7 米。人必须在这个扇形区域内、并且有移动动作才会触发。你如果站在传感器正下方,往往恰好在探测盲区里,半天不触发是很常见的事。

5.2 一直高电平/反复误触发怎么办

如果串口一直在打印“有人”,先把环境因素排除掉:传感器前方是不是有一个固定热源,比如暖气片、加热器、电脑机箱排风口;有没有空调气流直吹;有没有小动物在活动。还要给模块足够的预热时间,刚上电的几十秒内输出不稳定很正常,等一分钟再看。

如果排除了环境因素还在乱触发,就把灵敏度电位器逆时针调低一点,同时让传感器探测方向尽量避开热源和窗户。另一个常见因素是供电不稳,ESP32 板载的 3V3 稳压器在 Wi-Fi 开启的瞬间会有压降,可能导致传感器输出抖动。这种情况可以尝试独立电源,或者干脆改用 5V 供电加电阻分压。

代码层面也可以做去抖。比如连续读到 3 次高电平才认为是有效触发,连续读到 3 次低电平才认为是离开。这个思路和按键消抖完全一样,在硬件条件受限时非常管用。

5.3 代码能编译但烧录失败

ESP32 烧录失败的坑几乎人人都会踩一次。最常见的是串口没选对,或者开发板型号选错。如果烧录时提示Connecting...卡住不动,试着按住开发板上的 BOOT 键,然后点烧录,看到开始写入时再松开。很多自动下载电路不完善的开发板必须这样操作。

还有一种情况是你把 HC-SR501 的 OUT 接到了 GPIO0 这类特殊引脚。ESP32 在烧录时会检测 GPIO0 的电平,如果外部传感器把它拉住了,就可能进不了下载模式。所以我在前面特意建议用 GPIO4,目的就是避坑。如果你已经接在了 GPIO0 上,烧录前拔掉那根线,烧完再接回去。

另外,Arduino IDE 的串口监视器如果还开着,也会占用串口,导致烧录失败。烧录前把串口监视器窗口关掉,然后再点上传,是效率最高的习惯。

5.4 问题速查表

把上面这些常见问题整理成一张表,方便你以后遇到问题直接对照。

现象最可能原因解决办法
上电模块灯不亮VCC/GND 接反或接触不良重新拔插杜邦线,用万用表量供电电压
传感器一直输出高环境热源/气流干扰,灵敏度太高调低灵敏度,避开空调口,等待预热完成
远处感应不到灵敏度太低,或供电电压不足调高灵敏度,改用 5V 供电并加分压电阻
串口无输出引脚配置错误,或端口没选对确认 GPIO 编号、串口端口、波特率 115200
烧录失败串口占用、BOOT 模式没进、特殊引脚被拉低关闭串口监视器,按住 BOOT,拔掉 GPIO0 接线
人走灯不灭模块延时调太大,或软件计时逻辑错误调小延时电位器,检查 millis() 差值逻辑

排查问题一定要从简单到复杂,先查供电和接线,再查环境干扰,最后才怀疑代码和硬件。大多数情况下,问题都出在接线和环境上,真正损坏的模块其实很少。

6. 从感应到智能:几个值得动手的扩展方向

6.1 配合继电器控制真实灯具

HC-SR501 的 OUT 信号很弱,只能直接驱动 LED 或者 GPIO 输入,绝对不能直接控制大功率设备。想让它控制 220V 的灯,需要加一个继电器模块。继电器本质是一个电磁开关,ESP32 用 GPIO 控制三极管或光耦,再由小信号带动继电器线圈吸合,从而控制大电流回路。

接线时注意,继电器模块的 VCC 和 GND 通常接 ESP32 的 5V 和 GND,IN 接一个 GPIO。代码逻辑和点 LED 几乎一样,把digitalWrite(LED_PIN, HIGH)改成继电器控制引脚对应的高电平即可。需要特别提醒的是,强电部分有触电风险,新手最好先用直流小灯泡练手,确认整套逻辑跑通之后,再考虑接市电设备。继电器模块裸露的接线端子一定要做好绝缘,别摸带电部分。

6.2 配合蜂鸣器或 OLED 做提醒

你还可以把系统从“感知人体”变成“主动提醒”。接一个有源蜂鸣器到另一个 GPIO,检测到人时蜂鸣器响。再接一个 0.91 寸 OLED 显示屏,把触发次数、当前状态、时间显示出来。OLED 用 I2C 协议通信,SCL 接 GPIO22,SDA 接 GPIO21,这是 ESP32 DevKit 上默认的 I2C 引脚,配合 U8g2 或 Adafruit SSD1306 库就能显示中文和图形。

这种组合可以做进门欢迎器、老人活动监测、卫生间空位提示之类的小项目。HC-SR501 扮演的是“事件发生器”,后面接什么输出设备,完全取决于你的需求。

6.3 接入 Wi-Fi 和 OTA 升级

ESP32 最大的优势是自带 Wi-Fi 和蓝牙。你可以让传感器检测到人之后,通过 MQTT 协议把消息推送到手机,或者联动智能家居。比如检测到有人回家,自动开灯开空调;人离开后所有设备进入待机状态。

项目越来越成熟之后,每次改代码还要插 USB 线,装在天花板上的传感器就非常不方便。这时候可以研究一下 OTA 升级,让 ESP32 通过网络接收新固件并完成烧写。OTA 的坑主要在分区表上,需要在编译时选择带 OTA 功能的 Flash 分区方案,还要给网络失败设置回滚逻辑。等你把 HC-SR501 的信号处理玩明白了,再折腾 OTA 会顺畅很多。

6.4 多传感器联动和更复杂的状态机

单个 HC-SR501 只能判断一个区域有没有人移动。想覆盖更大空间,可以把两个三个传感器分别放在不同位置,用不同 GPIO 读取。根据“哪个传感器先触发”判断人的移动方向,比如门口内外各放一个,就能知道人是进门还是出门,再配合灯光逻辑做自动照明。

多传感器意味着代码从简单 if-else 变成状态机:当前有人还是没人、上次触发来源是哪个传感器、防抖时间多长、超时时间多长,都需要统一管理。嵌入式项目里比较实用的路线是先跑通单传感器,再把功能拆成函数或者类,不要一开始就追求架构完美。能稳定运行的系统,永远是从简单控制逐步迭代出来的。

最后再分享一个我自己的习惯:无论做什么传感器项目,第一版代码都会把关键变量用串口打印出来,并且在模块上贴一个小标签,标注电位器的当前旋向。HC-SR501 的电位器没有刻度,拧多少全凭手感,一旦调乱又忘了原始位置,调试时就容易怀疑人生。我吃过几次亏之后,每块模块都用油性笔在旁边画一个参考刻度,再在程序里把状态变化打印出来,排查效率高了很多。如果你也希望从这堆线材和模块里找到一点“创造硬件”的乐趣,那就别怕折腾,先点亮那个 LED 再说。

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

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

立即咨询