简介这是一套面向电子类专业学生与单片机初学者的心率检测课程设计资料基于51单片机搭配LCD1602显示在Proteus中完成整机仿真。系统将实测脉搏与80120次/分的正常区间比对正常时屏幕显示“very good”超出范围则提示“a little bad”同时通过判断相邻脉冲间隔是否落在50199次/分的有效区间来滤除干扰降低测量误差。压缩包共38个文件、约847KB包含Proteus仿真工程、Keil C源代码与工程文件、原理图、流程图、元件清单及功能介绍等覆盖从电路搭建到程序烧录的完整链路。已有270人学习下载。读者可据此直接复现仿真、对照原理图理解信号采集与显示逻辑并借助流程图和清单快速梳理设计思路适合课程设计、毕业设计及自学练手使用。1. 从一块“跑不起来”的51心率板说起这份资源到底能帮你省掉多少事很多人第一次做51单片机的心率检测项目卡住的地方往往不是代码而是“我明明照着原理图连了线Proteus里就是不亮”。心率检测这个题目在课程设计和入门实战里出现频率极高核心链路其实不复杂光电传感器采集脉搏信号经过放大和整形送进单片机做计数和换算最后在LCD上显示每分钟心跳数。但真正动手时传感器信号太弱、比较器阈值不对、定时器算错、LCD时序差半个周期任何一个环节都能让你调一晚上。这份资源包解决的正是这个痛点。它把基于51单片机的心率检测项目拆成了Proteus仿真工程、原理图、流程图、物料清单和源代码几个部分适合正在做课程设计的学生、刚接触单片机的嵌入式新手以及需要快速搭一个心率检测Demo的从业者。你拿到的不只是一段代码而是一整套可以照着复现的工程文件。下面我会按“先看懂原理、再跑通仿真、最后避开坑”的顺序把这份资源里的关键内容拆开讲清楚。2. 心率检测的信号链与51单片机选型为什么是这套方案2.1 从脉搏到数字信号调理的三个阶段心率检测的本质是把微弱的脉搏跳动转换成单片机可以计数的方波。常见做法是使用红外对管或光电传感器贴在指尖或耳垂上随着血液容积变化接收端的光电流会产生微小波动。这个波动通常在毫伏级别而且夹杂着工频干扰和基线漂移直接送进单片机基本没法用。所以信号链一般分三段第一段是前置放大把微伏到毫伏级的信号放大到伏级第二段是滤波去掉50Hz工频和高频噪声第三段是整形用比较器把模拟波形变成干净的方波送给单片机的外部中断或定时器引脚。这份资源里的原理图应该就是按这个思路走的你在Proteus里能看到对应的运放和比较器模块。注意仿真里光电传感器的输出往往被理想化实际搭电路时信号要弱得多比较器阈值需要根据实测调整。2.2 为什么选51而不是STM32这个问题我被问过很多次。51单片机的优势在于结构简单、时序直观、资料多到泛滥对于心率检测这种“计数显示”的任务完全够用。你只需要一个定时器做1秒闸门一个外部中断或者计数器引脚来数方波个数再驱动LCD1602显示资源占用很低。STM32当然性能更强但如果你连51的定时器中断都没调明白换到STM32只会更懵。这份资源用51单片机做核心配套的Proteus仿真对电脑配置要求也低跑起来快适合反复修改参数验证逻辑。常见做法是先用51把整个流程跑通理解定时器计数和LCD驱动的配合再迁移到其他平台。2.3 资源包里的文件怎么对应到工程拿到压缩包后建议先按类型分类不要一上来就双击打开。我一般会这样整理文件类型用途建议打开顺序Proteus工程文件仿真运行验证逻辑最后打开原理图查看硬件连接先看流程图理解程序结构先看源代码编译下载中间看物料清单实际焊接参考按需看先看流程图和原理图把信号从传感器到LCD的路径在脑子里过一遍再打开源代码对照最后跑Proteus仿真。这个顺序能帮你少走很多弯路。3. 在Proteus里把仿真跑起来从加载工程到看到心跳数3.1 加载工程与检查元件库Proteus仿真能不能跑第一步取决于元件库是否完整。打开工程后先看有没有黄色感叹号或者红色缺失提示。常见缺失的是光电传感器模型和LCD1602这两个在较新的Proteus版本里一般都有但如果你的版本较老可能需要手动添加库文件。加载工程后双击单片机查看加载的hex文件路径是否正确。如果路径指向的是原作者电脑上的目录你需要重新定位到你自己解压后的hex文件。这一步看似简单但很多人卡在这里仿真跑起来没反应其实就是hex没加载对。# 在Proteus中检查hex加载路径的步骤手动操作非命令行 # 1. 双击单片机元件 # 2. 在Program File栏点击文件夹图标 # 3. 定位到解压目录下的.hex文件 # 4. 确认Clock Frequency设置为11.0592MHz或12MHz # 5. 点击OK保存时钟频率这个参数很关键。51单片机的定时器计算依赖晶振频率如果仿真里设的是12MHz而代码里按11.0592MHz算的定时初值计数结果就会偏。常见做法是统一用11.0592MHz因为串口通信时波特率更准虽然这个项目不一定用串口但保持一致性没坏处。3.2 信号源的模拟与激励Proteus里没有真实的指尖给你测所以需要用一个信号发生器来模拟脉搏波形。常见做法是用Pulse Generator或者Sine Source频率设置在1Hz到3Hz之间对应60到180次每分钟的心率范围。具体操作是把信号发生器的输出接到原理图中传感器输出对应的节点然后调整幅度到比较器能翻转的水平。如果你发现LCD一直显示0先别急着改代码用Proteus的虚拟示波器看一下比较器输出有没有方波。没有方波后面计数全是白搭。# Proteus虚拟示波器使用要点 # 1. 从左侧工具栏拖出Oscilloscope # 2. 通道A接比较器输出通道B接信号源输出 # 3. 运行仿真观察是否有方波 # 4. 调整信号源幅度直到方波占空比接近50%提示仿真里信号源幅度可以设大一点比如1V到2V实际电路里可能需要放大1000倍才能达到这个水平。3.3 定时器计数逻辑与LCD刷新源代码里核心的逻辑应该是定时器0负责1秒定时外部中断0或者定时器1的计数模式负责数方波个数。每满1秒把计数值乘以60得到每分钟心率然后刷新LCD。这里有个容易翻车的地方如果方波信号在1秒内个数很少比如只有1个乘以60就是60看起来没问题。但如果信号抖动一个心跳产生多个脉冲读数就会翻倍。所以代码里通常要加一个简单的滤波比如连续两次读数接近才更新显示或者限制最大最小值。// 定时器0中断服务函数示例1秒闸门 void Timer0_ISR() interrupt 1 { static unsigned int count 0; TH0 0x3C; // 50ms定时初值20次中断为1秒 TL0 0xB0; count; if (count 20) { count 0; heart_rate pulse_count * 60; // 换算成每分钟 pulse_count 0; // 清零计数器 update_lcd(heart_rate); // 刷新显示 } }这段代码里pulse_count是在外部中断里自增的每来一个方波加一。TH0和TL0的初值对应50ms20次中断正好1秒。如果你用的晶振频率不同这两个值要重新算。参数说明heart_rate是最终显示值update_lcd是你自己写的LCD刷新函数注意不要在中断里做太耗时的操作否则会丢脉冲。4. 源代码结构与流程图对照把每个函数落到硬件动作上4.1 主循环、中断与LCD驱动的分工拿到源代码后不要从头到尾逐行读先看流程图。流程图一般会把程序分成几个块初始化、主循环、定时器中断、外部中断、LCD显示。你对照着看代码就能知道每个函数对应哪个硬件动作。主循环通常只做两件事初始化之后等待中断以及定期刷新LCD。真正的心率计数在中断里完成。这种结构的好处是主循环不阻塞LCD刷新和计数互不干扰。但要注意如果在主循环里做延时或者等待按键可能会影响中断响应。// 主函数结构示例 void main() { init_timer0(); // 定时器0初始化1秒闸门 init_ext0(); // 外部中断0初始化计数引脚 init_lcd(); // LCD1602初始化 while(1) { // 主循环可以做按键扫描或者低功耗等待 // 心率刷新已经在定时器中断里完成 } }init_timer0里要设置TMOD寄存器把定时器0设为模式116位定时然后开中断。init_ext0设置IT0为下降沿触发这样方波的每个下降沿都会触发一次中断pulse_count加一。LCD初始化按HD44780的时序来延时不能省。4.2 外部中断计数与消抖处理外部中断计数看起来简单但实际信号往往有毛刺。如果比较器输出不够干净一个心跳可能触发多次中断导致读数偏高。常见做法是在外部中断里加一个简单的软件消抖比如检测到下降沿后延时几个微秒再确认电平。// 外部中断0服务函数带简单消抖 void Ext0_ISR() interrupt 0 { delay_us(50); // 短延时避开毛刺 if (INT0 0) { // 确认还是低电平 pulse_count; } }delay_us是一个微秒级延时函数可以用循环实现。INT0是外部中断0的引脚。这个消抖逻辑只能对付比较窄的毛刺如果信号本身抖动严重还是要在硬件上加RC滤波。参数上延时时间根据信号质量调整一般50到100微秒够用。4.3 从计数值到心率值的换算与显示计数值到心率的换算公式是心率 每分钟脉冲数。因为闸门时间是1秒所以心率 pulse_count * 60。但这里有个细节如果闸门时间不是精确的1秒比如定时器初值有误差换算结果就会有系统偏差。常见做法是在代码里用宏定义闸门时间方便调整。#define GATE_TIME_MS 1000 // 闸门时间1秒 #define SAMPLE_COUNT (GATE_TIME_MS / 50) // 50ms中断一次共20次 // 在定时器中断里 if (count SAMPLE_COUNT) { heart_rate pulse_count * (60000 / GATE_TIME_MS); // 当GATE_TIME_MS为1000时系数为60 }这样写的好处是如果你把闸门时间改成2秒来提高低心率下的精度只需要改GATE_TIME_MS换算系数自动跟着变。LCD显示时注意刷新频率不要太高否则闪烁严重一般1秒刷新一次刚好。5. 避坑与排查仿真跑不通时先查这五条5.1 现象LCD亮但无显示或者只显示一排黑块原因LCD初始化时序不对或者对比度电压没调好。Proteus里的LCD1602模型对初始化顺序比较敏感如果init_lcd里延时不够或者指令顺序错了就会卡在初始化阶段。解决对照HD44780手册检查初始化流程确保上电后延时足够长一般大于15ms然后依次发送功能设置、显示开关、清屏等指令。Proteus里可以双击LCD查看对比度参数适当调整。5.2 现象心率读数一直是0原因外部中断没有触发或者比较器输出没有方波。常见的是信号源幅度太小比较器阈值设得太高输出一直不翻转。解决用Proteus示波器看比较器输出。如果没有方波调大信号源幅度或者降低比较器阈值。如果有方波但读数还是0检查外部中断是否使能IT0是否设为下降沿触发以及EX0和EA是否打开。5.3 现象心率读数偏高比如实际60显示120原因一个心跳触发了两次中断通常是信号毛刺或者比较器回差太小。仿真里信号源太理想实际电路里更常见。解决在外部中断里加消抖延时或者在硬件上增加比较器回差。代码里也可以加一个最小脉冲间隔判断比如两次中断间隔小于200ms就忽略后一次。5.4 现象Proteus提示hex文件找不到或格式错误原因hex文件路径不对或者编译时没有生成hex。Keil默认不生成hex需要在Output选项卡里勾选“Create HEX File”。解决重新编译源代码确认勾选了生成hex。然后在Proteus里重新定位hex文件路径注意不要有中文路径Proteus对中文路径支持不好。5.5 现象仿真运行一段时间后LCD数值乱跳原因定时器中断和主循环里的LCD刷新冲突或者变量没有加volatile导致编译器优化出错。解决把中断里修改的全局变量加上volatile关键字比如volatile unsigned int pulse_count。LCD刷新尽量放在主循环里用标志位触发不要在中断里直接调用LCD驱动函数。6. 进阶技巧把仿真参数映射到真实电路仿真跑通只是第一步真正焊接电路时你会发现很多参数要重新调。我一般会做一张参数对照表把仿真里的理想值和实际电路的调整范围都记下来。参数仿真典型值实际电路调整范围调整方法信号源幅度1V~2V受传感器和放大倍数影响调前置放大反馈电阻比较器阈值0.5V0.1V~1V调比较器分压电阻定时器初值50ms12MHz根据晶振频率重算改TH0/TL0LCD对比度默认值0.5V~2V调电位器消抖延时50us20us~200us改延时循环次数这张表的意义在于仿真里能跑通的参数实际电路里往往要往某个方向偏。比如仿真里信号源幅度1V就能让比较器翻转实际电路里传感器输出可能只有几毫伏你需要把放大倍数调到几百甚至上千。比较器阈值也要跟着调否则要么不翻转要么一直翻转。还有一个技巧是在代码里加一个串口输出把pulse_count和heart_rate实时打印出来。这样即使LCD显示不正常你也能通过串口助手看到内部数据快速定位是计数问题还是显示问题。51单片机的串口配置不复杂定时器1做波特率发生器SCON设为模式1TI和RI用中断或者查询都行。// 串口初始化9600波特率11.0592MHz void init_uart() { TMOD | 0x20; // 定时器1模式28位自动重装 TH1 0xFD; // 9600波特率初值 TL1 0xFD; TR1 1; // 启动定时器1 SCON 0x50; // 串口模式1允许接收 EA 1; ES 1; // 开串口中断 } // 串口发送一个字节 void uart_send_byte(unsigned char dat) { SBUF dat; while(!TI); TI 0; }TH1和TL1的初值0xFD对应9600波特率晶振必须是11.0592MHz。如果你用的是12MHz晶振这个初值会有误差波特率会偏串口助手收到的就是乱码。所以再次强调晶振频率和定时器参数要统一。从那以后我每次做单片机项目都会先把串口调通把关键变量打印出来看。仿真里LCD显示正常不代表内部逻辑没问题串口输出才是真正的黑匣子。希望这份资源能帮你把心率检测这个项目跑通少熬几个晚上。本文还有配套的精品资源点击获取