简介:本资源是一套完整的基于STM32的脉搏电子血压计高分毕业设计项目,面向电子信息、嵌入式系统及相关专业本科生,解决课程设计、期末大作业及毕业设计中硬件驱动开发、生理信号采集与算法实现等核心实践难题。压缩包共161个文件,含35个C源码(主控逻辑与传感器驱动)、34个H头文件(外设配置与函数声明)、22个编译中间文件(.d)、21个调试符号文件(.crf)及Keil工程文件(uvproj、uvopt等),总大小仅2.22MB,结构规范、模块清晰,涵盖ADC采样、FFT脉搏分析、袖带加压控制、OLED人机交互等完整功能链。已有182人学习下载,项目经实机严格调试,代码全程中文注释,含系统初始化、血压计算模型、数据存储与显示等关键模块,可直接部署运行,无需额外适配,特别适合嵌入式初学者快速掌握STM32外设协同开发与医疗电子系统集成方法。 做毕业设计选嵌入式方向,最容易“出彩但不作死”的题目,其实不是追热门的人工智能,也不是看起来复杂的四轴无人机,而是一台踏踏实实能做出来、能测出真实数据的医用小设备——基于STM32的脉搏电子血压计。
这项目麻雀虽小五脏俱全:压力传感器、信号调理电路、ADC采样、数字滤波、症状识别算法、气泵阀控制、人机交互,一个不落。STM32在这类项目里几乎是标配,网上相关源码也确实不少,但很多同学下载下来根本跑不通,或者跑通了测出来的数据完全没法看。今天就把我从整体架构到信号处理算法,再到调试阶段踩过的坑,完整捋一遍。这篇东西既适合正在准备毕设答辩的在校生,也适合想低成本入门嵌入式医疗设备开发的工程师参考。
1. 这个项目到底在做什么:一条完整测压链路的设计思路
1.1 示波法测血压的原理,以及为什么选它
电子血压计的核心不是“测血压”,而是“通过袖带压力变化反推血压”。目前主流方案有两种:听诊法(柯氏音法)和示波法(振荡法)。
听诊法在临床上被当作金标准,但它是靠人耳分辨袖带放气过程中血流冲击血管壁产生的“柯氏音”来判定收缩压和舒张压。难点在于需要拾音传感器、需要精确找到第一声和最后一声音,算法上极度依赖特征检测,做不好就容易误判。毕设里做这个方案风险不小。
示波法的思路完全不同:袖带在放气过程中,内部压力不仅会缓慢下降,还会叠加一个随心跳搏动的小信号。这个“脉搏波”的幅度会随着袖带压力变化走一条先增后减的曲线。当袖带压力高到完全压闭血管时,脉搏波幅值几乎为零;压力逐渐降低时,血流冲击血管壁的力度增大,脉搏波幅度上升;当袖带压力正好等于平均动脉压时,血管壁处于最“松弛”状态,脉搏波幅度达到峰值;压力继续下降后,血管完全开放,脉搏波幅度减小并趋于稳定。
通过分析脉搏波包络线,可以找出三个关键点:包络峰值对应的压力是平均动脉压,峰值之前幅度上升到最大值的50%左右对应的压力是收缩压,峰值之后幅度下降到最大值的70%左右对应的压力是舒张压。
这个算法看似简单,但做出来之后,我最大的感受是:它不需要高精度拾音传感器,不需要复杂的时频分析,只要压力传感器线性度足够、ADC采样率够、放气速度控制得当,就能得到稳定可读的结果。对于嵌入式新手来说,这几乎是性价比最高的方案。
1.2 STM32在这个项目里的角色定位
STM32扮演的角色通俗讲就是“大脑加管家”。它既要控制气泵充气、电磁阀放气,又要以固定频率读取压力传感器的ADC值,还要在采集过程中实时处理数据,最终把测量结果送到OLED屏幕上显示。
为什么选STM32而不是51单片机或者Arduino?答案很实在:51的处理能力和外设资源太紧张,做20Hz以上的AD采样加上滤波算法就已经很费劲;Arduino虽然上手快,但ADC精度和定时器资源无法精细控制,而且教授和评委对这种“拼积木”式的开发方式通常不太认可。STM32F103系列主频72MHz、12位ADC、多个定时器、丰富的中断资源,应付这种数据采集加实时处理的任务绰绰有余,而且资料极为丰富,遇到问题一搜就能找到答案。
我用的是STM32F103C8T6这颗经典芯片,成本低、引脚够用、功耗也不高。核心板加外围电路一共不到三十块钱成本,对毕设预算非常友好。
2. 硬件部分:传感器选型与驱动电路,踩坑从这里开始
2.1 压力传感器的选型:国产与进口的取舍
血压计对压力传感器的要求集中在两方面:量程和线性度。人体血压范围一般在0到200mmHg,袖带充气时压力会更高一些,所以我选择量程在0到300mmHg以上的传感器比较稳妥,留出余量。
市面上常见的方案有几种,我放在一张表里对比:
| 传感器型号 | 输出方式 | 量程 | 优点 | 缺点 |
|---|---|---|---|---|
| MPX5050GP | 模拟电压输出 | 0~375mmHg | 集成度高、线性度好、经典方案 | 价格略高、体积稍大 |
| XGZP6847 | I2C/模拟输出可选 | 0~300mmHg | 国产、便宜、容易买到 | 一致性一般,需要标定 |
| 血压计专用传感器模块 | 模拟电压输出 | 0~300mmHg | 厂家已校准、直接可用 | 成本较高,资料少 |
我最后选了MPX5050GP,原因是它的输出电压范围是0.2V到4.7V,对应0到375mmHg,线性误差在全量程范围内不超过2.5%,而且自带温度补偿。这个传感器的原理是内部集成了一个压阻式压力桥和放大电路,外部供电5V即可,输出阻抗较低,可以直接驱动ADC输入引脚。
实际画板子的时候有个小经验:压力传感器的气管接口方向和PCB布局方向一定要提前规划好,不然装进外壳之后气嘴方向和袖带气管对不上,就得重新飞线,很狼狈。
2.2 气泵、电磁阀、电源:容易被忽视的“外围设备”
很多人把注意力放在传感器和单片机上,结果三个月后卡在气泵上。这个项目里气泵和电磁阀才是真正的“耗材级”部件,选型不当直接导致整机测量失败。
气泵负责给袖带充气。我用的是一种微型隔膜泵,额定电压5V,最大输出气压能到300mmHg以上。启动电流比较大,能达到400mA左右,所以不能用STM32的GPIO直接驱动,必须通过三极管或MOS管扩流。我用的方案是NPN三极管2N2222加续流二极管,配合一个PWM通道控制充气速度。
电磁阀负责放气。常见的有两种:两通开关阀和比例阀。开关阀只有“开”和“关”两种状态,控制简单,但放气速度没法精确调节;比例阀可以通过PWM占空比控制开度,从而控制放气速率,但价格贵一些。我一开始图便宜用了开关阀,结果放气速度太快,一个心跳周期内压力降幅太大,包络线采样点稀疏到无法正确找到峰值。后来换成比例阀,配合PWM慢慢放气,数据才稳定下来。
电源树也要引起重视。整个系统里有数字电路(STM32)、模拟电路(传感器放大)、感性负载(气泵、电磁阀),如果不做隔离,气泵启动瞬间会把电源电压拉低,导致模拟电路输出抖动。我的做法是:主电源用两节18650锂电池串联经过降压模块稳定到5V,再用AMS1117把5V降到3.3V给STM32供电。模拟部分和数字部分的地线在PCB上单点汇合,避免形成地环路。
2.3 信号调理电路:传感器的微弱信号如何进ADC
压力传感器输出的模拟电压虽然已经是放大后的,但还是要经过一级低通滤波才能送入STM32的ADC。原因是传感器输出中包含了高频噪声,直接进ADC会导致采样值跳动明显。
我用了一阶RC低通滤波,R取1kΩ,C取0.1μF,截至频率约1.6kHz。这个频率远高于脉搏波频率(正常人心率范围是0.5Hz到3Hz),所以对有用信号完全没有影响,但对高频毛刺的抑制非常明显。如果想做得更讲究,可以加一级由运算放大器构成的二阶低通滤波器,比如Butterworth结构,截至频率设置在20Hz左右。
ADC参考电压也很关键。STM32内部有VREF引脚,如果直接用3.3V电源做参考电压,电源纹波会被直接引入到转换结果中。我使用了一个高精度基准电压源芯片REF3033,把3.3V参考电压做得干干净净。这一改动效果非常显著,ADC的跳动范围直接从几十个LSB降到了两三个LSB。
ADC配置方面,我使用定时器触发ADC采样,频率设置为200Hz,也就是每5ms采一次。这个频率经过实际测试,既能捕捉到脉搏波的细节形状,又不会让CPU忙于处理中断。采样使用了DMA方式,ADC转换完成的数据自动搬运到内存数组中,不占CPU时间。这样主循环可以专心处理控制逻辑和算法运算。
3. 软件实现:从ADC采集到血压算法全流程
3.1 程序整体架构与状态机设计
嵌入式程序最忌讳把所有逻辑堆在一个大while循环里,血压计程序尤其如此。我用一个有限状态机来组织整个测量流程,状态之间的切换非常清晰。
状态定义如下:
- IDLE(空闲):等待启动按键,此时气泵和电磁阀都关闭
- INFLATE(充气):启动气泵,持续检测袖带压力,升压到预设值后停止
- MEASURE(测量):关闭气泵,打开比例阀控制放气速度,同步记录压力和脉搏波
- FINISH(结束):完成测量,计算并显示结果,快速放气
- ERROR(错误):检测到超时或异常压力,安全放气后回到空闲状态
状态机的好处在于:每个状态内的代码职责清晰,后续加功能(比如存储多组记录、蓝牙上传数据)时不会牵一发动全身。而且答辩的时候老师问起代码结构,状态机这个思路本身就是评分亮点。
充气预设值是动态计算的。我设定默认目标压力为180mmHg,但在充气过程中会实时检测脉搏波是否开始出现。如果压力还没到180mmHg就已经出现明显的脉搏波,说明用户血压可能较低,可以提前停止充气,避免袖带过度压迫用户手臂。这是从商用血压计上学到的策略,虽然不是所有电子血压计都这么做,但确实能提升使用体验。
3.2 数字滤波与脉搏波提取
ADC采到的原始数据是一个一维序列,里面混合了两种成分:一条缓慢下降的基线,也就是袖带压力本身;还有一条叠加在基线上的波动,也就是脉搏波。要把这两者分离,我用的是“低通滤波加减法”的思路,简单有效。
第一步,用滑动平均滤波对原始ADC序列做平滑处理,窗口大小设为5个点(对应25ms),用来去除随机噪声。第二步,再用一阶低通数字滤波器提取基线,滤波系数a取0.2,这样基线跟随比较快,能实时反映袖带压力变化。第三步,原始信号减去基线信号,得到的就是包含脉搏波的交流分量。
脉搏波虽然出来了,里面还是混着一些呼吸引起的慢波和肌肉抖动的干扰。算血压之前还需要再做一次带通滤波。我用了一个简单的二阶IIR带通滤波器,通带设置为0.5Hz到8Hz。下限滤掉呼吸干扰,上限滤掉高频肌肉伪差。
说到这里必须提醒一下:IIR滤波器的系数计算对新手来说有点门槛,别自己硬推导,直接用MATLAB的fdatool或者在线滤波器设计工具生成系数。我当年直接在MATLAB里设计好系数,然后把它填入C语言数组里,省了不少时间。注意浮点运算在STM32F103上虽然能跑,但速度慢,而且占用Flash很大。我的做法是先用浮点算好系数,再把整个滤波器实现改为定点化,牺牲一点动态范围换取运行速度。
3.3 示波法算法的具体实现与参数标定
脉搏波提取出来之后,接下来的工作就是找包络线。我在代码里维护了一个结构体数组,每个元素包含一个压力值和对应的脉搏波幅度。具体实现是:对每个放气阶段的脉搏波信号取极值,把相邻峰值的差值作为该压力点的脉搏波幅度。
关键算法代码如下,这个函数直接完成了从压力数组和幅度数组到收缩压、舒张压的映射:
// 示波法核心算法:寻找包络峰值并计算收缩压与舒张压 // pressure[]: 袖带压力数据,单位mmHg // amplitude[]: 对应压力点的脉搏波幅度 // count: 数据点数量 // sbp: 输出收缩压 // dbp: 输出舒张压 uint8_t oscillometric_alg(uint16_t *pressure, uint16_t *amplitude, uint16_t count, uint16_t *sbp, uint16_t *dbp) { uint16_t max_idx = 0; for (uint16_t i = 1; i < count; i++) { if (amplitude[i] > amplitude[max_idx]) { max_idx = i; } } // 平均压直接取包络峰值对应压力 uint16_t map_pressure = pressure[max_idx]; // 收缩压:从峰值往前找,幅度首次降到峰值50%的位置 uint16_t sbp_thr = amplitude[max_idx] / 2; uint16_t sbp_idx = 0; for (int i = max_idx; i > 0; i--) { if (amplitude[i] <= sbp_thr) { sbp_idx = i; break; } } // 舒张压:从峰值往后找,幅度首次降到峰值70%的位置 uint16_t dbp_thr = amplitude[max_idx] * 7 / 10; uint16_t dbp_idx = 0; for (uint16_t i = max_idx; i < count; i++) { if (amplitude[i] <= dbp_thr) { dbp_idx = i; break; } } *sbp = pressure[sbp_idx]; *dbp = pressure[dbp_idx]; return 0; }这段代码隐藏了一个细节:阈值系数0.5和0.7不是随便写的。它们对应的是商用血压计中最常用的固定比例系数,但不同人群的血压特征不一样,比如老年人血管弹性较差,包络比阈值应该适当调整。我在实际调试中发现,0.5和0.7的默认值在20到30岁的测试者身上表现良好,但对中老年测试者,收缩压阈值取0.45更准确。
标定的方法是:用一台水银血压计或商用电子血压计作为参照,分别测量同一人多个时间点的血压,记录自己的算法输出和参照输出的差值,然后做线性回归,修正阈值参数。毕设论文里如果能把这个标定过程写进去,内容的含金量会立刻不一样。
算法还有一个边界情况要处理:如果放气过快,包络线峰值附近的采样点太少,有可能峰值被漏掉或者位置偏移。所以我在算法前会做一次插值处理,用三次样条把脉搏波幅度序列插值成两倍密度的数组,这样峰值定位的精度就有保障了。
4. 调试日记:我在这个项目里踩过的坑与排查思路
4.1 ADC数值飘得离谱,先检查电源和地
第一次把硬件焊好上电的时候,我遇到的情况是:传感器静止不动,ADC读数却在500到1200之间大幅跳动。当时第一反应是传感器坏了,后来用万用表量了一下传感器输出引脚,发现模拟电压确实也在跳,但幅度没有ADC读数那么夸张。
排查了一圈,问题出在两个地方。第一个是电源:我用USB供电,USB的5V经过AMS1117降到3.3V,但AMS1117后面没有加足够大的滤波电容。气泵不工作时还好,气泵一转,电流骤增,电源上就会叠加一个很大的纹波。解决方案是在3.3V和GND之间加了一个470μF的电解电容和一个0.1μF的陶瓷电容,纹波立刻降下来了。第二个是参考电压:直接用VDD做VREF,芯片内部没有隔离,电源纹波直接进入ADC。解决方案是换用REF3033基准源,这个改动效果立竿见影。
所以调试ADC类的项目,先别急着调软件,拿示波器看看电源轨的纹波、看看传感器输出端是否有毛刺,往往能省下几个小时的时间。
4.2 脉搏波时有时无,问题可能出在袖带和放大
软件写好之后,第一次完整测量发现脉搏波提取结果非常不稳定:有时候能看到明显的脉搏波,有时候整段数据清一色都是直线。我仔细检查了代码逻辑,排除了算法问题,最后发现是袖带的问题。
电子血压计袖带和普通医用袖带略有区别,里面其实是一根连接气管的橡胶气囊。气囊通过气管与压力传感器相连,袖带绑在手臂上充气后,气囊内的空气压力变化会通过气管传到传感器。如果袖带绑得太松,气囊和手臂之间存在空隙,血管搏动就无法有效地传递到气囊内部,导致传感器只能测到气压而测不到搏动。
解决方案也简单:绑袖带时确保气囊中心正好压在肱动脉上方,松紧程度以“能插进一根手指”为准。这个经验需要在操作指南里写清楚,因为测试者的使用方式直接影响测量结果。
另外,如果传感器输出经过放大电路之后脉搏波幅度仍然很小,比如只有几十毫伏,也不要急着改放大倍数。先用示波器确认传感器直接输出端有没有搏动信号,如果有,说明放大电路的地线布局有问题;如果没有,说明传感器连接的气路有漏气,或者袖带放置不正确。
4.3 测量结果与商用血压计对比偏差大,参数怎么调
有一段时间,我的设备测出来的收缩压总是比商用血压计高15到20mmHg。这种整体偏差不是随机误差,而是系统误差,基本可以确定是算法参数的偏差。
按照前面讲的方法,我找了5个测试者,分别用我的设备和商用设备测了30组数据,然后跑了一次简单的统计分析。发现如果收缩压阈值从0.5降到0.45,偏差明显缩小到±5mmHg以内。舒张压阈值从0.7改成0.72后,也获得一定改善。
调参的过程不复杂,但需要耐心。我写了一个上位机辅助脚本,通过串口把每次测量的压力序列和脉搏波序列导出来,在Python里离线分析,这样能快速迭代算法参数,而不用每次烧录代码重新测一遍。如果你用的是Keil开发环境,用串口打印原始数据再放到脚本里分析,完全可以复刻这个流程。
还有一个容易忽略的问题:测量时用户的手臂位置。如果手臂与心脏不在同一高度,水银血压计的读数本身就会出现偏差,电子血压计也是同理。我最初测试时手放在桌上和手举高贴身,读数差异能达到10mmHg以上。确保所有对比测试的姿势一致,数据的可比性才成立。
这里列出我在调试后期建立的一个排查速查表,方便对照:
| 问题现象 | 可能原因 | 检查方法 | 解决措施 |
|---|---|---|---|
| ADC数值跳动大 | 电源纹波,VREF不稳 | 示波器查看3.3V纹波 | 加大滤波电容,改用基准源 |
| 脉搏波幅度小 | 袖带漏气或位置不对 | 检查气路是否密封 | 调整袖带,更换气管 |
| 脉搏波不定期消失 | 放气速度过快 | 检查放气阀控制波形 | 改用PWM控制比例阀 |
| 收缩压整体偏高 | 算法阈值不合适 | 与标准设备对比记录 | 降低收缩压阈值 |
| 测量结果不稳定 | 测量姿势不一致 | 规范测量动作 | 固定姿势,连续测3次取平均 |
| 充气超时 | 气泵老化或气管漏气 | 听气泵声音,检测压力上升速度 | 更换气泵,重新连接气路 |
5. 毕设资料整理清单:让这份代码真正成为你的加分项
5.1 源码工程应该怎么组织
很多同学拿到项目直接Ctrl+C、Ctrl+V,到头来自己都没读过一遍代码,这种方式即使通过了查重也过不了答辩。真正的做法是理解每一段关键代码的用途,然后重构工程目录。
我建议的工程目录结构如下:
Project/ ├── Core/ │ ├── Inc/ // 头文件 │ └── Src/ // 主函数、中断、系统时钟配置 ├── Hardware/ │ ├── Sensor.c // 压力传感器驱动 │ ├── Pump.c // 气泵控制 │ ├── Valve.c // 电磁阀控制 │ ├── OLED.c // 显示屏驱动 │ └── Key.c // 按键扫描 ├── Algorithm/ │ ├── Filter.c // 数字滤波实现 │ └── BP_Calc.c // 示波法血压算法 ├── Data/ │ └── history.c // 数据存储 └── main.c代码风格上,每个函数前面都写了简洁的函数说明和输入输出参数说明,全局变量尽最大努力减少,模块间的接口用函数封装。这样做不仅答辩时加分,后续扩展蓝牙模块或者增加WiFi传输时也会方便很多。
5.2 从原理图到论文:一份完整毕设资料的构成
一个能拿高分的毕设,交付的绝不仅仅是能跑的代码。完整的资料包至少包含六项:源码工程、原理图、PCB文件、毕业设计说明书、演示视频、答辩PPT。
原理图我用的Altium Designer画的,但如果你更熟悉立创EDA也可以,关键是所有元器件都标注清楚封装和数值,方便别人照着购买和焊接。PCB走线要特别注意:模拟信号走线要远离气泵和电磁阀驱动的功率走线,晶振下方不要走任何信号线。
毕业设计说明书的结构,我建议这样组织:第一章绪论写国内外电子血压计研究现状;第二章讲示波法测量原理和系统总体设计;第三章写硬件电路设计,包括传感器选型和驱动电路;第四章写软件设计,包括滤波算法和核心血压判定算法;第五章写系统测试与误差分析。论文里贴几张示波器抓到的脉搏波图和OLED显示的测量结果图,很有说服力。
答辩时老师最喜欢问的几个问题包括:为什么选示波法不选听诊法?压力传感器量程为什么选这个?滤波器的截止频率是怎么确定的?算法中0.5和0.7比例系数为什么是这个值?这些问题我在正文里都已经给出了答案,把原理吃透,答辩时就能稳住。
5.3 演示环节的准备:让评委两分钟看懂你的项目
毕设答辩的演示环节时间有限,我的做法是提前录一段两分钟左右的视频:把袖带绑在测试者手臂上,按下开始键,能看到OLED屏上显示“充气中”,然后是一段实时压力变化曲线,最后显示收缩压、舒张压和心率。同时用一台商用血压计同步测量,做一次直观对比。这个对比的数据会直接展示误差范围,比任何口头说明都有说服力。
如果现场条件允许,也可以让评委实际操作一次。不过要注意血压计是医疗设备,需要提前说明使用禁忌和注意事项,并且征得对方同意后再操作。
写在最后,关于这个项目的一点个人体会
做完这个项目之后,一个明显的感受是:嵌入式医疗设备真正的技术含量并不在硬件本身,而在信号处理与算法参数取舍这些“看不见”的地方。血压计看起来简单,但要做到准确、稳定、可重复,需要下不少功夫。
另外想多说一句,做毕设千万不要只做个“代码搬运工”。把示波法原理彻底弄懂、亲手跑过一遍数据、体验过从波形异常到找到原因的过程,这些经历才是毕设真正留给你的东西。代码可能过两年就忘了,但排查问题的思路和对一个完整闭环系统的掌控感,会伴随你进入下一阶段的工作或学习。以后再做类似的设备,哪怕是心率计、血氧仪,你会发现核心的思路是相通的。
本文还有配套的精品资源,点击获取