C51单片机00-99计数器:Proteus仿真与动态扫描数码管实现
2026/9/15 13:41:23 网站建设 项目流程

简介:面向单片机初学者和电子类课程学生,这份压缩包以C51单片机“00-99计数器”实验为载体,整合Proteus仿真工程、程序源码与实验说明,帮助读者快速理解定时器/计数器模块的工作原理,并完成从程序编写到仿真验证的完整练习。整个压缩包仅112KB,共33个文件,涵盖Proteus仿真工程、Keil工程、ASM汇编与C语言两套源码、HEX烧录文件及DOC实验指导文档。包内将Keil工程划分为Keil-H与Keil-C两个独立项目,便于对照汇编与C语言的编程思路;文档对实验目的、操作步骤、程序设计及Proteus仿真要点均有说明。目前已有210人学习/下载,适合按文档逐步完成“00-99”动态计数仿真,也可自行修改源码,锻炼定时器配置、中断处理与数码管显示等基础技能。

1. 00-99计数器实验:C51单片机与Proteus仿真先跑通逻辑

“00-99计数器”大概是C51单片机里被做烂了的一个实验:一个按键按下去,数码管从00跳到01、02……到了99再回到00。可越是这种基础实验,越能看出一个人对端口结构、动态扫描和按键去抖的理解。用Proteus做仿真的好处是没有硬件成本也能把时序调对,坏处是仿真环境比真实单片机理想化,有些坑不踩一遍根本不会注意,比如数码管共阴共阳选错、扫描频率太低导致闪烁、计数器溢出忘记回绕。下面就把整个回路拆开:先交代硬件接线,再给一张能直接“抄作业”的C51代码,最后用Proteus把HEX文件跑起来,并顺手说说怎么把手动按键升级成脉冲计数。适合刚学51的人照做,也适合那些想把C51代码从“能跑”改成“稳定”的同事参考。

2. 计数器硬件回路:C51端口分配与两个数码管的驱动方式

先确定Proteus里要用哪些元件,再谈代码。虽然软件仿真的重点在逻辑,但端口分配直接决定C51代码怎么写。这里选AT89C51作为主控,它是Proteus里最常见的“单片机仿真芯片”之一,仿真模型很稳定;显示部分用两位共阴数码管,段码接P0口,位选接P2.0和P2.1,按键接P3.2(外部中断0输入)。之所以把按键接到中断引脚,是为了省掉主循环里的轮询阻塞,顺便把计数逻辑放在中断里,后文代码会这样写。如果手头只有AT89S52,接线不变,只是单片机型号在Proteus里选另一个封装。

2.1 元件参数选型:AT89C51、共阴数码管与限流电阻

元件清单可以按表1准备:AT89C51、两位数码管(共阴)、轻触按键、12MHz晶振、两个30pF电容、10kΩ复位电阻、10uF复位电容、P0口上拉排阻、220Ω段码限流电阻。表1是信号连接关系。需要说明的是,P0口在C51里是准双向口,但它输出驱动能力弱,而且没有内部上拉,所以接数码管段码时必须外接上拉排阻到5V。Proteus里如果省略这个上拉,仿真多数情况下也能显示,因为仿真模型的I/O做了简化,但真实板子上就出鬼影或段码暗。另一个容易踩的是共阴共阳选择:如果把共阴数码管接成共阳,段码表需要按位取反,0x3F会变成0xC0,否则0-9的段会闪烁不对。

表1 00-99计数器引脚连接参考

信号C51引脚对应外设
段码 a~g,dpP0.0~P0.7数码管段码端
十位公共端P2.0数码管十位位选
个位公共端P2.1数码管个位位选
计数按键P3.2轻触按键接地,另一侧接5V与上拉
晶振XTAL1/XTAL212MHz晶振与30pF负载电容

