基于STM32的可穿戴心率检测仪设计(数据采集+心率分析)

简介: 基于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/contentali/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光电传感器实现无创心率监测,数字滤波算法有效去除噪声干扰,峰值检测算法精准计算心率值。

相关文章
|
15天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23512 12
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
4天前
|
人工智能 BI 持续交付
Claude Code 深度适配 DeepSeek V4-Pro 实测:全场景通关与真实体验报告
在 AI 编程工具日趋主流的今天,Claude Code 凭借强大的任务执行、工具调用与工程化能力,成为开发者与自动化运维的核心效率工具。但随着原生模型账号稳定性问题频发,寻找一套兼容、稳定、能力在线的替代方案变得尤为重要。DeepSeek V4-Pro 作为新一代高性能大模型,提供了完整兼容 Claude 协议的 API 接口,只需简单配置即可无缝驱动 Claude Code,且在任务执行、工具调用、复杂流程处理上表现极为稳定。
1264 3
|
9天前
|
人工智能 缓存 Shell
Claude Code 全攻略:命令大全 + 实战工作流(完整版)
Claude Code 是一款运行在终端环境下的 AI 编码助手,能够直接在项目目录中理解代码结构、编辑文件、执行命令、执行开发计划,并支持持久化记忆、上下文压缩、后台任务、多模型切换等专业能力。对于日常开发、项目维护、快速重构、代码审查等场景,它可以大幅减少手动操作、提升编码效率。本文从常用命令、界面模式、核心指令、记忆机制、图片处理、进阶工作流等维度完整说明,帮助开发者快速上手并稳定使用。
2323 4
|
3天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
917 7
|
19天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
5907 22
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
20天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
7078 16
|
2天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
752 0