AT89C51交通灯控制系统设计:四相位状态机与定时器中断
发布时间:2026/11/2 8:11:35 锦皓数字建站

简介面向单片机课程设计的AT89C51交通灯控制系统结课报告适合电子、自动化等专业学生参照完成方案设计与报告撰写。文档以MSC-51系列AT89C51为核心依次覆盖项目需求分析与功能、非功能需求硬件系统结构中的系统方案原理图、独立键盘扫描、时间显示电路、交通灯输出模块及紧急控制与时间调整模块原理并给出软件体系架构、运行效果展示与附录程序。压缩包仅含1个docx文件约353KB属可直接编辑复用的报告模板。已有2882人学习下载读者可从中掌握P1口驱动东西、南北方向红黄绿灯P0与P2口接共阳数码管完成倒计时十位个位显示定时器产生秒信号以及五个按键实现紧急通车与直行时间增减的具体做法便于快速搭建仿真、排查逻辑错误并迁移到同类课题。1. 需求先立住这套交通灯要交付的到底是什么「单片机课设」这四个字通常意味着一块实验箱、几根杜邦线和紧巴巴的三周时间。多数人的路径是找一份交通灯代码、烧进去、灯亮了就写报告答辩时被问一句「黄灯为什么在第 55 秒开始闪、闪几下、谁在数这个数」当场卡壳。这套基于 AT89C51 的十字路口交通灯控制系统要交付的不是「灯能亮」而是四件必须同时成立的事四相位状态按固定节拍迁移、数码管倒计时与灯色严格同步、五个按键能在任意时刻打断并正确恢复、异常情况下四向全红兜底。它面向的是做单片机原理及应用结课设计、嵌入式入门课设的人——你需要一份能讲清每个引脚为什么这么接、每个初值为什么这么算的完整方案而不是一段跑起来就不敢改的代码。2. 需求与选型22 个 I/O 是怎么数出来的为什么不是 STM322.1 把「交通灯」拆成六条可验收的硬指标需求分析这一章最怕写成散文。原文的功能描述是通顺的但对做方案的人来说不够用——「能控制四个路口红黄绿正常工作」这句话没法验收。我一般会把它翻译成六条带判据的指标每一条都能在实验箱上做一次动作、看一次现象、打个勾。编号功能要求可观测判据实现落点F1东西、南北四相位轮转上电后 120 秒内按「绿 55 → 黄 5 → 绿 55 → 黄 5」循环主循环状态机F2四个道口倒计时同步显示数码管每秒减 1切换相位那一秒显示 0Timer0 中断 显示扫描F3绿灯转红前 5 秒黄灯闪烁倒计时剩 5 秒起黄灯以约 2Hz 明暗交替相位 1/3 的闪烁标志F4K1 紧急控制按住 K1 四向全红、数码管熄灭松开立即恢复电平检测 TR0 开关F5K2 进入设置、K3/K4 增减、K5 恢复通行时间可在 099 秒内改按 K5 生效独立按键扫描 变量改写F6上电即工作冷启动 1 秒内进入相位 0无需额外操作初始化函数这张表的价值在于把「设计」变成了「验收」。F3 那条尤其重要很多人只做「黄灯亮」不做「黄灯闪」答辩时被追问闪多少下、闪烁频率由什么决定就答不上来了。黄灯闪烁本质上是一个 2Hz 的方波由 10ms 定时中断计数到 50 翻转一次电平不是靠 delay 硬拖出来的。需求阶段把这条写清楚后面写代码的时候就不会用delay(500)去糊。F5 里有个原文特意提到的边界黄灯时长和循环次数限定在 09十位输入非 0 值时系统仍按 0 处理红绿灯时长可设 099。这是典型的课设细节题加一个范围钳位就够但报告里写出来和不写出来给人的印象差很多。我一般在设置函数里直接写成if(val 99) val 99;和if(val 0) val 0;双保险。2.2 主控与外围的选型账40 个引脚够不够用选 AT89C51 这件事在今天的教学场景里几乎不需要辩论但答辩时你得说出理由。理由不是「老师要求用 51」而是三条硬账一是速度够交通灯最小时基是 10ms 级AT89C51 在 12MHz 晶振下机器周期 1μs跑一个状态机加上数码管动态扫描绰绰有余二是 I/O 数量刚好卡在够用线上不浪费也不至于要扩展三是 4KB 片内 Flash、128 字节可用 RAM这份代码编译下来两三千字节变量十几个压缩到 128 字节里完全没有压力。I/O 的账要一条条数交通灯输出 6 个东西方向绿、黄、红南北方向绿、黄、红全部挂 P1 口数码管段码 8 个ag 加 dp挂 P0 口整口用完数码管位选 3 个P2.2、P2.3、P2.4 接 74LS138 的三根地址线8 选 1 正好覆盖四个道口 × 两位独立按键 5 个K1 到 K5用 P2.0、P2.1、P2.5、P2.6、P2.7。合计 6 8 3 5 22 个 I/OP0 全用、P1 用 6 个、P2 全用P3 口的 8 根线一根没动留给串口调试和后续扩展。这就是「刚好够用」的典型形态——如果换成 STM32价格上去、外设一大半用不上、报告里还得解释为什么用 32 位机做 6 个灯的事如果用纯数字电路搭555 定时器加 74 系列计数器能实现倒计时但时间改不了、紧急控制加不进去F4 和 F5 直接落空。AT89C51 在这个项目里是「性价比 可扩展性」的最优点不是唯一的点但是最好讲清楚的点。外围器件一共三样74LS138 译码器做位选省下 5 根 I/O红黄绿高亮发光二极管 6 只共阳或共阴八段数码管 8 位。成本低实验箱上大概率已经焊好这也是选它的现实理由。提示如果手头的实验箱是 11.0592MHz 晶振而不是 12MHz第 4 章的定时器初值要重算不能直接抄 0xD8F0。2.3 工具链分工Keil4 编代码Proteus8.8 画电路两套软件各管一段不要混着用。Keil4 负责建工程、写 C 文件、编译出 .hex它的行号报错和变量观察窗口是排查逻辑错误的主力Proteus8.8 负责把原理图搭起来把 .hex 加载进 AT89C51 模型跑仿真看灯和数码管的现象对不对。分工的关键在于先仿真通了再上实物。我见过太多人反过来做——连好线再上电灯不亮才开始翻代码这时候分不清是接线错了还是代码错了两头排查时间全耗在猜上。正确顺序是 Proteus 里先把四个相位、倒计时、K1 紧急跑顺再照着同一张原理图去接线。仿真阶段能抓出八成的逻辑错剩下两成才是硬件问题排查面窄得多。Keil 里有个设置容易被忽略工程选项的 Output 页要勾上 Create HEX File否则编译成功了也找不到 .hexProteus 里加载不到程序现象就是「八个灯全亮或全灭、数码管乱码」白折腾半天。Proteus 里双击单片机Program File 指向那个 .hexClock Frequency 填 12MHz必须和代码里的定时器初值对应这一项填错了倒计时会以肉眼可见的速度跑偏。这两条是工具链上最容易踩的坑却几乎每份报告都不写。写进去方案的可复现性立刻上一个档次。3. 硬件系统设计六个模块与一张引脚分配表3.1 系统框图六个模块之间的信号流向整个系统按「核心 外围」切开一共六块电源电路、单片机最小系统、独立按键电路、时间显示电路、紧急情况控制电路、交通灯输出电路。用文字描述数据流是这样的——电源给最小系统供 5V最小系统里 AT89C51 跑起来之后从 P1 口输出六路开关量去驱动红黄绿 LED从 P0 送段码、P2 送位选去驱动八位数码管同时不断扫描 P2 上的五个按键一旦 K1 被按下立刻切断定时器并强制 P1 输出四向红灯。这里有个设计上的取舍值得在报告里写按键和位选共用 P2 口。P2.0、P2.1 给 K1、K2P2.2P2.4 给 74LS138 做位选P2.5P2.7 给 K3、K4、K5。共用不等于冲突因为位选是持续输出的按键是输入读的是引脚电平写的是另外三根线互不干扰。但读按键之前必须先往对应引脚写 151 的准双向口特性否则读回来永远是 0。这条在代码里体现为初始化时一句P2 | 0xE3;把 P2.0、P2.1、P2.5、P2.6、P2.7 全部置高作为输入前置。少了这一句按键永远读不到按下现象是「怎么按都没反应」然后在代码里翻半天找不到原因。3.2 最小系统时钟、复位与一根不能忘的 EA最小系统三件套主控、时钟、复位。AT89C51 的 XTAL1 和 XTAL2 之间接 12MHz 晶振两只 30pF 瓷片电容分别对地这是皮尔斯振荡电路的标准接法电容取值在 2030pF 之间都能起振30pF 更稳。晶振尽量靠近芯片引脚走线短不然容易起振不良——这是典型的玄学问题仿真里永远不会出实物上偶尔抽风。复位电路用 10μF 电解电容加 10kΩ 电阻做上电复位RST 引脚高电平持续两个机器周期以上就复位。电容正极接 VCC负极接 RST10k 电阻从 RST 下拉到地上电瞬间电容相当于短路RST 被拉到高电平随后电容充电RST 慢慢回到低电平完成一次复位。这个时间常数 10k × 10μF 100ms远大于 2μs 的要求稳妥。真正容易被忘掉的是 EA 引脚第 31 脚。EA 接高电平时 CPU 从片内 Flash 取指令接低电平就从外部存储器取。烧进去的程序在片内所以 EA 必须接 VCC。仿真里这个引脚常常默认拉高看不出来问题实物上如果悬空或者接地现象就是「上电后毫无反应所有灯都不亮」跟芯片烧了一个样。我在实验室见过不止一次有人把板子拆了重焊最后发现是 EA 少接一根线。这条建议写在原理图最显眼的位置。3.3 交通灯输出电路220Ω 限流电阻是怎么算出来的六只高亮 LED 直接挂 P1.0P1.5低电平点亮也就是灌电流驱动。为什么是灌电流而不是拉电流AT89C51 的 P1 口内部有弱上拉输出高电平时能提供的拉电流只有几十微安到几百微安推不亮 LED输出低电平时灌电流能力单脚可达 1020mA整片总和不超 71mA这才是能用的方向。所以接线是 LED 阳极经限流电阻接 VCC阴极接 P1 引脚写 0 点亮。限流电阻按公式 R (VCC - Vf) / I 算。红色 LED 正向压降 Vf 约 1.8VVCC 取 5V想让电流落在 1015mA 这个「够亮又不烧口」的区间取 R 220Ω得 I (5 − 1.8) / 220 ≈ 14.5mA。单脚 14.5mA 在承受范围内。真正要算的是同一时刻有几只灯亮。四相位下每个方向同时只亮一只两个方向合计同时亮 2 只总灌电流 2 × 14.5 29mA离 71mA 的上限还有余量。如果哪个方案让六只灯全亮87mA 就超了口线会发热、亮度发虚那才需要把电阻放大到 330Ω约 9.7mA或者加三极管扩流。注意算完电阻别急着焊先用万用表量一遍 LED 的正负极。高亮管长脚是阳极但散装元件经常被剪短靠外形判断会翻车。3.4 数码管显示电路共阳接法加 74LS138 位选八位数码管按四个道口 × 两位分配东西方向两个道口显示同一组时间南北方向两个道口显示另一组内容重复这样四个方向抬头都能看到。段码由 P0 口并行送出 ag 和 dp位选用 P2.2、P2.3、P2.4 接 74LS138 的 A、B、C8 根输出分别选中一位同一时刻只亮一位靠人眼视觉暂留形成「八位同时亮」的错觉这就是动态扫描。P0 口有个必须处理的问题它是开漏结构做输出时内部没有上拉驱动共阳数码管时需要外部提供电流通路所以接 470Ω 排阻上拉到 VCC。原文提到按每段 6mA 算全亮「8」时每位 48mA这个电流动态扫描时只走一位P0 八只脚各承担 6mA单脚没问题整口 48mA 也压在 71mA 以内。这里有个必须点破的地方原文文字写的是「共阳极接法」但附录里给的段码表0x3F, 0x06, 0x5B...是标准的共阴段码。共阴下写 1 段亮0x3F 对应数字 0如果是共阳同一个字形要取反0 应该是 0xC0。这两者在实物上接错现象就是「数码管显示的字全是乱的或者该亮的段不亮」。动手前必须确认手头数码管的型号用万用表二极管档量一下公共端极性或者干脆写个测试程序把 0x3F 和 0xC0 各送一次看哪个对。这不是理论问题是焊上去就会暴露的问题。/* 共阴数码管 0~9 段码表bit0~bit7 对应 a~g,dp写 1 点亮 */ u8 code TABLE[10] { 0x3F, /* 0 */ 0x06, /* 1 */ 0x5B, /* 2 */ 0x4F, /* 3 */ 0x66, /* 4 */ 0x6D, /* 5 */ 0x7D, /* 6 */ 0x07, /* 7 */ 0x7F, /* 8 */ 0x6F /* 9 */ };这张表是查表显示的基础倒计时的十位和个位分别通过TIME / 10和TIME % 10取出来当索引送进 P0 之前不需要做任何运算速度最快。3.5 独立按键电路五个键的分工与上拉五个独立按键一端接地另一端接 P2 的对应引脚靠 51 内部弱上拉维持高电平按下拉低。分工是K1 紧急控制按住即四向全红K2 进入时间设置K3 增加时间K4 减少时间K5 恢复显示。这五条正好对应需求表里的 F4 和 F5。独立按键相比矩阵键盘的好处是编程简单、不会出现「两个键同时按下读错行」的问题代价是占 I/O。五个键占五根线在这个项目里 IO 有富余用独立按键是划算的。矩阵键盘要写行扫描列扫描还要处理组合键对课设来说是给自己找麻烦。按键电路里唯一需要加的硬件是并联小电容0.1μF做硬件消抖但实验箱上一般没有所以消抖只能靠软件检测到低电平后延时 10ms 再读一次两次都是低才算真按下。这个 10ms 不是随便定的机械按键的抖动时间通常在 520ms 之间取 10ms 落在中间既能滤掉抖动又不至于让手感变迟钝。原文还提到「防止一次按键多次读入采取延时 1.116s」——这个 1.116s 的来历是 10ms 延时的 111.6 倍实际上就是让主循环在按键处理完后等一段时间再进入下一轮扫描。这个做法能用但代价是这 1 秒多里系统对其它按键完全无响应。我一般改成「等键释放」while(K3 0);一直等到手指抬起来才继续既解决重复触发又不牺牲响应速度。4. 软件体系四状态机、定时器与显示扫描4.1 主程序骨架初始化、扫描、状态迁移软件按模块化拆成四块主程序、显示程序、定时中断程序、按键处理程序。主程序负责初始化和调度采用顺序执行加反复循环的结构每一轮做三件事——扫按键、刷显示、推进状态。定时器中断只做一件事给出 10ms 的时基。四相位是整套软件的核心用一张表定死相位东西方向南北方向持续数码管显示0绿灯亮红灯亮55s55→11黄灯闪红灯亮5s5→12红灯亮绿灯亮55s55→13红灯亮黄灯闪5s5→1一个完整周期 55 5 55 5 120 秒。这里要纠正一个常见误解原文摘要里说「两垂直方向的准行时间均为 60 秒」那是把「绿灯 55 秒 黄灯 5 秒」合起来算的一个方向通行窗口不是整个周期。答辩被问到「周期多长」答 120 秒才对。/* 相位灯色输出低电平点亮1灭 0亮 */ void lamp_output(u8 ph) { switch(ph){ case 0: DXG0; DXY1; DXR1; NBG1; NBY1; NBR0; break; /* 东西绿南北红 */ case 1: DXG1; DXY0; DXR1; NBG1; NBY1; NBR0; break; /* 东西黄南北红 */ case 2: DXG1; DXY1; DXR0; NBG0; NBY1; NBR1; break; /* 东西红南北绿 */ case 3: DXG1; DXY1; DXR0; NBG1; NBY0; NBR1; break; /* 东西红南北黄 */ } }用一个函数集中管理六只灯的亮灭好处是任何时刻灯色只由ph一个变量决定不会出现「改了相位但忘了关哪个灯」的状态残留。这比在状态机里东一句DXR 0西一句NBG 1要可靠得多调试时也只需要盯一个入口。4.2 定时器初值0xD8F0 只在 12MHz 下成立整套系统的时间基准来自 Timer0工作在方式 116 位定时器不开自动重装。初值算法是机器周期 12 / 晶振频率12MHz 下为 1μs要 10ms需要 10000 个机器周期16 位计数器满值是 65536所以初值 65536 − 10000 55536 0xD8F0。高字节 0xD8 送 TH0低字节 0xF0 送 TL0。void Timer0Init(void) { TMOD 0xF0; /* 清 T0 控制位保留 T1 的配置 */ TMOD | 0x01; /* T0 方式116 位定时器 */ TH0 0xD8; /* 12MHz 晶振下 10ms 的高字节 */ TL0 0xF0; /* 低字节65536-10000555360xD8F0 */ EA 1; /* 开总中断 */ ET0 1; /* 开 T0 中断 */ TR0 1; /* 启动计数 */ }方式 1 不自动重装所以中断服务函数第一件事必须手动补回初值否则下一次定时长度就乱了。void Timer0_ISR(void) interrupt 1 { TH0 0xD8; TL0 0xF0; /* 方式1必须手动重装 */ if(tick_10ms 100){ /* 100 × 10ms 1s */ tick_10ms 0; timer_calculate(); /* 每秒推进一次倒计时和状态机 */ } DigDisplay(); /* 每 10ms 扫一遍数码管刷新率 100Hz */ }这里我做了个和原文不同的分配原文把显示扫描和秒计数都堆在中断里我拆成「10ms 扫显示、100 次进 1 秒算状态」。好处是数码管刷新率稳定在 100Hz肉眼完全看不到闪烁状态机每秒只跑一次中断占用时间短主循环扫按键的响应更及时。代价是多一个tick_10ms计数变量值了。如果晶振换成 11.0592MHz机器周期变成 1.085μs10ms 需要约 9216 个周期初值变成 65536 − 9216 56320 0xDC00。抄 0xD8F0 进去倒计时就会慢约 8%跑一整天差出去快两个小时。这就是为什么第 2 章末尾提醒你确认晶振频率——不是废话是必查项。4.3 显示函数动态扫描、消隐与位码八位数码管每一位的点亮时间靠delay(100)控制这个延时在 12MHz 下大约 1ms八位扫完一轮约 8ms刷新率 125Hz远高于人眼 50Hz 的融合阈值看起来是连续稳定的。真正决定显示质量的不是刷新率是消隐。void DigDisplay(void) { u8 i; for(i 0; i 8; i){ P0 0x00; /* 先消隐段码全灭防止换位串影 */ switch(i){ case 0: LSA0; LSB0; LSC0; P0TABLE[led1]; break; /* 东西十位 */ case 1: LSA1; LSB0; LSC0; P0TABLE[led2]; break; /* 东西个位 */ case 2: LSA0; LSB1; LSC0; P0TABLE[led3]; break; /* 南北十位 */ case 3: LSA1; LSB1; LSC0; P0TABLE[led4]; break; /* 南北个位 */ case 4: LSA0; LSB0; LSC1; P0TABLE[led1]; break; /* 东道口重复显示 */ case 5: LSA1; LSB0; LSC1; P0TABLE[led2]; break; case 6: LSA0; LSB1; LSC1; P0TABLE[led3]; break; case 7: LSA1; LSB1; LSC1; P0TABLE[led4]; break; } delay(100); /* 每位点亮约 1ms */ P0 0x00; /* 换位前再消隐一次 */ } }P0 0x00这两句消隐是整段代码里最容易被删掉的。删掉之后现象是「数码管所有位上都隐隐约约叠着别的位的字」亮度不匀看起来像坏了。原理不复杂位选切换的瞬间上一位的段码还挂在 P0 上新一位已经被选中那一位就闪了一下上一个字。加消隐就是先关段码再换位选切换干净。位码的分配上前四位是东西、南北各两位后四位是另外两个道口重复显示同样的内容。实际接线时 74LS138 的八根输出按物理位置接到八个道口的数码管上就行顺序无所谓只要和LED1到LED4的对应关系写对。4.4 状态机核心黄灯闪烁与 55/5 秒的切分timer_calculate每秒被调一次内部做三件事两个方向的剩余时间各减 1、判断是否到切换点、在黄灯相位翻转黄灯电平。原文给出的骨架是对的我把结构重新理一遍让每个相位的进入条件更清楚。void timer_calculate(void) { if(phase 0){ /* 东西绿、南北红 */ TIME--; TIME1--; if(TIME 5){ DXY 0; huang 0; } /* 剩5秒黄灯起亮 */ if(TIME 0){ /* 进入黄闪相位 */ phase 1; TIME 5; TIME1 5; huang 0; } }else if(phase 1){ /* 东西黄闪、南北红 */ TIME--; TIME1--; if(huang 0) DXY ~DXY; /* 每进一次翻转黄灯 → 1Hz */ if(TIME 0){ phase 2; TIME tiao_time; TIME1 tiao_time; } } /* phase 2、3 与 0、1 完全对称交换 DX/NB 前缀即可 */ lamp_output(phase); /* 统一出口刷新灯色 */ }黄灯闪烁的实现要点在这里翻转动作必须放在每秒调用一次的函数里翻转频率就是 1Hz占空比 50%肉眼看起来是「闪」而不是「亮」。如果把DXY ~DXY放进 10ms 的中断里频率变成 50Hz人眼完全看不出闪看起来就是常亮——这是一个非常典型的翻车点第 5 章会展开说。huang这个标志的作用是「进入黄闪相位时先让黄灯稳定亮起来」避免从绿灯直接跳到熄灭再闪。第一次进相位 1 时huang 0下一秒翻转成亮再下一秒灭交替下去。4.5 按键处理消抖、K1 紧急与 K3/K4 增减按键分为两类逻辑K1 是电平触发按住就生效松开就恢复K2K5 是边沿触发按一下触发一次松手才算完成。这两类必须分开写混在一起就会出现「按住 K1 不放其它键全失灵」或者「K1 松手后系统卡死」。K1 的正确写法是非阻塞的电平判断放在主循环里void emergency_check(void) { if(K1 0){ /* 按住进入紧急态 */ if(!emg_flag){ emg_flag 1; save_phase phase; /* 记下被打断的相位 */ TR0 0; /* 冻结倒计时 */ DXG1; DXY1; DXR0; NBG1; NBY1; NBR0; /* 四向全红 */ P0 0x00; /* 关段码数码管熄灭 */ } }else{ /* 松开恢复 */ if(emg_flag){ emg_flag 0; lamp_output(save_phase);/* 灯色回到被打断的那一相位 */ TR0 1; /* 时间从冻结处继续走 */ } } }对比原文的while(K1 0);阻塞等待那种写法在按住期间主循环完全停住数码管不刷、其它键不扫虽然现象上也是「全红」但松手瞬间的恢复依赖中断容易出现灯色和倒计时不同步。用emg_flag记状态、TR0开关定时器主循环一秒都没停恢复时save_phase直接把灯色还原、时间从断点接着数这才叫「恢复原工作状态」。K3、K4 的增减处理要点是范围和边界void key_adjust(void) { if(K2 0){ delay10ms(); if(K2 0){ set_mode 1; while(K2 0); } } if(set_mode){ if(K3 0){ delay10ms(); if(K3 0){ if(tiao_time 99) tiao_time; /* 上限钳在 99 */ while(K3 0); }} if(K4 0){ delay10ms(); if(K4 0){ if(tiao_time 0) tiao_time--; /* 下限钳在 0 */ while(K4 0); }} if(K5 0){ delay10ms(); if(K5 0){ set_mode 0; TIME tiao_time; TIME1 tiao_time; while(K5 0); }} } }while(Kx 0);这一步是等手指抬起来作用等同于原文那 1.116s 的延时但响应更自然不会出现「按住 1 秒才加 1」的迟滞感。set_mode保证只有在按下 K2 之后 K3、K4 才起作用避免误触把正常运行的通行时间改掉。5. 常见问题排查五个最容易翻车的点5.1 数码管重影、某一位偏暗现象八个道口都亮着但每个数字周围有一圈淡淡的残影或者某一位明显比别的位暗仔细看能看出叠着相邻位的字。原因九成是消隐没做或者做晚了。位选换了但段码没清零上一位的字在切换瞬间被送到新一位上。另一种是扫描时间分配不均某一位的delay被别的代码挤掉了。也有少数情况是 74LS138 的某根输出线虚焊或者接触不良导致那一位时亮时不亮。解决在DigDisplay的循环里坚持「先关段码、再改位选、再送新段码、延时、再关段码」这个顺序一句都不能省。检查delay(100)是不是被其它中断抢占必要时把显示扫描放进定时中断而不是主循环。硬件上拿万用表量 138 的八根输出选中对应位时应该是低电平不到 0.3V 以下就是驱动能力不够或者线没接好。5.2 黄灯常亮不闪或者闪得肉眼看不出来现象倒计时剩 5 秒黄灯确实亮了但是一直亮着不闪或者用示波器看有翻转眼睛看着就是常亮。原因翻转语句放错了位置。放进 10ms 定时中断里翻转频率 50Hz人眼的临界融合频率大概在 2450Hz 之间50Hz 已经看不出闪了。放进timer_calculate每秒一次里才是 1Hz肉眼清清楚楚。还有人把DXY ~DXY写成了DXY 1——那当然永远是亮的。解决确认翻转语句所在的函数每秒被调用一次不是每 10ms。可以在翻转前后各加一句P2 ^ 0x80用备用引脚输出方波示波器一量周期是不是 1 秒一目了然。另外占空比要控制在 50% 左右如果亮的时间远长于灭的时间看起来也像常亮。5.3 按一次键时间跳好几个数现象按一下 K3时间从 55 直接跳到 58 甚至 60怎么按都控制不住。原因典型的抖动误判。机械按键按下和松开的瞬间会产生 520ms 的连续通断主循环扫得快一段抖动被当成好几次按下。另一种原因是while(K3 0)漏写手指按住的几百毫秒里每一轮循环都执行一次按得越久加得越多。解决双保险。软件上先延时 10ms 再复读一次电平两次都是低才认确认之后加while(K3 0);等释放。硬件上如果按键是散件自己焊的可以在按键两端并联 0.1μF 电容。还有一招是在key_adjust里加个「上次触发时间」的变量两次触发间隔小于 200ms 就忽略对付连击很有效。5.4 倒计时走时不准一分钟差好几秒现象看上去每次减 1 都很正常但拿秒表一掐跑一分钟下来数码管显示只走了 55 秒左右或者反过来走快了。原因晶振频率和定时器初值不匹配。最常见的是用 11.0592MHz 的晶振配了 12MHz 的 0xD8F0 初值慢约 8%。第二种是中断里没手动重装初值方式 1 不自动重装第一次 10ms 之后计数器就从 0 开始数满 65536 个周期变成 65.5ms慢了好几倍。第三种是中断服务函数里干了太多事扫八个数码管加延时花了 8ms10ms 的中断周期里只剩 2ms 干活中断嵌套或者丢中断。解决先确认晶振12MHz 才用 0xD8F011.0592MHz 改成 0xDC00。中断服务函数开头必须补初值。中断里的活尽量少干显示扫描可以改成一次中断扫一位八次中断扫完一轮而不是一次扫八位。最直接的验证方法是用 P3.0 输出一个 1 秒翻转的方波拿秒表掐或者示波器量对不上就回来查这三条。5.5 Proteus 里好好的焊到板上全灭现象仿真跑得完美灯色、倒计时、按键全对按同一张图焊出来上电八个数码管全灭、六只灯不亮、按什么键都没反应。原因这类问题基本都在硬件侧。按出现概率排EA 引脚没接 VCC最常见复位电路接反RST 一直是高电平芯片永远在复位晶振和 30pF 电容离芯片太远根本没起振P0 口没接上拉排阻段码送不出去数码管极性搞反了段码全是反的看起来像坏了。解决拿万用表按顺序量——先量 VCC 和 GND 之间是不是 4.85.2V再量 RST 上电后是不是从高电平降到 0再量 EA 引脚是不是稳稳的 5V最后量 XTAL2 对地有没有 12V 的直流偏置起振时会有。这四步过了芯片一定在跑剩下的才是外围接线问题。我一般还会烧一个最简单的「P1 口全亮全灭」测试程序进去能亮就说明最小系统没问题问题在外设把排查面一刀切开。6. 验收与答辩用一张表把「能跑」变成「可验证」答辩现场最怕的不是被问住是说不清自己做了什么。我的习惯是进门之前先把验收表在脑子里过一遍表上每一行对应一次可执行的动作和一次可观测的现象。验收项操作预期现象判据冷启动断电 3 秒再上电1 秒内东西绿、南北红数码管显示 55无乱码、不全亮相位迁移静置观察 120 秒55→黄闪 5→55→黄闪 5 循环两次切换点秒数均为 0倒计时同步盯住切换瞬间秒数归 0 的同一刻灯色改变目测无错位紧急控制按住 K1 三秒四向全红、数码管灭松手回原相位恢复后秒数接续不归零时间设置K2→K3 连按 5 次→K5东西绿灯由 55 延长到 60增减在 099 内有效边界时间调到 99 再往下调到 0不出现乱码、不越界十位为 0 时按 0 处理极限保持K1 按住 10 秒不松一直全红不自动恢复无死机、无乱闪这张表不只是给自己看的答辩时可以直接摊在桌上。老师问「你这个系统能干什么」指着表答比嘴上说「实现了基本功能」有说服力得多。三个必答题提前准备好一是为什么选 AT89C51答 I/O 数量、4KB Flash、机器周期和成本的匹配附上 22 个 I/O 的分配账二是定时器初值 0xD8F0 怎么来的答 65536 减 10000并主动补充「换成 11.0592MHz 晶振要改成 0xDC00」这一句能拉开差距三是 K1 为什么用电平判断而不是边沿判断答按住生效、松开恢复的语义决定了它必须是电平型边沿触发会导致按住期间状态不稳。还有一个小技巧演示时不要一上电就急着按按键。先让系统自己跑完一个完整周期让老师看清 55/5/55/5 的节拍再动手干预。顺序反了老师看到的永远是「一团乱灯」讲多少原理都救不回来。从那以后我每次交课设前都会把这张验收表打印出来一条条打勾哪个勾打不上就回去查对应的引脚和初值绝不带着问题进答辩教室。这套习惯比代码本身值钱得多。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。