回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

基于C4002毫米波雷达的卫生间防跌倒报警系统 简单

头像 Buzz 2026.06.23 13 0

一、项目背景与痛点

卫生间是老年人意外跌倒的高风险场所。据世界卫生组织统计,65岁以上老人每年约有1/3发生跌倒,其中近60%发生在卫生间。跌倒后若长时间无人发现,可能因延误救治导致严重后果。

传统卫生间安全方案存在明显不足:

• 摄像头方案:卫生间隐私敏感,无法安装;

• 红外PIR方案:只能检测运动,人静止后无法感知(跌倒后常无法动弹);

• 可穿戴设备:老人常忘记佩戴,且需定期充电。

C4002毫米波雷达具备穿透性和隐私保护特性,且能同时检测运动和静止人体(含呼吸微动),是卫生间场景的理想传感器。本项目基于C4002模块实现了一套非接触式、全天候的卫生间防跌倒报警系统。

二、系统方案设计

2.1 系统架构

系统以ESP32为主控,C4002毫米波雷达为核心传感器,配合蜂鸣器、LED指示灯和手动取消按钮,构成完整的检测-预警-报警-解除闭环。

2.2 状态机逻辑

系统采用四状态有限状态机(FSM)设计,确保状态转换清晰可控:

状态

触发条件

系统行为

IDLE(空闲)无人待机,LED灭,蜂鸣器关
OCCUPIED(有人)检测到运动或静止记录进入时间,运动时重置静止计时
PRE_ALARM(预警)静止≥30秒LED慢闪(1秒周期),持续监测
ALARM(报警)静止≥60秒LED快闪(300ms周期)+ 蜂鸣器持续响

解除条件:检测到运动(人恢复了活动)或人离开(无人状态),系统自动回到IDLE;报警状态下按按钮可手动取消。

2.3 逻辑流程图

 

image.png 

图1 系统状态机逻辑流程图

三、硬件清单与接线

3.1 硬件清单

序号

模块

型号/规格

备注

1

主控板ESP32板载WiFi/蓝牙

2

毫米波雷达DFRobot C400224GHz FMCW,UART输出

3

蜂鸣器模块数字蜂鸣器3线VCC/GND/SIG

4

LED模块DF Digital Blue LED V2高电平触发(HIGH=亮)

5

按钮模块DF数字大按钮按下=高电平

6

扩展板ESP32 扩展板方便接线

3.2 接线表

模块

模块引脚

ESP32引脚

说明

C4002VCC5V5V
C4002GNDGND 
C4002TXD16 (GPIO16 RX)交叉连接
C4002RXD17 (GPIO17 TX)交叉连接
蜂鸣器VCC/GND/SIG5V/GND/D4SIG接D4
LEDVCC/GND/D5V/GND/D5D接D5,HIGH=亮
按钮VCC/GND/SIG5V/GND/D13SIG接D13

 

四、核心代码与实现

4.1 技术路线

开发环境:Arduino IDE 2.x + ESP32 

通信方式:ESP32硬件串口Serial1(GPIO16/17)与C4002通信,波特率9600

数据解析:手动解析C4002 UART输出帧(绕过官方库兼容性问题,详见第五章)

4.2 关键代码段

状态机主循环:IDLE→OCCUPIED→PRE_ALARM→ALARM四级递进,运动重置计时,静止累计时长。预警LED慢闪,报警LED快闪+蜂鸣器响。

按钮消抖:50ms延时消抖 + 等待释放,避免误触发。

五、开发难点与解决思路

5.1 DFRobot_C4002库初始化失败

1. 问题:调用 DFRobot_C4002.begin() 始终返回false,无法完成初始化。更换波特率、重装库均无效。

2. 排查:用逻辑分析仪确认Serial1能正常接收C4002的原始数据,帧头FA F5 AA A5完整可见。

3. 解决:放弃官方库,直接手动解析UART帧。按C4002协议文档逐字节提取targetState、distance等字段,实测可行。

5.2 DF LED模块不亮

问题:DF Digital Blue LED Module V2 接线后无论HIGH还是LOW都不亮。

排查:编写最简测试代码,单独对LED引脚输出HIGH/LOW,发现该模块为高电平触发(HIGH=亮,LOW=灭),与DF部分RGB模块的低电平触发相反。

解决:修正LED控制逻辑为HIGH=亮/LOW=灭。

六、实测数据与场景验证

6.1 串口输出示例

=== 卫生间防跌倒报警系统启动 ===

C4002初始化中...

系统就绪,开始监测

[原始] targetState=2 dist=0.15m

[状态] → 有人进入

[状态] 有人 | 目标:静止 | 距离:0.20m | 静止:15秒

[状态] → 预警!静止超过30秒

[状态] 预警 | 静止:45秒

[状态] → 报警!静止超过60秒

[按钮] 报警已取消

