news 2026/11/3 7:50:55

基于单片机的脉搏心跳检测Proteus仿真:从传感器选型到代码联调

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于单片机的脉搏心跳检测Proteus仿真:从传感器选型到代码联调

简介:这份资源面向需要在Proteus中完成STM32脉搏心跳检测仿真的学习者,适合课程设计、毕业设计或单片机入门进阶阶段使用。方案以STM32F103为核心,用按键模拟脉搏跳动,每按一次即计为一次心跳,数码管实时显示心跳次数,LCD同步显示当前时间,覆盖了GPIO输入检测、定时器计数、数码管动态扫描与LCD驱动等典型知识点。压缩包共198个文件,约6.1MB,包含c与h源码、o与d编译中间文件、crf与lst链接列表、uvprojx与uvoptx工程配置、hex可执行文件以及pdsprj仿真工程等,完整保留了从Keil工程到Proteus仿真的全套链路。目前已有1272人学习下载,读者可直接打开仿真工程加载hex文件运行,观察按键触发计数与时间显示效果,并对照源码理解脉搏检测的计数逻辑与显示刷新机制,便于快速复现与二次修改。

1. 脉搏心跳检测仿真:为什么很多人第一步就选错了传感器

带学生做单片机课程设计那几年,我见过太多人栽在同一个地方:拿到「基于单片机的脉搏心跳检测设计」这个题目,第一反应是去 Proteus 元件库里翻一个叫「pulse sensor」的东西,翻不到就慌了。其实这个题目的核心矛盾从来不是「有没有现成模块」,而是「脉搏信号到底长什么样、用什么手段把它变成单片机读得懂的电平」。脉搏是皮下动脉随心脏搏动产生的微弱容积变化,频率大约 0.8 到 3 Hz,对应 48 到 180 次每分钟,幅度极小,还混着工频干扰和运动伪迹。你要在 Proteus 里把它仿真出来,就得先决定用红外对管做透射式还是反射式、信号放大几级、滤波截止频率设多少、单片机用什么方式测周期。这套链路想清楚了,仿真才有意义;想不清楚,画出来的图只是能跑,答辩一问就露馅。这篇笔记就按我实际带项目的顺序,把选型、电路、代码、联调和踩坑一次讲透,适合正在做单片机毕业设计或课程设计、手里只有 Proteus 和 Keil 的同学。

2. 脉搏信号链路拆解:从光电传感器到单片机引脚

2.1 为什么仿真里常用红外对管而不是成品模块

真实项目里,指夹式脉搏传感器内部就是一对红外发射管和光敏接收管,血液对红外光的吸收随容积变化,接收管电流跟着波动。Proteus 元件库里没有封装好的「指夹模块」,但有 IR LED 和光敏三极管,可以自己搭。常见做法是用一个红外 LED 正向偏置发射,对面放光敏三极管接收,中间「夹」一个模拟手指的介质——仿真里没法真夹手指,所以要么用信号源直接注入等效的微弱交流,要么用光敏器件的受控特性去模拟。

我一般会跟学生讲清楚:Proteus 仿真的价值不在「还原真实光学过程」,而在「验证信号调理电路和单片机算法」。所以更稳的做法是,把光电转换后的微弱信号用一个交流电压源加直流偏置来等效,频率设 1.2 Hz 左右(约 72 次每分钟),幅度设几毫伏到十几毫伏,然后重点仿真后面的放大、滤波、整形和测频。这样既避开了 Proteus 光学模型不准的坑,又能把真正要考核的电路和代码跑通。

如果你坚持要用光敏器件搭,也行,但要知道 Proteus 里光敏三极管的响应是理想化的,和真实器件的暗电流、响应时间差得远,仿真波形会「太干净」,答辩时老师一眼能看出问题。所以我的建议是:光电部分用等效信号源代替,把精力放在信号调理和算法上。

2.2 三级调理电路:放大、滤波、整形的参数怎么定

脉搏信号从传感器出来,典型幅度是毫伏级,还带着直流偏置。单片机 ADC 要读,或者比较器要整形,都得先把它放大到伏级。我常用的链路是:第一级仪表放大或同相放大,增益 50 到 100;第二级带通滤波,下限 0.5 Hz 去基线漂移,上限 5 Hz 去高频噪声;第三级比较器整形,输出方波给单片机外部中断引脚。

