手把手教你用STM32F103C8T6驱动MAX30102,在0.96寸OLED上做个心率血氧仪(附完整代码)
基于STM32F103C8T6的MAX30102心率血氧监测系统实战指南1. 项目概述与硬件选型在个人健康监测设备领域低成本、高精度的解决方案一直备受创客和电子爱好者关注。STM32F103C8T6作为蓝桥杯等电子竞赛的常用芯片搭配MAX30102传感器和0.96寸OLED显示屏可以构建一套完整的心率血氧监测系统总成本控制在50元以内。核心硬件对比表组件STM32F103C8T6方案传统方案差异MCU64KB Flash/20KB RAM比ZET6系列减少外设但保留I2C/SPI传感器MAX30102含环境光抑制与高端方案相同显示屏4线SPI OLED128x64比I2C版本刷新率更高供电3.3V直连无需电平转换简化电源设计提示C8T6的GPIO数量虽少但完全满足传感器显示屏的接口需求注意避免使用被烧录接口占用的引脚实际开发中我们特别选择了以下性价比配置主控STM32F103C8T6最小系统板带USB转串口传感器MAX30102模块带电平转换电路显示屏SSD1306驱动的0.96寸OLED4线SPI接口2. 硬件连接与电路设计2.1 引脚分配优化针对C8T6的引脚限制我们采用如下连接方案MAX30102连接VIN → 3.3VGND → GNDSCL → PB6I2C1_SCLSDA → PB7I2C1_SDAINT → PB5外部中断OLED连接4线SPIVCC → 3.3VGND → GNDD0/SCK → PA5SPI1_SCKD1/MOSI → PA7SPI1_MOSIRES → PA1GPIO控制DC → PA2数据/命令选择CS → PA3片选可接GND常使能// 引脚初始化示例HAL库 void GPIO_Init(void) { // I2C1初始化 GPIO_InitStruct.Pin GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_OD; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); // SPI1初始化 GPIO_InitStruct.Pin GPIO_PIN_5|GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }2.2 电源管理技巧由于C8T6系统板通常直接USB供电需注意为MAX30102的LED供电添加100μF电容滤波OLED电源走线尽量短以减少干扰在3.3V总线上并联0.1μF去耦电容注意避免同时使用PA11/PA12USB DM/DP作为普通GPIO否则会导致USB功能异常3. 软件架构与关键代码实现3.1 传感器驱动移植针对C8T6的存储限制我们对原始算法进行了优化// 精简版FIFO读取函数 uint8_t MAX30102_ReadFIFO(uint32_t *pun_red, uint32_t *pun_ir) { uint8_t temp[6]; HAL_I2C_Mem_Read(hi2c1, MAX30102_ADDR, REG_FIFO_DATA, I2C_MEMADD_SIZE_8BIT, temp, 6, 100); *pun_red ((uint32_t)temp[0] 16) | ((uint32_t)temp[1] 8) | (uint32_t)temp[2]; *pun_ir ((uint32_t)temp[3] 16) | ((uint32_t)temp[4] 8) | (uint32_t)temp[5]; return 0; }数据处理流程优化采用200样本的滑动窗口原工程500样本使用Q15定点数运算替代浮点运算简化峰值检测算法3.2 显示界面设计OLED显示层采用分层渲染策略void UpdateDisplay(int32_t hr, int32_t spo2) { // 第一层静态元素 OLED_ShowString(0, 0, HR:, 16); OLED_ShowString(64, 0, SpO2:, 16); // 第二层动态数值 char buffer[10]; sprintf(buffer, %3d, hr); OLED_ShowString(24, 0, buffer, 16); sprintf(buffer, %3d%%, spo2); OLED_ShowString(96, 0, buffer, 16); // 第三层波形显示 PlotPPGWaveform(); }4. 系统调试与性能优化4.1 常见问题解决信号质量提升方案手指接触不良添加接触检测电路环境光干扰启用MAX30102的环境光抑制功能运动伪影增加移动平均滤波// 运动伪影抑制算法示例 #define FILTER_DEPTH 5 int32_t FilterHR(int32_t new_val) { static int32_t buf[FILTER_DEPTH] {0}; static uint8_t idx 0; buf[idx] new_val; if(idx FILTER_DEPTH) idx 0; int32_t sum 0; for(uint8_t i0; iFILTER_DEPTH; i) { sum buf[i]; } return sum / FILTER_DEPTH; }4.2 低功耗设计针对电池供电场景的优化策略动态调整采样率默认100Hz待机时降至25Hz使用STM32的Stop模式配合传感器中断唤醒关闭OLED背光时的功耗仅0.1mA功耗对比表模式电流消耗唤醒延迟全速运行12mA0ms低采样率5mA1msStop模式0.5mA10ms5. 进阶功能扩展5.1 数据存储与导出利用C8T6内置的Flash实现简易数据记录#define LOG_START_ADDR 0x0801F000 // 使用最后1KB Flash void SaveToFlash(uint32_t hr, uint32_t spo2) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress LOG_START_ADDR; erase.NbPages 1; uint32_t pageError; HAL_FLASHEx_Erase(erase, pageError); uint32_t data (hr 16) | (spo2 0xFFFF); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, LOG_START_ADDR, data); HAL_FLASH_Lock(); }5.2 无线传输方案通过HC-05蓝牙模块添加手机连接功能配置USART1为115200波特率使用NRF Connect等APP接收数据传输格式JSON字符串{ hr: 72, spo2: 98, timestamp: 1234567890 }实际测试中发现当同时运行蓝牙传输和传感器采集时需要将I2C时钟降至100kHz以确保稳定性。