
简介面向单片机初学者与课程设计需求者的答辩倒计时系统设计文档围绕AT89C51单片机实现15分钟倒计时功能适用于学术答辩、课堂演示等对计时精度要求不高的场合也可作为51单片机入门练习与课程设计参考案例。压缩包内共1个doc文件约4.38MB以文字与图表形式完整记录了系统设计方案。内容从总体方案、主要元件选型讲起涵盖晶振电路、复位电路、开关控制与显示电路、提示警告电路的局部设计并给出总体电路图软件部分包含总体程序流图、倒计时与按键消抖流程框图说明了Keil环境下C语言编程与Proteus仿真验证的思路。读者可据此理清AT89C51搭配74LS245总线收发器与7SEG-MPX4-CC-BLUE数码管的硬件连接方式掌握剩余3分钟短鸣提醒、计时结束长鸣报警以及LED电源指示的实现要点。目前已有578人学习适合对照动手搭建与调试参考。1. 一份能直接跑通 Proteus 的答辩倒计时设计卡在哪几个点上15 分钟的答辩倒计时看起来是最没门槛的 51 单片机练手题一个定时器、四位数码管、三个按键、一个蜂鸣器教科书上都能找到对应章节。但我见过太多人把原理图连完、代码抄完Proteus 一运行数码管乱闪、按键按了没反应、蜂鸣器像蚊子叫最后卡在仿真能跑但说不清为什么这一步。这份基于 AT89C51 的答辩倒计时系统设计真正的价值不在电路多复杂而在于它把 51 单片机最典型的几个坑全踩了一遍P0 口要不要挂总线驱动、动态扫描怎么消影、1ms 中断里哪些事能干、长鸣怎么不阻塞按键。下面按硬件选型、定时链、显示与按键、避坑、验证的顺序拆开讲每一段都能直接对着 Proteus 复现。2. 硬件清单反推从 1500 秒倒计时倒推 AT89C51 的引脚分配拿到一份单片机设计我一般不看原理图先看引脚表。因为引脚分配定错了后面软件怎么写都是补丁。这份设计的引脚规划是P0 出段码、P2 出位选、P1 的三个脚接按键加一个状态灯、P3.0 驱动蜂鸣器。看着朴素但每个口的分配都有不得不这么做的理由理解这些理由比抄图有用得多。2.1 P0 出段码、P2 出位选为什么不是反过来51 单片机的四个并行口里P0 是唯一没有内部上拉电阻的。它的内部结构是开漏输出做通用 I/O 时必须外接上拉电阻才能输出高电平。而这份设计用的是共阴极数码管段码要给高电平才亮8 个段对应 8 根线正好把整个 P0 占满配一组上拉排阻就够了。P2 负责位选也就是四位一体数码管的公共阴极。位选同一时刻只有一位是低电平其余三位是高电平用 P2 直接灌电流是可行的——共阴数码管的位选端是灌电流单片机拉低即可不依赖上拉。如果把段码和位选对调P2 驱动 8 个段会出现亮度不足P0 又得给位选加上拉纯属自找麻烦。提示P2 做位选时低电平有效的那一位对应的段码才被点亮写程序时不要搞反有效电平否则会出现四个数字叠在一起的经典画面。这里还有一个容易忽略的细节数码管是四位一体封装段选线内部已经并联四位的 a 段是同一根线。这意味着你没法让第 1 位显示 1、第 2 位显示 5 的同时各自保持不同亮度只能靠分时扫描轮流点亮靠人眼视觉暂留合成。这也是后面动态扫描章节的核心。2.2 74LS245 不是可选项P0 的驱动能力到底够不够原设计里 P0 和数码管之间串了一颗 74LS245很多人做仿真时会把它删掉因为 Proteus 里删了也能亮。但真实硬件上这是必须的。AT89C51 每个 I/O 脚的灌电流能力约 10mA 到 20mA 量级整个芯片所有引脚的总灌电流有上限。共阴数码管一位 8 段全亮每段按 5mA 算就是 40mA四位同时驱动虽然不会同时全亮但峰值电流依然吃紧。74LS245 是八路同相三态双向总线收发器在这里当单向缓冲器用CE 接地保持使能DIR 固定一个方向A 侧接单片机 P0B 侧接数码管段脚把电流压力从单片机身上转移出去。接线时有两个脚不能搞错。CE有的封装标 G必须拉低拉高就是高阻态数码管会全灭这是新手最常见的电路连完一点反应没有的原因。DIR 决定传输方向A 到 B 还是 B 到 A接反了同样没有输出。原设计把它当成总线驱动器使用图省事的话也可以换成 74HC245逻辑一样只是电平标准不同。2.3 共阴还是共阳段码表是从这里长出来的七段数码管的内部就是八个发光二极管七个笔画加一个小数点共阴是把八个二极管的阴极并在一起接公共端共阳是把阳极并在一起。这两种接法决定了段码表完全相反也决定了位选的有效电平。这份设计选共阴段码表是这一组显示字符段码十六进制二进制dp g f e d c b a00x3F0011 111110x060000 011020x5B0101 101130x4F0100 111150x6D0110 110190x6F0110 1111看 0x3F 就明白了低六位全 1对应 a 到 f 六个笔画点亮g 段是 bit6 为 0中间那横不亮正好是 0 的形状。小数点对应 bit7所以想在第二位后面点亮小数点只需要把段码和 0x80 按位或。这份设计显示格式是15.00这种分钟加两位秒的形式小数点固定在第 2 位代码里就是一个三元判断的事不需要单独硬件。选共阴还有个现实好处位选灌电流由 P2 直接完成段码高电平由 74LS245 推出去整个链路的电平逻辑是高亮低选读代码时心智负担小。共阳则全部反过来位选要出高电平段码出低电平一旦段码表和位选逻辑有一处写反现象是数码管全亮但显示乱码排查起来比全灭还费劲。2.4 晶振 12MHz 与 33pF这个组合决定了后面所有时间参数晶振电路决定机器周期机器周期决定定时器初值定时器初值决定整个倒计时准不准。这是一条不能断的因果链。AT89C51 支持内部时钟和外部时钟两种方式这份设计用内部时钟晶振跨接在 XTAL1 和 XTAL2 之间两端各接一个瓷片电容到地。晶振频率选了 12MHz理由是 51 单片机的机器周期是 12 个时钟周期12MHz 除下来正好 1μs 一个机器周期所有定时器初值都是整数心算就能验算。如果换成 11.0592MHz串口通信更准但定时器初值会变成一堆小数做倒计时反而麻烦。两个电容的取值原文写的是 33pF落在常见的 10pF 到 50pF 区间内。这两个电容不是滤波电容这么简单它们和晶振一起构成皮尔斯振荡电路的一部分容值影响起振速度和频率稳定性。容值太小起振慢太大可能不振或者频率偏移。仿真里电容随便填都能跑实际焊板子时如果发现晶振不起振先查这两个电容和晶振本身有没有虚焊。复位电路这边是经典的 RC 加按键上电瞬间电容充电RST 脚得到一个正脉冲只要高电平维持 10ms 以上单片机就能完成复位。按键并联在电容两端按下时把电容短路放电松开后重新充电等于手动制造一次复位脉冲。这份设计里复位键的效果就是把显示拉回 15.00、计时清零属于硬件级复位比软件里写个变量归零可靠得多。3. 定时器 0 撑起 15 分钟初值、中断节拍与 1500 秒计数链15 分钟是 900 秒如果按秒去数一个 16 位定时器在 12MHz 下最多只能定 65.536ms差了两个数量级。所以必须做级联计数定时器产生小节拍节拍累计成大单位。这份设计把计数单位定成秒内部变量 ClockNum 从 1500 往下减减到 0 就是时间到。1500 而不是 900是因为显示格式是分十、分个、秒十、秒个四位按秒存最直观取位的时候做除法就行。3.1 1ms 中断的初值推导不要把 50ms 和 1ms 搞混先算初值。方式 1 是 16 位定时器计数范围 0 到 65535溢出时产生中断。要定 1ms在 12MHz、1μs 机器周期下需要计 1000 个数初值 65536 - 1000 64536 0xFC18 TH0 0xFC TL0 0x18如果按原设计里的 50ms 节拍初值是 65536 - 50000 15536 0x3CB0那就是 TH00x3C、TL00xB0中断里再数 20 次凑 1 秒。两种做法都行但我更倾向 1ms 节拍原因写在后文蜂鸣器方波和按键时间戳都需要毫秒级粒度50ms 的节拍做不了音频。方式 1 有一个必须记住的点它不自动重装初值。中断服务函数第一件事就是把 TH0 和 TL0 重新写回去否则下一次溢出时间会漂移表现为倒计时越走越慢。方式 2 是 8 位自动重装省事但只能定 256 个机器周期不够用。#include reg52.h #define uchar unsigned char #define uint unsigned int sbit KSTART P1^0; // 开始 sbit KPAUSE P1^1; // 暂停/继续 sbit KSTOP P1^2; // 停止 sbit LED P1^3; // 电源/运行状态指示 sbit BEEP P3^0; // 蜂鸣器Proteus 里用 Speaker 代替 uint ClockNum 1500; // 剩余秒数1500 对应 15:00 uint MsCnt 0; // 1ms 节拍累计满 1000 进 1 秒 bit RunFlag 0; // 1 正在倒计时 bit TimeUp 0; // 1 已归零等待停止或复位 /* T0 方式11ms 中断12MHz 晶振机器周期 1us */ void Timer0_Init(void) { TMOD 0xF0; // 只清 T0 的低四位保留 T1 配置 TMOD | 0x01; // T0 方式116 位定时器 TH0 0xFC; // 65536-1000 64536 0xFC18 TL0 0x18; ET0 1; // 允许 T0 中断 EA 1; // 开总中断 TR0 1; // 启动计数是否真倒计时由 RunFlag 控制 }参数说明TMOD 的低四位管 T0高四位管 T1这里用先与后或是为了不破坏 T1 的设置——虽然本设计没用 T1但养成这个习惯以后加串口或者第二路定时器时不会互相踩。ET0 和 EA 是两级开关缺一个中断都进不来这是新手调试时程序跑了但时间不动的头号原因。3.2 1ms 到 1 秒到 1 分钟一条不能断的计数链中断服务函数里只做三件事重装初值、推进毫秒计数、在满 1000 时把 ClockNum 减一。所有涉及显示的取位运算全部放到主循环中断里不做除法这是 51 上的基本纪律——中断执行时间越短越好否则主循环的数码管扫描会被打断出现亮度不均。void Timer0_ISR(void) interrupt 1 { TH0 0xFC; // 方式1不自动重装必须手动补 TL0 0x18; if (!RunFlag || TimeUp) return; // 暂停或已归零节拍空转 if (MsCnt 1000) { // 1000 个 1ms 1 秒 MsCnt 0; if (ClockNum 0) { ClockNum--; if (ClockNum 0) TimeUp 1; // 归零锁存主循环据此长鸣 } } }这段的关键在if (!RunFlag || TimeUp) return;这一行。暂停时定时器照常溢出但时间不推进恢复时接着走不需要重新初始化定时器。TimeUp 置位后同样直接返回避免 ClockNum 减到负数回绕成 65535——这是无符号数最阴险的坑现象是时间到了之后突然变成 1092 分钟。从秒到分钟的换算是显示层的事ClockNum 除以 600 得分钟十位除以 60 取余再除 10 得分钟个位以此类推。1500 秒除以 600 等于 2这明显不对所以注意 ClockNum 按秒存的时候分钟十位应该是 ClockNum / 600 吗不对1500 秒是 25 分钟。这里必须想清楚15 分钟是 900 秒如果 ClockNum 存 1500那单位其实是百分之一分钟或者干脆就是显示的四个数字拼成的整数。原设计里的做法是把 1500 当成显示值直接拆位千位、百位、十位、个位读出来就是 1、5、0、0显示成 15.00每秒减一减到 0000 结束。这种写法显示直白但每一步不是 1 秒而是显示的百分之一分钟。如果时钟节拍真是 1 秒那 1500 步就是 25 分钟和需求对不上。正确的处理是二选一要么 ClockNum 存秒数 900显示时换算成 15:00要么保留 1500 的显示值但节拍改成 0.6 秒一步。我一般选前者因为秒是最自然的时间单位后面接任何逻辑都不用换算。换算代码如下/* 900 秒 - 显示缓冲分十、分个、秒十、秒个 */ void Refresh_Buf(void) { DispBuf[0] ClockNum / 60 / 10; // 分钟十位 DispBuf[1] ClockNum / 60 % 10; // 分钟个位 DispBuf[2] ClockNum % 60 / 10; // 秒十位 DispBuf[3] ClockNum % 10; // 秒个位 }注意这一段是整份设计里最容易含糊过去的地方。仿真里只看到数字在减很少有人去掐表核对 15 分钟到底走了多久。等到答辩现场发现倒计时比实际时间快了一分半就来不及改了。3.3 三分钟提示与结束长鸣两个状态两种驱动方式需求写得很清楚剩余 3 分钟响三声结束时长鸣直到按停止。这两件事对蜂鸣器的要求不同——提示音要断续长鸣要持续。如果都在主循环里用延时函数硬翻电平问题立刻出现延时期间数码管不扫描显示会卡住不动按键也扫不到按停止没反应。我的做法是把蜂鸣器驱动也交给 T0 中断用 1ms 的节拍翻转电平。无源蜂鸣器Proteus 里的 Speaker需要方波才能发声1ms 翻转一次就是 500Hz正好在蜂鸣器谐振频率附近响度足够。有源蜂鸣器则只需要给电平不用翻转接线时注意区分。uchar BeepMode 0; // 0静音 1三声提示 2长鸣 uint BeepLeft 0; // 剩余发声毫秒数 /* 挂在 1ms 中断末尾非阻塞驱动 */ void Beep_Tick(void) { if (BeepMode 0) { BEEP 0; return; } BEEP ~BEEP; // 每 1ms 翻转约 500Hz 方波 if (BeepLeft 0 --BeepLeft 0) { BeepMode 0; // 计时到了自动静音 BEEP 0; } }主循环里只需要在两处触发ClockNum 减到 180 时置 BeepMode1、BeepLeft600响 600ms中间靠计数分段成三声TimeUp 置位时置 BeepMode2、BeepLeft 给一个大数或者干脆不递减。三声的实现可以更简单在中断里用另一个计数器响 100ms 停 100ms重复三次比在主循环里写三个 DelayMS 靠谱得多。3.4 中断里能放什么、不能放什么51 的中断没有优先级抢占的复杂机制同级中断不能嵌套中断服务函数执行期间主循环完全停摆。所以中断里只放三类操作重装初值、位运算、简单的加减计数。凡是带除法、带循环延时、带数码管刷新的一律挪到主循环。一个具体的判断标准中断函数体控制在 50 个机器周期以内。1ms 中断留给你的时间窗口是 1000 个机器周期占 5% 左右比较安全。如果非要在中断里做除法可以查表代替——把 0 到 59 的十位和个位预先做成数组用索引取值比除法快得多。4. 四位数码管动态扫描与三个按键刷新率、消影与误触硬件把四个数码管的段线并在一起就意味着任何时刻只能有一位在亮。要让四位看起来同时亮就得快速轮流点亮靠视觉暂留骗眼睛。这个快速有具体数字整帧刷新率低于 50Hz 会看到明显闪烁做到 100Hz 以上就基本无感。四位一帧每位 2ms 到 2.5ms整帧 8ms 到 10ms对应 100Hz 到 125Hz这个区间是安全的。4.1 段码送出的顺序消影这一步不能省最朴素的写法是送位选、送段码、延时、换下一位。这么写出来的效果是数字边缘有拖影看起来发虚。原因是换位选的瞬间上一位的段码还挂在 P0 上新选中的那一位会短暂显示上一位的数字。正确顺序是三拍先关段码P0 清零全灭、再换位选、最后送新段码。中间那一小段全灭时间只有几个机器周期人眼看不出但足够把残影清干净。uchar code SEG_CODE[] {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; uchar DispBuf[4] {1,5,0,0}; // 分十、分个、秒十、秒个 void Display_Scan(void) { uchar i, sel 0xFE; // P2 低电平选中从最低位开始 for (i 0; i 4; i) { P0 0x00; // 第一拍消影先关段码 P2 sel; // 第二拍切换位选 /* 第三拍送段码第 2 位索引1附加小数点 */ P0 (i 1) ? (SEG_CODE[DispBuf[i]] | 0x80) : SEG_CODE[DispBuf[i]]; sel (sel 1) | 0x01; // 0xFE - 0xFD - 0xFB - 0xF7 DelayMS(2); // 每位 2ms整帧约 8ms } }参数说明sel 的初值 0xFE 是二进制 1111 1110最低位为 0选中第 1 位。左移一位再或 1得到 0xFD、0xFB、0xF70 的位置依次左移。如果用_crol_循环左移函数效果一样但要多包含 intrins.h。DelayMS(2) 里的 2 是可以调的调大到 5ms 亮度上去了但开始闪调小到 1ms 不闪但偏暗。仿真环境没有真实亮度概念上板之后要按实际 LED 重新标定。4.2 三个按键的有效性判断电平、边沿、长按键盘这边最容易翻车的地方不是消抖而是哪个键在什么状态下有效。原设计明确了三条规则暂停键只在倒计时进行时有效再次按下继续停止键任何时候按下都停止复位键属于硬件复位不参与软件扫描。先看消抖。机械按键闭合瞬间会有 5ms 到 20ms 的抖动单片机跑起来之后能采到一串高低变化。标准做法是检测到低电平后延时 10ms 再确认一次然后等松手避免一次按压被识别成多次。/* 返回 0无键 1开始 2暂停/继续 3停止 */ uchar Key_Scan(void) { uchar k 0; if (KSTART 0 || KPAUSE 0 || KSTOP 0) { DelayMS(10); // 10ms 消抖窗口 if (KSTART 0) k 1; else if (KPAUSE 0) k 2; else if (KSTOP 0) k 3; /* 等松手防止长按被连续识别 */ while (KSTART 0 || KPAUSE 0 || KSTOP 0) { } } return k; }这个写法简单但那个 while 是阻塞的按住不放主循环就停在那数码管会熄灭。对倒计时器这种场景其实可以接受因为人按键的时间通常不到 200ms。如果想做得更稳可以改成记录上次电平 时间戳的状态机只有上次是高、这次是低、且距上次变化超过 20ms才算一次有效按下。代价是多几个全局变量好处是按多久都不影响显示。按键功能的分发放在主循环里用一个 switch 处理void Key_Process(uchar k) { switch (k) { case 1: // 开始 if (!TimeUp) RunFlag 1; break; case 2: // 暂停 / 继续 if (!TimeUp) RunFlag !RunFlag; break; case 3: // 停止 RunFlag 0; TimeUp 0; ClockNum 900; // 回到 15:00 BeepMode 0; BEEP 0; break; default: break; } }注意 case 3 里把 TimeUp 也清了。如果只清 RunFlag 不清 TimeUp中断里会一直提前返回倒计时永远启动不了现象是按停止之后再按开始没反应。这类状态耦合问题在仿真里很难一眼看出因为数码管显示是对的只是时间不动。4.3 主循环的时间预算把上面几块拼起来主循环大致是刷新显示缓冲、扫描四位、扫一次按键、处理蜂鸣器状态、检查三分钟提示。按 8ms 一帧显示来算主循环一轮大概 8ms 到 10ms留给按键和逻辑的时间很充裕。三分钟提示的判断放在主循环而不是中断里是因为它需要触发一次性的动作置 BeepMode中断里做这个会和显示逻辑抢变量。判断写法可以是一个标志位bit Warn3min 0; // 三分钟提示是否已触发 /* 主循环内 */ if (ClockNum 180 ClockNum 0 !Warn3min) { Warn3min 1; BeepMode 1; // 三声提示 BeepLeft 600; } if (ClockNum 900) Warn3min 0; // 复位后重新武装ClockNum 180用小于等于而不是等于是为了防止某次循环刚好错过 180 这个值——虽然在这种轮询频率下几乎不可能但加上更稳妥。这类一次性事件的判断用小于等于加标志位是通用写法。5. 避坑实录仿真跑不起来、蜂鸣器不响、时间跳变的五类成因5.1 上电后数码管全灭代码没问题现象Proteus 里加载 HEX 文件供电正常四个数码管一点光都没有用示波器看 P0 也没有变化。原因九成是 74LS245 的 CE 脚接成了高电平。CE 高电平是三态输出A、B 两侧完全断开P0 再怎么输出都到不了数码管。另一成是 DIR 方向接反数据从 B 往 A 走。解决CE 直接接地保持使能DIR 按A 进 B 出接固定电平别悬空。悬空在仿真里可能被默认成某个电平上板就是随机行为。5.2 数码管显示发虚、数字叠影现象四位数字都能认出来但笔画边缘有淡淡的残影看起来像两个数字叠在一起。原因换位选的时候没有先关段码上一位的段码在 P0 上多停留了几个机器周期新选中的位把它显示了出来。解决按关段码 → 换位选 → 送新段码的三拍顺序写中间的全灭时间几个微秒就够不影响亮度。这一条在仿真里不明显实板上特别突出。5.3 蜂鸣器只在按开始键瞬间响一下现象Proteus 里 Speaker 发出嗒的一声就没动静了三分钟提示和结束长鸣都没有。原因Speaker 是无源器件需要持续方波驱动。如果只给一个电平或者用单次脉冲只会听到一次机械振动。另一个可能是长鸣逻辑写在主循环的 while 里死等把显示扫描和按键都堵死了。解决把方波翻转放进定时中断1ms 翻转一次得到约 500Hz长鸣用状态位加剩余毫秒数实现主循环只负责置位和清零不阻塞。如果用的是有源蜂鸣器则直接给电平即可两种器件驱动方式不同接线前先确认型号。5.4 倒计时速度不对15 分钟跑成了 25 分钟现象功能全对就是时间不匹配掐表发现实际走完的时间比显示的多了一大截。原因显示值 1500 被直接当成秒数而中断节拍确实是 1 秒一次1500 次自然就是 25 分钟。这是显示层和计时层混在一起的典型后果。解决内部统一用秒计数15 分钟存 900显示时再换算成 15:00 的四位。换算放在显示刷新函数里中断只管减秒两层职责分开。5.5 归零后按停止没反应或者时间变成 1092现象倒计时走到 0000 之后蜂鸣器长鸣按停止键关不掉或者某次减速之后数码管跳出一个莫名其妙的四位数。原因ClockNum 是无符号整型减到 0 之后没有锁存继续减就到 65535取位显示出来就是一串乱数。同时长鸣逻辑如果依赖 ClockNum 的判断也会跟着失效。解决归零的瞬间置 TimeUp 标志中断里看到这个标志直接返回不再递减。停止键的处理必须清掉 TimeUp否则下次开始进不去。6. 上板前的验证清单把 Proteus 现象和真实硬件对齐仿真跑通不等于板子能跑这是 51 项目里最容易掉以轻心的一步。我一般会在 Proteus 里做一遍按秒掐表的核对找一个手机秒表从按下开始键的那一刻起计时看数码管走到 14:00 时秒表是不是正好走了 60 秒。误差超过 2 秒就说明初值或者晶振频率设错了别急着焊板子。下面这张表是我自己做这类项目时的核对顺序按它走一遍基本能把九成问题挡在上板之前。验证项仿真里怎么看期望现象不符时的排查方向上电初值加载 HEX 后立即观察显示 15.00状态灯亮复位电路、DispBuf 初值开始与暂停按开始等 5 秒按暂停数字停住再按继续接着走RunFlag 取反逻辑三分钟提示手动把 ClockNum 改成 182 再跑走到 180 时响三声中断里 Beep_Tick 是否被调用归零长鸣把 ClockNum 改成 3 快速跑完长鸣按停止后静音TimeUp 是否锁存、停止键是否清零显示无闪烁肉眼观察或录屏慢放四位稳定无叠影消影三拍顺序、DelayMS 时长按键不连击长按开始键 2 秒再松只触发一次等松手循环是否存在这里有个我自己的血泪经验Proteus 里把 ClockNum 改成小值快速验证功能这个技巧很好用但一定要记得改回来。我就干过一次把测试用的 ClockNum5 直接生成 HEX 交出去结果仿真一上电显示 00.05倒计时五秒就长鸣看起来像系统一启动就结束排查了半小时才发现是测试残留。从那以后我每次生成 HEX 前都强制走一遍搜索测试常量这一步确认没有任何 DEBUG 值留在代码里。另一个建议是把三分钟提示和归零长鸣拆开单独验证。这两个功能共用蜂鸣器一旦同时有问题很难判断是驱动的问题还是状态机的问题。先把 BeepMode2 的长鸣路径单独跑通再加上三声提示的自动静音逻辑出问题时排查范围就缩小了一半。最后说一个仿真和实物的差异点Proteus 里 Speaker 的响度、数码管的亮度都只是示意实际板子上这两个都要重新标定。蜂鸣器可能因为方波频率偏离谐振点而声音很小数码管可能因为限流电阻没加而偏亮发热。这些在仿真阶段看不出来只能靠上板之后调参数。把这套流程走顺了后面再做可预置时间的倒计时、加 LCD 显示或者加串口上报剩余时间都只是在同一个骨架上换零件。希望帮到你。本文还有配套的精品资源点击获取