输入搜索…

· notes · 10 min 阅读

使用 ESP32 + LD2410D 实现人体存在检测

使用 ESP32 驱动 LD2410D 毫米波雷达传感器,通过 WebSocket 实现人体存在检测与远程联动

使用 ESP32 + LD2410D 实现人体存在检测

@danielsessler

前言

传统 PIR(被动红外)传感器只能检测运动,人静止不动时就会误判为无人。而毫米波雷达传感器可以检测微小的生命体征(如呼吸),即使人静止坐着也能准确感知存在。

本文记录使用 ESP32 + LD2410D 毫米波雷达的完整开发过程,包括接线、串口协议解析、两种工作模式的处理,以及通过 WebSocket 与服务端联动的实现。


硬件介绍

LD2410D 参数

参数
工作频率24GHz
检测距离0.2m ~ 6m
检测角度±60°
通信方式UART
工作电压3.3V
默认波特率115200

接线方式

     LD2410D        ESP32
───────        ─────
VCC    ──►     3.3V
GND    ──►     GND
TX     ──►     GPIO16 (Serial2 RX)
RX     ──►     GPIO17 (Serial2 TX)

注意:LD2410D 的逻辑电平为 3.3V,可直接连接 ESP32,无需电平转换。


两种工作模式

LD2410D 有两种数据输出模式:

