ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C51单片机99秒秒表设计:定时器、动态扫描与Proteus仿真详解

C51单片机99秒秒表设计:定时器、动态扫描与Proteus仿真详解 简介99秒马表设计是基于C51单片机的电子计时器项目面向单片机初学者、课程设计及毕业设计学生通过Proteus仿真实现0~99秒精确计时。资源共29个文件压缩包84KB包含Proteus仿真工程DSN/DBK、Keil源码C/ASM/HEX以及设计说明文档DOC/TXT同时附带OBJ/LST/M51等编译中间文件便于查看编译过程与调试信息。目前已有186人学习下载。使用者可获得完整仿真电路图、可烧录的HEX文件与参考代码文档中梳理了计时逻辑、按键控制与数码管显示等关键设计思路可直接用于实物搭建或作为课设/毕设的参考资料帮助快速理解C51定时器、中断及动态扫描等核心技术。1. 从 00 到 99 的秒表并不简单C51 定时器、防抖、动态扫描要一次拿捏在 C51 单片机课程设计和电子设计竞赛题里出现频率最高的入门整机题目就是“99 秒马表”用 AT89C51 这类 51 内核芯片做一个从 00 计数到 99 的秒级秒表2 位数码管实时显示用三个按键分别控制启动、停止和清零。很多打包资源叫“99秒马表设计-C51单片机Proteu仿真实例及软件源码文档说明.zip”打开之后往往是 Keil C51 工程、Proteus 原理图和一份写好的设计文档但真正让新手卡壳的从来不是“抄代码”而是三件事定时器初值怎么算、数码管为什么闪烁、按键为什么按一次跳两下。这篇文章就顺着这三条线把以 AT89C51 为核的 99 秒秒表从原理讲到你真能打开 Proteus 8 Professional 跑通。适合打算用 Proteus 仿真 51 单片机外设工程、想把定时器中断和动态扫描一次放对位置的工程师也适合要把这段代码改造成“分钟计时器”“倒计时闹钟”的毕设党。全程不依赖实物板只讨论那套“在仿真里能按时跑、参数能随意调”的可靠方案。2. C51 定时器、数码管与按键的方案选型2.1 定时器 T0 的工作模式和 50ms 时基计算AT89C51 内部有两个 16 位定时器/计数器T0 和 T1。99 秒秒表只需要一个周期稳定的时基所以常规做法是把 T0 配成方式 1也就是 16 位定时器模式TMOD 0x01。定时器每收到一个机器周期计数加 1计满 65536 产生溢出标志触发中断。所谓“时基”就是你希望多久进一次定时器中断。如果晶振选 12MHz51 单片机的机器周期是 12 个时钟周期所以一个机器周期就是 1us。16 位定时器最大能计 65536us也就是 65.536ms。很多教程喜欢取 50ms因为 50ms × 20 1000ms正好凑成 1 秒。50ms 需要计数器走 50000 个机器周期所以初值是65536 - 50000 15536 0x3CB0也就是把TH0 0x3CTL0 0xB0。这个初值必须在每次中断里重新装载否则定时器会从 0 开始继续跑造成明显的计时偏快。初值重装后用全局变量timer_cnt累加累计 20 次就表示 1 秒再去修改秒个位和十位。这里有一个值得注意的边界12MHz 晶振是仿真里最容易对齐的数但实物开发板如果用了 11.0592MHz 晶振为了串口波特率那 0x3CB0 就不再是严格 50ms。此时需要重算第 5 章会给替代初值。工程中“先定晶振再算初值”的顺序不能反。2.2 数码管显示方案共阴共阳、位选与段选2 位 99 秒秒表最少需要 2 个数码管常见选择是 2 位一体数码管或者在 Proteus 里直接放两个独立 7SEG。显示方案有静态显示和动态扫描两种静态显示每个数码管占 8 个 I/O2 位就要 16 个引脚51 单片机引脚立刻不够用。所以工程上默认用动态扫描同一时刻只点亮一个数码管用肉眼余晖效应让人感觉两个数同时显示。数码管分共阴和共阳。共阴数码管的公共端接地段选引脚输出高电平点亮共阳则相反段选低电平点亮。两份段码表不同我把 0 到 9 的常用段码列在下面后文代码用的是共阴接法。数字共阴段码共阳段码数字共阴段码共阳段码00x3F0xC050x6D0x9210x060xF960x7D0x8220x5B0xA470x070xF830x4F0xB080x7F0x8040x660x9990x6F0x90动态扫描的位选信号决定了当前点亮十位还是个位。仿真电路里可以直接用 P2.0 控制十位数码管的公共端P2.1 控制个位不需要额外的译码器。刷新率至少要达到 200Hz 以上人眼才不会看到闪烁。如果 2 位数码管一个扫描周期是 2ms显示刷新率就是 500Hz足够稳定。2.3 按键、复位电路与晶振电路三个按键一般接在 P3 口。按键的常规接法是一端接 I/O 引脚另一端接地平时靠上拉电阻保持高电平按下时引脚被拉低。AT89C51 的 P3 口内部有上拉但 Proteus 仿真里为了稳妥我习惯在按键到 VCC 之间外接一个 10k 上拉电阻减少悬空干扰。复位电路用一个 10uF 电解电容加一个 10k 电阻即可上电时 RST 引脚维持几十毫秒高电平让单片机可靠复位。晶振电路在 Proteus 里简单得多不需要真实晶振模型只要在芯片属性的 Clock Frequency 一栏填写 12MHz 就能工作。不过改成 11.0592MHz 时记得同步改定时器初值否则显示出来的秒会明显偏快或偏慢。3. Keil C51 源码解析定时器中断、按键防抖与动态扫描参数3.1 一个能直接跑的最小 C51 工程用 Keil C51 建工程时芯片型号选 AT89C51 或 AT89S51 均可。如果调试器目标选错了会出现“目标芯片不匹配”的编译提示。Keil 5 安装后默认不带 C51 支持需要安装 C51 芯片包网上搜“keil5怎么添加c51芯片包”就能找到现成步骤这里不再展开。下面的代码是可编译的最小集合包含定时器初始化、中断服务函数、按键处理和动态扫描显示。#include reg52.h #define SEG P0 // 段码输出端口 #define BIT P2 // 位选端口 sbit START P3^0; // 启动/暂停键低电平有效 sbit STOP P3^1; // 停止键 sbit RESET P3^2; // 清零键 unsigned char code segTable[10] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; unsigned char sec1 0; // 秒个位 unsigned char sec10 0; // 秒十位 unsigned char timerCnt 0; // 50ms 中断累计 bit running 0; // 秒表运行标志 void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); } void timer0_init(void) { TMOD 0x01; // T0 方式 116 位定时器 TH0 0x3C; // 50ms 12MHz TL0 0xB0; ET0 1; // 开 T0 中断 EA 1; // 开总中断 TR0 1; // 启动 T0 } void timer0_isr(void) interrupt 1 { TH0 0x3C; // 重装 50ms 初值 TL0 0xB0; timerCnt; if (timerCnt 20) { // 20 x 50ms 1s timerCnt 0; if (running) { sec1; if (sec1 10) { sec1 0; sec10; if (sec10 10) { sec10 0; // 99 后回绕为 00 } } } } } void key_scan(void) { static bit start_prev 1, stop_prev 1, reset_prev 1; bit start_now, stop_now, reset_now; start_now START; if (start_prev 1 start_now 0) { // 下降沿触发 running !running; } start_prev start_now; stop_now STOP; if (stop_prev 1 stop_now 0) { running 0; } stop_prev stop_now; reset_now RESET; if (reset_prev 1 reset_now 0) { sec10 0; sec1 0; } reset_prev reset_now; } void display(void) { SEG 0x00; // 先消隐防止残影 BIT 0xFE; // 点亮十位P2.0 0 SEG segTable[sec10]; delay_ms(1); SEG 0x00; BIT 0xFD; // 点亮个位P2.1 0 SEG segTable[sec1]; delay_ms(1); } void main(void) { timer0_init(); while (1) { display(); key_scan(); } }这段代码的显示刷新周期约 2ms一个循环内完成了 2 位数码管各显示一次刷新率 500Hz。delay_ms(1)的微小延时除了控制点亮时间还顺带充当了按键扫描的时间间隔。按键扫描放在显示之后两次扫描之间自然间隔至少 2ms再配合下降沿检测就能过滤掉大部分机械抖动。若按键键抖很严重可把delay_ms改成 5ms 到 10ms代价是显示亮度略有下降。3.2 TMOD 和中断初值详解为什么是 0x01 而不是 0x10TMOD 的高 4 位控制 T1低 4 位控制 T0。写TMOD 0x01意味着 T0 工作在方式 116 位定时器T1 完全没被配置保持原样。如果代码里同时用 T1 做串口波特率发生器就得写成TMOD 0x21这类组合方式高 4 位给 T1低 4 位给 T0。这也是网上很多秒表源码里看起来“同样初始化却跑不出效果”的原因之一别把 T0 和 T1 的 4 位搞混。中断装载初值那两行TH0 0x3C; TL0 0xB0;在主程序和中断里都出现过。主程序里的作用是上电后先让定时器从一个确定初值开始跑中断里的作用是每次溢出后重新装载保证每次溢出间隔严格相等。省略中断里的重装第一次计时 50ms之后每个周期变成 65.536ms秒表会明显变慢省略主程序里的初值第一次溢出时间可能是 0 到 65.536ms 之间的随机值秒表第一秒就不可信。3.3 动态扫描的常见误用消隐和位选顺序不能反动态扫描最容易出现的“残影”问题来自段码的保持。如果先把SEG赋成新数字再切换BIT数码管会在切换瞬间短暂显示旧数字的段码视觉上就是数字边缘发虚。正确顺序必须是我代码里的“先消隐 → 再切位选 → 后送段码”SEG 0x00; // 消隐 BIT 0xFE; // 切换位选 SEG segTable[sec10]; // 输出新段码位选信号和段码信号之间留一个极短的间隔就能避免串扰。这个间隔甚至不需要单独延时C51 的几条赋值指令执行时间已经够用。若在 Proteus 仿真中看到数字发花优先检查这部分逻辑而不是怀疑数码管型号选错。4. Proteus 仿真 51 单体实验元件清单、HEX 加载与常见时序问题4.1 元件清单与接线对照表打开 Proteus 8 Professional新建工程后从元件库搜索并放置以下元件。我把常用库名和数量列在下面方便直接检索。元件Proteus 内库名数量关键连接说明单片机AT89C511P0 接段码P2.0/P2.1 接位选2 位数码管7SEG-MPX2-CA 或 CC1共阳选 CA共阴选 CC排阻RESPACK-81接在 P0 与数码管段引脚之间按键BUTTON3一端接 P3另一端接地上拉电阻RES310k接在按键与 VCC 之间复位电容CAP-ELEC110uF接 RST 引脚复位电阻RES110k接 RST 与 GND 之间4.2 Keil 生成 HEX 文件并加载到仿真芯片Keil C51 工程写好源码后默认不会生成 HEX 文件要手动开启。点击工具栏的“Options for Target”在 Output 选项卡里勾选“Create HEX File”重新编译后工程目录下就会出现 .hex 文件。回到 Proteus双击原理图中的 AT89C51 芯片在弹出的属性对话框的 Program File 一栏选择这个 .hex 文件。同时确认 Clock Frequency 一栏填写 12MHz。这里有个非常容易被忽略的坑Proteus 的时钟频率默认值是 12MHz 但很多模板会改成 11.0592MHz 或 4MHz如果不跟代码里的定时器初值保持一致仿真跑出来的秒表就会快或慢。想在“等宽”里看到真实秒长务必让 Proteus 里的 Clock Frequency 与 TH0/TL0 计算时采用的晶振一致。4.3 数码管不亮、按键无效、计时偏快的排查路径仿真最常见的问题是数码管不亮。先检查电源符号Proteus 的 AT89C51 不会自动连接 VCC 和 GND必须显式放置 POWER 和 GROUND 终端否则仿真时芯片不工作。其次是排阻阻值7 段数码管段电流建议限制在 5mA 到 10mA排阻选 220 欧姆到 470 欧姆。阻值太小会仿真报过流太大会让数码管亮度不足。按键无效多数是极性反了。代码里用的是“按下为 0”的逻辑如果 Proteus 电路中按键另一端接 VCC按下时引脚变高下降沿检测永远等不到按键自然不响应。确认三个按键的未按下状态在仿真时都是高电平可在按键脚加一个虚拟探针观察仿真电平。计时偏快或偏慢先排除初值问题再排除中断次数。初值 0x3CB0 对应 50ms中断累计 20 次是 1 秒这里如果改成 200 次显示就会变成 10 秒跳 1 次。还有一种情况是 Proteus 仿真步长过小导致定时器重装和中断重叠可以把仿真时间设置从“实时”改成“模拟步长”步长设为 1ms让中断调度更符合实物节奏。5. 超时精度校准用虚拟探针和初值补偿把 99 秒误差压到毫秒级跑到这一步秒表已经能按“秒”工作但要做成能用于测量的东西还得处理最后一道精度问题。最直接的办法是在 Proteus 里加一个虚拟探针选定定时器中断内翻转的一个空引脚比如 P3.7然后用虚拟示波器测量电平翻转周期。P3_7 !P3_7; // 放在 timer0_isr 的 TH0/TL0 重装之后如果初值正确P3.7 应每 50ms 翻转一次示波器读到周期 100ms 的方波。实际仿真中你会发现周期不是严格 100ms因为中断进入、重装初值、函数返回这几条指令本身也消耗时间。要补偿这部分开销常用的手段是把timerCnt 20的判断改为每 199 次中断后加一个“修正周期”或者干脆把初值调小几个机器周期。12MHz 下修正量一般只有几十微秒对于 99 秒量级的秒表影响不足 0.1%多数人不做补偿也够用。但如果你把代码移植到 11.0592MHz 晶振上事情就变复杂了。11.0592MHz 下机器周期约 1.085us50ms 需要计数约 46078解得初值为0x4C02。这时仍然累加 20 次到 1 秒但每次中断实际间隔是 49.998ms20 次后是 999.95ms长期运行累计误差在 0.5% 以内肉眼很难察觉。真正需要高精度时把时间基准从“20 次 50ms”改成“1000 次整数微秒中断”会更直观。也就是让定时器每 1ms 中断一次再累计 1000ms虽然中断频率提高了 20 倍但初值可以用0xFC18这类干净数字逻辑上的修正点也更多。上述代码通过增加中断频率修改计数规则即每 1000 次timerCnt加 1秒位只在timerCnt 0时递增就能把设计从 99 秒秒表平滑迁移到 9 分 59 秒、甚至 24 小时制计时器。下次再拿到类似的 C51 计时项目先画时基树再改段码表整套框架不用重写。本文还有配套的精品资源点击获取
返回列表