简介:面向嵌入式开发初学者与医疗电子爱好者的一套智能电子血压计完整设计资料,围绕单片机实现血压采集、计算与无线传输的典型方案展开。压缩包共303个文件、约6.74MB,以55个c源文件、47个h头文件与75个obj中间文件为主体,辅以asm汇编启动代码、hex烧录文件、map映射表及工程配置与说明文档,另附一个配套安卓APK,便于对照固件与手机端联调。内容覆盖设计概要、示波法测量原理说明、压力传感器与气泵气阀等硬件设计、单片机固件源码分析以及APP数据存储与趋势查看功能,可帮助读者理清从袖带压力采集到收缩压、舒张压计算的完整链路。已有987人学习,适合作为课程设计、毕业项目或产品原型的参考底稿。
1. 从一块裸板到能测血压的样机:这套资料解决的是什么问题
很多做嵌入式的朋友都动过做电子血压计的念头,但真正动手时才发现,难点根本不在写几行代码。气泵什么时候打气、气阀什么时候放气、压力传感器读数怎么滤波、示波法包络怎么拟合出收缩压和舒张压,这一整套软硬件协同的细节,才是卡住大多数人的地方。这套智能电子血压计设计资料,就是围绕“单片机 + 压力传感 + 气路控制 + 算法处理”这条主线整理的一份完整工程参考,包含原理图、程序框架和算法思路,适合正在做课程设计、毕业设计,或者想给自家健康类产品补上血压测量能力的开发者。它不追求做成医疗器械级产品,而是把“能跑通、能测出数、能看懂原理”这件事讲透,让你拿到手就能对照着搭出一台能工作的样机。
2. 系统架构与硬件选型:为什么是这套组合
2.1 整体信号链路拆解
电子血压计的本质,是把袖带里的压力变化转换成一条随时间变化的压力曲线,再从这条曲线上把心脏搏动带来的微小振荡提取出来。整条链路可以拆成四段:气路执行、压力感知、信号调理、单片机处理。
气路执行部分由气泵和电磁阀组成,气泵负责给袖带充气,电磁阀负责按节奏放气。压力感知部分用压力传感器把袖带内的气压转成电压信号。信号调理部分做放大和滤波,把叠加在直流气压上的微弱交流振荡分离出来。单片机则负责控制充放气时序、采集 ADC、跑滤波和包络算法,最后把结果送到显示或串口。
这套资料里,单片机选的是常见的 8 位或 32 位通用型号,压力传感器用的是带模拟输出的集成式传感器,气泵和阀通过三极管或 MOS 管驱动。选这套组合的理由很实际:成本低、资料多、调试门槛低,出了问题容易定位。血压计不是靠堆料堆出来的,靠的是时序和算法配合,硬件只要稳定可靠就够。
2.2 关键器件与接口分配
下面这张表是我根据资料里的原理图整理的核心器件与接口对应关系,实际动手时可以直接照着核对引脚:
| 模块 | 典型器件 | 接口方式 | 作用 |
|---|---|---|---|
| 主控 | 通用单片机 | — | 时序控制、ADC 采集、算法运算 |
| 压力传感器 | 集成式模拟输出传感器 | ADC 通道 | 采集袖带气压 |
| 气泵 | 微型直流气泵 | GPIO + 驱动管 | 充气 |
| 电磁阀 | 两位两通阀 | GPIO + 驱动管 | 分级放气 |
| 显示 | 段码屏或小尺寸点阵屏 | SPI/I2C/并口 | 显示压力与结果 |
| 按键 | 独立按键 | GPIO | 启停、模式切换 |
| 电源 | 电池 + 稳压 | — | 供电 |
接口分配上有个细节值得注意:压力传感器的模拟输出一定要接到单片机 ADC 输入阻抗匹配良好的通道上,中间不要串大电阻,否则采样值会飘。气泵和阀的驱动管基极要加限流电阻,线圈两端要并续流二极管,这是保护单片机 IO 的基本操作,资料里的原理图已经画出来了,照着抄就行。
2.3 软件分层与主循环设计
软件部分,资料给的是一个前后台结构:主循环轮询按键和状态机,定时器中断里做 ADC 采样和时序推进。这种结构在血压计这种实时性要求不算极端的场景里完全够用,而且比上 RTOS 更容易调试。
状态机大致分几个状态:待机、充气、稳压、放气测量、计算、显示。每个状态有明确的进入条件和退出条件,比如充气到设定压力(常见是 180 到 200 mmHg 之间)就切到稳压,稳压几十毫秒后开始按固定步长放气,放气过程中持续采样。下面是一段状态机骨架的示意代码:
typedef enum { STATE_IDLE = 0, STATE_INFLATE, STATE_HOLD, STATE_DEFLATE, STATE_CALC, STATE_DISPLAY } bp_state_t; static bp_state_t g_state = STATE_IDLE; static uint16_t g_pressure = 0; // 当前袖带压力,单位 mmHg static uint16_t g_target = 190; // 充气目标压力 void bp_task(void) { switch (g_state) { case STATE_IDLE: if (start_key_pressed()) { pump_on(); valve_close(); g_state = STATE_INFLATE; } break; case STATE_INFLATE: g_pressure = read_pressure_mmhg(); if (g_pressure >= g_target) { // 到达目标压力 pump_off(); g_state = STATE_HOLD; } break; case STATE_HOLD: delay_ms(50); // 稳压,等气流稳定 g_state = STATE_DEFLATE; break; case STATE_DEFLATE: valve_step_open(); // 按固定步长放气 sample_and_store(g_pressure); // 存压力与振荡幅值 if (g_pressure <= 40) { // 放到下限,测量结束 valve_close(); g_state = STATE_CALC; } break; case STATE_CALC: compute_bp(); // 包络算法求收缩压/舒张压 g_state = STATE_DISPLAY; break; case STATE_DISPLAY: show_result(); g_state = STATE_IDLE; break; } }这段代码的关键不在语法,而在几个参数的取值逻辑。g_target设 190 是留了余量,因为不同人的收缩压差异大,充气不够高会导致包络不完整;放气下限设 40 是为了覆盖舒张压较低的情况。delay_ms(50)的稳压时间不能省,气泵刚停时袖带内气流还在动,立刻采样会引入噪声。放气步长决定了采样点的密度,步长太大包络点太少拟合不准,步长太小测量时间又太长,常见做法是每步 3 到 5 mmHg。
3. 核心算法与采样处理:从压力曲线到血压读数
3.1 压力信号的滤波与振荡提取
原始 ADC 采到的是一条“直流气压 + 微小交流振荡”的混合曲线。直流部分是袖带的基础压力,交流部分才是心脏搏动引起的振荡,幅度通常只有直流分量的百分之几。要把振荡提出来,常见做法是先用滑动平均或低通滤波得到直流基线,再用原始值减去基线得到交流分量。
#define FILTER_LEN 16 static uint16_t buf[FILTER_LEN]; static uint8_t idx = 0; static uint32_t sum = 0; // 滑动平均求直流基线 uint16_t dc_baseline(uint16_t raw) { sum -= buf[idx]; buf[idx] = raw; sum += raw; idx = (idx + 1) % FILTER_LEN; return (uint16_t)(sum / FILTER_LEN); } // 交流振荡幅值 = 原始值 - 基线 int16_t ac_component(uint16_t raw) { return (int16_t)raw - (int16_t)dc_baseline(raw); }FILTER_LEN取 16 是个折中:窗口太短基线跟得太紧,会把振荡也滤掉;窗口太长基线滞后,交流分量会失真。实际调试时这个值要结合采样率一起看,采样率越高窗口可以适当加长。ac_component返回的有正有负,后续求包络时取绝对值或做平方都可以,资料里用的是取绝对值后找峰值。
3.2 示波法包络与收缩压舒张压判定
示波法的核心逻辑是:随着袖带放气,振荡幅度先增大后减小,形成一个包络。包络峰值对应的袖带压力就是平均压,收缩压和舒张压则根据峰值幅度的一定比例去包络两侧找对应压力点。常见比例是收缩压取峰值的 0.5 到 0.6,舒张压取 0.7 到 0.8,具体系数和传感器、气路特性有关,需要实测标定。
typedef struct { uint16_t pressure; // 该采样点的袖带压力 uint16_t amplitude; // 该点的振荡幅值 } envelope_point_t; envelope_point_t env[64]; uint8_t env_cnt = 0; // 放气过程中记录包络点 void sample_and_store(uint16_t pressure) { int16_t ac = ac_component(read_adc()); uint16_t amp = (ac < 0) ? -ac : ac; if (env_cnt < 64) { env[env_cnt].pressure = pressure; env[env_cnt].amplitude = amp; env_cnt++; } } // 找包络峰值并推算收缩压/舒张压 void compute_bp(void) { uint16_t peak_amp = 0; uint8_t peak_idx = 0; for (uint8_t i = 0; i < env_cnt; i++) { if (env[i].amplitude > peak_amp) { peak_amp = env[i].amplitude; peak_idx = i; } } uint16_t map = env[peak_idx].pressure; // 平均压 uint16_t sys_th = peak_amp * 55 / 100; // 收缩压阈值 uint16_t dia_th = peak_amp * 75 / 100; // 舒张压阈值 // 从峰值向高压侧找收缩压,向低压侧找舒张压 // 具体查找逻辑略,按阈值匹配最近点即可 }这里sys_th和dia_th的系数是最需要实测调整的地方。资料里给的是经验值,但每个人的血管特性不同,同一台设备在不同人身上表现也会有差异。我的建议是先用标准血压计做对照,采集若干组数据,把系数往误差最小的方向调。包络点数组开 64 个够用,因为一次完整放气过程通常也就几十个采样点。
3.3 采样时序与抗干扰处理
血压测量对时序很敏感。放气太快,包络点稀疏,拟合不准;放气太慢,测量时间拉长,用户手臂受压不适。资料里的做法是固定步长放气配合固定间隔采样,保证每个压力台阶上都有稳定的读数。
抗干扰方面,除了前面说的滑动平均,还要注意两点:一是采样时刻要避开气阀动作的瞬间,阀开合的机械振动会串进传感器;二是 ADC 采样前做几次丢弃,取稳定后的值。这些细节资料里没有逐行写死,但思路是明确的,实际写的时候在sample_and_store里加个短延时再读就能明显改善。
提示:包络算法的系数没有放之四海皆准的值,一定要用对照设备做标定,别直接抄一组数字就上线。
4. 编译烧录与整机联调:把资料变成能跑的样机
4.1 工程导入与编译环境
资料里的程序是按通用单片机工程组织的,包含启动文件、外设驱动、主逻辑和算法模块。导入时先确认目标芯片型号和工程配置一致,时钟频率、ADC 参考电压、GPIO 复用这几项最容易对不上。编译前把驱动层的引脚定义改成你实际板子的接线,这一步不做,烧进去也是白跑。
# 以常见命令行编译流程为例,具体工具链按芯片型号替换 make clean make all # 产物一般在 build/ 或 output/ 目录下 ls build/*.hex build/*.bin编译报错先看是不是头文件路径没配、芯片型号宏没定义。链接阶段报空间不足,多半是算法里开了大数组,把包络数组从 64 降到 32 或者改用压缩存储就能过。
4.2 烧录与上电自检
烧录工具按芯片选,串口 ISP 或调试器下载都行。烧完先别急着接气泵,做一次上电自检:看串口有没有正常打印、ADC 读数是否随手指按压传感器而变化、按键响应是否正常。这三项过了,再接气路。
气路连接顺序是:气泵出气口接袖带,袖带另一端接压力传感器和气阀。接头处一定要扎紧,漏气是新手最常见的翻车点,表现为充气充不到目标压力或者放气曲线异常平缓。
4.3 联调步骤与结果验证
联调按“空载—假负载—真人对照”三步走。空载时观察充放气时序是否符合预期,用串口把压力和振荡幅值打出来画曲线。假负载可以用一个密闭容器代替袖带,验证包络形状是否合理。最后找对照血压计做真人测量,比较读数差异。
// 调试期打开串口输出,方便看曲线 void debug_dump(uint16_t pressure, int16_t ac) { printf("P=%u,AC=%d\r\n", pressure, ac); }串口数据导出来用表格软件画个散点图,包络是不是一个漂亮的单峰,一眼就能看出来。如果包络出现双峰或者毛刺,先查气路漏气和阀动作干扰,再查滤波窗口参数。
5. 避坑与常见问题排查
5.1 充气充不到目标压力
现象是气泵一直转,压力读数涨到某个值就不动了。原因通常是气路漏气或者气泵驱动能力不足。先检查袖带和接头有没有松脱,再量气泵两端电压是否达到额定值。驱动管选型偏小也会导致带载后压降大,换更大电流的管子即可。
5.2 包络曲线杂乱无峰值
现象是放气过程中振荡幅值忽大忽小,找不到明显峰值。原因多半是滤波窗口和采样率不匹配,或者采样时刻撞上了阀动作。把采样点挪到阀动作之后几十毫秒,再适当调整FILTER_LEN,曲线会干净很多。
5.3 测量结果重复性差
同一人连续测几次,读数差十几个 mmHg。原因可能是袖带绑法不一致、放气步长太大、或者包络系数没标定。固定袖带位置和松紧,把放气步长调细,再用对照设备做一轮系数标定,重复性会明显改善。
5.4 显示正常但串口无输出
现象是屏幕有数,串口助手收不到数据。原因一般是串口引脚复用没配对,或者波特率与上位机不一致。核对原理图上 TX/RX 接到哪个引脚,确认工程里复用配置和波特率设置,两边对上就能通。
5.5 上电后气泵常转不停
现象是一上电气泵就转,按键无反应。原因通常是驱动管基极被拉高,或者状态机初始状态没设对。检查驱动电路有没有上拉电阻导致误触发,确认g_state初值是STATE_IDLE且启动条件确实由按键触发。
6. 二次开发改造:把样机往产品方向推一步
拿到这套资料,多数人不会停在“能测”这一步,而是想改成自己能用的东西。改造方向我按投入产出比排一下,供参考。
| 改造方向 | 改动量 | 收益 | 注意点 |
|---|---|---|---|
| 增加测量结果存储 | 小 | 可回看历史 | 注意存储介质寿命 |
| 换更大屏幕显示波形 | 中 | 调试直观 | 刷新率别拖慢采样 |
| 加蓝牙上传 | 中 | 数据可外传 | 协议栈占资源 |
| 算法系数自适应 | 大 | 个体差异更小 | 需要足够标定数据 |
以“算法系数自适应”为例,思路是在设备里存一组默认系数,同时留一个标定模式:进入标定模式后,用户配合对照设备测几组,程序根据差值微调sys_th和dia_th的系数并保存到非易失存储。这样同一套硬件在不同人身上表现会更稳。实现上要注意标定数据的有效性判断,差值过大时不要盲目写入,避免把系数带偏。
// 标定模式下的系数微调示意 void calibrate(int16_t ref_sys, int16_t ref_dia, int16_t meas_sys, int16_t meas_dia) { int16_t err_sys = ref_sys - meas_sys; int16_t err_dia = ref_dia - meas_dia; if (abs(err_sys) < 20 && abs(err_dia) < 20) { // 合理范围内才调整 g_sys_coef += err_sys / 10; g_dia_coef += err_dia / 10; save_coef_to_flash(g_sys_coef, g_dia_coef); } }验证改造是否有效,别只看单次读数,要连续测多组、覆盖不同压力区间,看误差分布有没有收窄。我自己的习惯是每次改完算法,先跑一轮固定数据集回放,确认包络判定逻辑没被改坏,再上真人测试。血泪经验是:算法改动一定要留回退开关,不然调着调着就忘了哪版是对的。希望这套资料的拆解能帮你少走几步弯路,把样机稳稳跑起来。
本文还有配套的精品资源,点击获取