按键引脚标注为P3.2接地,同时用10kΩ上拉到5V,常态为高,按下变低,配合外部中断0的下降沿触发。复位电路用典型的10uF电容串联10kΩ电阻,电容正极接VCC,负极接RST,电阻下端接地,仿真时不需要调参数,照着接线就行。

2.2 动态扫描连接方案:P0驱动段码,P2驱动位选

两位数字的显示,最省事的是用一个P口接一个数码管,这就是静态显示。静态显示代码简单,但引脚占用翻倍,8位段码加8位段码就是两个P口,后面再加按键、传感器、串口,引脚立刻捉襟见肘。所以我一般推荐动态扫描:两个数码管的段码端全部并接在P0口,位选端分别由P2.0和P2.1控制。显示时先给P0送十位段码,P2.0拉高,延时1到2ms,再换个个位段码,拉高P2.1,两个位交替点亮,视觉上看到的就是连续、稳定的两位数。

动态扫描里需要盯住的两个参数是每位点亮时间和整体刷新周期。每一位的单次点亮时间建议在1到5ms,两个位加起来的周期不要超过10ms。刷新率低于50Hz,人眼就会看到闪烁;Proteus里因为渲染帧率限制,可能比真实硬件更明显。P0口段码通道上再串220Ω电阻,既限制段电流,也让仿真波形里能看到一个正常的负载,不然示波器测出来全是高阻状态。

2.2.1 静态显示为什么不划算

静态显示的另一问题在仿真里不容易发现,但实体会表现得很明显:两个数码管各自占用独立段码口,每位段码电流不同,P0和P1的负载不均衡,容易亮度不一致。动态扫描让两个数码管共用段码口,电流负载被平均,显示也更均匀。这个实验虽然只有两位,后面做四位数码管时动态扫描是唯一可行方案,不如现在就开始用。

2.2.2 按键去抖在仿真里的处理边界

Proteus自带的Button组件默认没有机械抖动,按下去电平就干净地变低,所以在仿真里能跑通的代码,拿去真板子不一定稳定。常见做法有两种:硬件上给按键并联一个小电容到地,或者用RS触发器消抖;软件上在检测到低电平后延时10ms再判断一次。这个实验里用软件去抖就够,仿真的意义在于先把计数值、回绕和显示刷新调好,真机上如果发现偶发多计数再回来补硬件消抖。

3. C51源码实现:按键计数、定时扫描与00-99回绕

在Keil C51里新建工程时芯片选AT89C51,建好后把下面代码复制进一个.c文件。代码中Timer0中断负责每1ms刷新一位数码管,外部中断0负责按键计数,计数变量从0自增到99后回绕到0。这样的分工是C51里最常见的中断用法,按键不阻塞主循环,显示刷新时间准确。

#include <reg52.h> sbit DIG_TEN = P2^0; // 十位位选 sbit DIG_ONE = P2^1; // 个位位选 sbit KEY = P3^2; // 按键,按下为低电平 unsigned char code SEG_TAB[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; volatile unsigned char disp_value = 0; // 当前需要显示的数 volatile unsigned char display_bit = 0; // 0:显示十位 1:显示个位 void Timer0_Init(void) { TMOD &= 0xF0; // 只修改Timer0模式位,不碰Timer1 TMOD |= 0x01; // Timer0工作在16位定时器模式 TH0 = 0xFC; // 12MHz晶振,1ms中断一次 TL0 = 0x18; // 初值0xFC18,溢出周期为1000个机器周期 ET0 = 1; // 使能Timer0中断 TR0 = 1; // 启动Timer0 } void Timer0_ISR(void) interrupt 1 using 1 { TH0 = 0xFC; // 重新装入初值 TL0 = 0x18; P0 = 0x00; // 段码清空,消除切换拖影 if (display_bit == 0) { P0 = SEG_TAB[disp_value / 10]; // 送十位段码 DIG_TEN = 1; DIG_ONE = 0; } else { P0 = SEG_TAB[disp_value % 10]; // 送个位段码 DIG_ONE = 1; DIG_TEN = 0; } display_bit = !display_bit; } void Key0_ISR(void) interrupt 0 { unsigned int i; for (i = 0; i < 800; i++); // 简单去抖,约1~2ms if (KEY == 0) { disp_value++; // 计数值加1 if (disp_value > 99) { disp_value = 0; // 超过99回绕到00 } } } void main(void) { P0 = 0x00; P2 = 0x00; IT0 = 1; // 外部中断0选下降沿触发 EX0 = 1; // 使能外部中断0 Timer0_Init(); EA = 1; // 开全局中断 while (1) { // 主循环可留给按键状态机或显示数据预处理 } }

