|
$5 打造 ESP32 人体存在传感器:HLK-LD2410 毫米波雷达 DIY 完全指南

$5 打造 ESP32 人体存在传感器:HLK-LD2410 毫米波雷达 DIY 完全指南

为什么你需要一个毫米波人体存在传感器

传统 PIR 红外传感器只能检测运动——当你坐在沙发上看书、蹲在马桶上刷手机时,PIR 会认为”没人”然后关灯。这是智能家居中最令人抓狂的体验之一。

毫米波雷达(mmWave Radar) 彻底解决了这个问题。它通过发射 24GHz 电磁波并分析反射信号,能够检测到人体的微小运动——甚至包括呼吸引起的胸腔起伏。这意味着只要有人在房间里,哪怕一动不动,传感器也能准确报告”存在”。

毫米波雷达的工作原理基于多普勒效应FMCW(调频连续波)技术。模块发射的电磁波遇到物体后反射回来,通过分析反射波的频率变化和相位差,可以精确计算出目标的距离、速度和存在状态。这种技术最初用于军事和航空领域,现在已经民用化并大幅降低成本。

好消息是:一颗 ESP32(约 $3)加上一块 HLK-LD2410 毫米波雷达模块(约 $2),总成本不到 5 美元,就能做出一个性能媲美商业产品的存在传感器。

传感器选型:PIR vs 毫米波雷达 vs 超声波

在动手之前,先搞清楚三种主流人体检测方案的优劣:

特性PIR 红外毫米波雷达超声波
检测静止人体❌ 不能✅ 能✅ 能
检测距离5-12m0.2-6m(可调)0.2-3m
穿透能力需直视可穿透薄墙/塑料外壳需直视
功耗极低(μA 级)中等(~100mA)中等
抗光干扰差(受温度影响)
成本$1-2$2-5$2-3
隐私安全高(不采集图像)

PIR 红外传感器通过检测人体发出的红外辐射变化来工作。当人体移动时,红外辐射的变化被热释电传感器捕捉。但如果你静止不动,红外辐射分布稳定,传感器就无法检测。PIR 的优势是功耗极低,适合电池供电的走廊灯、楼梯灯等”必须有人走动”的场景。

毫米波雷达的优势在于能够检测微小运动,甚至可以区分运动目标和静止目标。HLK-LD2410 模块会分别报告”运动目标”和”静止目标”的距离和能量,让你可以判断房间里是有人走动还是有人静坐。这种能力对于智能照明、空调控制、安防监控等场景至关重要。

超声波传感器通过发射超声波并测量回波时间来计算距离。它可以检测静止物体,但波束角较窄,覆盖范围有限。超声波更适合精确测距(如倒车雷达),不太适合做房间级的存在检测。

结论:如果你要做智能照明自动化,毫米波雷达是最佳选择——能检测静止人体、可隐藏安装在墙壁/天花板内、成本极低。PIR 适合走廊、楼梯等”必须有人走动”的场景。超声波在短距离测距场景有优势,但不适合做存在检测。

硬件清单

物料型号参考价格
主控板ESP32-DevKitC / ESP32-WROOM-32$3
毫米波雷达模块HLK-LD2410(24GHz)$2
杜邦线母对母 4 根$0.5
Micro-USB 数据线$1
面包板(可选)830 孔$2

HLK-LD2410 模块说明:该模块基于 24GHz FMCW(调频连续波)雷达技术,内置信号处理算法,可直接输出目标状态(有人/无人)、目标距离和运动能量。模块内部集成了发射天线和接收天线,无需外接天线。支持 UART 串口通信,波特率默认 256000(可通过指令修改为 115200 以兼容 ESP32 软串口)。

模块引脚定义

  • VCC:供电引脚,支持 3.3V 或 5V
  • GND:地线
  • OUT(TX):数据输出引脚,发送雷达数据帧
  • IN(RX):数据输入引脚,接收配置指令

购买建议:HLK-LD2410 有多个版本,包括 LD2410(标准版)、LD2410P(增强版,检测距离更远)、LD2420(新版本,支持更多功能)。对于初学者,标准版 LD2410 已经足够,价格最便宜且资料最多。

接线图

HLK-LD2410 与 ESP32 的接线非常简单,仅需 4 根线:

HLK-LD2410          ESP32-DevKitC
┌──────────┐        ┌──────────┐
│      VCC │───────▶│ 3.3V     │
│      GND │───────▶│ GND      │
│      OUT │───────▶│ GPIO16   │  (UART2 RX)
│      IN  │◀───────│ GPIO17   │  (UART2 TX)
└──────────┘        └──────────┘