先说第一级。用 LM358 搭同相放大,增益公式是 1 + Rf/R1。假设传感器输出 5 mV,想放大到 0.5 V,增益就是 100,取 Rf = 99 kΩ、R1 = 1 kΩ。Proteus 里 LM358 模型是有的,但要注意它的带宽和压摆率,增益 100 时带宽只剩几十 kHz,对 1 Hz 信号绰绰有余,不用担心。

第二级带通滤波,我一般用两个一阶 RC 串起来,或者用一个二阶压控电压源带通。下限截止频率 f_L = 1/(2πRC),取 R = 100 kΩ、C = 3.3 μF,算出来约 0.48 Hz;上限 f_H 取 R = 10 kΩ、C = 3.3 μF,约 4.8 Hz。这样 1 Hz 左右的脉搏基波能过,50 Hz 工频被压得很低。

第三级比较器,用 LM393 或 LM358 当比较器都行。参考电压设在放大后信号的中心电平,比如 0.5 V。输出接单片机 P3.2(INT0),下降沿触发。这里有个细节:比较器输出是开集电极的,要加上拉电阻,Proteus 里别忘了放。

提示:Proteus 仿真时,信号源的内阻要设得和真实传感器输出阻抗接近,否则放大倍数会和理论值对不上。

2.3 单片机选型:51 还是 STM32,仿真里差别在哪

热词里 51 单片机和 STM32 都出现了,这个题目两种都能做。51 单片机(比如 AT89C51、STC89C52)在 Proteus 里模型成熟,代码简单,适合只测频率、显示心率的场景。STM32F103C8T6 在 Proteus 8.17 之后的版本里才有比较完整的模型,早期版本可能找不到,热词里「proteus没有stm32f407怎么办」说的就是这类问题。

如果你的 Proteus 版本里没有 STM32 模型,别硬找,直接用 51。这个题目的计算量很小,就是测两个下降沿之间的时间,51 的定时器完全够用。我一般用定时器 0 工作在方式 1,16 位计数,最大计 65536 个机器周期。晶振 12 MHz 时,一个机器周期 1 μs,最大测 65.5 ms,对应最低约 15 次每分钟,够用。如果心率更低,可以用定时器溢出中断累加。

选 STM32 的好处是 ADC 精度高、定时器多,但仿真里配置麻烦,对新手不友好。所以我的建议是:课程设计用 51,毕业设计想加显示和报警用 STM32,但先确认你的 Proteus 版本支持。

3. 在 Proteus 里把电路跑起来:元件、连线与激励设置

3.1 元件库对照与最小系统搭建

Proteus 元件库对照表是新手最需要的东西,我列一下这个题目常用的:

元件Proteus 关键字说明
51 单片机AT89C51 / AT89C52仿真用,STC 可用 AT89C52 替代
运放LM358双运放,放大和比较都能用
比较器LM393专用比较器,输出需上拉
红外发射管IR LED若用光学模拟
光敏三极管PHOTOTRANSISTOR若用光学模拟
交流信号源VSINE等效脉搏信号
直流电源DC给运放供 5 V
晶振CRYSTAL12 MHz
电容CAP滤波用
电阻RES放大和上拉用
液晶LM016L1602 显示
蜂鸣器BUZZER报警用,注意选有源还是无源

热词里「proteus中无源蜂鸣器是哪个」问的人很多。Proteus 里 BUZZER 是有源蜂鸣器,给电就响;SOUNDER 是无源,需要方波驱动。报警用有源 BUZZER 最简单,给高电平就响。

最小系统:单片机 + 晶振 + 复位电路 + 电源。Proteus 里单片机默认已经接了电源和地,不用画,但晶振和复位要自己加。晶振接 XTAL1、XTAL2,复位接 RST,用 10 μF 电容加 10 kΩ 电阻做上电复位。

3.2 信号源等效设置与比较器阈值调整

信号源用 VSINE,频率设 1.2 Hz,幅度设 5 mV,加一个 2.5 V 直流偏置(用两个电阻分压或者直接串一个 DC 源)。这样运放输入端看到的是 2.5 V ± 5 mV 的交流。

第一级放大 100 倍后,输出是 2.5 V ± 0.5 V,即 2.0 到 3.0 V。第二级带通滤波后幅度略减,中心还是 2.5 V。比较器参考电压就设 2.5 V,用两个 10 kΩ 电阻分压得到。这样比较器输出就是干净的方波,频率和信号源一致。

注意:Proteus 里 VSINE 的默认幅度是 1 V,一定要改,否则放大后直接饱和,波形削顶,测出来的频率虽然对,但看不出调理电路的作用。

