1. 项目概述
这个基于STM32和机智云平台的心率血氧手环项目,是我去年为一位医疗设备创业者开发的穿戴式健康监测原型。它能够实时采集使用者的心率、血氧饱和度数据,并通过低功耗蓝牙将数据上传到云端进行长期跟踪分析。相比市面上动辄上千元的专业医疗手环,这套方案在保证测量精度的前提下,将BOM成本控制在了200元以内。
核心硬件由STM32F103C8T6最小系统、MAX30102生物传感器模块和ESP8266 WiFi模块构成。软件层面则采用了FreeRTOS实时操作系统来协调传感器采集、数据处理和无线通信等任务。最关键的算法部分,我移植了开源的脉搏波特征提取算法,并针对STM32的运算能力做了定点数优化。
提示:MAX30102传感器对佩戴位置非常敏感,实际测试中发现稍微偏移2mm就会导致信号质量下降50%以上。后来我们在手环内侧设计了凹凸定位结构,这个问题才得到解决。
2. 硬件设计与选型
2.1 主控芯片选择
为什么选择STM32F103C8T6这颗"古董级"芯片?主要基于三点考量:
- 充足的ADC通道(10个12位ADC)满足多路生物信号采集需求
- 内置硬件I2C和SPI接口,与传感器模块通信更稳定
- 成熟的生态体系,在RTOS移植和低功耗处理上有丰富参考案例
实测运行FreeRTOS+LVGL图形界面时,CPU占用率仍能控制在65%以下。不过要注意的是,这款芯片的RAM只有20KB,需要精心管理内存分配。我的做法是:
- 为传感器数据开辟了4KB的专用缓存区
- 使用内存池管理动态内存
- 禁用标准库的printf浮点输出功能
2.2 生物传感器方案
MAX30102作为集成式光电传感器,同时提供了心率(PPG)和血氧(SpO2)检测功能。其硬件设计有几个关键点:
-
供电设计:
- 必须使用独立的LDO供电(我选用TPS7A4700)
- 电源输入端要加π型滤波电路(10μF+0.1μF)
- LED驱动电流建议设置在6.4mA(对应50Hz采样率)
-
光学结构:
- 传感器表面要加装医用级硅胶遮光罩
- LED与光电二极管距离控制在3.5-4.2mm
- 使用1mm厚度的漫反射片改善信号均匀性
-
信号调理:
c复制// 典型的配置代码示例 void MAX30102_Init(void) { I2C_Write(REG_MODE_CONFIG, 0x40); // 复位 delay_ms(100); I2C_Write(REG_SPO2_CONFIG, 0x47); // 采样率50Hz, 脉冲宽度411us I2C_Write(REG_LED1_PA, 0x24); // RED LED电流6.4mA I2C_Write(REG_LED2_PA, 0x24); // IR LED电流6.4mA I2C_Write(REG_MODE_CONFIG, 0x03); // 开启SpO2模式 }
2.3 低功耗设计
手环在持续监测模式下的平均电流需要控制在5mA以内,这涉及到几个关键优化:
-
动态频率调整:
- 正常模式:CPU运行在48MHz
- 待机模式:降频到8MHz并关闭外设时钟
-
传感器工作周期:
mermaid复制graph TD A[唤醒MCU] --> B[开启传感器] B --> C[采集30秒数据] C --> D[进入算法处理] D --> E[无线传输] E --> F[休眠3分钟] -
电源管理实测数据:
工作状态 电流消耗 持续时间 全速运行 12.6mA 30s 蓝牙传输 8.2mA 5s 深度休眠 0.05mA 175s
3. 软件架构实现
3.1 实时操作系统适配
FreeRTOS的任务划分体现了医疗设备的响应要求:
- 高优先级任务(心率算法处理)
- 中优先级任务(蓝牙通信)
- 低优先级任务(用户界面更新)
关键配置参数:
c复制#define HEART_TASK_PRIO (tskIDLE_PRIORITY + 4)
#define BLE_TASK_PRIO (tskIDLE_PRIORITY + 3)
#define GUI_TASK_PRIO (tskIDLE_PRIORITY + 2)
#define HEART_STACK_SIZE (configMINIMAL_STACK_SIZE * 4)
#define BLE_STACK_SIZE (configMINIMAL_STACK_SIZE * 3)
3.2 信号处理算法
PPG信号处理流程包含五个关键步骤:
-
直流分量去除:
python复制# 伪代码示例 def remove_dc(signal): moving_avg = np.convolve(signal, np.ones(50)/50, mode='same') return signal - moving_avg -
带通滤波(0.5Hz-5Hz):
- 采用IIR滤波器实现
- 系数通过MATLAB fdatool生成
- 在STM32上使用定点数运算优化
-
峰值检测算法:
- 自适应阈值法
- 动态更新噪声基底
- 最小峰间距约束(对应200BPM)
-
血氧计算:
$$ SpO_2 = 110 - 25 \times \frac{AC_{red}/DC_{red}}{AC_{ir}/DC_{ir}} $$ -
运动伪迹消除:
- 三轴加速度计辅助补偿
- 基于小波变换的信号分离
3.3 机智云平台对接
数据上传采用自定义的紧凑型协议:
code复制| 头字节 | 心率值 | 血氧值 | 时间戳 | CRC校验 |
|--------|--------|--------|--------|--------|
| 0xAA | 1字节 | 1字节 | 4字节 | 2字节 |
云端解析服务的关键配置:
json复制{
"product_key": "your_product_key",
"device_secret": "your_device_secret",
"data_points": [
{
"name": "heart_rate",
"type": "int",
"unit": "bpm"
},
{
"name": "blood_oxygen",
"type": "float",
"unit": "%"
}
]
}
4. 实测性能优化
4.1 精度提升技巧
通过临床对比测试发现三个重要改进点:
-
佩戴压力影响:
- 最佳压力范围:20-30mmHg
- 压力传感器选用FSR402
- 通过振动马达提示用户调整
-
肤色补偿算法:
c复制// 根据肤色调整LED电流 void adjust_for_skin_tone(uint8_t skin_type) { const uint8_t current_table[] = {0x1F, 0x2A, 0x35, 0x40}; I2C_Write(REG_LED1_PA, current_table[skin_type]); I2C_Write(REG_LED2_PA, current_table[skin_type]); } -
动态基线校准:
- 每10分钟自动更新DC分量
- 用户静止状态下触发
- 通过加速度计检测运动状态
4.2 常见问题排查
-
信号质量差:
- 检查硅胶遮光罩是否贴合
- 测量供电电压纹波(应<50mV)
- 重新校准I2C上拉电阻(通常4.7KΩ)
-
蓝牙连接不稳定:
- 调整天线匹配电路(π型网络)
- 修改广播间隔(建议80-100ms)
- 检查周围2.4GHz干扰源
-
数据上传失败:
- 确认机智云三元组配置正确
- 检查网络时间同步状态
- 验证MQTT心跳包间隔(建议120s)
5. 生产测试方案
为批量生产设计的自动化测试流程:
-
光学测试工装:
- 使用标准模拟手指(含已知PPG信号)
- 测试项目包括:
- LED发光强度
- 光电二极管响应度
- 信噪比(>45dB合格)
-
功能测试项:
测试项目 合格标准 测试方法 心率精度 ±2BPM 与ECG对比 血氧精度 ±2% 与医用血氧仪对比 续航时间 >7天 恒温恒湿箱测试 -
烧录配置:
bash复制# 使用J-Flash批量烧录示例 JFlash.exe -openprjSTM32F103.jflash -openBracelet.hex -auto -exit
这个项目最让我意外的是MAX30102的环境光抗干扰能力。在强光下测试时,只要保证遮光罩紧密贴合,即使是在正午阳光下也能获得可用的信号。不过要特别注意,传感器窗口如果有汗液凝结,会导致信号完全失真——后来我们在产品手册中特别强调了运动后要擦干手腕再测量。