1. 可穿戴设备心率数据采集与实时显示

可穿戴设备心率数据采集与实时显示 学习卡片

PPG 心率采集链路与实时刷新机制

智能手表上的 PPG(光电容积脉搏波)心率采集,本质是一条从光学前端到界面刷新的完整信号链。要让界面上的心率值稳定跳动,需要同时打通四个环节:光学驱动与采样、原始信号预处理、心率估计、UI 刷新。任何一环的采样率、缓冲长度或刷新周期设置不当,都会表现为数值跳变、长时间不变或干脆无数据。

光学前端 绿光 LED 525nm 光电二极管 PD 驱动电流 5mA ADC 采样 采样率 25 Hz 分辨率 16 bit 环形缓冲 128 点 信号预处理 去直流 / 带通 0.5–5 Hz 滑动平均去噪 心率估计 峰值检测 IBI 间隔计算 60 / IBI → BPM 原始PPG 数字序列 滤波信号 UI 实时刷新 刷新周期 1 s 显示当前 BPM 无效值显示 "--" BPM 结果 采样质量反馈 / 运动伪影检测 关键约束:采样率 ≥ 25 Hz 保证峰值可分辨;缓冲 ≥ 4 s 数据保证 IBI 统计稳定;刷新周期 1 s 兼顾实时性与可读性

采集与刷新实现要点

下面以智能手表常见的嵌入式环境为例,给出采集到显示的关键实现。核心思路是:传感器中断驱动采样,环形缓冲累积数据,定时器触发心率计算与 UI 刷新。

// 心率采集与显示核心逻辑(伪代码,适配嵌入式环境)
#define PPG_SAMPLE_RATE   25      // Hz
#define PPG_BUFFER_SIZE   128     // 约 5.1 s 数据
#define UI_REFRESH_MS     1000    // 1 s 刷新一次

static uint16_t ppg_buffer[PPG_BUFFER_SIZE];
static volatile uint16_t buf_index = 0;
static volatile bool     data_ready = false;

// 1. PPG 传感器中断:每 40 ms 触发一次(25 Hz)
void ppg_sensor_isr(uint16_t raw_value) {
    ppg_buffer[buf_index] = raw_value;
    buf_index = (buf_index + 1) % PPG_BUFFER_SIZE;
    if (buf_index == 0) {
        data_ready = true;   // 缓冲满,通知主循环
    }
}

// 2. 带通滤波 + 峰值检测,估算心率
uint16_t estimate_heart_rate(void) {
    // 去直流:减去滑动均值
    uint32_t sum = 0;
    for (int i = 0; i < PPG_BUFFER_SIZE; i++) sum += ppg_buffer[i];
    uint16_t dc = sum / PPG_BUFFER_SIZE;

    // 简单带通:保留 0.5–5 Hz 成分(此处用差分近似)
    int16_t filtered[PPG_BUFFER_SIZE];
    for (int i = 1; i < PPG_BUFFER_SIZE; i++) {
        filtered[i] = (int16_t)ppg_buffer[i] - (int16_t)ppg_buffer[i-1];
    }

    // 峰值检测:找局部最大值,计算 IBI
    uint32_t ibi_sum = 0;
    uint16_t ibi_count = 0;
    uint32_t last_peak = 0;
    for (int i = 2; i < PPG_BUFFER_SIZE - 2; i++) {
        if (filtered[i] > filtered[i-1] && filtered[i] > filtered[i+1]
            && filtered[i] > 50) {          // 阈值可调
            if (last_peak > 0) {
                ibi_sum += (i - last_peak);
                ibi_count++;
            }
            last_peak = i;
        }
    }
    if (ibi_count == 0) return 0;           // 无有效峰值

    // IBI 单位:采样点数 → 秒
    float avg_ibi_sec = (float)ibi_sum / ibi_count / PPG_SAMPLE_RATE;
    return (uint16_t)(60.0f / avg_ibi_sec); // BPM
}

// 3. 主循环:定时刷新 UI
void main_loop(void) {
    uint32_t last_refresh = 0;
    while (1) {
        uint32_t now = get_tick_ms();
        if (data_ready && (now - last_refresh >= UI_REFRESH_MS)) {
            data_ready = false;
            uint16_t bpm = estimate_heart_rate();
            if (bpm >= 30 && bpm <= 220) {
                ui_set_heart_rate(bpm);     // 有效值显示
            } else {
                ui_set_heart_rate(0);       // 无效值显示 "--"
            }
            last_refresh = now;
        }
    }
}
验证采集链路是否正常:用手指轻按传感器,观察 UI 心率值是否在 1 s 内更新;若长时间显示 "--",检查中断是否触发、缓冲是否填满、峰值阈值是否过高。正常静息状态下,心率应在 60–100 BPM 范围内波动,且相邻两次刷新差值不超过 5 BPM。
常见问题:采样率低于 25 Hz 时,PPG 峰值间隔分辨率不足,心率估计误差会明显增大;缓冲长度小于 4 s 时,IBI 统计样本太少,数值容易跳变。运动状态下光信号受伪影干扰,可临时增大峰值阈值或暂停刷新,避免显示错误心率。

关键参数与刷新机制对照

参数 推荐值 作用 异常表现
采样率 25 Hz 保证 PPG 峰值可分辨 低于 15 Hz 时心率误差 > 10 BPM
缓冲长度 128 点(约 5.1 s) 提供足够 IBI 统计样本 过短导致数值频繁跳变
刷新周期 1 s 兼顾实时性与可读性 过快闪烁,过慢失去实时感
峰值阈值 50(可调) 过滤噪声引起的假峰 过高漏检,过低误检
有效心率范围 30–220 BPM 排除无效计算结果 超出范围显示 "--"

完成上述实现后,可通过以下方式确认链路与刷新机制均已生效:用手指稳定按压传感器,观察 UI 心率值是否每秒更新一次;改变按压力度或轻微移动手指,观察数值是否出现合理波动而非固定不变;若数值持续为 "--",则回到中断触发和缓冲填充环节逐级排查。