基于STM32的人体健康监测装置:传感器选型、驱动与心率算法实现
发布时间:2026/9/17 6:25:03 作者:尧图编辑部 阅读量:1,286

简介面向课程设计与毕业设计的STM32人体健康监测装置完整工程包涵盖温度、心率等生理参数采集的嵌入式实现方案。资源适合电子、嵌入式、物联网等专业学生作为课设、毕设或项目演示参考也便于企业开发者快速搭建原型并二次开发。包内共231个文件以C语言源文件、头文件、Keil工程配置、编译生成文件以及图片和说明文档为主压缩包约29.32MB。源码模块划分清晰包含液晶显示、姿态传感器等外设驱动及主控逻辑配套编译链接脚本可帮助理解STM32外设编程与系统集成。代码功能经过验证稳定可靠目前已有378人学习除直接用于课程设计或毕业答辩外还可作为嵌入式与传感器融合技术的学习素材适合在此基础上扩展联网、云端上报或报警功能具备较好的拓展空间。1. 基于STM32实现人体健康监测装置从毕业设计选题到可演示样机又一年的毕业季选题表里“基于STM32的人体健康监测装置”几乎是常驻选手。它把单片机入门最常碰的三条技术线全占了单总线传感器读温度、I2C传感器读心率、OLED或串口做数据展示难度适中做出来又有一个看得见摸得着的实物。网上流传的“源码及完整资料”也大多围绕这套结构展开DS18B20测体温MAX30102通过光电容积脉搏波测心率STM32在中间做读取、滤波和显示控制。拿到资料别急着烧录先把传感器为什么这么选、外设怎么分配、数据怎么从ADC变成心率这三件事理清楚后面复现速度会快很多答辩时被问到设计思路也不会卡壳。这篇文章就按这个顺序把从零复现这套装置的关键环节拆开讲。2. 传感器选型与STM32外设分配先想清温度、心率从哪来2.1 温度测量DS18B20为何是毕业设计里的“标准答案”体温测量用NTC热敏电阻也能做但NTC要自己查表、做线性化标定没有精密电阻或恒温槽时标定精度很难保证。DS18B20把这些工作全部内置单总线接口一根数据线就能完成通信和供电12位分辨率下精度为±0.5℃测温范围覆盖-55℃到125℃对贴近皮肤测体温的场景完全够用。选它还有两个现实理由资料极多调试极简单。串口打印出来的数值在35~37℃附近稳定波动说明时序基本没写错。接线只有三根VCC接3.3VGND接地DQ接一个GPIO。数据线上必须加一颗4.7kΩ上拉电阻到3.3V否则总线在高速读写时处于浮空状态读回来的温度会随机跳变。STM32侧把GPIO配置成开漏输出配合外部上拉读时序时再把引脚方向切回输入这是单总线半双工通信的标准做法。这里有一个容易翻车的点DS18B20支持寄生供电即只接GND和DQ两根线靠数据线上的高电平给芯片供电。实际贴片测量时寄生电容有限芯片做温度转换的几百毫秒内电压跌落明显容易出现转换失败或读数漂移。毕设装置里建议老老实实把VCC接上不要省这一根线。2.2 心率测量MAX30102的PPG原理与接线要点心率传感器方面近年毕业设计基本被MAX30102一统天下。这颗芯片内部集成了红光LED、红外LED和光电二极管原理是光电容积脉搏波PPG心脏收缩时毛细血管血流量增加对光的吸收量变大光电二极管接收到的反射光变弱ADC输出值随之下降心脏舒张时趋势相反。这个随心跳周期性波动的信号就是后续心率算法的原始素材。MAX30102通过I2C接口与STM32通信器件地址0xAE7位地址0x57左移一位。硬件连接除了电源和I2C两根线还有一根INT中断脚用于通知主机FIFO数据准备好。常见外设分配可以参考这张表外设引脚功能说明DS18B20PA6单总线数据4.7kΩ电阻上拉到3.3VMAX30102PB8 / PB9I2C1 SCL / SDA各接4.7kΩ上拉INT接PB5OLED屏PB10 / PB11I2C2 SCL / SDA地址0x3C可软件调背光USART1PA9 / PA10调试串口115200 8N1SWD调试口PA13 / PA14烧录与调试ST-Link标准四线把OLED放到I2C2而不是和MAX30102共用一条I2C总线这是刻意为之。I2C从设备多挂几个之后总线上所有上拉电阻并联等效阻值变小长时间运行偶发出现总线被拉死的情况会变多。调试阶段传感器挂在I2C1屏幕挂在I2C2互不干扰用逻辑分析仪看波形也更清晰。MAX30102的原始数据包含红光和红外两个通道的ADC值实际使用中以红外通道为主它对指尖放偏的容忍度更高。FIFO深度默认32个样本建议把SMP_AVE采样平均配成4相当于每4个原始采样取平均后再进入FIFO输出数据率降到25Hz左右给后续软件滤波省不少事。2.3 时钟与引脚复用为什么先定分配再写驱动STM32的复用功能引脚不是随便选的。以F103C8T6为例I2C1的硬件SCL/SDA可选PB6/PB7或PB8/PB9USART1固定在PA9/PA10SWD固定在PA13/PA14选Pin的时候得先把这几路的冲突排查清楚。时钟方面F103最高主频72MHz外部8MHz晶振经过PLL倍频后得到HCLK、APB1、APB2、ADC时钟都从这条链路分出来。APB1上限36MHz挂USART2、I2C2、TIM3这类外设APB2上限72MHz挂USART1、ADC、TIM1。在CubeMX里先把时钟树配置好再去看外设初始化代码就不会出现I2C时钟被误配到40MHz以上导致通信失败的问题。这里顺带说一个常被问到的“stm32晶振电容计算”问题。8MHz主晶振的负载电容CL若标称18pF匹配电容一般取C1C22×CL-CsCs是引脚杂散电容约3~5pF算下来约15~20pF取20pF贴片电容基本能稳定起振。32.768kHz的RTC晶振就不一样了它频率低、驱动能力弱负载电容偏差大会直接停振优先按数据手册给的6.8~12.5pF选改板后要逐片验证起振。3. 源码工程搭建与核心代码从CubeMX配置到温度、心率数据落地3.1 环境对齐Keil5芯片包安装与CubeMX工程生成拿到“源码及完整资料”第一步不是读代码而是把开发环境对齐。很多人在Keil5里打开工程报“Device not Found”或者编译时出现大量未定义类型大概率是电脑里没装对应型号的芯片支持包。在Pack Installer里搜索并安装STM32F1xx_DFP即可注意版本号要大于工程生成时的版本。F103C8T6工程对应的支持包名称是Keil::STM32F1xx_DFP。用STM32CubeMX从零生成工程的顺序如下选择芯片STM32F103C8SYS里把Debug配成Serial WireRCC里HSE选Crystal/Ceramic ResonatorClock Configuration把HCLK拉到72MHzI2C1和I2C2都开Standard Mode100kHzUSART1开Asynchronous并设置为115200 8N1。GPIO部分把PA6设为开漏输出用于单总线PB5设为输入用于接收心率传感器中断。生成时Toolchain选MDK-ARM代码生成方式建议勾选“生成独立.c/.h文件”方便后续把自己写的驱动模块加进去。如果拿到的资料是F103RCT6工程而手里板子是C8T6不需要重写外设初始化。CubeMX里直接改Device型号再重新生成即可但要注意启动文件、链接脚本要跟着换大容量型号用startup_stm32f103xe.s中小容量用startup_stm32f103xb.sFlash起始地址和大小由Keil的Target页面决定忘记改会直接进HardFault。提示CubeMX生成工程后先编译一次再写自己的外设驱动。如果编译就报错多半是HAL库版本差异或芯片包缺失先解决这个再往下走。3.2 读温度单总线复位与时隙的实现DS18B20的通信完全靠延时凑时序。最常用的三个参数区间要记牢复位主机拉低总线480μs以上释放后等待15~60μs读到从机拉低的存在脉冲再拉高恢复。写1拉低1~15μs后释放靠上拉电阻把总线拉高整个时隙维持至少60μs。写0拉低60~120μs后释放。读位主机拉低1~15μs后释放在15μs内采样总线电平整个读时隙60μs。最容易踩坑的是时隙之间的间隔。读位操作结束后总线必须保持高电平至少1μs再进入下一次操作否则时序粘连读回来的字节全是0xFF。延时函数建议用SysTick实现微秒级延时不要在主循环里写一堆空while去“估”时间挂上调试器暂停后程序时间轴和真实硬件时间完全对不上。uint8_t DS18B20_ReadBit(void) { uint8_t bit 0; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET); /* 拉低总线 */ delay_us(3); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET); /* 释放总线 */ delay_us(8); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)) bit 1; /* 15us内采样 */ delay_us(50); return bit; }拉低3μs、释放8μs后读引脚目的是避开总线刚释放时的毛刺同时保证采样点落在规范要求的15μs窗口内。如果读出来的位全是1多半是上拉电阻没焊总线一直浮空如果温度跳变到-0.5℃和125℃两个极端值通常是时序错位导致CRC错误回到复位函数查存在脉冲是否正常。3.3 配置MAX30102模式、FIFO与LED电流的常用取值MAX30102的寄存器不多初始化核心是配好几件事工作模式、FIFO平均与回滚策略、ADC量程和采样率、LED电流。以下是一组常用配置寄存器位域的具体映射以芯片手册为准但这些取值是多数开源驱动里验证过的#define MAX30102_ADDR 0xAE int MAX30102_Init(void) { /* 中断使能开FIFO满中断关其他 */ I2C_WriteReg(MAX30102_ADDR, 0x02, 0xC0); /* FIFO配置平均4个样本允许回卷FIFO快满时中断 */ I2C_WriteReg(MAX30102_ADDR, 0x08, 0x50); /* SpO2配置ADC量程4096nA采样率100Hz脉冲宽度411us */ I2C_WriteReg(MAX30102_ADDR, 0x09, 0x4B); /* 红光和红外LED电流先都设成约6.4mA后续按波形微调 */ I2C_WriteReg(MAX30102_ADDR, 0x0A, 0x24); I2C_WriteReg(MAX30102_ADDR, 0x0C, 0x24); /* 模式SpO2模式红红外同时采样 */ I2C_WriteReg(MAX30102_ADDR, 0x0B, 0x03); return 0; }0x08寄存器里的SMP_AVE010代表4样本平均这一步相当于芯片内部先做了一级低通ROLLOVER位打开后FIFO写满会回卷覆盖旧数据对连续采集很重要否则数据停在满状态不再更新。0x09里ADC量程选4096nA是兼顾了手指厚度和肤色的折中量程太小信号容易饱和量程太大时低幅脉搏波会被量化噪声淹没。读取数据时用I2C连续读FIFO数据寄存器一次读6字节包含红光2字节、红外2字节和温度2字节。心率计算多数只用红外通道红光字节丢弃即可。3.4 心率计算从波形到波峰的动态阈值法心率算法不需要上FFT静息测量场景下做波峰检测就够。直接套固定阈值的做法有两个坑一是基线漂移手指按压力度变化会让波形整体上下移动固定阈值会漏检或误检二是高频毛刺手指微抖会制造出假波峰。常用的替代方案是动态阈值加迟滞代码可以这样写#define WINDOW_SIZE 4 uint16_t ir_buf[WINDOW_SIZE]; uint8_t buf_idx 0; uint32_t hr_peak_detect(uint32_t ir, uint32_t *peak_count) { static uint32_t running_max 0; static uint8_t above 0; uint32_t threshold; uint8_t i; if (ir 200) { /* 数值太低认为手指未放好 */ return running_max * 6 / 10; } ir_buf[buf_idx] ir; buf_idx (buf_idx 1) % WINDOW_SIZE; running_max 0; for (i 0; i WINDOW_SIZE; i) { if (ir_buf[i] running_max) running_max ir_buf[i]; } threshold running_max * 6 / 10; /* 动态阈值取近期峰值的60% */ if (ir threshold !above) { /* 上穿阈值且未处于触发态 */ (*peak_count); above 1; } else if (ir threshold * 8 / 10) { above 0; /* 掉到阈值80%以下才复位 */ } return threshold; }这段逻辑维持一个很小的滑动窗口求近期最大值动态阈值约等于峰值的60%。只有当信号上穿阈值、且上一次触发已经解除时才计一个峰信号要掉到阈值的80%以下才解除挂起。这种迟滞处理能把分布在阈值附近的抖动排除掉手指轻微移动不会导致心率乱跳。每秒统计一次峰个数乘以60就是瞬时心率。更进一步可以记录最近10个峰间隔去掉最大最小取平均再换算成bpm比数峰更稳。对毕业设计来说在峰检测之前先做一次一阶低通滤波会显著改善效果y[n] y[n-1] (x[n] - y[n-1]) / 8。这里的一阶低通本质上就是一个平滑因子为0.125的滑动平均滤波后波形上小的锯齿会被抹掉但心搏的主峰仍然保留。4. 完整资料怎么用原理图、烧录、串口与实物调试4.1 原理图和PCB里的三个检查点完整资料里的原理图大部分是F103C8T6最小系统加模块接口的拼板设计。拿到手先看电源部分USB 5V进来用AMS1117-3.3稳压MAX30102的LED驱动峰值电流能到几十毫安OLED全亮背光也类似3.3V总线上至少要放两个22μF左右的陶瓷电容并且靠近传感器模块引脚位置。有些板子为省成本省略去耦电容I2C在通信高峰时波形变圆表现为偶发读不到FIFO数据。再看晶振部分8MHz晶振的两个负载电容位置不对或漏焊STM32上电后直接停在内核复位循环复位脚波形一直有毛刺但程序不跑。32.768kHz晶振的负载电容不匹配会停振RTC时间不走但这个不影响主功能可后查。第三个检查点是BOOT0和NRST。BOOT0悬空或者被覆铜串扰到高电平程序烧录成功后不运行表现是Keil提示下载完成但板子没有任何反应。资料板一般会在BOOT0上串一颗10kΩ下拉电阻验证时万用表量BOOT0电压应为0V。4.2 烧录ST-Link接线与no stm32 target found排查烧录用STM32 ST-LINK Utility比Keil更直观。打开HEX文件Target-Connect连上目标板点下载即可。如果ST-Link和目标板没连上会弹出经典的“error: no stm32 target found! if your product embeds debug authentication, pl...”之类的错误提示。出现这个报错别急着怀疑芯片坏了按顺序查四件事SWDIO和SWCLK两个引脚是否对调这是最常见的低级错误。目标板是否独立上电。ST-Link的3.3V输出功率有限带了大屏之后带不动改成外部5V转3.3V供电。SWD四条线是否共地。共地线断掉会导致连接时好时坏表现为多插几次偶尔能连上。芯片是否开了读保护。RDP level 1以上时Utility会拒绝连接此时可以尝试Target-Option Bytes里执行Unlock但会擦除Flash。在Keil里调试器选ST-Link DebuggerSettings里能看到唯一的IDCODE才算连上。连接正常后把Flash Download标签页勾上Reset and Run下载完程序自动运行省去每次手动按复位键。HAL库工程经常遇到的“Flash Timeout”报错多半是复位模式选择问题把Debug页面Reset类型从Normal改成Hardware Reset即可解决。4.3 串口打印重定向printf到USART1调试驱动阶段OLED显示的是经过处理的值不方便看原始数据先把串口打通。重定向printf的常用写法是int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; } /* 主循环中 */ printf(temp%.1f ir%lu hr%lu\r\n, (double)temperature, ir_value, hr_value);重定向依赖Keil的MicroLIB。如果Options-Target里没有勾选Use MicroLIBprintf会链接到半主机模式程序卡在调试器里跑不动。串口助手能看到每一行对应一次采样波特率必须和CubeMX配置一致115200 8N1。有些电脑安装CH340驱动后设备管理器里会出现带感叹号的“stm32 virtual com port”右键更新驱动手动指定路径指向CH340驱动目录即可恢复这不是板子问题。4.4 实物调试顺序先最小系统、再三路数据、后算法按下面的顺序动手一次把问题定位死。先确认3.3V和地之间万用表读数在3.2~3.4V之间然后烧一个点灯程序确定下载链路是通的再烧串口打印程序确定发送通道正常接着依次初始化I2C1读MAX30102的器件ID寄存器0xFF应返回0x15初始化I2C2点亮OLED最后才把三路数据合到一起跑心率算法。把常见故障整理成表排查时按行对号入座现象最可能原因检查方法温度打印-0.5或125DS18B20上拉缺失或GPIO没配成开漏万用表量DQ静态电压应接近3.3V心率一直为0模式寄存器没生效或FIFO读写指针差值恒为0读FIFO写指针寄存器0x04观察是否变化I2C通信偶发失败多个模块上拉并联后等效阻值过小示波器看SDA波形上拉电阻统一换4.7kΩOLED不亮但串口有数据I2C地址0x3C/0x3D配反写一个扫描程序枚举0x02~0x7E地址指针差值检查是定位心率数据为空的关键手段MAX30102的FIFO写指针和读指针可读正常采集时差值应在1到样本数之间变化。若差值恒为0说明芯片没有采样回到模式寄存器排查确认芯片处于SpO2模式而非待机模式。调试中一个效率很高的做法把原始红外ADC值按行打印用串口助手的文件保存功能导出放到Excel里画折线图。波形有起伏、周期在0.6~1.2秒之间说明传感器和读取链路都正常剩下的问题只在算法侧。5. 进阶技巧校准、低功耗与可信度验证5.1 温度标定与心率校准两表对照法体温部分拿标准医用电子体温计对照测5组数据记录装置读数与标准值的平均偏差显示时统一加上这个偏移。DS18B20的偏差通常是固定值只要探头没有泡水或虚焊不会时有时无。校准后再加一道限幅滤波两次读数差超过0.3℃就丢弃新值防止单次毛刺直接把36.8℃弹到37.5℃。心率部分不必花太多时间在校准上。静止场景下对着手机秒表数自己脉搏采集60秒实际脉搏数对比装置显示值。如果装置偏高多半是波峰检测把手指微抖也算了一拍把动态阈值的60%上调到70%同时丢弃间隔小于250ms的相邻波峰效果立竿见影。5.2 低功耗与数据记录从演示级到“可用级”毕设答辩现场基本都是充电宝供电但如果想体现电池续航能力可以把外设轮询改成事件驱动进入STOP模式RTC闹钟每10秒唤醒一次采集一轮后立即回STOP或者用MAX30102的INT引脚作为外部中断唤醒源检测到手指放上再开始采样。前一种方案更可控功耗大约在毫安量级一块1000mAh锂电池能撑很久。加一个MicroSD卡模块SPI模式做顺序写文本每行记录时间戳、温度、心率和原始红外ADC值演示时不依赖串口线答辩材料里也能展示“数据可追溯”。SD卡初始化时SPI时钟先降到400kHz以下等CMD0响应0x01之后再切到18MHz。注意SD卡供电必须是3.3V不要直接接5V。5.3 数据展示OLED之外的上位机方案展示层除了0.96寸OLED更直观的方案是通过串口把数据发给PC端上位机做成实时曲线。上位机可以用C#或Qt写串口协议建议固定为“帧头长度温度心率CRC8”的结构帧头0xAA 0x55长度固定7字节CRC8用查表法接收端校验通过才刷新UI。这套协议篇幅不大但能在答辩时说明数据完整性和抗干扰设计性价比很高之前用过的很多毕业设计都采用了类似方案。5.4 可信度验证把三个渠道的数据放在同一时间轴最后一步很关键但容易被跳过把传感器读数、串口打印的原始波形、手按秒表数的真实脉搏放在同一时间轴对比。具体做法是短按一个按键触发“校准模式”装置连续打印20秒原始波形你同步用秒表记录自己脉搏结束后取中间10秒数一下波形里波峰个数和手数脉搏个数做误差对比误差在±3bpm以内说明装置可信度足够。温度校准类似用体温计同时测5分钟取均值差判定。这些数据整理成表放进论文附录比任何截图都有说服力。真到这一步整个工程才算真正握在手里。哪天把滤波器窗口调大导致心率响应变钝或者换了手指厚度的测试者导致波峰检测失灵直接用这套对照方法一小时就能定位到底该调阈值还是调滤波系数。本文还有配套的精品资源点击获取