简介:以单片机为控制核心的电子血压计课程设计全套方案,完整实现血压与体温二合一测量,面向电子信息类学生课设、毕设或动手实践。方案涵盖压力传感器与温度传感器的信号采集、处理模块、显示模块及电源管理,通过按键切换血压/体温显示。压缩包共22个文件,以Keil工程文件为主体,包含C语言源代码、A51启动文件、hex烧录文件、Proteus仿真图纸(DSN/DBK/PWI)及设计报告doc/docx等,覆盖开发、仿真到文档撰写全流程;lst/obj为编译中间文件,plg/uvopt为工程配置,整体约865KB。目前已有589人学习下载,对快速上手51单片机外设与仿真调试具有参考价值。通过该包可获取完整的代码框架、仿真电路和报告范例,帮助理解血压计信号链路与模块化编程思路,为二次开发和课设答辩提供直接支撑。
1. 从袖带压力到数字读数:这套单片机血压计课设到底在做什么
打开这份 zip,里面没有复杂的 DSP 芯片,也没有临床级别的无创血压算法论文,而是一套能直接编出 hex 文件的 Keil4 工程,搭配一份 Proteus 仿真。核心逻辑比想象中简单:单片机读压力传感器的模拟电压,按键切换后再读温度传感器,将两组数值送到 LCD 显示。真正容易让人卡住的地方不是传感器型号,而是“怎么从袖带压力波形里算出收缩压和舒张压”。课设里常见做法是把算法简化成阈值比较加状态机,硬件上只保留充气泵、放气阀、压力传感器和温度传感器。这套资源适合正在做单片机课程设计的人,也适合想快速补齐“传感器→ADC→算法→显示”这条完整链路、但暂时没有实物电路板的嵌入式爱好者。整理时我按源码和仿真把模块拆开,重点讲哪些代码可以直接抄,哪些参数必须按自己的传感器重新标定。
2. 压力与温度信号链:从传感器输出到 ADC 和单总线读数值
2.1 示波法还是柯氏音法:课设为什么选了阈值检测
电子血压计临床常用的是示波法:袖带放气过程中,脉搏波振荡幅度先增大后减小,包络峰值对应平均压,峰值左侧某一比例点为收缩压,右侧为舒张压。柯氏音法需要麦克风或听诊头,而且对充放气速度很敏感,课程设计里的硬件平台通常不具备稳定采集条件。所以这套工程的代码在放气—检测—显示这条流程上做了明显简化:先充到固定压力,再慢放气,把 ADC 读到的压力序列里幅度差最大的位置当做脉搏波事件,再根据经验系数折算收缩压和舒张压。仿真阶段不会有真实脉搏,用电压源模拟特定波形也能跑通,这一点在后面的调试章节会再提。这样选择的好处是算法代码量小,51 单片机完全跑得动;坏处是换成不同袖带和压力传感器后,系数必须重新标定,否则读数会偏移。
2.2 压力传感器与温度传感器:常见型号和信号特点
压力传感器常见型号是 MPX5050GP,量程 0~50 kPa,5V 供电时零位输出约 0.2V,满量程约 4.7V。血压测量范围通常在 0~250 mmHg,1 mmHg 约等于 0.133 kPa,所以 120 mmHg 对应约 16 kPa,此时传感器输出电压约 0.2 + 16 / 50 × 4.5 ≈ 1.64V。这个电压要经过运放放大和直流偏置后送进 ADC,否则直接接单片机内置 ADC 会丢失低位分辨率。温度部分常见 DS18B20,单总线数字输出,不需要 ADC,但时序要求严格;也有课设直接用 LM35 加 ADC 通道,省去单总线协议。两者各有取舍:DS18B20 测温精度高、代码稍长;LM35 电路简单但要占用一路 ADC,并且 5V 供电下 100℃ 时输出 1V,需要软件换算。
| 信号 | 常见型号 | 输出形式 | 接口 | 需要电路 |
|---|---|---|---|---|
| 血压压力 | MPX5050GP | 模拟电压 0.2~4.7V | ADC 引脚 | 运放、偏置、去耦 |
| 体温 | DS18B20 | 单总线数字 | 普通 IO 加 4.7k 上拉 | 无源上拉即可 |
| 体温(替代) | LM35 | 模拟电压 10mV/℃ | ADC 引脚 | 跟随器或直接连接 |
我一般会按“压力模拟 + 温度数字”的方案设计:压力信号走 ADC,温度信号走单总线,这样两个信号链互不干扰,调试时也能分开定位。注意压力传感器供电必须单独并联 0.1μF 和 10μF 电容,避免充气泵启动瞬间电源跌落导致 ADC 跳动。
2.3 温度读取实现:DS18B20 时序与 C 代码
DS18B20 单总线协议分三个步骤:复位、写入 ROM 命令、读写数据。课设中一般只有一个传感器,直接发送跳过 ROM 的 0xCC 命令,然后启动温度转换 0x44,等待 750ms,再读取暂存器的 0xBE 命令。以下代码是初始化函数,放在 Keil4 的 ds18b20.c 中。
sbit DQ = P3^7; // 初始化DS18B20,返回0表示存在,1表示未检测到 uint8_t ds18b20_reset(void) { uint8_t presence = 1; DQ = 0; // 主机拉低总线 DelayUs(480); // 至少480us,产生复位脉冲 DQ = 1; // 释放总线 DelayUs(70); // 等待器件响应 presence = DQ; // 若器件存在会拉低60~240us DelayUs(400); // 补足时序 return presence; }这段代码里的 DelayUs 必须精确,我用 Keil 软件延时或定时器实现。presence = DQ必须在释放总线后 60~240us 窗口内读取,如果提前读会误判为不存在。初始化成功后,再从总线读取温度的两个字节,低字节是高四位为符号位的补码,换算成十进制温度时要注意负温度处理。实际项目中 DS18B20 的供电最好用外部 3.3V,而不是从单片机 VCC 直接拉,否则长导线在转换大电流时会引起总线误动作。
3. 系统硬件模块与 Proteus 建模:LCD 显示、按键切换和电源处理怎么搭
3.1 主控最小系统和电源模块
主控芯片在这套工程里用的是 51 内核单片机,仿真文件对应 AT89C51,如果要做实物,我一般会换成 STC12C5A60S2,因为它内置 10 位 ADC,可以省掉外扩 ADC0809 的连线。最小系统包含 12MHz 晶振、两个 30pF 负载电容、10k 上拉复位电路和电源滤波电容。电源模块是整个设计的薄弱点:泵和阀启动电流约 300mA,直接和单片机共用一只 7805 会拉低模拟电压,导致压力采样跳动。
实操时建议模拟部分和数字部分分开走线,在压力传感器电源引脚处加 LC 滤波,泵和阀用 PNP 三极管或 NMOS 驱动,并在电机两端反并联二极管吸收浪涌。Proteus 仿真不需要这些细节,但实物调试时不去做这一层很容易出现“液晶乱码、压力值随机波动”的问题。单片机最小系统的复位脚还要接一个 10uF 电容到 VCC,按下复位键时低电平保持 10ms 左右,705 周期复位信号才有效。
3.2 LCD1602 显示驱动与按键切换
显示模块用 LCD1602,8 位数据线接 P0 口,RS 接 P2.0,RW 接 P2.1,E 接 P2.2。为了省 IO,可以改成 4 线模式,但课程设计里为了看明白时序,多数工程保留 8 线全接。按键切换“血压”和“体温”两种模式,我把按键接在 P3.2 外部中断引脚,下降沿触发,用中断标志变量切换模式。这样主循环不需要反复扫描按键,也不会阻塞 ADC 采样。以下是写命令和写数据函数。
#define LCD_RS P2_0 #define LCD_RW P2_1 #define LCD_E P2_2 #define LCD_DATA P0 void lcd_write_cmd(uint8_t cmd) { LCD_RS = 0; // 命令模式 LCD_RW = 0; // 写模式 LCD_DATA = cmd; // 送入命令字节 LCD_E = 1; DelayUs(50); LCD_E = 0; // 下降沿锁存 } void lcd_write_data(uint8_t dat) { LCD_RS = 1; // 数据模式 LCD_RW = 0; LCD_DATA = dat; LCD_E = 1; DelayUs(50); LCD_E = 0; }LCD 的 E 引脚下降沿才写入,代码里先置 1 再清 0,中间延时不能太短,否则 P0 端口数据还没稳定就被锁存。每次写完命令后要等待 40us 以上,清屏命令则需要 1.5ms。按键中断里我一般只改一个mode_flag,不在中断函数中写 LCD,避免中断里耗时过长影响 DS18B20 的时序和压力采样。按键消抖放在主循环每 10ms 检查一次,这样能把 5ms 以内的毛刺滤掉,同时不会出现按一下触发两次切换的情况。
3.3 Proteus 仿真中的传感器替代方案:电压源模拟压力曲线
Proteus 里没有真实的 MPX5050GP 模型,常见做法是用电位器或 RV1 可变电阻分压,再经过一个电压跟随器接到 ADC 输入。这套工程的体温血压.DSN里也是这个思路:压力传感器位置放一个 0~5V 的交互式电压源,温度传感器用 DS18B20 仿真模型。调电压时打开 Proteus 的 “Interactive Voltage Control” 小窗口,手动输入电压值,观察液晶上的血压变化。充气泵和电磁阀分别用直流电机和开关代替,只是为了看动作时序,不参与算法计算。
下面是仿真时需要放置的主要元件清单,元件名与实际库一致:
| 元件 | Proteus 关键字 | 数量 | 用途 |
|---|---|---|---|
| 单片机 | AT89C51 或 STC89C52 | 1 | 主控 |
| LCD 液晶 | LM016L | 1 | 显示 |
| 温度传感器 | DS18B20 | 1 | 测温 |
| 压力模拟 | POT-HG | 1 | 模拟压力电压 |
| 运放 | LM358 | 1 | 电平跟随/放大 |
| 独立按键 | BUTTON | 1 | 切换模式 |
| 直流电机 | MOTOR | 1 | 模拟充气泵 |
POT-HG 在仿真里只能手动拖动,无法做到自动变化。想演示完整压力曲线,可以在代码里加一个“测试模式”:不读 ADC,而是用一个数组存预置的 200 个压力点,循环输出到 LCD。这样即使没有电压源,答辩时也能展示收缩压和舒张压的变化过程。仿真时钟和代码里的 DelayUs 需要匹配,系统默认 12MHz 晶振,代码里如果按 11.0592MHz 算,串口会差 1% 左右,普通显示无明显问题,但涉及 ADC 采样周期时会累积误差。
4. Keil4 工程与软件状态机:从 main.c 到可烧录 hex 的测量流程
4.1 Keil4 工程文件拆解与编译设置
zip 里的文件结构很典型:STARTUP.A51是 Keil 自动生成的启动文件,负责片内 RAM 清零和堆栈指针初始化;main.c是主代码;main.hex是编译产物;main_uvopt.bak和main_uvproj.bak是工程备份文件。用 Keil4 打开main.uvproj后,需要确认两点:Target 选项里晶振频率填写 12.0MHz;Output 选项卡勾选 “Create HEX File”,否则编译不生成 hex,Proteus 就加载不了。
新建工程时,我通常把 AT89C51 或 STC12C5A60S2 的器件库导入。如果芯片型号选错,比如把 AT89C51 选成 AT89S51,寄存器定义没有差别,但烧录时的 ID 校验会失败。下面是一个常见的最小编译流程:
- 新建工程,保存到独立文件夹;
- 添加 main.c、ds18b20.c、lcd1602.c 和 STARTUP.A51;
- 在 C/C++ 选项卡里添加头文件路径;
- 编译后查看 Build Output 窗口,确认 “0 Error(s), 0 Warning(s)”;
- 如果出现 “target not created",检查代码里是否有未定义的
sbit或串口中断向量重复定义。
Keil4 对代码优化等级默认是 8 级,我一般改成 0 级调试,等算法稳定后再拉高优化,避免在优化下出现变量被提前更新的问题。main.c里有大量while循环和延时,优化等级过高时,空循环会被编译器删除,导致实际速度变快,血压计算随之中断。
4.2 主程序状态机:充气、测量、放气和显示
血压测量流程不适合写成单线程顺序执行,因为充气和放气期间还要同时采样 ADC、检测按键和刷液晶。用状态机可以让每个阶段的分工明确。我把状态定义为 S_IDLE、S_CHARGING、S_MEASURING、S_RELEASE、S_DISPLAY,下面是状态表和主循环代码骨架。
| 状态 | 进入条件 | 执行动作 | 跳转条件 |
|---|---|---|---|
| S_IDLE | 系统上电 | 显示“PRESS KEY” | 按键触发 |
| S_CHARGING | 按键按下 | 打开充气泵,ADC 采样 | 压力 > 180mmHg |
| S_MEASURING | 压力达标 | 记录压力序列 | 压力下降到 40mmHg |
| S_RELEASE | 采集结束 | 关闭泵,打开放气阀 | 压力 < 5mmHg |
| S_DISPLAY | 泄压完成 | 计算收缩压/舒张压并显示 | 按键切换体温 |
void main_loop(void) { switch (state) { case S_IDLE: if (start_flag == 1) { pump_on(); state = S_CHARGING; } break; case S_CHARGING: press = read_pressure_adc(); if (press >= 180) // 目标压力阈值 { pump_off(); valve_off(); state = S_MEASURING; sample_index = 0; } break; case S_MEASURING: sample_buf[sample_index++] = read_pressure_adc(); if (sample_index >= 200) state = S_RELEASE; break; case S_RELEASE: valve_on(); DelayMs(100); valve_off(); calc_blood_pressure(sample_buf, 200); state = S_DISPLAY; break; case S_DISPLAY: display_result(); break; } }注意S_CHARGING里的read_pressure_adc()返回的是电压值,我把它换算成 mmHg 再和阈值比较。换算公式是mmHg = (adc_voltage - 0.2) / 0.09,因为 1kPa 电压增量约 0.09V,1kPa 约 7.5mmHg。不同传感器这个系数不一样,MPX5050GP 的典型灵敏度是 0.09V/kPa,温度变化还会影响零位,所以实物上应当先在没有压力时读一次零点电压,再在测量结果里减去。状态机里延时 100ms 是等放气阀完全开启,实际项目可能改成检测阀门的限位开关,以便在阀卡住时及时停止。计算血压前要先关闭状态切换,否则测量中途按键会把液晶切到体温模式,数据覆盖导致结果错乱。
4.3 收缩压和舒张压的计算:从压力序列里找脉搏波
课设版算法不需要真正的傅里叶分析。常见做法是把 ADC 序列做一阶差分,也就是diff[i] = buf[i] - buf[i-1],然后找出连续正负变化形成的脉冲。脉搏波出现时,袖带压力下降曲线会叠加一个小的波动,差分后波动的幅度明显大于放气本身的斜率变化。
为了抗抖动,我一般给差分序列设一个自适应阈值:先取序列中最大的差分值max_diff,阈值设为max_diff * 0.4。从压力高到低扫描,每次跨过阈值就记录当前压力值,这就是一次脉搏波事件。把所有事件对应的压力值存放在pulse_press[]数组,最后取最大值作为收缩压参考点,取最小值作为舒张压参考点。
uint8_t detect_pulses(uint16_t *buf, uint16_t *pulse_press, uint8_t len) { uint8_t i, n = 0; int16_t max_diff = 0; int16_t diff; for (i = 1; i < len; i++) { diff = (int16_t)buf[i] - (int16_t)buf[i - 1]; if (diff < 0) diff = -diff; if (diff > max_diff) max_diff = diff; } if (max_diff < 30) return 0; // 无脉搏波,可能袖带漏气 for (i = 1; i < len; i++) { diff = (int16_t)buf[i] - (int16_t)buf[i - 1]; if (diff < 0) diff = -diff; if (diff > (uint16_t)(max_diff * 0.4)) { pulse_press[n++] = buf[i]; i += 2; // 跳过脉冲尾部,避免重复计数 } } return n; }max_diff * 0.4是用 40% 峰值作为自适应阈值,能排除放气下降沿本身造成的慢变差分。i += 2是常见跳变点,目的是跳过同一个脉搏波里的二次抖动。最终把pulse_press[]的首位元素拿来显示,再乘一个修正系数。这套代码在仿真里用一段叠加了正弦波的斜坡电压就能跑出曲线,但在真实袖带上脉搏波幅度会随压力变化,阈值算法会受到袖带松紧影响。更可靠的做法是记录每个脉搏波对应的峰值和相位,但那是实际项目的后续工作,课设里能显示出合理范围往往就够了。
5. 仿真调试与参数标定:让 Proteus 里的血压计接近实物的四个关键点
5.1 晶振频率和代码延时不一致
Proteus 默认单片机晶振 12MHz,而 Keil 代码里如果用DelayMs(100)做 100ms 延时,内部循环次数必须按 12MHz 计算。如果复制了网上 11.0592MHz 的延时函数,实际延时变成约 108ms,充气时间虽然长一点,但 DS18B20 的 750ms 转换时间会被压缩,温度读出偶尔变成 85℃。检查办法是编译后看仿真左下角时间轴,用示波器观察一个 IO 的翻转周期,再对照代码里的DelayMs。
5.2 ADC 参考电压和传感器满量程匹配
STC12C5A60S2 内置 ADC 参考电压是 VCC,也就是 5V。MPX5050GP 满量程 4.7V 正好卡在量程边缘,但压力超过 50kPa 时输出会超过 5V 或靠近 VCC,读取值会有截断。我一般把传感器 VCC 接 4.5V 稳压管降一档,或者用电阻分压把满量程压到 4.2V 再送 ADC。Proteus 里直接改电压源数值,不用改动代码。
5.3 按键消抖不要阻塞 ADC 采样
在S_MEASURING期间,每 10ms 一次的按键消抖延时会导致采样间隔不均匀,压力波形出现锯齿。更好的方法是把按键检测放到定时器中断里,中断每 5ms 置一个标志,主循环只在sample_index为空时才扫描。也就是说,状态机的每个状态不能随意调用DelayMs遍历按键。修改后可以在测量中途按键,等测量完成后再切换到体温显示。
5.4 验证技巧:用恒定电压源做单点标定
把 Proteus 里的压力电压源设成 1.64V,对应 120mmHg,观察 LCD 是否显示 120 附近。如果偏大,说明零位电压 0.2V 没有在换算公式里减掉。下面这个简化代码可以直接烧录测试:
uint16_t voltage_to_mmhg(uint16_t adc_value) { uint32_t mv = (uint32_t)adc_value * 5000UL / 1024UL; // 10位ADC uint32_t mmhg = (mv - 200UL) * 100UL / 90UL; // 0.2V=0, 0.09V/kPa return (uint16_t)mmhg; }10 位 ADC 的满量程是 0~1023,对应 0~5000mV。mv - 200先减去零位输出,再除以 0.09V 得到 kPa,最后乘以 7.5 得到 mmHg,合并后就是 100/90。给这个函数输入 1024 的一半时得到约 611mmHg,显然超出传感器量程,说明电压源设置不能超过 4.7V。建议把电压从 0.2V 到 4.0V 每隔 0.1V 打一次点,记录 LCD 读数,画一条直线看线性度,如果读数呈现弯曲,多半是 Proteus 的电位器模型没有接成线性分压。
本文还有配套的精品资源,点击获取