6.2 场景测试记录

序号

测试场景

预期行为

实测结果

1

人进入雷达范围状态变为OCCUPIED正常触发,响应<1秒

2

人在范围内活动显示运动,计时重置运动检测灵敏,计时正常重置

3

人静止站立30秒LED慢闪预警30秒后LED慢闪,计时准确

4

继续静止至60秒LED快闪+蜂鸣器60秒后LED快闪+蜂鸣器持续响

5

报警后按按钮报警取消,回IDLE按钮响应正常,状态复位

6

预警后人恢复活动解除预警,回OCCUPIED运动检测后自动解除预警

7

人离开雷达范围自动回到IDLE无人5秒后自动复位

七、总结与展望

本项目成功验证了C4002毫米波雷达在卫生间防跌倒场景中的可行性。C4002能够穿透非金属材质(如浴帘、薄木板),在保护隐私的前提下精确检测静止人体(含呼吸微动),这是传统PIR红外传感器无法实现的。

主要成果:

• 实现了完整的四状态检测报警逻辑(空闲→有人→预警→报警)

• 30秒预警+60秒报警的双阶段设计,兼顾及时性和误报率

未来优化方向:

• 增加WiFi/蓝牙远程通知(手机App/微信推送)

• 利用C4002的距离数据实现跌倒姿态识别(突然下降+静止)

• 低功耗优化,电池供电实现真正免布线部署

 

【后附代码】

代码
// ============================================
// 卫生间防跌倒报警系统 - C4002毫米波雷达版
// 硬件: ESP32 + C4002 + 蜂鸣器 + LED + 按钮
// ============================================

// --- 引脚定义 ---
#define BUZZER_PIN  4   // 蜂鸣器 Signal -> D4
#define LED_PIN     5   // LED Signal -> D5
#define BTN_PIN     13  // 按钮 Signal -> D13

// --- 状态机 ---
enum State { IDLE, OCCUPIED, PRE_ALARM, ALARM };
State sysState = IDLE;

// --- 时间阈值(毫秒) ---
#define PRE_ALARM_MS  30000   // 静止30秒进入预警
#define ALARM_MS      60000   // 静止60秒进入报警

// --- C4002解析变量 ---
uint8_t frameBuf[64];
uint8_t frameIdx = 0;
bool inFrame = false;

// --- 目标数据 ---
uint8_t targetState = 0;   // 0=无人, 1=静止, 2=运动
float targetDist = 0.0;

// --- 计时 ---
unsigned long stationaryStart = 0;
unsigned long stationaryDuration = 0;
unsigned long lastPrint = 0;
unsigned long lastBlink = 0;
bool ledOn = false;

// ========================
// C4002帧解析
// ========================
bool parseC4002() {
  while (Serial1.available()) {
    uint8_t b = Serial1.read();
    
    if (!inFrame) {
      if (frameIdx < 4) {
        // 检测帧头 FA F5 AA A5
        if ((frameIdx == 0 && b == 0xFA) ||
            (frameIdx == 1 && b == 0xF5) ||
            (frameIdx == 2 && b == 0xAA) ||
            (frameIdx == 3 && b == 0xA5)) {
          frameBuf[frameIdx++] = b;
          if (frameIdx == 4) inFrame = true;
        } else {
          frameIdx = 0;
        }
      }
    } else {
      frameBuf[frameIdx++] = b;
      if (frameIdx >= 6) {
        uint16_t frameLen = frameBuf[4] | (frameBuf[5] << 8);
        if (frameLen > 64) {
          // 帧异常,重置
          inFrame = false;
          frameIdx = 0;
          continue;
        }
        if (frameIdx >= frameLen) {
          // 完整帧收到,解析
          // byte[7]=帧类型, byte[8]=CMD, byte[9]=RESP
          if (frameBuf[7] == 0x04 && frameBuf[8] == 0x60 && frameBuf[9] == 0x01) {
            uint16_t dataLen = frameBuf[10] | (frameBuf[11] << 8);
            if (dataLen >= 1) {
              targetState = frameBuf[12];
            }
            // 解析距离
            if (targetState == 1 && frameLen >= 18) {
              // 静止目标距离 byte[15-16] 小端
              targetDist = (frameBuf[15] | (frameBuf[16] << 8)) / 100.0;
            } else if (targetState == 2 && frameLen >= 24) {
              // 运动目标距离 byte[19-20] 小端
              targetDist = (frameBuf[19] | (frameBuf[20] << 8)) / 100.0;
            } else {
              targetDist = 0.0;
            }
            Serial.print("[原始] targetState=");
            Serial.print(targetState);
            Serial.print(" dist=");
            Serial.print(targetDist);
            Serial.println("m");
          }
          inFrame = false;
          frameIdx = 0;
          return true;
        }
      }
    }
  }
  return false;
}

