一、项目背景与痛点
卫生间是老年人意外跌倒的高风险场所。据世界卫生组织统计,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 逻辑流程图
图1 系统状态机逻辑流程图
三、硬件清单与接线
3.1 硬件清单
序号 | 模块 | 型号/规格 | 备注 |
1 | 主控板 | ESP32 | 板载WiFi/蓝牙 |
2 | 毫米波雷达 | DFRobot C4002 | 24GHz FMCW,UART输出 |
3 | 蜂鸣器模块 | 数字蜂鸣器 | 3线VCC/GND/SIG |
4 | LED模块 | DF Digital Blue LED V2 | 高电平触发(HIGH=亮) |
5 | 按钮模块 | DF数字大按钮 | 按下=高电平 |
6 | 扩展板 | ESP32 扩展板 | 方便接线 |
3.2 接线表
模块 | 模块引脚 | ESP32引脚 | 说明 |
| C4002 | VCC | 5V | 5V |
| C4002 | GND | GND | |
| C4002 | TX | D16 (GPIO16 RX) | 交叉连接 |
| C4002 | RX | D17 (GPIO17 TX) | 交叉连接 |
| 蜂鸣器 | VCC/GND/SIG | 5V/GND/D4 | SIG接D4 |
| LED | VCC/GND/D | 5V/GND/D5 | D接D5,HIGH=亮 |
| 按钮 | VCC/GND/SIG | 5V/GND/D13 | SIG接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);
}
返回首页
回到顶部


评论