完成鼾声识别模型部署后,聚焦CY8CKIT-062S2-AI的外设扩展与生物传感数据采集能力,以主流的MAX30102心率血氧模块为核心,实现心率实时监测系统。重点解决“模块硬件适配”“I2C通信配置”“PPG信号采集”“心率算法解析”等关键问题,同时结合前序内容,为后续“鼾声+心率”综合健康监测系统的整合奠定基础。
一、核心原理:心率监测的技术逻辑与硬件选型
心率监测基于光体积描记法(PPG),通过MAX30102模块的红外与红光LED照射皮肤,利用血液中血红蛋白对不同波长光的吸收差异,采集血管搏动引起的光强变化信号,再通过算法解析得到心率值。整个系统的技术架构与硬件选型逻辑如下:
1. 技术架构解析
系统采用“传感器采集→I2C通信传输→信号预处理→心率计算→结果输出”的核心流程,各环节功能如下:
1. 传感器层:MAX30102负责发射红光/红外光并接收反射光,将光信号转换为电信号;
2. 通信层:CY8CKIT-062S2-AI通过I2C总线与MAX30102通信,配置模块参数并读取原始数据;
3. 处理层:PSoC™ 6 MCU对原始PPG信号进行滤波、峰值检测等预处理,通过心率算法计算心率值;
4. 输出层:通过串口打印心率数据,配合LED指示灯提示监测状态(如心率过高/正常)。
2. 硬件选型说明
选择MAX30102模块作为心率采集核心,适配CY8CKIT-062S2-AI的优势如下:
• 兼容性强:采用标准I2C通信协议,CY8CKIT-062S2-AI的GPIO可直接配置为I2C引脚;
• 集成度高:模块内置LED驱动、光电探测器和信号放大电路,无需额外外围元件;
• 性价比高:成本低且支持心率、血氧双参数采集,可扩展功能;
易调试:多数模块自带电压 regulator,支持3.3V供电,与开发板电平匹配。
二、硬件连接:MAX30102与开发板的适配接线
MAX30102采用I2C通信,需与CY8CKIT-062S2-AI的I2C引脚连接,同时提供稳定供电。接线前需确认开发板的GPIO引脚定义(参考CY8CKIT-062S2-AI官方手册),推荐使用P6_0(SDA)和P6_1(SCL)作为I2C通信引脚,具体接线步骤如下:
1. 引脚定义对应表
MAX30102模块引脚
| 功能说明
| CY8CKIT-062S2-AI引脚
| 接线说明
| VCC
| 电源输入(3.3V)
| 3V3
| 模块供电,不可接5V(会烧毁模块)
| GND
| 接地
| GND
| 与开发板共地,保证信号稳定
| SDA
| I2C数据引脚
| SCL
|
| SCL
| I2C时钟引脚
| SDA
|
| INT
| 中断输出(可选)
| P9_2
| 数据就绪时触发中断,减少轮询开销
|
三、软件开发:从驱动适配到心率算法实现
软件开发核心分为“I2C驱动配置→MAX30102初始化→PPG信号采集→心率算法解析→结果输出”五大步骤,基于ModusToolbox™开发,兼容Keil编译环境。
1. 项目创建与依赖配置
1.创建项目:
打开ModusToolbox™,点击「New Application」,选择「CY8CKIT-062S2-AI」开发板;
2.搜索并选择「mtb-example-psoc6-i2c-master」(I2C主机示例项目),点击「Create」,项目路径设为`D:\mtb_projects\Heart_Rate_Monitor`(无中文无空格)。
;
3.手动创建「max30102」文件夹,用于存放模块驱动文件(`max30102.h`和`max30102.c`)。
2. I2C驱动配置(核心步骤)
CY8CKIT-062S2-AI的PSoC™ 6 MCU支持多组I2C外设,此处使用SCB1外设配置为I2C主机模式,对应引脚SDA和SCL,代码实现如下:
c
#include "cyhal.h"
#include "cybsp.h"
// I2C配置参数
#define I2C_MASTER_ADDR 0x00 // 主机地址(四轴飞行器时可设为0)
#define I2C_MASTER_SDA_PIN P6_0 // SDA引脚
#define I2C_MASTER_SCL_PIN P6_1 // SCL引脚
#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz,标准模式)
cyhal_i2c_t i2c_master_obj; // I2C主机对象
cyhal_i2c_cfg_t i2c_master_cfg = {
.is_slave = false, // 配置为主机模式
.address = I2C_MASTER_ADDR,
.frequency = I2C_MASTER_FREQ_HZ
};
// I2C初始化函数
cy_rslt_t i2c_master_init(void) {
cy_rslt_t result;
// 初始化I2C引脚
result = cyhal_gpio_init(I2C_MASTER_SDA_PIN, CYHAL_GPIO_DIR_BIDIRECTIONAL, CYHAL_GPIO_DRIVE_PULLUP, CYBSP_GPIO_INIT_STATE_LOW);
if (result != CY_RSLT_SUCCESS) return result;
result = cyhal_gpio_init(I2C_MASTER_SCL_PIN, CYHAL_GPIO_DIR_BIDIRECTIONAL, CYHAL_GPIO_DRIVE_PULLUP, CYBSP_GPIO_INIT_STATE_LOW);
if (result != CY_RSLT_SUCCESS) return result;
// 初始化I2C外设
result = cyhal_i2c_init(&i2c_master_obj, I2C_MASTER_SDA_PIN, I2C_MASTER_SCL_PIN, NULL);
if (result != CY_RSLT_SUCCESS) return result;
// 应用I2C配置
result = cyhal_i2c_configure(&i2c_master_obj, &i2c_master_cfg);
return result;
}
|
3. MAX30102驱动开发
MAX30102的驱动核心是通过I2C读写其内部寄存器,实现模块初始化、模式配置和PPG数据读取,需先明确模块的关键寄存器定义。
(1)关键寄存器定义
c
#ifndef MAX30102_H
#define MAX30102_H
#include "cyhal.h"
// MAX30102 I2C从机地址(固定为0x57)
#define MAX30102_I2C_ADDR 0x57
// 关键寄存器地址
#define MAX30102_REG_INT_STATUS 0x00 // 中断状态寄存器
#define MAX30102_REG_MODE_CONFIG 0x06 // 模式配置寄存器
#define MAX30102_REG_SPO2_CONFIG 0x07 // 血氧/心率配置寄存器
#define MAX30102_REG_LED_CONFIG 0x09 // LED亮度配置寄存器
#define MAX30102_REG_FIFO_DATA 0x0A // FIFO数据寄存器
#define MAX30102_REG_PART_ID 0xFF // 器件ID寄存器(固定为0x15)
// 模式定义
#define MAX30102_MODE_HR_ONLY 0x02 // 仅心率监测模式
#define MAX30102_MODE_SPO2_HR 0x03 // 血氧+心率模式
// 采样率定义(单位:Hz)
#define MAX30102_SAMPLE_RATE_100 0x00 // 100Hz
#define MAX30102_SAMPLE_RATE_200 0x01 // 200Hz(推荐)
#define MAX30102_SAMPLE_RATE_400 0x02 // 400Hz
// 心率数据结构体
typedef struct {
uint32_t ir_data; // 红外光PPG数据
uint32_t red_data; // 红光PPG数据
uint8_t valid; // 数据有效性标志(1=有效,0=无效)
uint8_t heart_rate; // 心率值(单位:次/分钟)
} max30102_data_t;
// 函数声明
cy_rslt_t max30102_init(cyhal_i2c_t *i2c_obj);
cy_rslt_t max30102_read_data(cyhal_i2c_t *i2c_obj, max30102_data_t *data);
cy_rslt_t max30102_set_mode(cyhal_i2c_t *i2c_obj, uint8_t mode);
#endif /* MAX30102_H */
|
(2)模块初始化实现
初始化流程包括“器件ID校验→模式配置→采样率配置→LED亮度配置”,确保模块工作在稳定的心率监测模式:
c
#include "max30102.h"
#include "cyhal.h"
// I2C写入函数(向指定寄存器写入1字节数据)
static cy_rslt_t i2c_write_byte(cyhal_i2c_t *i2c_obj, uint8_t reg_addr, uint8_t data) {
uint8_t tx_buf[2] = {reg_addr, data};
return cyhal_i2c_master_write(i2c_obj, MAX30102_I2C_ADDR, tx_buf, 2, 1000);
}
// I2C读取函数(从指定寄存器读取n字节数据)
static cy_rslt_t i2c_read_bytes(cyhal_i2c_t *i2c_obj, uint8_t reg_addr, uint8_t *data, uint8_t len) {
cy_rslt_t result;
// 先发送寄存器地址
result = cyhal_i2c_master_write(i2c_obj, MAX30102_I2C_ADDR, ®_addr, 1, 1000);
if (result != CY_RSLT_SUCCESS) return result;
// 再读取数据
return cyhal_i2c_master_read(i2c_obj, MAX30102_I2C_ADDR, data, len, 1000);
}
// MAX30102初始化
cy_rslt_t max30102_init(cyhal_i2c_t *i2c_obj) {
cy_rslt_t result;
uint8_t part_id;
// 1. 校验器件ID(确认通信正常)
result = i2c_read_bytes(i2c_obj, MAX30102_REG_PART_ID, &part_id, 1);
if (result != CY_RSLT_SUCCESS) return result;
if (part_id != 0x15) return CY_RSLT_TYPE_ERROR; // ID不匹配,通信异常
// 2. 软复位模块(恢复默认配置)
result = i2c_write_byte(i2c_obj, MAX30102_REG_MODE_CONFIG, 0x40);
cyhal_system_delay_ms(100); // 等待复位完成
// 3. 配置为仅心率监测模式
result = max30102_set_mode(i2c_obj, MAX30102_MODE_HR_ONLY);
if (result != CY_RSLT_SUCCESS) return result;
// 4. 配置采样率(200Hz)和分辨率
result = i2c_write_byte(i2c_obj, MAX30102_REG_SPO2_CONFIG, MAX30102_SAMPLE_RATE_200 | 0x10);
if (result != CY_RSLT_SUCCESS) return result;
// 5. 配置LED亮度(中等亮度,避免功耗过高)
result = i2c_write_byte(i2c_obj, MAX30102_REG_LED_CONFIG, 0x1F);
if (result != CY_RSLT_SUCCESS) return result;
return CY_RSLT_SUCCESS;
}
// 设置模块工作模式
cy_rslt_t max30102_set_mode(cyhal_i2c_t *i2c_obj, uint8_t mode) {
uint8_t reg_val;
cy_rslt_t result;
// 读取当前模式配置
result = i2c_read_bytes(i2c_obj, MAX30102_REG_MODE_CONFIG, ®_val, 1);
if (result != CY_RSLT_SUCCESS) return result;
// 清除原有模式位,设置新模式
reg_val &= ~0x07;
reg_val |= mode;
// 写入新配置
return i2c_write_byte(i2c_obj, MAX30102_REG_MODE_CONFIG, reg_val);
}
|
(3)PPG数据读取实现
MAX30102的PPG数据存储在FIFO缓冲区中,每个数据点由3字节红外光数据和3字节红光数据组成,需按顺序读取并组合:
c
// 读取PPG原始数据
cy_rslt_t max30102_read_data(cyhal_i2c_t *i2c_obj, max30102_data_t *data) {
cy_rslt_t result;
uint8_t fifo_data[6]; // 存储6字节数据(IR 3字节 + RED 3字节)
uint8_t int_status;
// 1. 读取中断状态,判断数据是否就绪
result = i2c_read_bytes(i2c_obj, MAX30102_REG_INT_STATUS, &int_status, 1);
if (result != CY_RSLT_SUCCESS) return result;
// 2. 若数据未就绪,返回无效数据
if (!(int_status & 0x01)) {
data->valid = 0;
return CY_RSLT_SUCCESS;
}
// 3. 读取FIFO数据
result = i2c_read_bytes(i2c_obj, MAX30102_REG_FIFO_DATA, fifo_data, 6);
if (result != CY_RSLT_SUCCESS) {
data->valid = 0;
return result;
}
// 4. 组合数据(低字节在前,高字节在后)
data->ir_data = (uint32_t)fifo_data[0] | ((uint32_t)fifo_data[1] << 8) | ((uint32_t)fifo_data[2] << 16);
data->red_data = (uint32_t)fifo_data[3] | ((uint32_t)fifo_data[4] << 8) | ((uint32_t)fifo_data[5] << 16);
// 5. 标记数据有效
data->valid = 1;
return CY_RSLT_SUCCESS;
}
|
4. 心率算法解析
原始PPG信号包含大量噪声(如运动干扰、基线漂移),需通过“滤波→峰值检测→心率计算”三步得到准确心率值,再网上也能很容易找到历程,算法实现如下:
c
#include "heart_rate_algorithm.h"
#include "arm_math.h"
// 算法配置参数
#define HR_BUFFER_SIZE 200 // PPG数据缓存大小(对应1秒数据,200Hz采样率)
#define HR_FILTER_ORDER 4 // 低通滤波器阶数
#define HR_LOWPASS_FREQ 5.0f // 低通滤波截止频率(5Hz,滤除高频噪声)
#define HR_PEAK_THRESHOLD 0.6f // 峰值检测阈值(相对最大值的60%)
// 静态变量(算法内部使用)
static float32_t ppg_buffer[HR_BUFFER_SIZE];
static uint16_t buffer_index = 0;
static arm_biquad_casd_df1_inst_f32 filter_inst;
static float32_t filter_coeffs[2 * (HR_FILTER_ORDER + 1)]; // 滤波器系数
// 初始化心率算法(初始化低通滤波器)
void hr_algorithm_init(float32_t sample_rate) {
// 计算低通滤波器系数(巴特沃斯滤波器)
arm_biquad_cascade_df1_init_f32(&filter_inst, HR_FILTER_ORDER, filter_coeffs, NULL);
arm_fir_lpf_f32(&filter_inst, sample_rate, HR_LOWPASS_FREQ);
// 清空缓存
memset(ppg_buffer, 0, sizeof(ppg_buffer));
buffer_index = 0;
}
// 心率计算(输入红外光PPG数据,输出心率值)
uint8_t hr_algorithm_calc(uint32_t ir_data) {
float32_t filtered_data;
float32_t max_val, min_val;
uint16_t peak_count = 0;
uint16_t peaks[10]; // 存储峰值位置
float32_t hr = 0.0f;
// 1. 数据归一化(将32位原始数据转换为0~1的浮点值)
float32_t raw_float = (float32_t)ir_data / 16777215.0f; // 24位数据最大值为2^24-1=16777215
// 2. 低通滤波(滤除高频噪声和运动干扰)
arm_biquad_cascade_df1_f32(&filter_inst, &raw_float, &filtered_data, 1);
// 3. 缓存滤波后的数据
ppg_buffer[buffer_index++] = filtered_data;
if (buffer_index >= HR_BUFFER_SIZE) {
buffer_index = 0; // 缓存满后循环覆盖
}
// 4. 峰值检测(仅当缓存满时计算)
if (buffer_index == 0) {
// 4.1 找到缓存中的最大值和最小值
arm_max_f32(ppg_buffer, HR_BUFFER_SIZE, &max_val, NULL);
arm_min_f32(ppg_buffer, HR_BUFFER_SIZE, &min_val, NULL);
float32_t threshold = min_val + (max_val - min_val) * HR_PEAK_THRESHOLD;
// 4.2 检测峰值(连续3个点:上升→峰值→下降)
for (uint16_t i = 1; i < HR_BUFFER_SIZE - 1; i++) {
if (ppg_buffer > threshold && ppg_buffer > ppg_buffer[i-1] && ppg_buffer > ppg_buffer[i+1]) {
peaks[peak_count++] = i;
}
}
// 5. 计算心率(根据峰值间隔计算)
if (peak_count >= 2) {
// 计算平均峰值间隔(单位:样本数)
float32_t avg_interval = 0.0f;
for (uint16_t i = 0; i < peak_count - 1; i++) {
avg_interval += (float32_t)(peaks[i+1] - peaks);
}
avg_interval /= (peak_count - 1);
// 心率 = 采样率 / 平均峰值间隔 * 60(转换为次/分钟)
hr = (200.0f / avg_interval) * 60.0f;
}
}
// 6. 心率值约束(正常范围60~120次/分钟,超出范围返回0表示无效)
if (hr > 60.0f && hr < 120.0f) {
return (uint8_t)hr;
} else {
return 0;
}
}
|
5. 主函数流程整合
整合I2C驱动、模块驱动和心率算法,实现“初始化→数据采集→心率计算→结果输出”的全流程:
c
#include "cybsp.h"
#include "cyhal.h"
#include "retarget_io.h"
#include "i2c_master.h"
#include "max30102.h"
#include "heart_rate_algorithm.h"
int main(void) {
cy_rslt_t result;
max30102_data_t hr_data;
uint8_t heart_rate;
// 1. 硬件初始化(BSP、串口、I2C)
result = cybsp_init();
CY_ASSERT(result == CY_RSLT_SUCCESS);
retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX); // 初始化串口(115200波特率)
result = i2c_master_init();
CY_ASSERT(result == CY_RSLT_SUCCESS);
// 2. 模块与算法初始化
result = max30102_init(&i2c_master_obj);
CY_ASSERT(result == CY_RSLT_SUCCESS);
hr_algorithm_init(200.0f); // 采样率200Hz
printf("Heart Rate Monitor Started. Place your finger on the sensor...\n");
// 3. 主循环:采集与计算
while (1) {
// 3.1 读取PPG数据
result = max30102_read_data(&i2c_master_obj, &hr_data);
if (result != CY_RSLT_SUCCESS) {
printf("Failed to read MAX30102 data!\n");
cyhal_system_delay_ms(100);
continue;
}
// 3.2 计算心率(仅使用红外光数据,抗干扰性更强)
if (hr_data.valid) {
heart_rate = hr_algorithm_calc(hr_data.ir_data);
hr_data.heart_rate = heart_rate;
// 3.3 输出结果(串口+LED)
if (heart_rate != 0) {
printf("IR Data: %lu, Red Data: %lu, Heart Rate: %d bpm\n",
hr_data.ir_data, hr_data.red_data, heart_rate);
cyhal_gpio_toggle(CYBSP_USER_LED); // 心率有效时LED闪烁
} else {
printf("IR Data: %lu, Red Data: %lu, Heart Rate: Invalid\n",
hr_data.ir_data, hr_data.red_data);
cyhal_gpio_write(CYBSP_USER_LED, CYBSP_LED_STATE_OFF);
}
}
// 3.4 延时(控制采样频率)
cyhal_system_delay_ms(5); // 200Hz采样率,间隔5ms
}
}
|
四、编译烧录与功能测试:验证心率监测效果
编译烧录流程与前序项目一致,重点关注测试时的环境要求与结果验证方法,确保心率数据准确。
1. 编译与烧录
1. Keil编译:
在ModusToolbox™中右键点击项目,选择「Export to Keil」,生成Keil项目;
2. 打开Keil项目,添加「max30102.c」「heart_rate_algorithm.c」等源文件,配置I2C和GPIO引脚;
3. 点击「Build」编译项目,生成「Heart_Rate_Monitor.hex」固件文件。
4. 烧录固件:
将开发板通过Type-C线连接电脑,选择Keil的「CMSIS-DAP Debugger」;
5. 点击「Download」烧录固件,烧录成功后开发板自动重启。
2. 功能测试与验证
1. 测试准备:
打开串口工具,配置波特率115200、8N1,连接开发板对应的COM口;
2. 将手指轻轻按压在MAX30102的传感器面上,确保皮肤完全覆盖传感器,避免光线干扰。
3. 测试场景与预期结果:
测试场景预期串口输出预期LED状态手指未按压传感器IR Data: 0~1000, Red Data: 0~1000, Heart Rate: Invalid常灭手指正常按压(静止状态)IR Data: 50000~200000, Red Data: 30000~150000, Heart Rate: 60~100 bpm500ms闪烁一次(与心率同步)手指按压并轻微运动IR Data: 波动较大, Red Data: 波动较大, Heart Rate: 偶尔无效闪烁不稳定使用心率带校准Heart Rate与心率带读数误差≤5 bpm稳定闪烁
4. 精度优化技巧:
测试时保持手指静止,避免运动干扰(运动时可增加运动 artifact 滤波算法);
5. 调整LED亮度(修改`MAX30102_REG_LED_CONFIG`寄存器值),皮肤较厚可增大亮度;
6. 延长数据缓存时间(如将`HR_BUFFER_SIZE`改为400,对应2秒数据),提升心率计算稳定性。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?注册
×
|