基于STM32的可穿戴心率检测仪设计(数据采集+心率分析)
·
一、系统概述与核心功能
1. 系统定位
基于STM32的便携式心率检测仪,通过MAX30102光电传感器采集人体脉搏波信号(PPG),利用STM32内置ADC采集模拟心电/压力信号,结合数字滤波算法与峰值检测算法实时计算心率值(BPM)。系统支持蓝牙数据上传与OLED实时显示,可作为医疗辅助设备或运动健康监测终端。
2. 核心功能模块
| 模块 | 功能描述 | 技术指标 |
|---|---|---|
| 信号采集 | MAX30102采集脉搏波(红光+红外),ADS1015采集模拟心电信号 | 采样率100Hz,分辨率16位 |
| 数据处理 | 数字滤波(带通+陷波)、峰值检测、心率计算、运动伪影消除 | 心率误差±3BPM,响应时间<3秒 |
| 人机交互 | OLED显示实时心率、波形滚动、电池电量;按键控制测量启停 | 128×64分辨率,I2C接口 |
| 数据通信 | 蓝牙BLE上传原始数据/心率值至上位机/手机APP | 传输距离10m,波特率115200 |
| 低功耗设计 | 动态采样率调整,无操作时自动休眠,锂电池续航>48小时 | 待机电流<50μA |
二、硬件设计方案
1. 核心硬件选型
| 模块 | 型号 | 关键参数 | 接口方式 |
|---|---|---|---|
| 主控MCU | STM32L432KC | 80MHz Cortex-M4,超低功耗,256KB Flash,支持DSP指令 | 核心控制器 |
| 心率传感器 | MAX30102 | 集成红光(660nm)+红外(880nm)LED,16位ADC,I2C接口 | I2C1(PB6/PB7) |
| 模拟采集 | ADS1015 | 12位ADC,4通道,I2C接口,支持可编程增益 | I2C1(复用总线) |
| 显示模块 | OLED 12864 | 0.96寸,128×64像素,自发光,I2C接口 | I2C1(地址0x3C) |
| 蓝牙模块 | JDY-08(BLE4.0) | 超低功耗,透传模式,支持AT指令配置 | UART1(PA9/PA10) |
| 电源管理 | MCP73831+TPS61099 | 锂电池充电管理+高效升压,200mAh锂电池 | 3.3V系统供电 |
| 交互模块 | 触摸按键+振动马达 | 单按键控制,振动马达反馈异常心率 | GPIO(PA0/PA1) |
2. 硬件电路设计要点
2.1 信号采集电路
MAX30102电路:
- VDD → 3.3V(并联0.1μF+10μF电容)
- SDA/SCL → PB6/PB7(4.7kΩ上拉)
- INT → PA2(中断引脚,数据就绪唤醒)
- LED引脚 → 外接100nF电容稳定电流
ADS1015电路(可选模拟心电):
- AIN0 → 心电电极输入(通过仪表放大器AD620)
- AIN1 → 压力传感器输入
- ADDR → GND(I2C地址0x48)
2.2 抗干扰设计
- 电源隔离:模拟电源(AVDD)与数字电源(DVDD)通过磁珠隔离
- 信号屏蔽:MAX30102加装黑色硅胶遮光罩,避免环境光干扰
- 电极设计:心电采集使用银/氯化银电极,降低接触阻抗
- PCB布局:模拟信号线远离数字线,大面积铺地减少EMI
3. 传感器佩戴方案
手指佩戴式:
┌─────────────┐
│ MAX30102 │ ← 食指指尖(指甲面朝下)
│ [传感器] │
└──────┬──────┘
│
FPC软排线(10cm)
│
┌──────▼──────┐
│ STM32主控板 │
│ OLED显示 │
└─────────────┘
手腕佩戴式(可选):
- 集成在手表表带中
- MAX30102朝向手腕内侧
- 松紧度可调节魔术贴
三、软件设计与核心算法
1. 系统架构(FreeRTOS多任务)
采用FreeRTOS实时操作系统,划分6个核心任务:
| 任务 | 优先级 | 功能 | 周期 |
|---|---|---|---|
| 信号采集 | 5(最高) | MAX30102数据读取,ADS1015采样 | 10ms |
| 数字滤波 | 4 | 带通滤波(0.5-3Hz)、50Hz陷波、去基线漂移 | 事件触发 |
| 峰值检测 | 4 | PPG波形峰值识别,RR间期计算 | 事件触发 |
| 心率计算 | 3 | BPM计算,数据平滑,异常值剔除 | 1s |
| 显示更新 | 2 | OLED波形滚动显示,心率数值更新 | 100ms |
| 蓝牙传输 | 2 | 数据包封装,蓝牙透传发送 | 500ms |
2. 核心算法实现(基于HAL库)
2.1 MAX30102驱动与数据采集
#include "max30102.h"
#include "i2c.h"
// MAX30102寄存器定义
#define MAX30102_INT_STATUS 0x00
#define MAX30102_FIFO_WR_PTR 0x04
#define MAX30102_FIFO_DATA 0x07
#define MAX30102_MODE_CONFIG 0x09
#define MAX30102_SPO2_CONFIG 0x0A
#define MAX30102_LED_CONFIG 0x0C
// 全局变量
uint32_t red_buffer[100]; // 红光数据缓冲区
uint32_t ir_buffer[100]; // 红外数据缓冲区
uint8_t buffer_index = 0;
// MAX30102初始化
uint8_t MAX30102_Init(void) {
uint8_t part_id;
// 读取器件ID
HAL_I2C_Mem_Read(&hi2c1, 0x57<<1, 0xFF, 1, &part_id, 1, 100);
if (part_id != 0x15) return 1; // 器件ID错误
// 复位器件
MAX30102_WriteReg(MAX30102_MODE_CONFIG, 0x40);
HAL_Delay(100);
// 配置SpO2模式,100Hz采样率
MAX30102_WriteReg(MAX30102_MODE_CONFIG, 0x03); // SpO2模式
MAX30102_WriteReg(MAX30102_SPO2_CONFIG, 0x27); // 100Hz,411μs脉冲
MAX30102_WriteReg(MAX30102_LED_CONFIG, 0x24); // 红光/红外LED电流7.6mA
return 0;
}
// 读取FIFO数据
void MAX30102_ReadFIFO(uint32_t *red, uint32_t *ir) {
uint8_t fifo_data[6];
HAL_I2C_Mem_Read(&hi2c1, 0x57<<1, MAX30102_FIFO_DATA, 1, fifo_data, 6, 100);
*red = ((uint32_t)fifo_data[0] << 16) | ((uint32_t)fifo_data[1] << 8) | fifo_data[2];
*ir = ((uint32_t)fifo_data[3] << 16) | ((uint32_t)fifo_data[4] << 8) | fifo_data[5];
// 取低18位有效数据
*red &= 0x3FFFF;
*ir &= 0x3FFFF;
}
2.2 数字滤波算法(关键!)
#include "digital_filter.h"
// 带通滤波器系数(0.5-3Hz,对应30-180BPM)
typedef struct {
float b[3]; // 分子系数
float a[3]; // 分母系数
float x[3]; // 输入历史
float y[3]; // 输出历史
} BandpassFilter_t;
BandpassFilter_t bp_filter;
// 初始化带通滤波器(Fs=100Hz,Fc1=0.5Hz,Fc2=3Hz)
void Bandpass_Init(void) {
// 二阶带通滤波器系数(双线性变换法设计)
bp_filter.b[0] = 0.0976f;
bp_filter.b[1] = 0.0f;
bp_filter.b[2] = -0.0976f;
bp_filter.a[0] = 1.0f;
bp_filter.a[1] = -1.7955f;
bp_filter.a[2] = 0.8045f;
// 清空历史数据
memset(&bp_filter.x, 0, sizeof(bp_filter.x));
memset(&bp_filter.y, 0, sizeof(bp_filter.y));
}
// 50Hz陷波滤波器(去除工频干扰)
float Notch50Hz_Filter(float input) {
static float x1=0, x2=0, y1=0, y2=0;
float output;
output = 0.9317f*input - 1.8634f*x1 + 0.9317f*x2 + 1.8634f*y1 - 0.8692f*y2;
x2 = x1; x1 = input;
y2 = y1; y1 = output;
return output;
}
// 执行带通滤波
float Bandpass_Filter(float input) {
float output;
// 移位历史数据
bp_filter.x[2] = bp_filter.x[1];
bp_filter.x[1] = bp_filter.x[0];
bp_filter.x[0] = input;
bp_filter.y[2] = bp_filter.y[1];
bp_filter.y[1] = bp_filter.y[0];
// 差分方程计算
output = bp_filter.b[0]*bp_filter.x[0] + bp_filter.b[1]*bp_filter.x[1] +
bp_filter.b[2]*bp_filter.x[2] - bp_filter.a[1]*bp_filter.y[1] -
bp_filter.a[2]*bp_filter.y[2];
bp_filter.y[0] = output;
return output;
}
2.3 峰值检测与心率计算
#include "heart_rate_algorithm.h"
// 峰值检测结构体
typedef struct {
float threshold; // 动态阈值
uint32_t peak_interval; // 峰值间隔(采样点数)
uint8_t peak_detected; // 峰值检测标志
uint32_t last_peak_time; // 上次峰值时间
uint32_t rr_intervals[10]; // RR间期数组
uint8_t rr_index; // RR间期索引
} PeakDetector_t;
PeakDetector_t peak_detector;
// 初始化峰值检测器
void PeakDetector_Init(void) {
peak_detector.threshold = 0.5f; // 初始阈值
peak_detector.peak_interval = 0;
peak_detector.peak_detected = 0;
peak_detector.last_peak_time = 0;
peak_detector.rr_index = 0;
memset(&peak_detector.rr_intervals, 0, sizeof(peak_detector.rr_intervals));
}
// 峰值检测算法
uint8_t Detect_Peak(float filtered_signal) {
static uint8_t above_threshold = 0;
uint8_t peak_found = 0;
// 动态阈值更新(自适应)
if (filtered_signal > peak_detector.threshold) {
if (!above_threshold) {
above_threshold = 1;
peak_detector.peak_detected = 1;
peak_found = 1;
// 计算RR间期
if (peak_detector.last_peak_time > 0) {
uint32_t rr_interval = peak_detector.peak_interval - peak_detector.last_peak_time;
peak_detector.rr_intervals[peak_detector.rr_index] = rr_interval;
peak_detector.rr_index = (peak_detector.rr_index + 1) % 10;
}
peak_detector.last_peak_time = peak_detector.peak_interval;
}
} else {
above_threshold = 0;
}
peak_detector.peak_interval++;
// 更新动态阈值(指数移动平均)
peak_detector.threshold = 0.95f * peak_detector.threshold + 0.05f * filtered_signal;
return peak_found;
}
// 计算心率(BPM)
uint8_t Calculate_HeartRate(void) {
float avg_rr_interval = 0;
uint8_t valid_intervals = 0;
// 计算平均RR间期
for (uint8_t i = 0; i < 10; i++) {
if (peak_detector.rr_intervals[i] > 0) {
avg_rr_interval += peak_detector.rr_intervals[i];
valid_intervals++;
}
}
if (valid_intervals < 3) return 0; // 数据不足
avg_rr_interval /= valid_intervals;
// 转换为BPM(采样率100Hz)
float heart_rate = 6000.0f / avg_rr_interval; // 60s * 100Hz / RR_interval
// 合理性检查(30-180BPM)
if (heart_rate < 30 || heart_rate > 180) return 0;
return (uint8_t)heart_rate;
}
2.4 FreeRTOS任务实现
#include "freertos.h"
#include "task.h"
// 信号采集任务
void Signal_Acquisition_Task(void *pvParameters) {
uint32_t red_value, ir_value;
while (1) {
// 读取MAX30102数据
MAX30102_ReadFIFO(&red_value, &ir_value);
// 存储到缓冲区
red_buffer[buffer_index] = red_value;
ir_buffer[buffer_index] = ir_value;
buffer_index = (buffer_index + 1) % 100;
// 发送信号到滤波任务
xQueueSend(filter_queue, &ir_value, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(10)); // 100Hz采样率
}
}
// 数字滤波任务
void Digital_Filter_Task(void *pvParameters) {
float raw_signal, filtered_signal;
while (1) {
if (xQueueReceive(filter_queue, &raw_signal, portMAX_DELAY) == pdTRUE) {
// 50Hz陷波滤波
filtered_signal = Notch50Hz_Filter(raw_signal);
// 带通滤波
filtered_signal = Bandpass_Filter(filtered_signal);
// 发送滤波后的信号到峰值检测任务
xQueueSend(peak_queue, &filtered_signal, portMAX_DELAY);
}
}
}
// 心率计算任务
void Heart_Rate_Calculation_Task(void *pvParameters) {
float filtered_signal;
uint8_t heart_rate;
static uint8_t stable_hr[5];
static uint8_t hr_index = 0;
while (1) {
if (xQueueReceive(peak_queue, &filtered_signal, portMAX_DELAY) == pdTRUE) {
// 峰值检测
if (Detect_Peak(filtered_signal)) {
// 计算心率
heart_rate = Calculate_HeartRate();
if (heart_rate > 0) {
// 滑动平均滤波心率值
stable_hr[hr_index] = heart_rate;
hr_index = (hr_index + 1) % 5;
uint8_t avg_hr = 0;
for (uint8_t i = 0; i < 5; i++) avg_hr += stable_hr[i];
avg_hr /= 5;
// 发送心率值到显示任务
xQueueSend(display_queue, &avg_hr, portMAX_DELAY);
}
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
3. 数据采集与蓝牙传输协议
3.1 数据包格式
// 蓝牙数据包结构
typedef struct {
uint8_t header; // 帧头 0xAA
uint8_t data_type; // 数据类型:0x01=心率,0x02=原始波形
uint16_t timestamp; // 时间戳(ms)
uint32_t data_value; // 数据值
uint8_t checksum; // 校验和
uint8_t footer; // 帧尾 0x55
} Bluetooth_Packet_t;
// 发送心率数据
void Send_HeartRate_Data(uint8_t heart_rate, uint16_t timestamp) {
Bluetooth_Packet_t packet;
packet.header = 0xAA;
packet.data_type = 0x01; // 心率数据
packet.timestamp = timestamp;
packet.data_value = heart_rate;
packet.checksum = packet.header ^ packet.data_type ^
(packet.timestamp >> 8) ^ (packet.timestamp & 0xFF) ^
packet.data_value;
packet.footer = 0x55;
// 通过串口发送给蓝牙模块
HAL_UART_Transmit(&huart1, (uint8_t*)&packet, sizeof(packet), 100);
}
3.2 Python上位机接收程序
import serial
import struct
import matplotlib.pyplot as plt
from collections import deque
class HeartRateReceiver:
def __init__(self, port='COM3', baudrate=115200):
self.ser = serial.Serial(port, baudrate, timeout=1)
self.hr_buffer = deque(maxlen=100) # 心率历史缓冲区
def parse_packet(self, data):
"""解析蓝牙数据包"""
if len(data) != 7:
return None
header, data_type, timestamp_high, timestamp_low, data_value, checksum, footer = data
if header != 0xAA or footer != 0x55:
return None
# 校验和验证
calc_checksum = header ^ data_type ^ timestamp_high ^ timestamp_low ^ data_value
if calc_checksum != checksum:
return None
timestamp = (timestamp_high << 8) | timestamp_low
return data_type, timestamp, data_value
def plot_realtime(self):
"""实时绘制心率曲线"""
plt.ion()
fig, ax = plt.subplots(figsize=(10, 6))
while True:
if self.ser.in_waiting >= 7:
data = list(self.ser.read(7))
result = self.parse_packet(data)
if result:
data_type, timestamp, value = result
if data_type == 0x01: # 心率数据
self.hr_buffer.append(value)
# 更新图表
ax.clear()
ax.plot(list(self.hr_buffer), 'r-', linewidth=2)
ax.set_title('Real-time Heart Rate Monitor')
ax.set_xlabel('Time (samples)')
ax.set_ylabel('Heart Rate (BPM)')
ax.grid(True, alpha=0.3)
ax.set_ylim(40, 180)
plt.pause(0.01)
plt.pause(0.1)
# 使用示例
if __name__ == "__main__":
receiver = HeartRateReceiver('COM3', 115200)
receiver.plot_realtime()
参考代码 基于STM32的数据采集+心率检测仪(原理图、PCB、程序源码等) www.youwenfan.com/contentcst/123483.html
四、系统调试与优化
1. 调试步骤
| 阶段 | 操作 | 工具 | 预期结果 |
|---|---|---|---|
| 硬件调试 | 测量MAX30102供电电压,验证I2C通信 | 万用表、示波器 | 3.3V稳定,I2C波形正常 |
| 信号调试 | 观察原始PPG波形,验证滤波效果 | 串口打印、MATLAB分析 | 波形清晰,无明显噪声 |
| 算法调试 | 测试峰值检测准确性,验证心率计算 | 秒表计时对比 | 误差<±3BPM |
| 系统测试 | 实际佩戴测试,连续监测稳定性 | 真人测试 | 连续30分钟数据稳定 |
2. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 信号不稳定 | 佩戴松动,环境光干扰 | 调整佩戴松紧度,加装遮光罩 |
| 心率计算错误 | 运动伪影,基线漂移 | 改进滤波算法,增加运动检测 |
| 功耗过高 | 采样率过高,LED电流过大 | 动态调节采样率,优化LED驱动 |
| 蓝牙连接不稳定 | 信号干扰,距离过远 | 更换天线,缩短通信距离 |
3. 性能优化建议
- 动态采样率:静止时降低采样率,运动时提高采样率
- 自适应阈值:根据信号强度自动调整峰值检测阈值
- 运动伪影消除:结合加速度传感器检测运动状态,运动时不计算心率
- 数据压缩:蓝牙传输时使用差分编码压缩数据
五、扩展功能
1. 血氧饱和度测量(SpO2)
// 基于红光/红外比值计算SpO2
float Calculate_SpO2(uint32_t red_ac, uint32_t red_dc, uint32_t ir_ac, uint32_t ir_dc) {
float ratio = (red_ac * ir_dc) / (ir_ac * red_dc);
float spo2 = 110.0f - 25.0f * ratio; // 经验公式
if (spo2 > 100.0f) spo2 = 100.0f;
if (spo2 < 70.0f) spo2 = 70.0f;
return spo2;
}
2. 心率变异性分析(HRV)
// 计算HRV指标(SDNN、RMSSD)
void Calculate_HRV(uint32_t *rr_intervals, uint8_t count) {
float mean_rr = 0, sdnn = 0, rmssd = 0;
uint32_t sum_squared_diff = 0;
// 计算平均RR间期
for (uint8_t i = 0; i < count; i++) {
mean_rr += rr_intervals[i];
}
mean_rr /= count;
// 计算SDNN
for (uint8_t i = 0; i < count; i++) {
float diff = rr_intervals[i] - mean_rr;
sdnn += diff * diff;
}
sdnn = sqrt(sdnn / count);
// 计算RMSSD
for (uint8_t i = 1; i < count; i++) {
float diff = rr_intervals[i] - rr_intervals[i-1];
sum_squared_diff += diff * diff;
}
rmssd = sqrt(sum_squared_diff / (count - 1));
}
3. 手机APP开发
- Android端:使用BluetoothLeService接收数据,MPAndroidChart绘制实时波形
- iOS端:CoreBluetooth框架,Charts库显示心率趋势
- Web端:WebSocket实时显示,ECharts绘制历史曲线
六、总结
基于STM32的心率检测仪通过MAX30102光电传感器实现无创心率监测,数字滤波算法有效去除噪声干扰,峰值检测算法精准计算心率值。
更多推荐



所有评论(0)