体温+心率+跌倒三路监测、一键求救、子女手机APP远程报警,附"摔倒检测抄成计步"翻车实录

独居老人最怕两件事:突发疾病没人知道、摔倒后起不来。市面上的商用养老监护设备动辄上千,其实用一块 STM32 + 三四个传感器就能做出一套能跑的原型:体温、心率、跌倒三路监测,超标或摔倒立刻蜂鸣 + 推送子女手机 APP,老人还能一键求救。本文基于一篇毕设论文改写,完整给出方案、选型、接线和代码——顺便把原文里"摔倒检测算法抄成计步器"的复制粘贴事故拆给你看,这种坑答辩老师一眼就能揪出来。

一、系统功能拆解

功能域模块说明
生理监测DS18B20 体温体表温度采集,超阈值报警(非医疗级,见文末声明)
生理监测PulseSensor 心率PPG 光电法,BPM 超阈值报警
行为监测ADXL345 三轴加速度跌倒检测:合加速度突变 + 跌倒后静止姿态
人机交互OLED12864实时显示体温/心率/时间/报警状态
人机交互一键求救按键老人按下:本地蜂鸣 + APP 推送
远程通信ESP8266数据上服务器,监护人 APP 实时查看 + 远程设阈值
报警执行蜂鸣器本地报警提醒

主逻辑:初始化 → 主循环采集体温/心率/加速度 → OLED 刷新 → 数据上云 → 任一参数超阈值或判定跌倒 → 蜂鸣器响 + APP 告警 → 响应求救按键和 APP 下发的阈值指令。

二、器件选型对比

2.1 主控:STM32F103C8T6 vs ESP32-WROOM-32

C8T6(72MHz/64KB Flash)外设够用、开发生态成熟;ESP32 双核 240MHz 自带 Wi-Fi 蓝牙。本系统需要 ADC(心率)+ I2C×2(OLED、ADXL345)+ 单总线(DS18B20)+ 串口(ESP8266),C8T6 完全够,且毕设资料多,选 C8T6 合理。

2.2 体温:DS18B20 vs LM35 

对比项LM35DS18B20
精度±0.25°C(更高)±0.5°C
量程-55~+150°C(更宽)-55~+125°C
输出模拟电压 10mV/°C单总线数字量
多点扩展一路一个 ADC 通道一条总线挂多个

原文结论是"DS18B20 提供了更高的测量精度和更广泛的温度范围,因此选方案2"——理由完全是反的:精度和量程都是 LM35 赢,DS18B20 真正的优势是数字输出(不用 ADC、抗干扰、可多点挂总线)。结论没错,理由错得离谱,这种"结论对了过程错了"最容易被答辩追问。正确写法:选 DS18B20 是因为数字输出省掉 ADC 通道且免标定。

2.3 心率:PulseSensor vs MAX30100

对比项PulseSensorMAX30100
原理单绿光 LED PPG红+红外双波长,心率+血氧
输出模拟量(接 ADC)I2C 数字量
价格≈15 元≈25 元

选 PulseSensor 做原型没问题。但原文 3.3 节把它写成"红色和红外 LED 的组合发出光束"——PulseSensor 板上只有一颗绿色 LED(约 565nm)和一个光电晶体管,红+红外双波长是 MAX30100/30102 的配置。这是传感器资料抄串了,连着几篇毕设论文都在犯同一个错,可见是同一份错误资料在流传。

2.4 跌倒检测:ADXL345 vs MPU6050

对比项ADXL345MPU6050
传感器三轴加速度三轴加速度+三轴陀螺仪(带 DMP)
分辨率16 位,量程 ±2~±16g16 位,±2~±16g
功耗更低略高
价格≈8 元≈10 元

跌倒检测的核心是捕捉"自由落体→冲击→静止姿态"的加速度突变序列,纯加速度计就能做(ADXL345 的 4g 量程 + 100Hz 采样足够);MPU6050 的陀螺仪加分但不必须。选 ADXL345 合理——但注意,原文夸它的理由是"静态角度测量中的高精度",这就跑偏了:跌倒恰恰是动态事件,靠静态角度判断"人歪了"会把手臂抬起、弯腰捡东西全误报成摔倒。选型理由和检测原理完全脱节。