比较器阈值不能设得太接近中心,否则噪声会让输出抖动。我一般让阈值偏离中心 50 mV 左右,即 2.45 V 或 2.55 V,这样既能可靠翻转,又不会因为信号幅度小而不触发。

3.3 用虚拟示波器看波形:四个必看节点

Proteus 的虚拟示波器(OSCILLOSCOPE)有四个通道,我一般接这四个点:

  1. 信号源输出:看原始脉搏等效信号,确认频率和幅度。
  2. 第一级运放输出:看放大后的信号,确认没有削顶。
  3. 第二级滤波输出:看带通效果,确认基线稳定。
  4. 比较器输出:看方波,确认占空比接近 50%。

调的时候先看通道 1 和 2,如果通道 2 削顶,说明增益太大,把 Rf 调小。如果通道 2 幅度太小,说明增益不够,调大 Rf。通道 3 如果基线漂移,说明下限截止频率太高,把电容调大。通道 4 如果方波占空比严重偏离 50%,说明比较器阈值偏了,调分压电阻。

这套流程走下来,电路基本就稳了。很多学生跳过示波器直接跑代码,结果频率测出来乱跳,回头查半天,其实波形早就告诉你了。

4. 单片机代码:定时器测周期与心率换算

4.1 外部中断加定时器的测频思路

脉搏信号整形后是方波,频率就是心率除以 60。测频率最稳的方法是测周期:用外部中断 INT0 捕获下降沿,两次下降沿之间的时间就是一个周期。定时器 0 自由运行,中断里读定时器值,算差值。

51 单片机定时器 0 方式 1 是 16 位,最大 65536 μs。心率 30 次每分钟时周期 2 s,远超范围,所以要处理溢出。我一般用定时器溢出中断累加一个变量,每次溢出加 65536,外部中断里把溢出计数和当前定时器值合起来算总时间。

#include <reg52.h> unsigned int timer_overflow = 0; // 定时器溢出次数 unsigned int last_capture = 0; // 上次捕获的定时器值 unsigned int period = 0; // 周期,单位微秒 bit flag_ready = 0; // 数据就绪标志 void Timer0_Init() { TMOD &= 0xF0; TMOD |= 0x01; // 定时器0,方式1,16位 TH0 = 0x00; TL0 = 0x00; ET0 = 1; // 允许定时器0中断 EA = 1; // 开总中断 TR0 = 1; // 启动定时器0 } void Int0_Init() { IT0 = 1; // 下降沿触发 EX0 = 1; // 允许外部中断0 } void Timer0_ISR() interrupt 1 { timer_overflow++; // 每次溢出加1 } void Int0_ISR() interrupt 0 { unsigned int now; unsigned long total; now = (TH0 << 8) | TL0; // 当前定时器值 total = (unsigned long)timer_overflow * 65536UL + now - last_capture; period = (unsigned int)total; // 简化处理,实际要处理溢出回绕 last_capture = now; timer_overflow = 0; // 清溢出计数 flag_ready = 1; } void main() { Timer0_Init(); Int0_Init(); while(1) { if(flag_ready) { flag_ready = 0; // 心率 = 60,000,000 / period,period单位微秒 // 后续送显示 } } }

这段代码的关键点:定时器 0 一直跑,外部中断来了就读当前值,和上次的值做差。timer_overflow在每次定时器溢出时加 1,算总时间时要乘 65536。注意last_capture和now的比较要考虑回绕,简单做法是每次中断后清零溢出计数,用total算差值。实际项目里我会用unsigned long存总时间,避免溢出。

参数说明:晶振 12 MHz,机器周期 1 μs,所以定时器计数值就是微秒数。心率 60 次每分钟时周期 1 s,即 1,000,000 μs,需要溢出约 15 次。period算出来后,心率 = 60,000,000 / period。

4.2 心率换算与显示刷新

心率换算就是 60 秒除以周期。周期单位是微秒,所以心率 = 60,000,000 / period。比如 period = 833,333 μs,心率 = 72。这个除法在 51 上要用长整型,直接算就行,不用浮点。

显示用 1602 液晶,LM016L 在 Proteus 里是标准 1602 模型。接法:RS、RW、E 接单片机 IO,D0 到 D7 接 P0 口(要加上拉电阻,因为 P0 是开漏)。显示内容就是「HR: 72 BPM」。