代码里有两个细节要注意。中断服务函数最后一行display_bit = !display_bit,配合1ms定时,两个数码管的刷新频率是500Hz,这个值远高于人眼临界闪烁频率,Proteus里也不会看到闪烁。按键中断里先去抖再判断是否真的按下,for循环产生约1到2ms的延时,足够跨过机械抖动的第一波毛刺;这里刻意没有等待按键释放,因为外部中断是下降沿触发,按住不放不会连续触发,松开后再按下才会生成下一个下降沿。

注意:P0口段码通道必须在Proteus中加上拉排阻,否则仿真显示可能正常,换到真实电路会缺段或亮度不均。

3.1 共用头文件与段码表的定义

代码顶部直接#include <reg52.h>,它定义了P0、P2、P3这些特殊功能寄存器的地址,也把中断号映射到了正确入口。段码表用unsigned char code定义,表示存放在程序存储区而不是内部RAM,这是C51里节省RAM的规定动作。0x3F对应共阴数码管显示“0”时P0口输出的电平,P0.0接a段,P0.1接b段,按这个顺序排列。如果你在Proteus里误选了共阳数码管,显示内容会全部反相,最简单的修正办法不是改表,而是回到Proteus里换成共阴型号。

3.1.1 为什么延时去抖不放在主循环

外部中断0的优先级默认和Timer0相同,同优先级下不能互相打断。如果去抖逻辑写在主循环里,按键按下时主循环可能正停在某个耗时操作上,计数延迟会变得不可控。放到中断里可以把这里的最大阻塞时间控制在2ms内,显示刷新即使被推后也不会超过一个中断周期。不过,如果你这个项目后面要做PWM输出或精确计时,最好把去抖改成Timer0中断里10ms采样一次的状态机,彻底去掉空循环。

3.2 重装初值0xFC18怎么算出来的

12MHz晶振下,C51的机器周期是12个时钟周期,所以一个机器周期正好1us。Timer0工作方式1是16位向上计数,从0xFC18开始数到0xFFFF溢出,需要 0x10000 - 0xFC18 = 1000 次计数,也就是1ms。每次中断重装TH0=0xFC、TL0=0x18,就能维持1ms的固定周期。如果以后换成11.0592MHz晶振,机器周期变成约1.085us,重装值就不能再用0xFC18,需要按实际频率重新计算,否则显示扫描周期会偏离设计值。

3.2.1 动态扫描的两个关键时间参数

动态扫描里第一个时间是刷新率,上述代码1ms切换一位,两位完整刷新周期2ms,也就是500Hz,这个值远超所需的50Hz。第二时间是消隐时长,每次切换前把P0清0,这行代码要保留。如果把它注释掉,在Proteus里用瞬态仿真能看到段码切换瞬间出现拖影,放在实际硬件上是数码管笔画快速换数时的短暂“鬼影”。1ms中断里加一行清P0不会占用多少时间,但效果很明显。

3.3 按键计数与越界回绕的代码写法

计数逻辑放在外部中断0里,这是这个实验最省代码的写法,但它不是唯一正确做法。IT0=1把外部中断设为下降沿触发,按键按下时P3.2从高变低,进入一次中断。中断里去抖后再读取一次引脚,如果确实为低,就执行disp_value++。当变量超过99时,通过判断回绕为0,然后继续显示00。回绕判断必须放在自增之后,不能放在进入中断之前,否则当当前值是99时就会提前丢弃一次计数。