void setup() {
  Serial.begin(9600);
  Serial1.begin(115200, SERIAL_8N1, 16, 17);
  
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BTN_PIN, INPUT);  // DF按钮模块,默认低电平
  
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(LED_PIN, LOW);
  
  Serial.println("=== 卫生间防跌倒报警系统启动 ===");
  Serial.println("C4002初始化中...");
  delay(2000);
  Serial.println("系统就绪,开始监测");
}

void loop() {
  unsigned long now = millis();
  
  // 读取C4002数据
  parseC4002();
  
  // 检测按钮(DF模块按下=高电平)
  if (digitalRead(BTN_PIN) == HIGH) {
    delay(50); // 消抖
    if (digitalRead(BTN_PIN) == HIGH) {
      // 取消报警,复位
      sysState = IDLE;
      stationaryDuration = 0;
      digitalWrite(BUZZER_PIN, LOW);
      digitalWrite(LED_PIN, LOW);
      Serial.println("[按钮] 报警已取消");
      while (digitalRead(BTN_PIN) == HIGH) {
        delay(10);
      }
    }
  }
  
  // 状态机
  switch (sysState) {
    case IDLE:
      if (targetState == 1 || targetState == 2) {
        sysState = OCCUPIED;
        stationaryStart = (targetState == 1) ? now : 0;
        Serial.println("[状态] → 有人进入");
      }
      break;
      
    case OCCUPIED:
      if (targetState == 0) {
        sysState = IDLE;
        stationaryDuration = 0;
        Serial.println("[状态] → 人已离开");
      } else if (targetState == 2) {
        // 运动中,重置静止计时
        stationaryStart = 0;
        stationaryDuration = 0;
      } else if (targetState == 1) {
        // 静止
        if (stationaryStart == 0) {
          stationaryStart = now;
        }
        stationaryDuration = now - stationaryStart;
        if (stationaryDuration >= PRE_ALARM_MS) {
          sysState = PRE_ALARM;
          Serial.println("[状态] → 预警!静止超过30秒");
        }
      }
      break;
      
    case PRE_ALARM:
      if (targetState == 0) {
        sysState = IDLE;
        stationaryDuration = 0;
        digitalWrite(LED_PIN, LOW);
        Serial.println("[状态] → 人已离开,解除预警");
      } else if (targetState == 2) {
        sysState = OCCUPIED;
        stationaryStart = 0;
        stationaryDuration = 0;
        digitalWrite(LED_PIN, LOW);
        Serial.println("[状态] → 检测到运动,解除预警");
      } else if (targetState == 1) {
        stationaryDuration = now - stationaryStart;
        // LED慢闪
        if (now - lastBlink > 1000) {
          lastBlink = now;
          ledOn = !ledOn;
          digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
        }
        if (stationaryDuration >= ALARM_MS) {
          sysState = ALARM;
          Serial.println("[状态] → 报警!静止超过60秒");
        }
      }
      break;
      
    case ALARM:
      if (targetState == 0) {
        sysState = IDLE;
        stationaryDuration = 0;
        digitalWrite(BUZZER_PIN, LOW);
        digitalWrite(LED_PIN, LOW);
        Serial.println("[状态] → 人已离开,解除报警");
      } else if (targetState == 2) {
        sysState = OCCUPIED;
        stationaryStart = 0;
        stationaryDuration = 0;
        digitalWrite(BUZZER_PIN, LOW);
        digitalWrite(LED_PIN, LOW);
        Serial.println("[状态] → 检测到运动,解除报警");
      } else {
        // 蜂鸣器响 + LED快闪
        digitalWrite(BUZZER_PIN, HIGH);
        if (now - lastBlink > 300) {
          lastBlink = now;
          ledOn = !ledOn;
          digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
        }
      }
      break;
  }
  
  // 每3秒打印状态
  if (now - lastPrint > 3000) {
    lastPrint = now;
    Serial.print("[状态] ");
    switch (sysState) {
      case IDLE:
        Serial.println("空闲 | 无人");
        break;
      case OCCUPIED:
        Serial.print("有人 | 目标:");
        Serial.print(targetState == 2 ? "运动" : "静止");
        Serial.print(" | 距离:");
        Serial.print(targetDist);
        Serial.print("m");
        if (targetState == 1 && stationaryStart > 0) {
          Serial.print(" | 静止:");
          Serial.print(stationaryDuration / 1000);
          Serial.print("秒");
        }
        Serial.println();
        break;
      case PRE_ALARM:
        Serial.print("⚠ 预警 | 静止:");
        Serial.print(stationaryDuration / 1000);
        Serial.println("秒");
        break;
      case ALARM:
        Serial.print("🚨 报警 | 静止:");
        Serial.print(stationaryDuration / 1000);
        Serial.println("秒 | 请确认安全!");
        break;
    }
  }
  
  delay(10);
}

评论

user-avatar