模式输出格式适用场景
正常模式ASCII 文本(OFFdistance: XX简单存在检测
工程模式二进制帧(含各距离门能量数据)精细调试、多目标分析

正常模式

输出简单的 ASCII 行:

  • OFF — 无人
  • distance: 42 — 有人,距离 42cm

工程模式

输出二进制帧,包含运动/静止目标的距离、能量,以及各距离门的详细能量数据。


串口协议详解

帧格式

数据上报帧(正常/工程模式):

     帧头: F4 F3 F2 F1
数据: 2字节长度(小端) + 2字节类型 + 数据体
帧尾: F8 F7 F6 F5

配置命令帧

     帧头: FD FC FB FA
数据: 2字节长度(小端) + 2字节命令字(小端) + 命令值
帧尾: 04 03 02 01

工程模式数据体结构

     字节0:     头部状态 (0x00=无人, 0x01=有人运动, 0x02=有人静止)
字节1:     运动/静止标志 (bit0=运动, bit1=静止)
字节2-3:   运动目标距离(小端, cm)
字节4:     运动目标能量
字节5-6:   静止目标距离(小端, cm)
字节7:     静止目标能量
字节8~8+N-1:   N个距离门的运动能量
字节8+N~8+2N-1: N个距离门的静止能量

常用配置命令

命令字说明
0x00FF进入配置模式
0x00FE退出配置模式
0x00FD参数掉电保存
0x0012设置数据输出模式
0x0007写入传感器参数
0x0008读取传感器参数
0x0000读取固件版本

代码实现

初始化雷达串口

     const int RADAR_RX_PIN = 16;  // ESP32 RX <- LD2410D TX
const int RADAR_TX_PIN = 17;  // ESP32 TX -> LD2410D RX

void setup() {
  Serial.begin(115200);
  
  // 初始化 LD2410D 雷达串口(波特率 115200)
  Serial2.begin(115200, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
  delay(500);
  
  // 配置雷达为正常模式(ASCII 输出)
  if (enterRadarConfigMode()) {
    setRadarOutputMode(0x00000064);  // 正常模式
    exitRadarConfigMode();
  }
}

发送配置命令

     const uint8_t LD2410_CFG_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
const uint8_t LD2410_CFG_TAIL[4] = {0x04, 0x03, 0x02, 0x01};

bool sendRadarCommand(uint16_t cmd, const uint8_t* value, uint8_t valueLen) {
  uint16_t dataLen = 2 + valueLen;

  // 发送帧头
  Serial2.write(LD2410_CFG_HEADER, 4);
  // 发送长度(小端)
  Serial2.write(dataLen & 0xFF);
  Serial2.write((dataLen >> 8) & 0xFF);
  // 发送命令字(小端)
  Serial2.write(cmd & 0xFF);
  Serial2.write((cmd >> 8) & 0xFF);
  // 发送命令值
  if (valueLen > 0 && value != NULL) {
    Serial2.write(value, valueLen);
  }
  // 发送帧尾
  Serial2.write(LD2410_CFG_TAIL, 4);
  Serial2.flush();

  // 等待 ACK(2秒超时)
  unsigned long start = millis();
  uint8_t ackBuf[64];
  int ackBufLen = 0;

  while (millis() - start < 2000) {
    while (Serial2.available()) {
      uint8_t b = Serial2.read();
      if (ackBufLen < sizeof(ackBuf)) {
        ackBuf[ackBufLen++] = b;
      }
      // 检测配置帧尾 04 03 02 01
      if (ackBufLen >= 4 &&
          ackBuf[ackBufLen-4] == 0x04 && ackBuf[ackBufLen-3] == 0x03 &&
          ackBuf[ackBufLen-2] == 0x02 && ackBuf[ackBufLen-1] == 0x01) {
        if (ackBufLen >= 10) {
          uint16_t status = ackBuf[8] | (ackBuf[9] << 8);
          return (status == 0);  // 0=成功
        }
      }
    }
    delay(10);
  }
  return false;
}

bool enterRadarConfigMode() {
  uint8_t val[] = {0x01, 0x00};
  return sendRadarCommand(0x00FF, val, 2);
}

bool exitRadarConfigMode() {
  return sendRadarCommand(0x00FE, NULL, 0);
}

bool setRadarOutputMode(uint32_t mode) {
  uint8_t val[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  val[2] = (uint8_t)(mode & 0xFF);
  val[3] = (uint8_t)((mode >> 8) & 0xFF);
  val[4] = (uint8_t)((mode >> 16) & 0xFF);
  val[5] = (uint8_t)((mode >> 24) & 0xFF);
  return sendRadarCommand(0x0012, val, 6);
}

正常模式解析(ASCII)

     void parseNormalModeLine(const char* line) {
  if (strcmp(line, "OFF") == 0) {
    // 无人
    radar.presence = false;
    Serial.println("[RADAR] 检测到无人");
  } else if (strncmp(line, "distance:", 9) == 0) {
    // 有人,解析距离
    int dist = atoi(line + 9);
    radar.presence = true;
    radar.motionDistance = dist;
    Serial.printf("[RADAR] 检测到有人 距离:%dcm\n", dist);
  }
}

工程模式解析(二进制帧)

     void processRadarFrame() {
  if (radarFrameLen < 20) return;

  // 帧类型: 偏移 头4 + 长度2
  uint8_t frameType = radarBuf[6];
  if (frameType != 0x01) return;  // 只处理实时上报帧

  int dataOffset = 8;  // 头4 + 长度2 + 类型2

  uint8_t headState = radarBuf[dataOffset];      // 0x00=无人, 0x01=运动, 0x02=静止
  uint8_t detectType = radarBuf[dataOffset + 1]; // bit0=运动, bit1=静止

  // 运动目标
  int motionDist = radarBuf[dataOffset + 2] | (radarBuf[dataOffset + 3] << 8);
  int motionEng = radarBuf[dataOffset + 4];

  // 静止目标
  int staticDist = radarBuf[dataOffset + 5] | (radarBuf[dataOffset + 6] << 8);
  int staticEng = radarBuf[dataOffset + 7];

  // 0x01=有人运动, 0x02=有人静止, 都视为有人
  bool newPresence = (headState == 0x01 || headState == 0x02);
  radar.presence = newPresence;
  radar.motionDistance = motionDist;
  radar.staticDistance = staticDist;
}

进阶:WebSocket 联动

在实际项目中,雷达数据需要上报到服务端进行处理。以下是通过 WebSocket 实现的方案:

架构

     ESP32 (WebSocket Client)

  ├── LD2410D 雷达 → 人体存在检测
  ├── 433MHz 模块 → 射频遥控
  └── WebSocket → 连接 Koa 服务端

                   ├── MySQL 存储
                   ├── JWT 设备鉴权
                   └── 业务逻辑处理

设备鉴权

通过 URL 参数传递设备令牌:

     const char* WS_HOST = "esp.ll1025.cn";
const int WS_PORT = 443;
const bool WS_SSL = true;
const char* WS_PATH = "/ws";
const char* DEVICE_TOKEN = "device_esp32-s3_xxxxx";

void connectWebSocket() {
  String wsPath = String(WS_PATH) + "?token=" + String(DEVICE_TOKEN);
  if (WS_SSL) {
    webSocket.beginSSL(WS_HOST, WS_PORT, wsPath);
  } else {
    webSocket.begin(WS_HOST, WS_PORT, wsPath);
  }
  webSocket.onEvent(webSocketEvent);
  webSocket.setReconnectInterval(5000);
  webSocket.enableHeartbeat(15000, 3000, 2);
}

状态上报

     // 周期上报(每2秒)
void reportRadarPeriodic() {
  String msg = "{\"type\":\"radar_status\",\"data\":{"
    "\"presence\":" + String(radar.presence ? "true" : "false") + ","
    "\"motionDistance\":" + String(radar.motionDistance) + ","
    "\"staticDistance\":" + String(radar.staticDistance) + "}}";
  webSocket.sendTXT(msg);
}

// 状态变化时立即上报
void sendRadarEvent(String eventType) {
  String msg = "{\"type\":\"" + eventType + "\",\"data\":{"
    "\"motionDistance\":" + String(radar.motionDistance) + ","
    "\"staticDistance\":" + String(radar.staticDistance) + "}}";
  webSocket.sendTXT(msg);
}

RGB LED 状态指示

使用板载 WS2812B LED 显示设备状态:

     // 绿色=在线无人, 橙色=有人, 蓝色闪烁=连接中, 红色闪烁=WiFi失败
void updateLed() {
  if (!wifiConnected) {
    // 红色闪烁
    rgbLed.setPixelColor(0, blinkOn ? rgbLed.Color(80, 0, 0) : 0);
  } else if (!wsConnected) {
    // 蓝色闪烁
    rgbLed.setPixelColor(0, blinkOn ? rgbLed.Color(0, 0, 80) : 0);
  } else if (radar.presence) {
    // 橙色常亮 = 检测到有人
    rgbLed.setPixelColor(0, rgbLed.Color(80, 30, 0));
  } else {
    // 绿色常亮 = 在线,无人
    rgbLed.setPixelColor(0, rgbLed.Color(0, 60, 0));
  }
  rgbLed.show();
}

常见问题

1. 雷达无响应

  • 检查 TX/RX 是否接反
  • 确认波特率为 115200(手册默认值)
  • 确认供电电压为 3.3V

2. 检测不灵敏

  • 调整雷达安装位置,避免金属遮挡
  • 通过配置命令调整灵敏度参数
  • 确保检测区域内无大型金属物体干扰

3. 误检测

  • LD2410D 对窗帘晃动、风扇转动较敏感
  • 可通过设置距离门阈值过滤干扰
  • 建议在实际场景中调试参数

应用场景

  • 智能灯光:人来灯亮,人走灯灭
  • 空调控制:检测房间是否有人,自动开关空调
  • 安防报警:检测异常存在,触发报警
  • 办公工位:检测工位占用状态

总结

ESP32 + LD2410D 是一个性价比很高的人体存在检测方案。相比传统 PIR 传感器,毫米波雷达能检测静止人体,适用场景更广。

实际开发中需要注意:

  • 默认波特率是 115200(部分文档写的 256000 是旧版本)
  • 正常模式简单易用,工程模式数据更丰富
  • 配置命令需要先进入配置模式(0x00FF),操作完退出(0x00FE
  • 建议加状态变化冷却时间,避免频繁上报

完整固件代码已开源,包含 433MHz 射频、WebSocket 通信、NVS 配置管理等功能。


参考资料

返回文章列表