3.3.1 软件去抖的延时陷阱

代码里for (i = 0; i < 800; i++);是依赖空循环的软件延时,Keil编译器如果开启O3优化,可能把这个空循环优化掉,去抖就失效了。我一般会给循环变量加volatile,或者把延时函数单独放到另一个文件里编译,降低被优化的概率。想彻底避免这个问题,就改成用定时器计数:把10ms作为一个去抖窗口,前后两次采样都为低才确认按键。在Proteus里这两种方式的仿真结果不会有太大差异,但代码的鲁棒性区别很大。

3.3.2 把按键计数改成外部脉冲计数

很多人拿这个00-99计数器实验,最终想做的其实是测转速或测频率,需要记录外部脉冲数量。改动很小:把脉冲源接到单片机的T0输入脚P3.4,把Timer0从定时器模式改成计数器模式,TMOD = 0x05,中断里做if (++disp_value > 99) disp_value = 0;。这样显示的刷新需要换到Timer1或者主循环轮询,因为Timer0已经被占用。计数器模式没有按键去抖问题,但接机械触点时反而更容易误触发,要用外部RC滤波或施密特触发器整形后再接脉冲输入。

4. Proteus仿真:把C51源码跑起来并排查显示故障

Proteus仿真的操作顺序很重要,很多同事一上来就拖元件,结果连线完成才发现单片机型号没有对应的HEX文件。正确流程是先搭最小系统、再连外设、最后加载程序。我用的是Proteus 8系列,不同8.x小版本界面差异不大。AT89C51在Pick Device里输入“AT89C51”就能找到,数码管搜“7SEG-MPX2-CC”或“7SEG-MPX2-CA”,CC是共阴,CA是共阳,这个实验用共阴所以选CC。按键搜“BUTTON”,晶振搜“CRYSTAL”,电容搜“CAP”,电阻用“RES”或直接放一个“RESPACK-8”。按下表连接时,优先把电源和地画清楚,再做信号线。

4.1 Proteus工程搭建:从元件列表到最小系统连线

连接最小系统时,AT89C51的31脚EA要接VCC,19脚XTAL1和18脚XTAL2分别接晶振两端,每个脚再接一个30pF电容到地,复位脚9接复位电路。把按键一端接P3.2,另一端接GND,同时在P3.2和VCC之间接一个10kΩ上拉电阻。段码P0的8根引脚全部连到数码管的段码端,位选P2.0和P2.1分别接两个数码管的公共端。注意,共阴数码管的公共端要接位选引脚,而不是直接接地;直接接地会让两个位永远同时点亮,显示内容就变成了固定数字无法独立变化。

连好以后双击单片机,在Program File里选择Keil输出目录下的.hex文件。这里有一个容易踩的坑:Proteus加载HEX之后不会提示成功或失败,你必须再次点运行按钮,观察左下角状态是不是变成PACING。如果程序一直是停止或全速运行却没有反应,先检查是不是没有导入HEX,或者导入的是上次编译的旧文件。在Design菜单里选择Configure Power Rails,确认VCC为5V,GND为0V,这一步能排除一大半“仿真发散”的莫名故障。

4.2 Keil生成HEX文件并加载到单片机

用Keil生成HEX文件前,先检查两个选项。打开Options for Target,在Target选项卡里把Crystal Frequency改成12MHz,这和代码里的Timer0初值、Proteus里的晶振模型保持一致;在Output选项卡里勾选Create HEX File。然后编译,看到“0 Error(s), 0 Warning(s)”就可以去加载了。如果报错说“target not created”,多半是C51编译器的许可或破解不完整,和代码逻辑无关。编译成功后到OBJ文件夹里找到.hex,路径不要带中文,Proteus对中文路径的读取不太稳定,我建议把Keil工程放在纯英文目录下。

