简介:基于51单片机与Proteus仿真的人体健康检测系统设计,面向电子、自动化等专业学生和嵌入式开发入门者,可用于课程设计、毕业设计或自学实践。系统以DS18B20采集人体温度,通过压力传感器模拟检测心率,检测结果实时显示在LCD1602液晶屏上;用户可通过按键切换测量温度或心率,也可主动结束测量。当体温或心率超过正常标准时,系统会自动触发声光告警。压缩包共42个文件,容量仅473KB,包含C语言源代码、头文件、Keil工程文件、Proteus仿真工程及备份、Hex烧录文件、编译中间文件和相关文档,结构清晰,便于直接打开仿真和二次开发,适合作为单片机课程设计或毕业设计的参考范本。目前已有157人学习下载,可作为理解单片机外设接口、传感器数据采集与处理、LCD显示和声光报警控制逻辑的完整参考,也适合扩展加入体温历史记录、心率变异性分析等进阶功能。
1. 这套人体健康检测系统,先想清楚它能演示什么
体温和心率是所有健康检测设备里最基础的两个指标。这个项目用 51 单片机跑 Proteus 仿真,主控是 AT89C51,配合 DS18B20 测温、压力传感器加 ADC0832 采集心率信号,再把结果输出到 LCD1602。按键可以切换测量模式,体温超过 37.8℃ 或心率不在 50~100 范围时,蜂鸣器和 LED 会同时报警。对学习单片机的人来说,它的价值在于把单总线时序、串行 ADC 采集、1602 并行驱动、按键状态机放在同一个工程里,一次串起来。对正在做课程设计或毕业设计的人,仿真图和 C51 源代码直接给了二次开发的底子。
2. 单片机仿真的健康指标链路:主控选型、传感器与信号走向
2.1 为什么用 51 而不是 STM32
这个系统的采样率要求不高,体温刷新一次需要几百毫秒,心率采样每 10 毫秒一次也足够,AT89C51 在这种场景下比 STM32 更合适。51 的 IO 口是准双向口,直接位操作就能模拟 DS18B20 的单总线协议和 ADC0832 的三线串行时序,新手能看清每个时钟沿在干什么。Proteus 里 AT89C51 模型稳定,Keil C51 编译出来的 HEX 文件拖进仿真芯片就能跑。换成 STC89C52 这类国产 51 内核芯片也只需要改芯片型号,Keil 工程不用动,这一点对硬件验证很有用。
从信号链路上看,系统可以分成三条路:第一条是 DS18B20 把温度转成 12 位数字量,通过单总线送到 P3.7;第二条是压力传感器输出模拟电压,经 ADC0832 的 CH0 通道转成 8 位数据,通过 P3.4~P3.6 送给 CPU;第三条是 LCD1602 显示并行数据,由 P2 口控制 RS、RW、EN,P0 口送字符数据。按键和报警则分别占用 P3.0、P3.1 和 P2.3、P2.4。
2.2 各模块引脚分配与 Proteus 连线
下面这张表是仿真电路里的默认分配,如果你拿到的是另一份引脚定义,只要同步修改头文件里的sbit即可,不用改逻辑:
| 模块 | 信号 | 单片机端口 | 说明 |
|---|---|---|---|
| 温度采集 | DS18B20 DQ | P3.7 | 单总线数据线,需上拉 4.7kΩ |
| 心率采集 | ADC0832 CS | P3.5 | 片选,低电平有效 |
| 心率采集 | ADC0832 CLK | P3.6 | 串行时钟 |
| 心率采集 | ADC0832 DO/DI | P3.4 | 分时复用的数据口 |
| LCD1602 | RS / RW / EN | P2.0 / P2.1 / P2.2 | 控制线 |
| LCD1602 | D0~D7 | P0 | 并行数据,P0 口接 10kΩ 排阻上拉 |
| 按键 | 模式切换 | P3.0 | 温度/心率切换,低电平触发 |
| 按键 | 结束测量 | P3.1 | 回到待机,低电平触发 |
| 报警 | 蜂鸣器 / LED | P2.3 / P2.4 | 超限时同时动作 |
P0 口上拉这一项很容易漏。Proteus 里 AT89C51 的 P0 是开漏输出,不接排阻时 LCD1602 会显示乱码或直接不显示。用排阻元件RESPACK-8就能解决。
2.3 DS18B20 测温为什么这么慢,又为什么够用
DS18B20 出厂默认 12 位测温精度,分辨率是 0.0625℃,对应转换时间最长 750ms。这个速度放在健康检测场景里没有任何问题,人体体温变化本来就慢,每秒刷新一次足够了。单总线协议最麻烦的是时序,初始化时主机拉低至少 480μs 再释放,等待 60μs 后若检测到低电平脉冲说明设备在线;写 0 和写 1 的时序宽度不同,读时序也要精确卡在 70μs 左右。Keil C51 里这些延时需要用循环实现,晶振频率变了延时参数就要跟着变,否则读出来可能是 85℃ 这种异常值。
2.4 心率检测:压力传感器变成血压脉搏的模拟量
实际硬件里压力传感器(常见是压电薄膜或应变片)贴在手指上,每一次脉搏会让传感器输出一个脉冲电压,经过放大调理后输入 ADC0832。在 Proteus 里没有真实的应变片,我用的是一个滑动变阻器和信号源组合,把 CH0 电压按脉搏波形调制到 1~2Hz,程序通过 ADC 采样值判断脉冲跳变。ADC0832 是 8 位逐次逼近型,输入范围 0~5V,测量精度 19.5mV/LSB,辨别人的脉搏包络足够。
3. 工程代码怎么拆:main 状态机、DS18B20 与 ADC0832 驱动
3.1 工程文件结构与主循环状态机
下载到的工程里除了主文件外,通常还有ds18b20.c、adc0832.c、lcd1602.c、delay.c和对应的.h头文件。这样拆分的好处是每个传感器驱动独立,替换硬件时只改对应的.c文件。主循环用一个mode变量记录当前测量模式,模式切换放在按键中断或主循环扫描里。
// main.c 主状态机 #include <REGX51.H> #include "ds18b20.h" #include "adc0832.h" #include "lcd1602.h" #include "delay.h" #define MODE_TEMP 0 #define MODE_HR 1 unsigned char mode = MODE_TEMP; // 初始进入体温测量 bit measuring = 0; // 测量进行标志,用于结束测量 void main(void) { unsigned int temp16, hr; lcd1602_init(); lcd1602_show_string(0, 0, "Health Monitor"); while(1) { key_scan(); // 扫描按键,更新 mode 和 measuring if(measuring) { if(mode == MODE_TEMP) { temp16 = ds18b20_read_temp(); // 返回 12 位温度原始值 lcd1602_show_temp(temp16); // 转换成实际温度并显示 temp_alarm(temp16); // 超限报警判断 } else { hr = heart_rate_get(); // 测心率,内部阻塞 5 秒 lcd1602_show_hr(hr); hr_alarm(hr); } } delay_ms(1); // 让出 CPU,防止按键扫描过频 } }key_scan()负责检测 P3.0 和 P3.1 的电平变化。按键按下一次模式切换,测量过程中再按一次 P3.0 就结束当前测量。这种状态机写法比直接在主循环里if(P3_0==0)可靠得多,因为测量心率时需要阻塞 5 秒,如果没有状态机,这 5 秒内按键会失效。
3.2 DS18B20 驱动:读温度原始值
DS18B20 驱动里最关键的是读温度子程序,它遵循“初始化 → 跳过 ROM → 启动转换 → 等待 750ms → 初始化 → 跳过 ROM → 读暂存器”的流程:
// ds18b20.c 读取 12 位温度 unsigned int ds18b20_read_temp(void) { unsigned char tl, th; ds18b20_reset(); // 复位单总线 write_byte(0xCC); // 跳过 ROM,不用匹配序列号 write_byte(0x44); // 启动温度转换 delay_ms(750); // 等转换完成,12 位精度最长为 750ms ds18b20_reset(); write_byte(0xCC); write_byte(0xBE); // 读暂存器,先读低字节 tl = read_byte(); th = read_byte(); return (th << 8) | tl; // 低 11 位是有符号温度数据 }write_byte(0xCC)表示所有 DS18B20 都响应,仿真里只有一颗传感器所以可以省去搜索 ROM 的步骤。返回值th<<8 | tl是一个 16 位整数,实际温度需要右移 4 位再乘 0.0625。如果温度是负数,最高位为 1,C51 的int型直接右移会自动填充符号位,负数也能正确转换。
3.3 ADC0832 驱动:双端转换结果比较
ADC0832 的 DI 和 DO 是同一个物理引脚,在发送起始位和通道选择时作为输入,在读取转换结果时作为输出。读取时先在第一段读高 8 位,再从第二段读反码,两段结果相同才认为有效:
// adc0832.c 读取指定通道的 8 位 ADC 值 unsigned char adc0832_read(unsigned char channel) { unsigned char i, dat1 = 0, dat2 = 0; AD_CS = 1; AD_CLK = 0; AD_DIO = 1; AD_CS = 0; // 片选拉低,进入转换 AD_CLK = 1; AD_CLK = 0; // 第一个时钟:起始位,DIO=1 AD_DIO = channel ? 1 : 0; // 第二个时钟:选择单端或差分 AD_CLK = 1; AD_CLK = 0; AD_DIO = 0; // 第三个时钟:选择通道 CH0/CH1 AD_CLK = 1; AD_CLK = 0; AD_DIO = 1; // 释放数据线,准备读高 8 位 for(i = 0; i < 8; i++) { AD_CLK = 1; AD_CLK = 0; dat1 <<= 1; if(AD_DIO) dat1 |= 0x01; } for(i = 0; i < 8; i++) { dat2 >>= 1; if(AD_DIO) dat2 |= 0x80; AD_CLK = 1; AD_CLK = 0; } AD_CS = 1; return (dat1 == dat2) ? dat1 : 0; }调用时传0读 CH0,传1读 CH1。第二个循环读的是反码,正常情况下dat1和dat2完全相等,如果两个数对不上,说明时钟时序有问题,返回值直接置 0 可以避免把错误电压当成正常测量值。仿真中如果发现 ADC 读数一直跳变,优先检查 CLK 引脚是否被其他模块占用。
3.4 心率算法:在 5 秒窗口内数波峰
心率检测不能只看一次 ADC 结果,需要连续采样一段时间,统计超过阈值的脉冲个数再换算成每分钟心率。我用的方法是对 ADC 采样值做阈值判决,检测到一个上升沿就计一次数:
// main.c 在心率测量模式下调用的统计函数 #define ADC_THRESHOLD 128 // 阈值,由第 5 章的标定步骤确定 #define SAMPLE_WINDOW_MS 5000 // 统计窗口 5 秒 unsigned int heart_rate_get(void) { unsigned int pulse_count = 0; unsigned int sample; bit last_level = 0; unsigned int elapsed = 0; lcd1602_show_string(1, 0, "HR Measuring..."); while(elapsed < SAMPLE_WINDOW_MS) { sample = adc0832_read(0); // 读压力传感器电压 if(sample > ADC_THRESHOLD && last_level == 0) { pulse_count++; // 低电平转高电平,算一次心跳 last_level = 1; } if(sample <= ADC_THRESHOLD) { last_level = 0; } delay_ms(10); // 10ms 采样一次 elapsed += 10; } return pulse_count * 12; // 5秒次数 × 12 = 每分钟心率 }这里pulse_count * 12是因为 5 秒内有pulse_count次跳动,折算到每分钟就是乘以 12。10ms 采样间隔对应 100Hz 采样率,人的心率一般不超过 180bpm,也就是 3Hz,所以不会漏采。阈值 128 是 0~5V 中间值,实际使用时要根据压力传感器的静态输出电压微调,否则手放上去可能一直判定为高电平。
4. Proteus 仿真搭建与联调:从原理图到 HEX 加载
4.1 元件清单和连线顺序
在 Proteus 中新建工程后,器件列表按下面这些名称放置:
AT89C51:主控DS18B20:温度传感器模型ADC0832:串行 ADCPOT:滑动变阻器,模拟压力传感器输出电压LCD1602:液晶显示RESPACK-8:P0 上拉排阻BUTTON×2:模式切换和结束测量BUZZER、LED:声光报警
连线时先连最小系统:AT89C51 的 18、19 脚接 12MHz 晶振,两个 30pF 电容接地,9 脚通过 10μF 电容和 10kΩ 电阻构成上电复位。然后依次接 LCD1602、DS18B20、ADC0832。所有芯片的电源脚都接 5V,地线统一接 GND。
4.2 加载 HEX 文件
双击 AT89C51,在 Program File 项选择 Keil 工程输出目录下的温度采集显示.hex。加载后点运行,LCD1602 应该能显示初始化信息。有个细节:PROTEUS 时序仿真默认是即时模式,如果 DS18B20 转换延时长,整个仿真会显得很卡。可以在菜单 Debug 里把仿真时间步长调快,或者临时把主程序里 750ms 的delay_ms改成 100ms,验证功能后再改回真实值。
4.3 心率信号源怎么搭,才像真实脉搏
实际压力传感器输出的是模拟脉冲,我见过很多人在仿真里直接用开关控制电压跳变,然后说心率不稳定。这其实是激励源的问题,不是代码问题。要在 Proteus 里模拟真实脉搏,我一般这样接:
- 用
VSINE正弦电压源,频率设成 1.2Hz,幅度 2V,偏置 2.5V。 - 把该电压源接到 ADC0832 的 CH0 输入引脚。
- 在 ADC0832 的 DO/DI 引脚挂一个
DIGITAL OUTPUT探针。
这样 ADC 输入波形就是 1.2Hz 的脉冲包络,ADC 采样值会在 0.5V~4.5V 之间变化,阈值 2.5V 左右能稳定触发波峰计数。代码跑完后显示的心率应在 72bpm 上下,偏差不超过 ±2。
4.4 联调时最容易踩的四个坑
第一个坑是 DS18B20 的 DQ 引脚在 Proteus 中不叫 1,叫 DQ,连错位置会一直显示 85℃。第二个坑是 LCD1602 的 VL 对比度引脚必须接一个 10kΩ 电位器,扭到合适位置才能看到字形。每个新工程我都要重新调一次对比度,因为接 5V 时显示全黑,接地时常看不到文字。
第三个坑是 ADC0832 的 CLK 频率。Proteus 仿真里如果主程序用while循环操作 CLK,速度可能太快,导致 ADC 响应不过来。可以适当在翻转电平之间加 2~3 个空指令延时,例如_nop_();。我见过很多人不是逻辑错,而是时序太快读不到有效数据。第四个坑是仿真时按键盘无效,先查按键引脚是否接了下拉电阻。AT89C51 的 P3 口内部有弱上拉,按键另一端接地即可,如果两个按键接在同一个网络里,就会互相干扰。
5. 参数标定、按键防抖与仿真验证技巧
5.1 报警阈值从哪来
代码里的体温和心率阈值不是拍脑袋定的,参考常见健康指标范围,我按下表设置:
| 参数 | 下限 | 上限 | 报警动作 |
|---|---|---|---|
| 体温 | 36.0℃ | 37.8℃ | 超过上限响蜂鸣器并亮 LED |
| 心率 | 60bpm | 100bpm | 低于下限或高于上限都报警 |
体温 37.3℃ 是健康成年人正常值上限,37.8℃ 作为报警阈值是给传感器误差留了余量。心率 60~100 是静息心率的常用参考,如果是仿真里用 1.2Hz 信号源,测出来的 72bpm 正好落在区间里。
5.2 按键消抖最好别用纯延时
很多例程在主循环里检测按键后用delay_ms(20)消抖,在仿真里跑没问题,但放进心率测量状态时,20ms 的延时会打断采样节拍。我的做法是用状态机和计时器计数,只在主循环里查询按键是否被处理过:
// 按键扫描,带标志位处理 void key_scan(void) { if(P3_0 == 0) // P3.0 按下,检查是否已处理 { delay_ms(5); // 短延时避开抖动 if(P3_0 == 0 && key_ok == 0) { mode = !mode; // 切换温度/心率 measuring = 1; key_ok = 1; // 防止同一按键重复触发 } } else { key_ok = 0; // 按键释放,恢复允许下一次触发 } }关键在key_ok这个标志:只有按键从未按下状态变成按下状态,标志位为 0,才能触发一次动作。如果在按住按键的时间里主循环反复扫描,P3_0 == 0一直成立,没有标志位就会连续切换模式。
5.3 验证方法:用秒表对心率,用水温对体温
调通后先用固定信号源检查算法:把 VSINE 的频率设成 1.0Hz,应该显示 60bpm;设成 1.5Hz 显示 90bpm。如果输出频率不对,问题一定在窗口计时或delay_ms(10)的晶振参数上。验证体温的时候,把 DS18B20 模型放在一个可控温度源旁边,在 Proteus 里直接右键 DS18B20 修改Temperature值为 36.5,观察液晶显示值偏差应在 ±0.2℃ 内。
5.4 一个可以立刻用上的技巧:把心率窗口做成可配置
调试时每次都改#define SAMPLE_WINDOW_MS 5000再重新编译太麻烦。我把窗口长度放到idata变量里,仿真时通过按键临时切换成 2 秒窗口,等算法调稳再改回 5 秒。这样做的另一个好处是,把SAMPLE_WINDOW_MS替换成外部变量后,后续版本可以做成用户可选的“快速测量”和“标准测量”,不需要重构心率算法。
本文还有配套的精品资源,点击获取