刷新频率不用太高,每测到一个周期更新一次就行。如果信号抖动,可以连续测 4 个周期取平均,再算心率。我一般用滑动平均,存最近 4 个 period,求和除以 4,这样显示稳定。

unsigned int period_buf[4] = {0}; unsigned char buf_index = 0; unsigned int get_avg_period() { unsigned long sum = 0; unsigned char i; for(i = 0; i < 4; i++) { sum += period_buf[i]; } return (unsigned int)(sum / 4); }

这段代码在每次外部中断里把新 period 存入缓冲区,覆盖最旧的值。取平均后再算心率,显示就不会跳来跳去。

4.3 报警阈值与蜂鸣器驱动

报警逻辑很简单:心率低于 50 或高于 120 就触发蜂鸣器。用有源 BUZZER 的话,给一个 IO 高电平就行。注意 51 的 IO 灌电流有限,蜂鸣器最好用三极管驱动,Proteus 里加一个 NPN 三极管和基极电阻。

sbit BUZZER = P1^0; void check_alarm(unsigned char hr) { if(hr < 50 || hr > 120) { BUZZER = 1; // 报警 } else { BUZZER = 0; // 正常 } }

阈值可以根据实际需求调,比如运动后心率上限可以设到 150。报警延时可以加,避免瞬时抖动误报。我一般连续 3 次超限才报警,这样更稳。

5. 联调避坑:仿真能跑但结果不对的五个原因

5.1 波形正常但心率显示乱跳

现象:示波器看比较器输出方波很干净,但液晶显示的心率在 60 到 90 之间乱跳。

原因:外部中断里读定时器值时没有关中断,或者定时器溢出计数和定时器值读取不同步。比如先读 TH0 再读 TL0,中间定时器可能进位,导致读到的值偏差。

解决:读定时器值时先关 ET0,读完再开。或者用两次读取比较法,连续读两次,如果差值小于某个阈值就认为有效。更简单的做法是在外部中断里先 TR0 = 0 停止定时器,读完再 TR0 = 1。

5.2 比较器输出一直是高电平

现象:比较器输出不翻转,一直是高。

原因:参考电压设错了,或者信号没有真正到达比较器输入端。常见的是第一级运放输出饱和到电源轨,第二级滤波后信号被削掉,比较器输入端一直是高电平。

解决:用示波器逐级看波形,先确认信号源有输出,再确认第一级放大倍数合适,最后确认比较器参考电压在信号中心。Proteus 里 LM393 的输出需要上拉电阻,忘了加上拉也会一直高。

5.3 定时器测出的周期总是 65536 的整数倍

现象:算出来的周期总是 65536 左右,心率显示 15 或 30 这种异常值。

原因:定时器溢出中断没有正确处理,或者溢出计数变量在外部中断里没有清零。比如timer_overflow在外部中断里忘了清零,下次算的时候累加了多次。

解决:每次外部中断处理完后,把timer_overflow清零,last_capture更新为当前值。如果周期确实超过 65536 μs,要用unsigned long存总时间,不能只用unsigned int。

5.4 液晶显示乱码或什么都不显示

现象:1602 液晶上电后显示一排方块,或者什么都不显示。

原因:P0 口没有加上拉电阻。51 的 P0 口是开漏输出,驱动液晶数据线必须加上拉,Proteus 里加一个 10 kΩ 排阻。另外,液晶初始化时序不对也会导致不显示。

解决:P0 口接 10 kΩ 上拉排阻到 5 V。初始化代码里延时要给够,Proteus 仿真可以适当缩短,但真实硬件必须按手册来。对比度调节引脚接一个电位器,Proteus 里可以用两个电阻分压。

5.5 仿真跑一会儿就卡死

现象:Proteus 仿真运行几秒后卡住,或者单片机不响应。

原因:中断里做了太多事情,或者中断嵌套导致堆栈溢出。51 的堆栈在内部 RAM,空间有限,中断里调用液晶显示函数这种耗时操作容易出问题。

解决:中断里只做标志位设置和数据保存,主循环里再做显示和计算。中断服务函数尽量短,不要在里面延时或调用复杂函数。Proteus 仿真速度受电脑性能影响,可以把仿真速度调慢一点,观察是否还有问题。

6. 进阶技巧:用信号源模拟不同心率与运动伪迹

仿真跑通之后,别急着收工。真正能让你在答辩里加分的是「边界测试」。我一般会做三组测试:第一组,信号源频率从 0.8 Hz 扫到 3 Hz,看心率显示是否线性跟随;第二组,在信号源上叠加一个 0.1 Hz 的慢漂移,模拟基线漂移,看带通滤波能不能压住;第三组,叠加一个 50 Hz 的小幅度正弦,模拟工频干扰,看比较器输出是否还干净。

具体操作:VSINE 的频率改一下就行,0.8 Hz 对应 48 次每分钟,3 Hz 对应 180 次每分钟。基线漂移用一个低频正弦源和脉搏源串联,幅度设 50 mV。工频干扰用一个 50 Hz 源串联,幅度设 1 mV。然后看液晶显示和示波器波形。

测试项信号源设置预期结果常见问题
心率范围0.8-3 Hz显示 48-180低频时周期溢出
基线漂移叠加 0.1 Hz/50 mV显示稳定高通截止频率太低
工频干扰叠加 50 Hz/1 mV方波干净低通截止频率太高
幅度变化1-20 mV显示不变比较器阈值需自适应

如果低频时周期溢出,把定时器溢出处理写对就行。如果基线漂移压不住,把高通电容调大,截止频率降到 0.3 Hz。如果工频干扰还在,把低通电容调大,截止频率降到 3 Hz。如果幅度变化导致不触发,可以考虑用自动增益控制,但仿真里没必要,固定增益加合适阈值就够了。

最后说个我自己的习惯:每次改完电路参数,一定先用示波器看四个节点,确认波形对了再跑代码。很多学生反过来,先跑代码,结果不对再回头查电路,浪费大量时间。仿真软件里的虚拟示波器就是你的眼睛,别省这一步。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 7:50:32

量子计算测试:从经典断言到概率验证的范式转变

1. 量子计算测试的范式革命凌晨三点十七分&#xff0c;实验室的量子比特校准警报第三次响起。我盯着屏幕上那组看似随机波动的量子态保真度数据&#xff0c;突然意识到传统测试方法论正在崩塌——当被测对象本身处于叠加态时&#xff0c;我们该如何定义"通过"与"…

作者头像 李华
网站建设 2026/11/3 7:50:08

CSS light-dark()函数:动态主题切换新方案

1. 为什么light-dark()函数值得前端开发者关注上周在重构公司后台管理系统时&#xff0c;我遇到了一个典型场景&#xff1a;用户希望在白天使用浅色主题&#xff0c;夜间自动切换深色模式。传统做法需要维护两套CSS变量&#xff0c;通过JavaScript监听prefers-color-scheme媒体…

作者头像 李华
网站建设 2026/11/3 7:49:04

WorkBuddy实操复盘:一个人用AI工作台从零到上架完成App开发

1. 项目概述&#xff1a;为什么拿 WorkBuddy 搭 App去年下半年我自己折腾了一个小工具类 App&#xff0c;从需求文档写到应用商店上架&#xff0c;全程一个人。用的主力工具是 WorkBuddy。说实话&#xff0c;一开始我对这类 AI 工作台半信半疑——能聊天、能补代码我都信&#…

作者头像 李华
网站建设 2026/11/3 7:48:49

Java核心技术卷一精读指南:从基础到集合泛型的实操路线

我书架上那本《Java核心技术 卷一》被翻得最烂的是第 4 章和第 9 章&#xff0c;书脊都快散了&#xff0c;而第 10、11 章讲图形界面的部分几乎是全新的。这个细节基本能说明一个问题&#xff1a;这本书的价值不在于“有没有”&#xff0c;而在于“读了哪几章、动手验了哪几段”…

作者头像 李华
网站建设 2026/11/3 7:48:49

《Java核心技术 卷一》高效精读指南:泛型、集合与并发学习路径

很多人把这本《Java核心技术 卷一》当成"工具书"供在书架上&#xff0c;买回来塑封都没拆&#xff0c;问起来就说"等有空了系统看一遍"。我带过几个新人&#xff0c;几乎每个人第一周都会问同一个问题&#xff1a;这本书该从哪一章开始看&#xff1f;问完之…

作者头像 李华
网站建设 2026/11/3 7:48:44

arXiv论文怎么读?从筛选题到复现落地的方法论

arXiv 的 cs.LG 分区&#xff0c;大概是机器学习圈子里每天最早“醒来”的地方之一。早上七点刷新页面&#xff0c;新一批论文已经挂在网上&#xff0c;标题、摘要、作者列表一屏都放不下。2026 年 9 月 22 日这一期也不例外&#xff0c;我照例先扫了一遍标题池&#xff0c;再挑…

作者头像 李华