2.5 显示与通信

OLED12864(0.96寸 I2C)显示数据 + 时间足够;通信选 ESP8266-01S(Wi-Fi 直接进家庭路由器),备选 nRF905(433MHz 远距离但要专门网关,家用场景不合适)。

2.6 BOM 成本清单

器件参考价
STM32F103C8T6 最小系统板≈15 元
DS18B20(防水探头款)≈6 元
PulseSensor≈15 元
ADXL345≈8 元
OLED12864(I2C)≈10 元
ESP8266-01S≈10 元
蜂鸣器、按键、洞洞板≈8 元
合计≈72 元

三、硬件接线

模块引脚说明
DS18B20 DQPA14.7kΩ 上拉到 3.3V
PulseSensor SIGPA2→改用 PA4(ADC1_IN4)⚠️ 见接线坑 1
ADXL345 SDA/SCLPB6/PB7(I2C1)与 OLED 共总线,地址不冲突(0x53 / 0x3C)
OLED SDA/SCLPB6/PB7(I2C1)—
ESP8266 TX/RXPA2/PA3(USART2)115200bps,复位脚 PA8
蜂鸣器PB0高电平响
求救按键PB12内部上拉,按下接地
报警 LEDPB1—

接线坑三条:

  1. 串口和 ADC 别打架:USART2 默认引脚是 PA2/PA3,若心率传感器 ADC 也随手分到 PA4 没问题,但有些例程把 ADC 配到 PA2——两处一撞,要么串口乱码要么心率死值。分配引脚前先查 F103 的引脚复用表。
  2. I2C 总线挂两个器件要确认地址:ADXL345 是 0x53(ALT_ADDRESS 接地时),OLED SSD1306 是 0x3C,不冲突;但 ADXL345 的 CS 脚必须拉高选 I2C 模式,悬空会通信失败。
  3. ESP8266 独立 3.3V 供电:01S 模块 Wi-Fi 发射瞬间电流 300mA+,从 C8T6 板载稳压器取电容易电压跌落导致反复重启,配一个 AMS1117 独立 3.3V 或大电容储能。

另外两个"纸面功能"要补齐:原文方案框架里写了"RTC 模块提供时间同步"和"一键报警求救按键",但硬件章节根本没设计这两块电路——RTC 没影(本文用 ESP8266 联网 NTP 对时替代,省一颗 DS1302),求救按键本文直接补上(PB12)。做系统时列在框图里的东西,电路、代码、测试三章必须都能找到对应,否则就是"功能只活在框图里"。

四、软件设计

4.1 主程序框架

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    delay_init();
    USART2_Init(115200);          /* ESP8266 */
    OLED_Init();
    DS18B20_Init();
    Adxl345_Init();               /* ±4g, 100Hz */
    Adc_Init();                   /* PulseSensor */
    Beep_Init(); Key_Init();
    ESP8266_Reset();              /* PA8 拉低500ms */
    ESP8266_ConnectWiFi(SSID, PWD);
    ESP8266_TCP_Connect(SERVER_IP, PORT);

    while (1)
    {
        temp = DS18B20_GetTemp();        /* 每 1s 一次 */
        bpm  = HeartRate_Update();       /* ADC 滑窗峰值 */
        fall = Fall_Detect_Update();     /* 合加速度算法 */
        OLED_Display(temp, bpm, fall);
        if (temp > g_temp_th || bpm > g_bpm_th || fall) {
            Alarm_Start();
            ESP8266_Report(ALARM);
        }
        if (Key_SOS_Pressed()) {         /* 一键求救 */
            Alarm_Start();
            ESP8266_Report(SOS);
        }
        Server_Cmd_Parse();              /* APP 阈值下发 */
    }
}

4.2 关键子程序一:DS18B20 单总线读温度

