AT89S52数字心率计设计:从光电脉搏波到数码管显示的完整实现
2026/9/19 18:37:23 网站建设 项目流程

简介:这份毕业设计文档面向电子信息、自动化等专业的本科毕业生,提供一套基于AT89S52单片机的数字心率计完整设计方案,可用于课程设计、毕业答辩或嵌入式入门实践。资源包内含1个doc文件,约2.38MB,即论文正文,涵盖摘要、总体方案、元器件选型、硬件电路图、程序流程图与系统测试等章节。设计以AT89S52为核心,采用红外光电传感器OPT101采集人体微弱信号,经OP07与LM324N双重放大、A/D转换后由单片机计算,最终通过四位数码管实时显示心率,并采用主程序、信号采集、放大处理、显示等模块化结构,便于扩展与维护。文中还给出硬件电路与软件编程思路,可帮助读者理解从信号采集到数字显示的完整链路,并参考其测试与误差分析方法。目前已有50人学习,适合需要低成本、高精度心率测量方案参考的读者。

1. 从光电脉搏波到数码管读数:AT89S52 数字心率计到底在做什么

心率计的核心不是“算心跳”,而是把一段微弱的、带直流偏置的模拟脉搏波,变成单片机可以稳定计数的方波。AT89S52 在这套方案里承担的角色很明确:它不负责模拟前端,只负责定时、计数、换算和显示。真正决定成败的是 OPT101 光电接收、LM324N 信号调理和 ADC0809 采样这三段模拟链路。

很多人做毕业设计时把精力全放在写显示程序上,结果板子焊完发现数码管数字乱跳。原因通常不在 8051 代码,而在光电信号没有经过正确的隔直和整形。OPT101 输出的是叠加在约 1V 直流上的脉搏交流分量,幅度可能只有几十毫伏,直接送 ADC0809 会被直流淹没。所以这套标题对应的完整链路是:OPT101 采集 → LM324N 隔直放大与滤波 → 比较器整形 → AT89S52 用定时器/计数器测周期 → 换算 BPM → 数码管显示。适合电子、自动化、生物医学工程方向的本科毕业设计,也适合想复习 51 单片机定时器与外部中断配合的工程师。

2. AT89S52 与 ADC0809 的硬件链路怎么搭

2.1 OPT101 与 LM324N 的信号调理电路

OPT101 内部集成了光电二极管和跨阻放大器,输出阻抗低,直接接 LM324N 的同相输入端即可。但它的输出含直流分量,必须先用高通滤波隔直。常见做法是用 1μF 电容串联 10kΩ 电阻构成一阶高通,截止频率约 16Hz,刚好让 0.8Hz 到 3Hz 的脉搏波通过,同时抑制直流。

LM324N 在这里用两级:第一级做同相放大,增益设为 100 倍左右,把几十毫伏的交流分量抬到伏级;第二级做二阶低通,截止频率设在 5Hz 附近,滤掉工频和运动伪迹。LM324N 是四运放,单电源供电时要注意输入共模范围,同相端偏置在 VCC/2 附近最稳。

# 用 LTspice 或 Multisim 验证高通截止频率的快速估算 # f_c = 1 / (2 * pi * R * C) # R = 10k, C = 1uF => f_c ≈ 15.9 Hz # 放大倍数 Av = 1 + Rf/R1,Rf=100k, R1=1k => Av=101

参数说明:Rf 和 R1 决定增益,增益不是越大越好。增益过大会让 LM324N 输出饱和,脉搏波的收缩峰和舒张峰被削平,后续比较器无法区分。建议先用示波器看 LM324N 输出,调到峰峰值 1V 到 2V 之间再往下接。

2.2 ADC0809 与 AT89S52 的接口时序

ADC0809 是 8 位逐次逼近型 ADC,8 通道,这里只用 IN0。它和 AT89S52 的连接分三组:数据线 D0-D7 接 P0 口,控制线 START、ALE、OE 接 P2 口,EOC 接 P3.2 外部中断。时钟端 CLK 需要 500kHz 左右,可以用 AT89S52 的 ALE 输出经 74LS74 二分频得到。

