为什么你的 PIR 传感器总是误触发?
用过 PIR(Passive Infrared,被动红外)人体传感器的朋友大概都遇到过这样的困扰:明明没人经过,传感器却莫名其妙触发;或者人站在那儿不动,它反而检测不到了。
这真不是传感器坏了,而是 PIR 的工作原理决定的。今天我们就来聊聊如何让 PIR 传感器变得"聪明"起来,把误触发率降到最低。
PIR 传感器工作原理速览
PIR 传感器检测的不是"人",而是红外辐射的变化。人体体温约 37°C,会辐射出特定波长的红外线。当人移动时,传感器接收到的红外辐射强度发生变化,从而触发信号。
关键点来了:PIR 检测的是变化,不是存在。这就是为什么人静止不动时,传感器会"失去目标"。
硬件清单
| 型号 | 描述 | 价格 | 备注 |
|---|---|---|---|
| HC-SR501 | 经典 PIR 模块,可调灵敏度 | ¥8-12 | 最常用,推荐新手 |
| HC-SR505 | 小型 PIR 模块 | ¥6-10 | 体积小巧 |
| AM312 | 微型 PIR 传感器 | ¥5-8 | 适合便携项目 |
| Arduino Nano | 开发板 | ¥15-20 | 或 ESP32/STM32 |
| 电位器 | 10kΩ | ¥1 | 用于灵敏度调节 |
| LED | 5mm | ¥0.5 | 状态指示 |
硬件连接(以 HC-SR501 为例)
HC-SR501 有 3 个引脚:
- VCC: 5V 电源
- OUT: 信号输出(高电平触发)
- GND: 接地
# Arduino 连接
HC-SR501 VCC → Arduino 5V
HC-SR501 OUT → Arduino D2
HC-SR501 GND → Arduino GND
模块上有两个可调电位器:
- 时间调节:控制触发后输出高电平的持续时间(5-300 秒)
- 灵敏度调节:控制检测距离(3-7 米)
基础代码示例
先从最简单的开始:
const int pirPin = 2;
const int ledPin = 13;
void setup() {
pinMode(pirPin, INPUT);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.println("PIR 传感器初始化完成");
}
void loop() {
int pirState = digitalRead(pirPin);
if (pirState == HIGH) {
digitalWrite(ledPin, HIGH);
Serial.println("检测到人体移动!");
delay(1000);
} else {
digitalWrite(ledPin, LOW);
}
}
这段代码能工作,但会有两个问题:
- 传感器刚上电时会预热 10-60 秒,期间可能误触发
- 输出信号可能有抖动,导致重复触发
优化方案一:预热处理
HC-SR501 上电后需要预热时间,让内部电路稳定。我们可以在 setup 中加入延时:
void setup() {
pinMode(pirPin, INPUT);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.println("PIR 传感器预热中...");
delay(60000); // 预热 60 秒
Serial.println("预热完成,开始检测");
}
优化方案二:软件滤波算法
硬件调节有局限,软件滤波可以更精细地控制。下面是一个实用的滤波算法:
const int pirPin = 2;
const int ledPin = 13;
// 滤波参数
const int SAMPLE_COUNT = 5; // 采样次数
const int TRIGGER_THRESHOLD = 3; // 触发阈值(5 次中 3 次检测到)
const int COOLDOWN_MS = 5000; // 冷却时间,避免重复触发
unsigned long lastTriggerTime = 0;
bool isTriggered = false;
void setup() {
pinMode(pirPin, INPUT);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.println("PIR 传感器预热中...");
delay(60000);
Serial.println("系统就绪");
}
bool readPIRWithFilter() {
int triggerCount = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
if (digitalRead(pirPin) == HIGH) {
triggerCount++;
}
delay(50); // 采样间隔 50ms
}
return triggerCount >= TRIGGER_THRESHOLD;
}
void loop() {
unsigned long currentTime = millis();
// 冷却时间内不处理
if (currentTime - lastTriggerTime < COOLDOWN_MS) {
return;
}
bool detected = readPIRWithFilter();
if (detected && !isTriggered) {
isTriggered = true;
lastTriggerTime = currentTime;
digitalWrite(ledPin, HIGH);
Serial.println("✅ 确认检测到人体移动!");
// 这里可以触发你的业务逻辑
// 例如:开灯、发送通知、启动摄像头等
} else if (!detected && isTriggered) {
isTriggered = false;
digitalWrite(ledPin, LOW);
Serial.println("人体离开检测区域");
}
}
这个滤波算法的核心思想:
- 多次采样:连续读取 5 次,避免单次误判
- 阈值判断:5 次中至少 3 次检测到才确认触发
- 冷却时间:触发后 5 秒内不再响应,防止重复触发
优化方案三:安装位置优化
很多时候误触发不是代码问题,而是安装位置不对。以下是实战经验:
❌ 错误安装位置
- 正对空调出风口:热气流会导致误触发
- 靠近窗户:阳光移动、车辆经过都会触发
- 正对暖气片:热辐射干扰
- 高度过低:宠物、扫地机器人会触发
- 角度朝下:检测范围太小
✅ 正确安装位置
- 高度 2-2.5 米:避开宠物,覆盖成人活动区域
- 倾斜 15-30 度:扩大水平检测范围
- 远离热源:距离空调、暖气至少 2 米
- 避开窗户:或拉上窗帘
- 检测区域覆盖通道:人必须经过的路径
优化方案四:环境适配
不同场景需要不同的参数配置:
家庭走廊(人快速通过)
const int SAMPLE_COUNT = 3; // 减少采样,快速响应
const int TRIGGER_THRESHOLD = 2; // 降低阈值
const int COOLDOWN_MS = 3000; // 短冷却时间
办公室(人可能静止)
const int SAMPLE_COUNT = 5;
const int TRIGGER_THRESHOLD = 3;
const int COOLDOWN_MS = 10000; // 长冷却时间,避免重复通知
仓库(防误报优先)
const int SAMPLE_COUNT = 10; // 多次采样
const int TRIGGER_THRESHOLD = 7; // 高阈值
const int COOLDOWN_MS = 30000; // 长冷却
进阶:多传感器融合
单个 PIR 传感器有局限,可以结合其他传感器提高准确性:
PIR + 光敏电阻
只在黑暗环境触发,避免白天误报:
const int pirPin = 2;
const int lightPin = A0;
bool isDarkEnough() {
int lightValue = analogRead(lightPin);
return lightValue < 500; // 根据实际环境调整阈值
}
void loop() {
if (isDarkEnough() && readPIRWithFilter()) {
Serial.println("夜晚检测到人体移动,触发报警");
// 执行报警逻辑
}
}
PIR + 超声波测距
确认检测区域内确实有物体:
const int pirPin = 2;
const int trigPin = 3;
const int echoPin = 4;
long readDistance() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
return duration * 0.034 / 2; // 单位:厘米
}
void loop() {
if (readPIRWithFilter()) {
long distance = readDistance();
if (distance > 0 && distance < 300) { // 3 米内有物体
Serial.println("PIR + 超声波确认:有人!");
}
}
}
实战案例:老人看护系统
张工给独居的父母做了一个简单的看护系统:
需求:
- 检测老人是否起床活动
- 如果早上 8 点前没有检测到活动,发送提醒
- 夜间起床自动开小夜灯
硬件配置:
- 卧室门口安装 PIR 传感器
- ESP32 作为主控
- 连接 WiFi 发送微信通知
- 继电器控制小夜灯
核心逻辑:
RTC_DATA_ATTR int activityCount = 0;
RTC_DATA_ATTR unsigned long lastActivityTime = 0;
void checkMorningActivity() {
struct tm timeinfo;
getLocalTime(&timeinfo);
// 早上 8 点检查
if (timeinfo.tm_hour == 8 && timeinfo.tm_min == 0) {
unsigned long hoursSinceLastActivity =
(millis() - lastActivityTime) / 3600000;
if (hoursSinceLastActivity > 12) {
sendWechatAlert("⚠️ 父母超过 12 小时未活动,请确认安全!");
}
}
}
void handleNightMotion() {
struct tm timeinfo;
getLocalTime(&timeinfo);
// 晚上 10 点到早上 6 点
if (timeinfo.tm_hour >= 22 || timeinfo.tm_hour < 6) {
// 缓慢开启小夜灯(20% 亮度)
analogWrite(NIGHT_LIGHT_PIN, 50);
// 2 分钟后自动关闭
setTimeout([]() {
analogWrite(NIGHT_LIGHT_PIN, 0);
}, 120000);
}
}
这个系统已经稳定运行 6 个月,成功预警 2 次老人身体不适的情况。
常见问题排查
问题 1:传感器一直触发
可能原因:
- 预热时间不足(等待 60 秒)
- 灵敏度调得太高(逆时针调节电位器)
- 环境有热源干扰(检查空调、暖气)
- 电源不稳定(使用 5V 稳压电源)
问题 2:检测不到人
可能原因:
- 灵敏度太低(顺时针调节电位器)
- 安装高度太高(降至 2-2.5 米)
- 检测角度不对(调整倾斜角度)
- 人移动太慢(PIR 检测的是变化)
问题 3:触发后不恢复
可能原因:
- 时间调节电位器设置太长(顺时针是增加时间)
- 代码中没有处理状态复位
- 有人一直在检测区域内移动
问题 4:宠物触发误报
解决方案:
- 提高安装高度(2.5 米以上)
- 调整角度朝下 15-30 度
- 使用"防宠物"型 PIR 传感器(如 HC-SR501 的改进版)
- 软件滤波增加采样次数
实战项目:智能走廊灯
结合以上优化技巧,做一个实用的智能走廊灯:
const int pirPin = 2;
const int lightPin = 9; // PWM 控制 LED 亮度
const int lightSensorPin = A0;
const int SAMPLE_COUNT = 5;
const int TRIGGER_THRESHOLD = 3;
const int COOLDOWN_MS = 30000; // 30 秒后自动关灯
unsigned long lastTriggerTime = 0;
bool isLightOn = false;
void setup() {
pinMode(pirPin, INPUT);
pinMode(lightPin, OUTPUT);
Serial.begin(9600);
delay(60000); // 预热
Serial.println("智能走廊灯就绪");
}
bool readPIRWithFilter() {
int triggerCount = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
if (digitalRead(pirPin) == HIGH) {
triggerCount++;
}
delay(50);
}
return triggerCount >= TRIGGER_THRESHOLD;
}
bool isNight() {
return analogRead(lightSensorPin) < 300;
}
void loop() {
unsigned long currentTime = millis();
// 只在夜晚工作
if (!isNight()) {
analogWrite(lightPin, 0);
isLightOn = false;
return;
}
// 自动关灯逻辑
if (isLightOn && currentTime - lastTriggerTime > COOLDOWN_MS) {
analogWrite(lightPin, 0);
isLightOn = false;
Serial.println("自动关灯");
return;
}
// 人体检测
if (readPIRWithFilter() && !isLightOn) {
analogWrite(lightPin, 200); // 80% 亮度
isLightOn = true;
lastTriggerTime = currentTime;
Serial.println("检测到人体,开灯");
}
}
成本分析
做一个完整的 PIR 人体检测系统,成本非常低:
| 组件 | 单价 | 数量 | 小计 |
|---|---|---|---|
| HC-SR501 PIR 模块 | ¥10 | 1 | ¥10 |
| Arduino Nano | ¥18 | 1 | ¥18 |
| 光敏电阻模块 | ¥5 | 1 | ¥5 |
| LED 灯珠 | ¥1 | 3 | ¥3 |
| 电阻电容若干 | ¥5 | 1 | ¥5 |
| PCB 洞洞板 | ¥3 | 1 | ¥3 |
| 外壳(3D 打印) | ¥10 | 1 | ¥10 |
| 总计 | ¥54 |
相比市面成品的智能人体传感器(¥80-200),DIY 方案成本降低 50% 以上,而且可以完全自定义功能。
功耗优化技巧
如果是电池供电项目,功耗是关键。HC-SR501 静态电流约 50μA,触发时约 2mA。以下是降低功耗的方法:
使用休眠模式
#include
const int pirPin = 2;
volatile bool pirTriggered = false;
// PIR 中断回调
void pirISR() {
pirTriggered = true;
}
void setup() {
pinMode(pirPin, INPUT);
attachInterrupt(digitalPinToInterrupt(pirPin), pirISR, RISING);
Serial.begin(9600);
}
void loop() {
if (pirTriggered) {
pirTriggered = false;
// 唤醒后处理
Serial.println("检测到人体!");
// 执行任务...
delay(1000);
}
// 进入休眠
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
// 唤醒后继续
sleep_disable();
}
使用休眠模式后,待机电流可降至 10μA 以下,两节 AA 电池可以工作 1 年以上。
降低采样频率
不需要持续检测时,可以间歇性工作:
void loop() {
// 工作 1 秒
bool detected = readPIRWithFilter();
if (detected) {
handleDetection();
}
// 休眠 10 秒
delay(10000);
}
这样平均功耗降低 90%。
ESP32 版本代码
如果你使用 ESP32,可以利用其深度睡眠特性:
#include
#include
#define PIR_PIN 4
#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 5
RTC_DATA_ATTR int bootCount = 0;
void print_wakeup_reason() {
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Wakeup caused by external signal using RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Wakeup caused by external signal using RTC_CNTL");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup caused by timer");
break;
default:
Serial.println("Wakeup was not caused by deep sleep");
break;
}
}
void setup() {
Serial.begin(115200);
bootCount++;
Serial.println("Boot number: " + String(bootCount));
print_wakeup_reason();
// 配置 PIR 引脚为唤醒源
gpio_hold_en((gpio_num_t)PIR_PIN);
if (digitalRead(PIR_PIN) == HIGH) {
Serial.println("人体检测!发送通知...");
// 连接 WiFi 并发送通知
WiFi.begin("your-ssid", "your-password");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 10) {
delay(500);
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin("http://your-server.com/webhook");
http.POST("{\"event\":\"motion_detected\"}");
http.end();
Serial.println("通知已发送");
}
}
// 5 秒后进入深度睡眠
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("进入深度睡眠...");
esp_deep_sleep_start();
}
void loop() {
// 不会执行到这里
}
ESP32 深度睡眠电流仅 10μA,配合 PIR 唤醒,非常适合电池供电的远程监控项目。
数据记录与可视化
如果你想记录人体活动数据,可以添加到 InfluxDB + Grafana:
#include
#include
InfluxDbClient client("http://your-influx-server:8086", "iot_db");
Point motion_point("motion_events");
void logMotionEvent() {
motion_point.clearFields();
motion_point.addField("detected", 1);
motion_point.setTime(DateTime.now());
if (client.writePoint(motion_point)) {
Serial.println("数据已记录到 InfluxDB");
}
}
void setup() {
// ... 初始化代码
client.setConnectionParamsV1();
}
然后在 Grafana 中创建仪表盘,可以看到每天的人体活动热力图。
与 Home Assistant 集成
如果你使用 Home Assistant,可以通过 MQTT 集成:
#include
#include
const char* mqtt_server = "home-assistant.local";
const char* mqtt_topic = "home/sensor/pir_livingroom";
WiFiClient espClient;
PubSubClient client(espClient);
void reconnect() {
while (!client.connected()) {
if (client.connect("ESP32-PIR-Sensor")) {
Serial.println("MQTT 已连接");
} else {
delay(5000);
}
}
}
void setup() {
client.setServer(mqtt_server, 1883);
// ... 其他初始化
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if (readPIRWithFilter()) {
client.publish(mqtt_topic, "ON");
Serial.println("MQTT: 发送 ON");
}
}
在 Home Assistant 的 configuration.yaml 中添加:
binary_sensor:
- platform: mqtt
name: "客厅人体传感器"
state_topic: "home/sensor/pir_livingroom"
payload_on: "ON"
payload_off: "OFF"
device_class: motion
这样你就可以在 Home Assistant 中创建自动化,例如"检测到人体时开灯"。
总结
PIR 人体传感器成本低、功耗小,是 IoT 项目的常用选择。但要用好它,需要:
- 硬件调节:合理设置灵敏度和触发时间
- 软件滤波:多次采样 + 阈值判断 + 冷却时间
- 安装优化:避开热源、调整高度和角度
- 场景适配:根据不同环境调整参数
- 传感器融合:结合光敏、超声波等提高准确性
- 功耗优化:使用休眠模式延长电池寿命
- 系统集成:接入 Home Assistant、MQTT 等平台
从简单的 LED 指示,到智能家居自动化,再到远程监控系统,PIR 传感器都能胜任。关键是理解它的特性,用合适的方法规避局限。
希望这篇博客文章对您有所帮助!