float DS18B20_GetTemp(void)
{
    u8 tl, th;
    s16 raw;

    DS18B20_Reset();              /* 主机拉低480us -> 释放 -> 等60~240us响应 */
    DS18B20_WriteByte(0xCC);      /* 跳过ROM(单器件) */
    DS18B20_WriteByte(0x44);      /* 启动温度转换 */
    delay_ms(750);                /* 12位分辨率最长750ms */

    DS18B20_Reset();
    DS18B20_WriteByte(0xCC);
    DS18B20_WriteByte(0xBE);      /* 读暂存器 */
    tl = DS18B20_ReadByte();
    th = DS18B20_ReadByte();

    raw = (th << 8) | tl;
    return raw * 0.0625f;         /* 12位:0.0625°C/LSB */
}

优化点:750ms 阻塞等待会拖慢整个主循环。实战做法是 0x44 转换命令发完就继续干别的,下一轮循环再读结果(DS18B20 转换结果保持到下次转换)。

4.3 关键子程序二:PulseSensor 峰值检测算心率

PPG 信号的处理套路:滑动窗口找 max/min → 动态中点 → 带迟滞的上升沿判定 → 两次心跳间隔 IBI → BPM=60000/IBI:

u16 HR_Update(void)
{
    static u16 buf[32], idx;
    static u8 rising = 0;
    static u32 last_beat;
    u16 v = Adc_Read_Avg(10);
    u16 mn = 0xFFFF, mx = 0, i, mid;
    u32 now = GetTick();

    buf[idx++ & 31] = v;
    for (i = 0; i < 32; i++) {          /* 滑窗极值 */
        if (buf[i] < mn) mn = buf[i];
        if (buf[i] > mx) mx = buf[i];
    }
    mid = (mx + mn) / 2;

    if (!rising && v > mid + Hyst) {    /* 带迟滞的上升沿 */
        rising = 1;
        if (now - last_beat > 300) {    /* 排除 <200BPM 的毛刺 */
            ibi = now - last_beat;      /* 心跳间隔 ms */
            bpm = 60000 / ibi;
            last_beat = now;
        }
    } else if (rising && v < mid - Hyst) {
        rising = 0;
    }
    return bpm;
}

坑:BPM 别用单次 IBI 直接算,手一抖就跳变;实际取最近 5 次 IBI 的中位数再换算,显示才稳。

4.4 关键子程序三:正确的跌倒检测(本篇重点)

先看原文 4.4 节怎么写的:读三轴加速度 → atan 算角度 → “当检测到角度偏移超过设定阈值就代表发生步数”。

两个问题:其一,结尾这句"就代表发生步数"是从计步器论文里复制粘贴的残留——跌倒检测的章节里蹦出"步数",等于把抄袭痕迹钉在了正文里;其二,就算把"步数"改成"摔倒",用静态角度判断跌倒也是错误方案:正常人的弯腰、侧躺、抬手臂都会让"角度偏移超阈值",误报率会高到没法用。

业界通用的三轴加速度跌倒检测是抓"自由落体 → 着地冲击 → 长时间静止"三段特征:

/* 合加速度 SMA = sqrt(x^2+y^2+z^2),单位 g */
#define G_LOW   0.4f    /* 自由落体门限:跌落瞬间接近0g */
#define G_HIGH  2.5f    /* 着地冲击门限 */
#define QUIET_MS 3000   /* 跌倒后静止确认窗 */

typedef enum { F_NORMAL, F_FALLING, F_IMPACT, F_QUIET } FallState;

u8 Fall_Detect_Update(void)
{
    static FallState st = F_NORMAL;
    static u32 t_mark;
    float sma = Acc_GetSMA_g();
    float tilt = Acc_GetTiltAngle();   /* 静止姿态角,判断是否躺倒 */

    switch (st) {
    case F_NORMAL:
        if (sma < G_LOW) { st = F_FALLING; }      /* 失重 */
        break;
    case F_FALLING:
        if (sma > G_HIGH) { st = F_IMPACT; t_mark = GetTick(); }  /* 冲击 */
        else if (sma > 1.2f) st = F_NORMAL;       /* 又稳住了,虚惊 */
        break;
    case F_IMPACT:
        if (GetTick() - t_mark > QUIET_MS) {
            /* 静止 3s 且姿态角异常(接近水平)-> 判定跌倒 */
            if (sma < 1.2f && tilt > 60) return 1;
            st = F_NORMAL;
        }
        break;
    }
    return 0;
}