4.2.1 修改计数器模式后的重新加载流程

如果你按第3章的方式把按键计数改成外部脉冲计数,需要重新编译生成新的HEX文件,然后回Proteus双击单片机重新选择一次文件。Proteus不会自动检测到HEX变化,哪怕两次编译相隔很久,它加载的还是仿真启动时读入的那份。手动停止仿真、重新加载、再点运行,是一个很有效的好习惯。

4.2.2 总线形式连接P0的注意事项

用总线方式画P0会省很多线,但网络标签必须和数码管段码引脚一一对应。比如P0下标为0到7,数码管的a段、b段、c段引脚就按这个顺序连过去。如果标签错位,显示出来的数字会乱,最常见的是“1”显示成“6”这类明显错位。Proteus里的总线走线不会替你检查标签逻辑,手工画线反而更容易发现错误。

4.3 仿真失败时先查这4个地方

第一个地方是RST复位脚。复位电容和电阻接反,或者RST脚悬空,程序可能始终停在复位状态,数码管全灭。第二个地方是晶振电路,电容短路或晶振频率没填,CLK信号不稳定,程序跑着跑着就停。第三个地方是共阴共阳接错,共阴数码管的公共端接位选,共阳数码管的公共端要接VCC,接反后数字显示乱且亮度极低。第四个地方是HEX文件路径带中文字符,Proteus读取时会静默失败。这四个点按顺序排查,基本能把00-99计数器的仿真问题控制在10分钟内解决。

5. 软件源码包的落地与计数器扩展方向

拿到一个标题带“软件源码+文档说明.zip”的压缩包,我一般不会急着解压。先看压缩包内文件后缀,如果有.uvproj、.c、.hex、.pdf或.docx,基本就是Keil工程、源码、可直接加载的HEX和说明文档。把整个压缩包解压到纯英文路径,比如D:\counter_lab,避免Proteus读HEX时遇到中文路径问题。打开文档说明,先按文档里的晶振频率和数码管型号检查一遍,再打开Keil工程重新编译,生成新的HEX文件,用这个新HEX替换Proteus中加载的旧文件。这样能排除代码被人改过但HEX没同步的情况。

5.1 拿到源码包后的验证顺序

我的验证顺序是:先编译,再单独跑Proteus,最后做边界测试。边界测试就是刻意按住按键超过1秒,看它会不会连续计数;再把计数器调到99,按一下看是否回00;最后用鼠标快速点击按键,别看数码管,看单步调试里的计数值。在Proteus里快速点击和真实按键不一样,它的点击间隔比硬件慢,所以测不出很极限的抖动场景,但你至少能确认回绕逻辑和中断没有冲突。如果发现按一次跳两个数,检查是不是外部中断触发方式被改成高电平触发,或者P3.2上拉电阻没有接好,导致电平接近阈值后在仿真里反复跳变。

5.2 从00-99到更多:把计数器扩展成定时器或转速表

这个实验最自然的扩展方向有三个。第一个是倒计时:去掉外部中断,按键改成设置初值,再用Timer0做一秒定时中断递减显示。第二个是转速表:用光电对管通过比较器输出脉冲到T0引脚,计数逻辑完全复用第3章的计数器模式,然后把每秒钟的脉冲数换算成转速并显示。第三个是秒表:用Timer0做50ms中断,累加到20次为1秒,显示从00.00到99.99。每一次扩展都要注意定时器初值和中断优先级,尤其是同时占用Timer0和外部中断时,两个中断之间不要有太长的禁中断区间。最后一个技巧:在仿真里想验证刷新率是否够,把按键改成信号发生器输出一个400Hz方波,在数码管位选线上用虚拟示波器看波形,如果位选波形高电平宽度稳定,说明显示刷新周期没有漂移,这个判断方法可以直接沿用。

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

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

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

立即咨询