接线要点

  • VCC → 3.3V:LD2410 支持 3.3V 供电,无需电平转换。如果你使用 5V 供电(如 USB 5V),模块也能正常工作,但要注意 ESP32 的 GPIO 是 3.3V 逻辑,可能需要电平转换(实际上 LD2410 的 3.3V 输出兼容 ESP32,无需转换)
  • OUT → GPIO16:模块的 TX 引脚接 ESP32 的 UART RX。GPIO16 是 ESP32 的 UART2 RX 引脚
  • IN → GPIO17:模块的 RX 引脚接 ESP32 的 UART TX。GPIO17 是 ESP32 的 UART2 TX 引脚
  • 如果你只需要读取传感器数据(不需要配置参数),可以只接 VCC、GND、OUT 三根线

常见接线错误

  1. TX/RX 接反:模块的 OUT(TX)应该接 ESP32 的 RX,模块的 IN(RX)应该接 ESP32 的 TX。如果接反,串口通信会失败
  2. 供电不足:使用 USB 供电时,确保数据线支持供电(不是纯充电线)。某些劣质 USB 线电阻过大,导致电压跌落
  3. 地线未连接:VCC 和 GND 必须同时连接,否则模块无法工作

Arduino 完整代码

以下代码通过 UART 读取 HLK-LD2410 的数据帧,解析目标状态和距离,并通过 MQTT 发布到 Home Assistant:

#include <WiFi.h>
#include <PubSubClient.h>
#include <HardwareSerial.h>

// ===== WiFi & MQTT 配置 =====
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "192.168.1.100";  // Home Assistant IP
const char* mqtt_user = "your_mqtt_user";
const char* mqtt_pass = "your_mqtt_password";

// ===== 引脚定义 =====
#define RADAR_RX  16   // 接 LD2410 的 OUT/TX
#define RADAR_TX  17   // 接 LD2410 的 IN/RX

// ===== MQTT 主题 =====
const char* topic_presence = "home/sensor/living_room/presence";
const char* topic_distance  = "home/sensor/living_room/distance";
const char* topic_energy    = "home/sensor/living_room/energy";

HardwareSerial RadarSerial(2);
WiFiClient espClient;
PubSubClient mqtt(espClient);

// ===== 数据解析变量 =====
bool targetState = false;     // true=有人, false=无人
int  moveDistance = 0;        // 目标距离 (cm)
int  moveEnergy = 0;          // 运动能量 (0-100)

void setup() {
  Serial.begin(115200);
  RadarSerial.begin(256000, SERIAL_8N1, RADAR_RX, RADAR_TX);
  
  // 连接 WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  
  // 配置 MQTT
  mqtt.setServer(mqtt_server, 1883);
  mqtt.setBufferSize(512);
}

void reconnectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("Connecting MQTT...");
    if (mqtt.connect("ESP32-Radar-Sensor", mqtt_user, mqtt_pass)) {
      Serial.println("OK");
    } else {
      Serial.printf("Failed, rc=%d\n", mqtt.state());
      delay(5000);
    }
  }
}

// ===== 解析 LD2410 数据帧 =====
void parseRadarData() {
  static uint8_t buffer[256];
  static int bufIndex = 0;
  
  while (RadarSerial.available()) {
    uint8_t c = RadarSerial.read();
    
    // 帧头: 0xF4 0xF3 0xF2 0xF1
    if (bufIndex == 0 && c != 0xF4) continue;
    if (bufIndex == 1 && c != 0xF3) { bufIndex = 0; continue; }
    if (bufIndex == 2 && c != 0xF2) { bufIndex = 0; continue; }
    if (bufIndex == 3 && c != 0xF1) { bufIndex = 0; continue; }
    
    buffer[bufIndex++] = c;
    
    // 帧尾: 0xF8 0xF7 0xF6 0xF5
    if (bufIndex >= 8 &&
        buffer[bufIndex-4] == 0xF8 &&
        buffer[bufIndex-3] == 0xF7 &&
        buffer[bufIndex-2] == 0xF6 &&
        buffer[bufIndex-1] == 0xF5) {
      
      // 解析目标状态 (byte 8-9, 0x0000=无人, 0x0001=有人)
      if (bufIndex >= 12) {
        targetState = (buffer[9] == 0x01);
        moveDistance = buffer[10] | (buffer[11] << 8);
      }
      // 解析运动能量
      if (bufIndex >= 16) {
        moveEnergy = buffer[14];
      }
      
      bufIndex = 0;  // 重置缓冲区
    }
    
    if (bufIndex >= sizeof(buffer)) bufIndex = 0;
  }
}

