简介基于单片机仿真的人体健康检测系统设计是一套面向电子、嵌入式初学者的完整实践资料以51单片机为主控结合Proteus仿真与Keil编程实现人体体温、心率检测并在LCD1602上显示同时提供超标声光告警。资源共42个文件涵盖C源码、头文件、Proteus仿真工程、Keil工程文件、Hex烧录文件及说明文档等压缩包仅473KB结构紧凑、便于直接打开学习与二次开发。目前已有157人学习下载。仿真中体温检测采用DS18B20传感器心率检测通过压力传感器模拟实际测量方式并支持按键切换测量体温或心率、结束测量当指标超过正常标准值时系统自动触发声光告警。整个方案从传感器数据采集、主控逻辑处理到显示与告警输出均有完整代码与仿真图配套适合课程设计、毕业设计或入门单片机项目时参考也可作为理解传感器应用与嵌入式系统流程的实战范例。1. 单片机仿真的人体健康检测系统先想清楚这几件事很多人在拿到“基于单片机仿真的人体健康检测系统设计”这个题目时第一反应是先把 Proteus 仿真图跑起来再对着源码改参数。但这套题目真正值钱的地方不是那个能动的画面而是从传感器信号采集、滤波换算、阈值报警到串口远传这一整条链路能不能在没有真实医疗硬件的情况下被完整地验证、测量和复现。这类系统通常以 51 单片机AT89C52 或 STC89C52为核心在 Proteus 里完成电路仿真在 Keil 里用 C51 写源代码最终交付的仿真图和源代码本质上是一份可运行的“参考实现”。它解决的典型问题是你手上没有体温探头、没有心率传感器但依然要把测量算法、报警逻辑和界面逻辑先跑通等实物回来只需要换传感器接口。适合课程设计、毕业设计以及想快速搭健康数据链路的嵌入式工程师。做这种仿真有个反常规律最耗时间的不是写逻辑而是让模拟传感器输出的电压源和 ADC 采样通道对齐这一点想清楚能省一半时间。2. 人体健康检测系统的信号链设计与传感器选型2.1 人体健康检测的信号流向与模块划分一个能测量体温和心率的人体健康检测系统按信号链而不是按功能菜单划分后面写代码会轻松很多。常见的做法是把系统拆成六块传感器、信号调理、采样、处理、显示报警、通信。信号从物理量出发变成电信号再经过调理网络进入单片机最终落到 LCD 显示、蜂鸣器报警和串口输出。在 Proteus 仿真中这条路可以逐段打点验证。比如用电压表直接量电位器输出看输出电压是否落在量程内用虚拟终端看串口是否收到正确帧用示波器看心率方波的边沿是否干净。每一段都能单独注入激励这比把整个系统焊在一起再调要快得多。选择 51 单片机做仿真并不是因为它的性能好而是因为 Proteus 对 AT89C51/52、LM016L 液晶、ADC0832、电位器、信号发生器这些元件库覆盖得足够完整C51 代码编译成 HEX 文件后用 Proteus 加载断点、引脚电平和寄存器都可以直接观察。51 单片机本身运行频率低外设简单反而让信号链的每个环节都变得透明不会出现 Cortex-M 那种“寄存器一堆不知道是哪里没配”的情况。2.2 传感器选型体温和心率为什么必须走两条独立通道体温通道输出的是模拟电压心率通道输出的是脉冲信号两者的采样方式完全不同。常见设计里体温用 NTC 热敏电阻配合固定电阻分压输出电压随温度变化心率用红外对管或指夹式心率传感器每次心跳产生一个脉冲。仿真阶段体温模拟源用一个电位器分压心率模拟源用信号发生器产生 TTL 方波。这带来一个关键选型点体温走 ADC 采样心率走外部中断或定时器计数。一条通道占一个外部中断和一路 ADC两者互不干扰否则心率脉冲计数会在温度采样时被阻塞。测量对象常见传感器电气输出Proteus 仿真替代模型采样方式体温NTC 热敏电阻电阻分压后的 0~5V 电压电位器分压ADC0832 单通道体温高精度DS18B20单总线数字信号直接用 DS18B20 仿真模型单总线时序心率红外对管/指夹传感器每搏一个脉冲方波信号发生器输出 1Hz 方波INT0 外部中断计数血氧可扩展红/红外双波长传感器两路直流电平双电位器模拟双通道 ADC很多人在“单片机仿真芯片有哪些”这个问题上纠结半天其实 Proteus 里常用的仿真芯片就那么几个AT89C51/52、STC89C52RC、ATmega328P。如果以后打算转 Arduino 生态wokwi 仿真平台也能做在线仿真但就 51 传统外设的仿真细腻程度来说Proteus 依然是最顺手的。我一般首选 AT89C52因为它在 Proteus 里加载 HEX 文件最省事源码无需改初始化逻辑就能直接迁移到 STC89C52 实物上。2.3 信号调理与 AD 采样分配体温通道的分压网络需要算一下。以 10kΩ NTC 配合 10kΩ 固定电阻为例体温从 35℃ 升到 42℃ 时NTC 阻值下降分压点的电压会从约 2.6V 降到约 1.4V这个范围正好落在 ADC0832 的 0~5V 量程内。如果量程用不满可以调整固定电阻的阻值让 35℃~42℃ 的电压摆幅尽量覆盖 ADC 的中间区域而不是靠近 0V 或 5V 的死区。心率通道的信号调理在仿真里常常被跳过因为它直接接信号发生器的 TTL 方波。但实物上必须加一个整形电路把传感器输出的小信号经过比较器或施密特触发器变成干净的脉冲否则边沿抖动会让计数器多计数。仿真时可以故意把信号发生器的上升沿调得不够陡观察计数器是否虚增用来验证后期整形电路的有没有必要。采样参数方面ADC0832 是 8 位逐次逼近型 ADC参考电压 5V单 LSB 分辨率约 19.5mV。按体温电压摆幅 1.2V 计算分辨率大概在 0.05℃ 左右看似够用但别忽略分压电阻的精度和噪声。如果实测温度误差超过 ±0.5℃就该考虑换用 DS18B20 数字传感器把校准问题交给数字接口的精度。心率通道不存在 ADC 精度问题它只关心边沿质量和计数窗口的准确性。3. 用 Proteus 搭建可仿真的人体健康检测电路3.1 元件清单与引脚接线规划搭建电路前先列一份元件清单防止画到一半发现某个器件是实物专用、仿真库里没有。元件Proteus 关键字数量接线要点AT89C52 单片机AT89C521P0 口必须接上拉排阻LCD 1602 液晶LM016L1D0~D7 接 P0RS/P2.0EN/P2.1ADC0832ADC08321CS/P3.5CLK/P3.6DIO/P3.7电位器POT-HG1~2中脚接 ADC 输入模拟体温/血氧电压信号发生器DIGITAL 方波源1输出接 INT0P3.2模拟心率脉冲蜂鸣器BUZZER1经三极管驱动接 P1.7复位电路RESISTOR CAP若干10kΩ 10μF 经典复位电路晶振电路CRYSTAL112MHz 2×22pF 电容画图时最容易踩的坑是 P0 口忘了接上拉。AT89C52 的 P0 是开漏输出直接接 LCD 数据总线会导致 LCD 显示乱码或完全不亮。常见做法是接一个 10kΩ 排阻到 VCC或者用 8 个 10kΩ 独立电阻代替仿真里用排阻比较省事。另一个坑是蜂鸣器不能直接接引脚到地51 单片机的 IO 灌电流能力有限仿真里虽然不会烧管子但音量输出很小后续实物上必须用三极管或 ULN2003 驱动。3.2 从空工程到能显示 ADC 值的最小步骤第一步在 Proteus 新建工程选择 AT89C52第二步放置晶振电路、复位电路、LM016L、ADC0832 和电位器按表里的引脚关系连线第三步在 Keil 新建 C51 工程编写代码并生成 HEX 文件第四步双击 Proteus 里的单片机把 HEX 文件加载进去把仿真频率设为 12MHz第五步点击运行确认 LCD 上能显示字符。这一步最容易卡住的不是画电路而是 Keil 默认不输出 HEX 文件。需要手动在 Options for Target 的 Output 选项卡里勾选 Create HEX File再重新编译。如果编译生成了 HEX 但 Proteus 里点击运行后没有任何反应优先检查单片机是否加载了 HEX以及晶振频率是否和代码里设定的波特率/定时器初值一致。搭建完成后建议先不写完整逻辑只放一个读 ADC 的最小程序验证数据通路是否通#include reg52.h #include intrins.h sbit adc_cs P3^5; sbit adc_clk P3^6; sbit adc_dio P3^7; unsigned char adc0832_read(unsigned char ch) { unsigned char i, dat 0; adc_cs 0; _nop_(); adc_clk 0; adc_dio 1; adc_clk 1; _nop_(); // 起始位 adc_clk 0; adc_dio ch; // 通道选择 adc_clk 1; _nop_(); adc_clk 0; adc_dio 1; // SGL/DIFF 选择 adc_clk 1; _nop_(); for (i 0; i 8; i) { adc_clk 0; _nop_(); dat 1; if (adc_dio) dat | 0x01; adc_clk 1; _nop_(); } adc_cs 1; return dat; // 返回 0~255 }这段代码的作用是把电位器的电压量化成 0~255 的数值ch 为 0 时读 CH0为 1 时读 CH1。代码里起始位、通道选择位、SGL/DIFF 位各占一个时钟周期顺序不能乱。读取阶段是下降沿移入数据每次先拉低再判电平。如果 LCD 上一直显示 0 或 255 不变说明引脚接错或者电位器滑动端没接到 ADC 输入先把万用表放到引脚上看电压再查代码。3.3 仿真调试的三个高频坑仿真阶段最典型的一个问题是仿真速度太慢。LCD 刷新在 Proteus 里是很重的操作如果主循环每几十毫秒刷一次屏仿真时间会被拖到接近真实时间的几十分之一。常见做法是把显示刷新频率降到 500ms 一次或者先用虚拟终端看数据等逻辑全部调通后再把 LCD 显示打开。第二个坑是电平冲突。Proteus 里如果两个输出引脚直接短接一个拉高一个拉低会出现红色或蓝色标记仿真结果发散或不稳定。画图时尽量让每个节点的驱动源只有一个按键上拉电阻选 10kΩ中断信号线上不要再挂其他输出。第三个坑是中断标志的处理。51 单片机的外部中断在进入中断服务函数后需要软件清标志或者配置为边沿触发避免重复进入。仿真时用信号发生器输入方波如果发现心率计数是实际值的两倍多半是电平触发模式下引脚悬空时的抖动造成的把信号源输出阻抗调低或者代码里改成下降沿触发。4. 人体健康检测源代码的关键模块与参数设定4.1 主循环与状态机划分源代码的组织方式决定了后续调试工作量。最忌讳的是在一个大 while 循环里堆满阻塞式延时温度滤波还没算完心率边沿已经丢了。常见做法是把主循环拆成采样、滤波、显示、报警四个任务由定时器标志位驱动void main(void) { init_timer(); init_lcd(); init_int0(); while (1) { if (adc_flag) { sample_temperature(); // 200ms 采样一次 adc_flag 0; } if (filter_flag) { update_filter(); // 采样后更新滑动平均缓存 filter_flag 0; } if (show_flag) { lcd_refresh(); // 500ms 刷新一次显示 show_flag 0; } if (alarm_flag) { check_alarm(); // 1s 判断一次报警 alarm_flag 0; } } }每个任务执行时间都很短中断里的心率计数不会因为温度换算被长时间阻塞。定时器中断中只置标志位不在中断里做 ADC 采样和 LCD 写入这样中断服务函数保持轻量。任务之间通过标志位解耦任何一个模块出了问题可以单独注释掉对应 if 分支定位起来比打断点还快。4.2 体温通道滑动平均与分段线性换算ADC 原始数据直接拿去显示会出现明显跳动因为电位器中位在仿真里虽然稳定但真实传感器的噪声会让相邻几次采样值差出几个 LSB。滑动平均窗口选 8 或 16窗口太大会让温度报警反应变慢太小又滤波效果差。#define TEMP_BUF 16 unsigned char adc_buf[TEMP_BUF]; unsigned char buf_idx; unsigned int temp_sum; void filter_init(void) { unsigned char i; for (i 0; i TEMP_BUF; i) { adc_buf[i] 0; } buf_idx 0; temp_sum 0; } unsigned char filter_temp(unsigned char adc_val) { temp_sum - adc_buf[buf_idx]; temp_sum adc_val; adc_buf[buf_idx] adc_val; buf_idx (buf_idx 1) % TEMP_BUF; return (unsigned char)(temp_sum / TEMP_BUF); }这里用了一个环形缓冲区最新值覆盖最旧值temp_sum 始终维护窗口内所有值的和返回平均值时用整数除法而不是浮点运算。注意 filter_init 里必须把缓冲区清零并初始化 sum否则前 16 次平均结果会偏低。换成实际温度时不能直接拿 ADC 值线性映射因为 NTC 的阻值和温度是对数关系分压后的电压并不线性。简单做法是分段线性查表取 35℃、37℃、39℃、41℃ 四个断点把对应的 ADC 值预先算好放进数组再在代码里做插值。断点的 ADC 值按下面公式计算温度对应的 NTC 阻值查分度表然后用 R_fixed/(R_ntcR_fixed)*255 得到 ADC 值。仿真阶段用两段线性近似就够实物阶段再增加断点数量。4.3 心率通道边沿检测与 10 秒计数心率通道不关心单个脉冲的宽度只关心单位时间内有多少个边沿。经典做法是外部中断每检测到一个下降沿就对计数器加一主循环每隔 10 秒读取一次计数并换算成每分钟心跳数unsigned int pulse_cnt; unsigned char pulse_ready; void int0_isr(void) __interrupt 0 { pulse_cnt; } void timer1_isr(void) __interrupt 3 { static unsigned char sec_cnt; sec_cnt; if (sec_cnt 10) { sec_cnt 0; pulse_ready 1; } }主循环里看到 pulse_ready 之后把 pulse_cnt 乘以 6 得到心率值然后清零计数器。用 10 秒窗口是为了避免单次脉冲周期抖动带来的误差相比每 60 秒刷新一次10 秒刷新能更快响应心率突变。仿真时信号发生器输出 1Hz 方波心率应显示 60bpm这是整个系统最直接的连通性验证。需要注意外部中断的触发方式。INT0 默认是电平触发电平触发模式下如果方波高电平持续时间过长可能造成重复触发或漏触发。在 init_int0 里显式配置为下降沿触发保证每个脉冲只计一次数void init_int0(void) { IT0 1; // 下降沿触发 EX0 1; // 使能外部中断0 EA 1; // 开总中断 }4.4 报警阈值、回差与延时去抖报警参数是健康检测系统的核心直接拿一个死阈值判断会产生非常难听的反复报警。比如高温阈值设在 37.5℃温度在 37.4℃ 和 37.6℃ 之间抖动时蜂鸣器会开启、关闭、再开启。常见做法是引入回差报警开启用 37.5℃报警关闭用 37.2℃中间留出 0.3℃ 的缓冲区间。参数开启阈值关闭阈值延时确认高温报警37.5℃37.2℃连续 5 秒低温报警35.0℃35.3℃连续 5 秒心率过高120bpm115bpm连续 10 秒心率过低50bpm55bpm连续 10 秒延时确认的原理是报警状态不能由单次采样决定瞬时尖峰和传感器接触不良都会造成单次越限。用一个计数器每次满足报警条件就累加不满足就清零累加值超过设定秒数才真正触发报警unsigned char alarm_cnt; bit alarm_on; void check_alarm(void) { if (!alarm_on (temp 37.5 || heart_rate 120)) { if (alarm_cnt 250) alarm_cnt; if (alarm_cnt 5) { alarm_on 1; alarm_cnt 0; buzzer_on(); } } else if (alarm_on temp 37.2 heart_rate 115) { alarm_on 0; buzzer_off(); } else if (!alarm_on) { alarm_cnt 0; } }alarm_cnt 的类型选择 unsigned char上限 250防止异常情况下计数溢出。回差的实现体现在第二个 if 分支里解除报警用的是 37.2℃ 而不是 37.5℃这样只要温度不跌回安全区就不会出现报警状态下反复切换蜂鸣器。心率报警同理开启 120bpm关闭 115bpm。4.5 串口输出把数据帧给上位机仿真时用 Proteus 的 Virtual Terminal 可以直接看到串口数据但这还不够上位机要能解析出温度和心率就得定义帧结构。常见做法是帧头加数据加校验比如 0xAA 作为帧头后跟温度、心率高字节、心率低字节、和校验void send_frame(unsigned char temp, unsigned int hr) { unsigned char sum 0xAA temp (hr 8) (hr 0xFF); SBUF 0xAA; while (!TI); TI 0; SBUF temp; while (!TI); TI 0; SBUF (unsigned char)(hr 8); while (!TI); TI 0; SBUF (unsigned char)(hr 0xFF); while (!TI); TI 0; SBUF sum; while (!TI); TI 0; }每个字节发送后都要等 TI 置位再清零否则下一个字节会覆盖当前发送缓冲。和校验的目的不是防黑客而是让上位机能识别串口线上的干扰数据。接收侧如果用状态机处理就构成了 modbus 单片机帧接收数据程序的基础形态逐字节判断帧头、长度、校验而不是阻塞等待整帧收完。做毕业设计时如果把串口数据直接打印到上位机不算完整的通信设计应该把这个帧结构保留下来后面接 WiFi 模块或蓝牙模块时可以直接复用。5. 仿真调试验证与后续改造看门狗、低功耗与远传5.1 仿真级验证的三步法仿真系统的验证不需要复杂的测试仪器三步就能覆盖大部分逻辑问题。第一步调电位器把模拟体温的电压调到对应 38℃ 的 ADC 值观察蜂鸣器是否在 5 秒延时后报警再把电压调回 37℃观察回差机制是否让报警正常解除。第二步改信号发生器频率从 1Hz 改成 1.5Hz确认心率从 60bpm 变成 90bpm如果数值翻倍或不变检查外部中断触发方式。第三步看串口用虚拟终端接收 0xAA 帧核对温度、心率和校验字节是否一致。5.2 给系统加一个看门狗和低功耗空转仿真环境里程序不会跑飞但实物上电瞬间或电源跌落时看门狗是最后一道防线。STC89C52 内部看门狗通过 WDT_CONTR 寄存器控制主循环里周期性喂狗void feed_wdt(void) { WDT_CONTR 0x35; // 使能看门狗并清计数器 }喂狗的位置放在主循环最后确保采样、滤波、报警任何一个任务卡死时看门狗都能触发复位。如果做电池供电的便携方案可以在没有按键操作时进入空闲模式用 PCON 寄存器把单片机切换到 IDL 状态定时器仍在工作外部中断可以唤醒void enter_idle(void) { PCON | 0x01; // 进入空闲模式 }空闲模式把 CPU 时钟停掉外设和中断继续工作唤醒后从下一条指令继续执行适合心率这类低频采样场景。进入空闲模式前要确保串口和 ADC 没有正在进行的操作否则唤醒后数据会错乱。5.3 从仿真到实物的迁移清单仿真和实物之间不是简单的换芯片接口差异集中在传感器部分。迁移前先把仿真代码里的传感器模拟源全部替换成真实接口再调整阈值和滤波参数。仿真部分实物替换需要改动的点电位器分压NTC 热敏电阻 固定电阻分压重新计算 ADC 断点用分度表插值信号发生器方波红外对管 比较器整形增加去抖电容调整触发边沿虚拟串口USB-TTL 模块确认波特率一致电平匹配按键模拟报警真实传感器数据阈值按传感器实际电压范围重新标定所有参数中必须重标定的是温度断点表因为 NTC 分压网络和电位器的电压范围不可能完全一样。迁移到实物后先打开串口输出记录环境温度和传感器电压用两点校准法修正断点表。心率通道如果不带整形电路脉冲边沿会很脏外部中断里加软件去抖或者改用定时器输入捕获模式。串口帧结构可以直接复用上位机不用改剩下的工作就是根据真实数据微调回差和报警延时。本文还有配套的精品资源点击获取