信号ADC0809 引脚AT89S52 引脚作用
START6P2.0启动转换
ALE22P2.1通道地址锁存
OE9P2.2输出使能
EOC7P3.2/INT0转换结束中断
CLK10ALE/2500kHz 时钟
D0-D717-14,8-5P0.0-P0.7数据总线

启动转换的代码要严格按手册时序:先送通道地址,再给 ALE 脉冲,再给 START 脉冲,然后等待 EOC 变高。

#include <reg52.h> #include <absacc.h> sbit START = P2^0; sbit ALE = P2^1; sbit OE = P2^2; sbit EOC = P3^2; unsigned char adc_read() { unsigned char val; ALE = 0; START = 0; OE = 0; ALE = 1; // 锁存通道地址 START = 1; // 启动转换 ALE = 0; START = 0; while(EOC == 0); // 等待转换结束 OE = 1; // 使能输出 val = P0; // 读取 8 位结果 OE = 0; return val; }

逻辑说明:ALE 上升沿锁存 P0 口低三位作为通道号,START 上升沿启动转换,EOC 从低变高表示转换完成,OE 拉高后 P0 口输出有效数据。参数上,ADC0809 转换时间约 100μs,500kHz 时钟下约 128μs,while 等待不会太久,但不要在主循环里频繁调用,否则会拖慢显示刷新。

2.3 用 AT89S52 定时器测周期间的取舍

测心率有两种思路:测频率和测周期。脉搏波频率低,0.8Hz 到 3Hz,直接测频率在 1 秒闸门内只有 1 到 3 个脉冲,分辨率太差。所以常见做法是测周期:用 INT0 捕获脉搏波整形后的上升沿,记录两次中断之间的定时器计数值,再换算成 BPM。

AT89S52 的定时器 0 工作在模式 1,16 位计数,机器周期 1μs 时最大计 65536μs,约 65ms。脉搏周期在 333ms 到 1250ms 之间,会溢出。解决办法是定时器 0 设 50ms 中断一次,在中断里累加溢出次数,INT0 中断里读取总时间。