这样误报率大幅下降:快速坐下(冲击有但之前没有失重段)、躺下睡觉(无冲击段)都不会触发。想更准,把采样率拉到 100Hz 以上并加 MPU6050 陀螺仪做角速度辅助。

4.5 关键子程序四:一键求救与报警状态机

void SOS_Handler(void)
{
    static u32 last_sos;
    if (!KEY_SOS_READ()) {                    /* 按下(低电平) */
        if (GetTick() - last_sos > 10000) {   /* 10s 防重复 */
            last_sos = GetTick();
            Beep_On();
            LED_AlarmBlink();
            ESP8266_Report("{\"type\":\"SOS\"}");   /* APP 立即弹窗 */
        }
    }
}

五、系统调试验证

测试项方法预期结果
体温采集DS18B20 与水银体温计对比误差 ±0.5°C 内
心率采集手指贴 PulseSensor,与手环对照BPM 偏差 ≤5
跌倒检测设备从 1m 高自由落体到软垫失重→冲击→静止三段触发,3s 后报警
误报测试正常坐下、躺下、弯腰均不触发
一键求救按下 SOS 键本地蜂鸣 + APP 弹窗,10s 内不重复
阈值设置APP 修改心率阈值OLED 与报警判定同步更新
断网测试关 Wi-Fi 30s重连后数据续传,断网期间本地报警不受影响

六、常见坑汇总(原文翻车实录)

  1. 跌倒检测抄成计步器:4.4 节结尾原文"当检测到角度偏移超过设定阈值就代表发生步数"——计步论文的复制残留,答辩时这就是"送命题"。跌倒检测请用失重+冲击+静止三段式合加速度算法。
  2. 静态角度判跌倒:选型夸 ADXL345"静态角度测量精度",检测流程也全在算角度——弯腰捡东西都会被当成摔倒。跌倒是动态事件,靠的是加速度突变,不是姿态角。
  3. PulseSensor 被写成"红色和红外 LED 组合":它只有一颗绿光 LED;红+红外双波长是 MAX30100/30102。多篇论文同错,抄资料时记得核对官方规格书。
  4. LM35 vs DS18B20 选型理由全反:精度(±0.25 vs ±0.5)和量程(150 vs 125°C)都是 LM35 赢,DS18B20 赢在数字输出。结论对、理由错,一样扣分。
  5. 框图里的功能没电路:RTC 模块、一键求救按键只出现在方案框架图里,硬件设计章节查无此物。框图列了,电路+代码+测试必须三对齐。
  6. 图注编号错乱:主程序流程图标成"图4.2",温度检测流程图也叫"图4.2",5.3.1 的测试图标成"图5.2"——交稿前逐一核对。
  7. 参考文献凑数:后三条是"零样本电机健康监测"“极地研究船推进轴冰载荷”“大坝安全监测异常识别”,与老人监护毫无关系。
  8. 体温≠核心体温:DS18B20 贴体表测的是皮肤温度,比腋下/核心温度低 1~2°C,阈值要按体表修正,且本系统不能当医疗设备用。

七、可扩展方向

  • 加 GSM 短信通道:断网时老人报警走 SIM800L 短信,监护人必达;
  • 升级 MAX30102:心率+血氧双参数,跌倒后血氧骤降是更危险信号;
  • 低功耗长续航:C8T6 进 STOP 模式 + ADXL345 中断唤醒,做电池版随身终端;
  • 多传感器融合:ADXL345 + MPU6050 陀螺仪 + 气压计(楼层跌落),提高检测准确率到商用级。

八、资料内容完整工程资料(原理图、PCB、代码、元件清单)


九、资料获取方式

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