void loop() {
  if (!mqtt.connected()) reconnectMQTT();
  mqtt.loop();
  
  parseRadarData();
  
  // 每 2 秒发布一次数据
  static unsigned long lastPublish = 0;
  if (millis() - lastPublish > 2000) {
    lastPublish = millis();
    
    mqtt.publish(topic_presence, targetState ? "ON" : "OFF");
    mqtt.publish(topic_distance, String(moveDistance).c_str());
    mqtt.publish(topic_energy, String(moveEnergy).c_str());
    
    Serial.printf("Presence: %s | Distance: %d cm | Energy: %d\n",
                  targetState ? "YES" : "NO", moveDistance, moveEnergy);
  }
}

代码说明

  • 使用 ESP32 的 HardwareSerial(2) 与 LD2410 通信,波特率 256000。注意不能使用默认的 Serial(UART0),因为那会被用于调试输出
  • 数据帧以 F4 F3 F2 F1 开头,F8 F7 F6 F5 结尾。代码通过状态机方式逐字节解析,确保即使在数据流中间开始读取也能正确同步
  • 解析目标状态(有人/无人)、目标距离(cm)和运动能量(0-100)。距离值由两个字节组成(低字节在前),能量值是一个字节
  • 通过 MQTT 发布三个主题:presence(ON/OFF)、distance(cm)、energy(0-100)。每 2 秒发布一次,避免 MQTT 服务器过载

依赖库

  • PubSubClient:MQTT 客户端库,通过 Arduino IDE 的库管理器安装
  • WiFi:ESP32 内置 WiFi 库,无需额外安装

Home Assistant 集成

方式一:MQTT 传感器(推荐)

在 Home Assistant 的 configuration.yaml 中添加:

mqtt:
  sensor:
    - name: "客厅存在传感器"
      state_topic: "home/sensor/living_room/presence"
      payload_on: "ON"
      payload_off: "OFF"
      device_class: occupancy
      unique_id: "lr_occupancy"
    
    - name: "客厅目标距离"
      state_topic: "home/sensor/living_room/distance"
      unit_of_measurement: "cm"
      icon: "mdi:signal-distance-variant"
      unique_id: "lr_distance"
    
    - name: "客厅运动能量"
      state_topic: "home/sensor/living_room/energy"
      unit_of_measurement: "%"
      icon: "mdi:pulse"
      unique_id: "lr_energy"

配置说明

  • device_class: occupancy 让 Home Assistant 自动识别这是一个占用传感器,会在界面上显示相应的图标
  • unique_id 确保传感器在重启后保持历史记录,不会重复创建
  • 如果你使用 MQTT 集成(通过 Home Assistant 的”配置” → “设备与服务” → “MQTT”),可以直接在界面上添加传感器,无需编辑 YAML

方式二:ESPHome(零代码方案)

如果你不想写 Arduino 代码,可以使用 ESPHome 的 ld2410 组件。ESPHome 是 Home Assistant 官方推荐的固件框架,支持通过 YAML 配置生成固件,无需编程。

esphome:
  name: living-room-presence
  friendly_name: "客厅存在传感器"

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:
  encryption:
    key: !secret api_key

logger:
  baud_rate: 0  # 禁用串口日志,避免与雷达冲突

uart:
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 256000

ld2410:
  timeout:
    no_target: 5s
    has_target: 2s
  max_distances:
    motion: 6m
    static: 4.5m

binary_sensor:
  - platform: ld2410
    has_target:
      name: "有人"
      device_class: occupancy

sensor:
  - platform: ld2410
    moving_distance:
      name: "运动目标距离"
    static_distance:
      name: "静止目标距离"
    moving_energy:
      name: "运动能量"
    static_energy:
      name: "静止能量"

将以上 YAML 保存为 living-room-presence.yaml,通过 ESPHome Dashboard 上传即可。ESPHome 原生支持 LD2410,无需额外组件。

ESPHome 的优势

  • 零代码:通过 YAML 配置,无需编程
  • 自动发现:Home Assistant 自动识别并添加设备,无需手动配置 MQTT
  • OTA 更新:支持无线更新固件,无需重新接线
  • 内置组件:LD2410 组件已经过充分测试,稳定性好

功耗优化:电池供电方案

HLK-LD2410 的工作电流约 100mA,持续运行。如果用 18650 电池(2500mAh)供电,理论续航约 25 小时——对于存在传感器来说不够理想。