unsigned int overflow = 0; unsigned int period = 0; unsigned char flag = 0; void timer0_isr() interrupt 1 { TH0 = 0x3C; TL0 = 0xB0; // 50ms 重装 overflow++; } void int0_isr() interrupt 0 { unsigned int now = overflow * 50000 + (TH0 << 8 | TL0); static unsigned int last = 0; if (last != 0) { period = now - last; flag = 1; } last = now; }

参数说明:TH0/TL0 重装值 0x3CB0 对应 50ms(12MHz 晶振)。overflow 每 50ms 加一,now 的单位是微秒。period 算出后,BPM = 60000000 / period。注意 INT0 中断里不要做除法,除法放在主循环,否则中断响应会变慢,丢失下一个脉冲。

3. 从 ADC 采样到 BPM 显示的完整程序流程

3.1 主循环的状态机设计

主循环不要写成 delay 堆砌,用状态机更稳。状态分三个:等待脉搏、计算 BPM、刷新显示。ADC 采样只在等待状态里做,用来判断信号是否有效,避免噪声触发 INT0。

void main() { unsigned int bpm; init_timer0(); init_int0(); while(1) { if (flag) { flag = 0; if (period > 300000 && period < 1500000) { // 40-200 BPM bpm = 60000000UL / period; display_bpm(bpm); } } // 用 ADC 做信号质量监测,阈值可调 if (adc_read() < 30) { display_error(); } } }

逻辑说明:period 的有效范围限制在 300ms 到 1500ms,对应 40 到 200 BPM,超出范围视为噪声丢弃。ADC 读值低于 30 说明 OPT101 没检测到手指或信号太弱,显示错误提示。参数上,阈值 30 是 8 位 ADC 的经验值,实际要根据 LM324N 输出偏置调整。

3.2 数码管动态扫描与 BPM 换算

四位共阳数码管用 P1 口段选、P2.3-P2.6 位选。动态扫描频率要高于 50Hz,否则会闪。定时器 1 设 2ms 中断一次,每次刷新一位。

unsigned char code seg[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; unsigned char disp[4]; void timer1_isr() interrupt 3 { static unsigned char idx = 0; P2 = (P2 & 0x87) | (1 << (idx + 3)); // 位选 P1 = seg[disp[idx]]; idx = (idx + 1) % 4; } void display_bpm(unsigned int bpm) { disp[0] = bpm / 1000; disp[1] = bpm / 100 % 10; disp[2] = bpm / 10 % 10; disp[3] = bpm % 10; }

参数说明:seg 数组是共阳段码,0 到 9 对应 0xC0 到 0x90。P2 口高三位用于其他控制,所以位选时要先屏蔽再置位。定时器 1 重装值对应 2ms,12MHz 晶振下 TH1=0xF8, TL1=0x30。BPM 超过 999 时千位显示 1,实际心率不会超过 200,所以 disp[0] 通常为 0。

3.3 串口输出调试数据到上位机

调试阶段用串口把 period 和 bpm 打到上位机,比盯着数码管快得多。AT89S52 串口用定时器 1 做波特率发生器时,定时器 1 就不能同时做显示扫描了。常见做法是调试时用串口,定型后再切回显示扫描。

void uart_init() { TMOD |= 0x20; // 定时器1模式2 TH1 = 0xFD; TL1 = 0xFD; // 9600bps @ 11.0592MHz SCON = 0x50; // 模式1,允许接收 TR1 = 1; } void uart_send(unsigned char dat) { SBUF = dat; while(TI == 0); TI = 0; }

逻辑说明:11.0592MHz 晶振下 TH1=0xFD 对应 9600bps,误差为 0。12MHz 晶振下 9600bps 误差约 8.5%,会乱码,所以串口调试建议换 11.0592MHz 晶振。发送前等 TI 清零,发送后手动清 TI,这是 51 串口的固定套路。

4. 信号质量差、读数乱跳时的排查顺序

4.1 用示波器逐级定位模拟链路

读数乱跳先别改代码,按 OPT101 输出、LM324N 第一级输出、LM324N 第二级输出、比较器输出这个顺序看波形。OPT101 输出应该是带直流偏置的脉搏波,幅度 50mV 到 200mV。如果这里就是一条直线,检查手指是否遮住 OPT101 窗口,或者供电是否正常。

LM324N 第一级输出应该是放大后的脉搏波,峰峰值 1V 到 2V。如果削顶,减小 Rf。如果还是直线,检查高通电容是否焊反或虚焊。第二级输出应该更干净,工频干扰明显减小。比较器输出应该是干净的方波,上升沿陡峭。如果方波占空比随手指压力变化,说明比较器阈值没设好,用可调电阻分压做阈值。

提示:LM324N 是低速运放,压摆率约 0.5V/μs,放大 100 倍后带宽只剩几十 kHz,处理脉搏波够用,但不要用它做高频比较器。

4.2 定时器溢出与中断优先级的坑

INT0 和定时器 0 同时开中断时,如果 INT0 中断服务程序执行时间过长,会丢失定时器 0 的溢出。AT89S52 的中断优先级里,INT0 默认高于定时器 0,所以 INT0 里只读 TH0/TL0 和 overflow,不做除法,执行时间控制在 20μs 以内。

另一个坑是定时器 0 重装值。如果 TH0/TL0 在中断里重装,而 INT0 中断又读了 TH0/TL0,可能读到重装一半的值。解决办法是在 INT0 里先关定时器 0,读完再开。

void int0_isr() interrupt 0 { TR0 = 0; unsigned int now = overflow * 50000 + (TH0 << 8 | TL0); TR0 = 1; // ... 后续处理 }

参数说明:TR0 是定时器 0 启动位,关掉后 TH0/TL0 不再变化,读到的值一致。这个操作只增加几个机器周期,不影响脉搏周期测量精度。

4.3 ADC0809 参考电压与量化误差

ADC0809 的参考电压直接决定量化精度。如果 VREF 接 5V,8 位 ADC 的量化单位是 19.5mV。脉搏波交流分量放大到 2V 峰峰值时,量化误差约 1%,够用。但如果 LM324N 输出只有 0.5V 峰峰值,量化误差就到 4%,BPM 会有几个字的跳动。

常见做法是把 VREF 降到 2.5V,用 TL431 产生,这样量化单位降到 9.8mV。注意 VREF 不能低于 2V,否则 ADC0809 线性度变差。另外 ADC0809 的模拟输入范围是 0 到 VREF,LM324N 输出偏置要设在 VREF/2 附近,否则波形下半周被削掉。

问题现象可能原因排查手段
数码管全灭位选或段选接反用万用表测 P1/P2 电平
BPM 固定 0INT0 没触发示波器看比较器输出
BPM 跳动 ±20信号噪声大加大低通滤波电容
串口乱码晶振不是 11.0592MHz换晶振或改 TH1
ADC 读数不变EOC 没接或 OE 时序错查 P3.2 电平和 OE 脉冲

5. 把心率计做成可演示作品的三个进阶技巧

5.1 用滑动平均滤波稳住 BPM 读数

单次周期测量受噪声影响大,连续测 4 次取平均,读数会稳很多。但要注意,如果手指移动导致某次周期异常,平均值会被拉偏。所以先做中值滤波再做平均:连续测 5 次,去掉最大最小,剩下 3 次取平均。

unsigned int buf[5]; unsigned char cnt = 0; unsigned int filter() { unsigned int max = 0, min = 0xFFFF, sum = 0; for (unsigned char i = 0; i < 5; i++) { if (buf[i] > max) max = buf[i]; if (buf[i] < min) min = buf[i]; sum += buf[i]; } return (sum - max - min) / 3; }

逻辑说明:buf 存最近 5 次周期值,去掉最大最小后取平均。这个滤波器计算量小,51 单片机跑得动。参数上,5 次窗口对应约 4 秒延迟,演示时能接受。如果要求响应快,改成 3 次窗口。

5.2 用 AT89S52 的看门狗防止程序跑飞

AT89S52 内置看门狗定时器,通过 WCON 寄存器控制。演示现场电机、继电器干扰大时,程序可能跑飞导致数码管卡死。开看门狗后,主循环里定期喂狗,跑飞后自动复位。

#include <at89s52.h> void wdt_init() { WCON = 0x00; // 看门狗溢出周期约 16ms // 喂狗指令 WCON = 0x40; WCON = 0x00; }

参数说明:WCON 的 bit6 是喂狗位,写 1 再写 0 完成喂狗。溢出周期由 WCON 低两位决定,00 对应 16ms,01 对应 32ms,10 对应 64ms,11 对应 128ms。主循环执行时间要小于溢出周期,否则会不断复位。喂狗放在主循环开头,不要放在中断里。

5.3 用 Proteus 仿真验证时序再焊板

Proteus 里有 AT89S52、ADC0809、LM324N 的模型,可以先把程序跑通再焊板。仿真时注意 ADC0809 模型需要手动给 EOC 信号,或者用 Proteus 的激励源模拟。脉搏波信号可以用 PWL 源生成,频率设 1.2Hz,幅度 100mV,叠加 1V 直流。

仿真通过后,焊板时先焊电源和单片机最小系统,用串口确认单片机跑得起来。再焊 ADC0809,用可调电阻给 IN0 一个已知电压,看串口读数是否线性。最后焊模拟前端,用信号发生器代替 OPT101 调试。这样分段调试,比一次焊完再排查快得多。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询