优化方案

  1. 深度睡眠 + 定时唤醒:ESP32 进入深度睡眠(~10μA),每 30 秒唤醒 2 秒读取雷达数据。综合功耗降至约 10mA,18650 续航可达 10 天以上。这种方案适合对实时性要求不高的场景,如房间占用统计。

  2. LD2410 OUT 引脚中断:LD2410 的 OUT 引脚在检测到目标时输出高电平。将该引脚接到 ESP32 的 GPIO,配置为唤醒源。无人时 ESP32 深度睡眠,有人时唤醒上报数据。这种方案兼顾了低功耗和实时性,是最佳选择。

  3. 降低发射功率:通过串口指令降低 LD2410 的发射功率,可将模块功耗降至 50mA 以下,代价是检测距离缩短。适合小房间(如卫生间)使用。

深度睡眠示例代码

#define WAKE_PIN GPIO4  // 接 LD2410 OUT 引脚

void setup() {
  // 配置 GPIO4 为唤醒源(高电平唤醒)
  esp_sleep_enable_ext0_wakeup(WAKE_PIN, HIGH);
  
  // ... 初始化代码 ...
}

void loop() {
  // 读取数据并发布
  readAndPublish();
  
  // 进入深度睡眠,等待 OUT 引脚高电平唤醒
  esp_deep_sleep_start();
  // 唤醒后从 setup() 重新开始
}

电池选择建议

  • 18650 锂电池:容量大(2000-3500mAh),适合长期运行。需要配合充电模块(如 TP4056)使用
  • 18650 电池盒:购买带开关的电池盒,方便更换电池
  • USB 供电:如果使用 USB 充电头供电,无需考虑功耗问题,但失去了电池供电的灵活性

实际部署建议

  1. 安装位置:天花板中央或墙壁高处,避免正对门窗(室外移动物体会误触发)。最佳高度是 2.5-3 米,可以覆盖整个房间
  2. 灵敏度调节:通过串口指令调整门限阈值,适应不同房间大小。大房间(客厅)可以提高灵敏度,小房间(卫生间)可以降低灵敏度以避免误触发
  3. 抗干扰:远离微波炉(2.4GHz)和金属反射面。微波炉工作时会产生强烈的 2.4GHz 干扰,可能导致传感器误报
  4. 外壳设计:3D 打印外壳时注意雷达波可穿透塑料(ABS/PLASTIC),但不能穿透金属。外壳厚度建议不超过 3mm,过厚会衰减信号
  5. 多传感器部署:如果房间有多个入口,可以部署多个传感器,通过 Home Assistant 的逻辑判断综合确定房间占用状态
  6. 调试技巧:使用串口监视器查看传感器输出的原始数据,观察距离和能量值的变化,帮助理解传感器的工作状态

常见问题排查

问题 1:串口没有数据输出

  • 检查接线:TX/RX 是否接反
  • 检查波特率:LD2410 默认波特率是 256000,代码中必须匹配
  • 检查供电:用万用表测量 VCC 引脚电压,确保在 3.3V 左右

问题 2:传感器一直报告”有人”

  • 检查安装位置:是否正对门窗、空调出风口、风扇等移动物体
  • 降低灵敏度:通过串口指令调整门限阈值
  • 检查干扰源:附近是否有微波炉、无绳电话等 2.4GHz 设备

问题 3:传感器一直报告”无人”

  • 检查检测范围:是否超出传感器的最大检测距离(6m)
  • 提高灵敏度:通过串口指令调整门限阈值
  • 检查目标:传感器是否能检测到微小运动(如呼吸引起的胸腔起伏)

问题 4:MQTT 连接失败

  • 检查 WiFi 信号:确保 ESP32 能稳定连接 WiFi
  • 检查 MQTT 服务器:确认 Home Assistant 的 MQTT 集成已启用
  • 检查用户名密码:确保 MQTT 用户名和密码正确

总结

5 美元就能做出一个商业级的人体存在传感器——这正是开源硬件的魅力所在。HLK-LD2410 毫米波雷达模块解决了 PIR 传感器”检测不到静止人体”的痛点,配合 ESP32 的 WiFi 和 MQTT 能力,可以无缝接入 Home Assistant 智能家居系统。

这个项目的核心优势:

  • 成本极低:总成本不到 5 美元,远低于商业产品(通常 $20-50)
  • 性能优秀:能检测静止人体,检测距离可达 6 米
  • 易于集成:通过 MQTT 或 ESPHome 无缝接入 Home Assistant
  • 高度可定制:可以根据需求调整灵敏度、检测范围、功耗等参数

如果你已经完成了 ESP32-C6 Matter 指南,这个传感器项目可以作为 Matter 网络中的第一个自定义设备——下一步就是把它封装成 Matter 传感器配件,实现真正的跨平台智能家居自动化。