资讯动态

AT89C51交通灯控制系统:定时器中断、动态扫描与状态机设计

发布时间:2026/10/31 20:42:59 来源:尧图企业网站定制
简介面向单片机原理及应用课程设计的学习者这份AT89C51交通灯控制系统结课报告提供从需求分析到总结体会的完整方案参考可帮助解决选题、硬件连线、软件流程与报告撰写等问题。压缩包共1个docx文件约353KB文档按项目需求分析、硬件系统结构、AT89C51原理、独立键盘扫描、时间显示电路、交通灯输出、紧急控制与时间调整、软件体系架构、运行效果展示及附录等模块展开。内容围绕定时器产生秒信号、P1口驱动东西南北红黄绿三灯交替、P0与P2口控制共阳数码管倒计时并设置五个按键实现紧急通车、紧急疏散和直行时间增减人机交互思路较清晰。已有2881人学习适合需要参考课设报告结构、功能拆分与基础实现思路的学生用于对照检查和查漏补缺。1. 单片机课设里最经典的一道题六盏灯、八个数码管怎么压进一片 AT89C51做单片机课设的人十个里有八个绕不开十字路口那套红黄绿。这份基于 AT89C51 单片机的交通灯控制系统设计把题目做成了一个闭环P1 口直接驱动东西、南北共六盏信号灯P0 送段码、P2 拆位选去驱动八段数码管做倒计时再用五个独立按键把紧急全红和时间增减塞进同一套状态机。它解决的不是点亮一个 LED这种演示级问题而是把定时器、中断、动态扫描、按键消抖和状态迁移全串在一条主线上的完整小系统。适合正在交课设、需要一份能仿真能烧片、又看得懂每一行为什么这么写的人也适合已经工作、想拿 51 回头补一遍底层时序的从业者。我把它拆开跑了一遍收获比预期多坑也比预期多。2. 硬件先立骨架P1 灌电流点灯、P0 送段码、P2 拆位选与按键拿到一份课设文档我的习惯是先把端口分配抄成一张表再动手写任何一行代码。原因是 51 的引脚复用极其拥挤一旦画原理图时把按键和位选排在同一组口上后面写软件会一直别扭。这份资源在这一点上做对了P2 口既承担 74HC138 的三根位选线又承担五个按键作者把按键挪到了 P2.0、P2.1、P2.5、P2.6、P2.7正好避开 P2.2 到 P2.4。这个细节不是巧合是画图时被咬过一次才会记住的。2.1 端口分配表先定死再画原理图把功能、引脚、电气特性三列对齐写出来后面查问题时能省一半时间。下面这张表是按资源里的接线方式整理的我补了电气要点一列引脚功能电气要点P1.0 / P1.1 / P1.2东西方向 绿 / 黄 / 红灌电流接法低电平点亮P1.3 / P1.4 / P1.5南北方向 绿 / 黄 / 红同上每路串 220Ω 限流P0.0 ~ P0.7数码管段码 a~g、dpP0 为开漏结构必须外接 10k 排阻上拉P2.2 / P2.3 / P2.474HC138 的 A / B / C三位编码选通 8 路位选P2.0 / P2.1 / P2.5 / P2.6 / P2.7K1 ~ K5 五个独立按键另一端接地靠 P2 内部上拉按下读 0XTAL1 / XTAL212MHz 晶振 两只 30pF 瓷片电容决定机器周期为 1μsRST10μF 电解电容 10k 电阻复位电路上电复位高电平有效并持续两个机器周期这张表里最容易被忽略的是 P0 那一行。很多人在 Proteus 里把数码管直接挂到 P0仿真跑得通因为仿真模型不较真开漏一旦焊到实物P0 输出高电平时是悬空的段码根本推不上去表现就是数码管全黑或者亮度极暗。十块钱的排阻能省掉一晚上的排查。2.2 共阳数码管配 74HC138电流账必须在画图前算完这段是整份资源里最容易翻车的地方也是文档前后矛盾最明显的地方。资源正文写的是八段数码管采用共阳极接法用单片机吸收电流驱动但给出的段码表是0x3F,0x06,0x5B,0x4F...——这是标准的共阴段码0x3F对应 a~f 六段点亮正好是数字 0。共阳的 0 应该是0xC0。接法选错不是显示错一位的问题是整个显示全乱。我把两种接法的取舍摆出来方案段码驱动位选驱动适用判断共阴 74HC138 直接位选P0 输出高电平点亮138 输出低电平公共端需要低不成立138 低有效与共阴公共端接地冲突共阳 74HC138 PNP 三极管P0 输出低电平灌电流138 输出低 → 8550 导通 → 公共端接 VCC推荐与本资源的接线描述一致选共阳方案后段码表要整体取反数字 0~9 变成0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90。这一步用脚算容易错我一般写个小脚本生成或者干脆在代码里用宏#define SEG(x) (~SEG_CODE[x])现场取反改接法时只动一个地方。再算电流。资源里写每段 6mA全显 8 字时每个数码需 48mA后面又写每段电流约 10mA两个数对不上。按 5V 供电、红色 LED 压降约 1.8V 算限流电阻取 470Ω 时单段电流是 (5−1.8)/470 ≈ 6.8mA八段全亮 54mA。这个电流分两路走段码那一路从 P0 灌进来P0 单脚灌电流能力约 15mA、整口约 71mA54mA 分到八个脚每脚不到 7mA勉强够位选那一路要整个 54mA 从一个 138 输出脚流进三极管基极回路74HC138 单脚连续灌电流的推荐值只有 6mA 左右绝对最大值 25mA直接接数码管公共端必烧。所以 8550 不是可选项是必需品基极再串 1k 电阻把电流压到 5mA 以内。还有一点动态扫描时八位数码管不是同时亮的任意时刻只有一位在导通所以真正考验的是位选通路的瞬时电流而不是平均值。这一点在算电源容量时会误导人——按 54mA 选电源没错但按 54mA 选 138 就错了。2.3 独立按键的接法与端口避让五个按键全部采用独立式接法一端接 P2 对应引脚另一端直接接地不加上拉电阻靠 P2 口内部的弱上拉维持高电平。这种接法省事但内部上拉只有几十微安量级线拉长一点或者板子脏一点就会误触发。我一般会在 P2 每个按键脚外面再并一个 10k 上拉到 VCC成本几毛钱稳定性提升明显。按键功能按资源里的定义是K1 紧急全红K2 进入时间设置K3 加时间K4 减时间K5 恢复正常显示。这里有个隐含约束需要注意——K3、K4、K5 用的是 P2.5、P2.6、P2.7而不是顺次的 P2.2、P2.3、P2.4。因为后三个脚已经给了 74HC138 做位选编码如果按键也接上去按下 K3 的瞬间位选编码就变了数码管会跳到另一位上闪一下。这种按下按键数码管乱跳的现象八成就是端口复用没避让。按键的机械抖动在 5~10ms 量级软件消抖的标准做法是检测到低电平后延时 10~20ms 再读一次两次都低才认。资源里用的是delay10ms()加二次判断思路正确但实现上有个隐患——这个延时函数是三层嵌套 for 循环空转实际耗时跟编译器优化等级强相关-O0和-O2下差好几倍。我在第 5 章会专门讲这个坑。3. 软件状态机四个相位、两个定时器和一次消隐硬件定了剩下的是把东西绿 55 秒、黄闪 5 秒、南北绿 55 秒、黄闪 5 秒这个循环翻译成代码。翻译方式有很多种最差的一种是写四个delay(55000)然后顺序执行——倒计时没法做按键按下去没反应黄灯闪烁也无从谈起。这份资源用的是定时器加状态标志的思路方向对但文档正文和附录代码在定时参数上打了一架。3.1 定时器怎么分T0 出秒信号T1 出扫描节拍先算清楚 12MHz 晶振下的机器周期。51 的机器周期是 12 个时钟周期12MHz 除以 12 得 1MHz也就是一个机器周期正好 1μs。定时器方式 1 是 16 位计数最大计数值 65536所以定时 N 微秒的初值是 65536 − N。资源正文写定时器定时 50ms确定 50ms 循环 20 次从而获得 1S 的延时时间附录代码里却是TH00xD8; TL00xF0。0xD8F0 5553665536 − 55536 10000这是 10ms 的初值不是 50ms。按代码跑20 次循环只有 200ms秒信号快 5 倍倒计时会像开了倍速。正确的几个常用值整理成表目标定时计数差THxTLx用途10ms100000xD80xF0资源代码里的值配 100 次循环才是 1s50ms500000x3C0xB0推荐20 次循环得 1s中断负担轻2ms20000xF80x30数码管动态扫描节拍我倾向把两个定时器分开用T0 走 50ms 出秒信号T1 走 2ms 专门刷数码管。八位数码管每 2ms 换一位一轮 16ms刷新率 62.5Hz人眼完全看不出闪烁。如果把扫描塞进 50ms 的 T0 里八位一轮就是 400ms刷新率 2.5Hz数码管会像跑马灯一样一位一位地亮这是新手最常见的显示闪烁投诉来源。#include reg52.h typedef unsigned char u8; typedef unsigned int u16; #define LED_ON 0 // P1 灌电流接法低电平点亮 #define LED_OFF 1 sbit EW_G P1^0; // 东西 绿 sbit EW_Y P1^1; // 东西 黄 sbit EW_R P1^2; // 东西 红 sbit NB_G P1^3; // 南北 绿 sbit NB_Y P1^4; // 南北 黄 sbit NB_R P1^5; // 南北 红 sbit LSA P2^2; // 74HC138 位选 A sbit LSB P2^3; // 74HC138 位选 B sbit LSC P2^4; // 74HC138 位选 C u8 ms_cnt 0; // T0 的 50ms 计数 u8 sec_flag 0; // 秒到标志主循环消费 void timer_init(void) { TMOD 0x11; // T1、T0 均工作在方式 116 位定时 TH0 0x3C; // T050ms 12MHz TL0 0xB0; TH1 0xF8; // T12ms 12MHz供数码管扫描 TL1 0x30; ET0 1; // 允许 T0 中断 ET1 1; // 允许 T1 中断 EA 1; // 开总中断 TR0 1; TR1 1; } void t0_isr(void) interrupt 1 { TH0 0x3C; // 方式 1 不自动重装必须手动补初值 TL0 0xB0; if (ms_cnt 20) { ms_cnt 0; sec_flag 1; // 20 x 50ms 1s } }TMOD 0x11把两个定时器都设成方式 1高四位管 T1、低四位管 T0每四位里低两位是工作方式位01就是方式 1。方式 1 是非自动重装的中断里必须重新写 THx/TLx漏写一次定时周期就变成 65536 个机器周期也就是 65.5ms 一跳秒信号整体变慢。这一点和方式 2 的自动重装完全不同换方式时这段代码要跟着改。sec_flag这个标志位是中断和主循环之间唯一的数据通道。中断里只做计数和置位不做任何耗时操作包括不刷新数码管、不调用延时——中断越长主循环响应越慢按键就越迟钝。3.2 四相位迁移表状态切换只认一个变量交通灯的四个状态其实很规整东西和南北永远互补。我把资源里的四种状态整理成迁移表后面写代码就照着表填状态东西方向南北方向持续时长迁移条件0绿灯亮红灯亮55s倒计时归零 → 状态 11黄灯闪红灯亮5s倒计时归零 → 状态 22红灯亮绿灯亮55s倒计时归零 → 状态 33红灯亮黄灯闪5s倒计时归零 → 状态 0状态 1 和状态 3 的黄灯闪烁频率按 1Hz 做也就是亮 0.5 秒、灭 0.5 秒。这个翻转不能在主循环里做因为主循环一秒才跑一次状态计算要放在 T0 的 50ms 中断里累计 10 次翻转一次。资源代码里用的是if(huang0){DXY~DXY;}写在秒级的计算函数里实际闪烁频率就是 0.5Hz慢得像坏了。这类闪烁频率对不上的问题本质是把需要 500ms 节拍的动作挂到了 1s 节拍上。u8 state 0; // 0~3 四个相位 u8 ew_sec 55; // 东西方向剩余秒数 u8 nb_sec 55; // 南北方向剩余秒数 u8 blink_en 0; // 黄灯闪烁使能 u8 blink_cnt 0; void load_state(u8 s) { EW_G LED_OFF; EW_Y LED_OFF; EW_R LED_OFF; NB_G LED_OFF; NB_Y LED_OFF; NB_R LED_OFF; blink_en 0; switch (s) { case 0: EW_G LED_ON; NB_R LED_ON; ew_sec 55; nb_sec 55; break; case 1: EW_Y LED_ON; NB_R LED_ON; ew_sec 5; nb_sec 5; blink_en 1; break; case 2: EW_R LED_ON; NB_G LED_ON; ew_sec 55; nb_sec 55; break; case 3: EW_R LED_ON; NB_Y LED_ON; ew_sec 5; nb_sec 5; blink_en 1; break; } } void time_tick(void) { if (ew_sec) ew_sec--; if (nb_sec) nb_sec--; if (ew_sec 0) { state (state 1) 0x03; // 0→1→2→3→0位与保证不出界 load_state(state); } }load_state先全部熄灭再点亮当前相位需要的灯这个顺序很重要。如果反过来只改变化的那一盏切换瞬间会出现两盏灯同时亮的重影实物上肉眼可见。(state 1) 0x03比if(state4) state0少一次判断也让状态数天然限制在 0~3改相位数量时只要改掩码。倒计时显示口径这里有个选择一种是两个方向都从 55 数到 0黄灯那 5 秒重新从 5 数到 0另一种是按资源正文说的准行时间 60 秒绿 55 秒内数到 5剩 5 秒亮黄闪到 0 切换。前者逻辑干净、每相位独立后者更贴近真实路口习惯。我按前一种实现因为改时长的时候不用重新对齐两个计数器的偏移量少一类错误。3.3 显示函数为什么必须做先消隐动态扫描的原理是同一时刻只点一位数码管靠人眼视觉暂留拼成完整画面。但如果两位之间不做处理前一位的段码会残留到下一位的位选时间里表现就是相邻两位数字串影——显示 12 的时候1 的位置隐隐透出一部分 2 的笔画。处理办法是每次换位之前先把段码清零。资源代码里写的是P00x00;做消隐对共阳接法来说 0x00 是全灭正确。但要注意顺序先送位选还是先送段码正确顺序是位选 → 段码 → 延时 → 消隐 → 换位如果先送段码再换位选那换位瞬间新一位会闪出上一位的段码消隐也救不回来。u8 code SEG_CODE[10] {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; void seg_scan(void) { static u8 pos 0; P0 0xFF; // 共阳先全灭段码全高 LSA pos 0x01; LSB (pos 1) 0x01; LSC (pos 2) 0x01; // 138 位选编码 switch (pos) { case 0: P0 SEG_CODE[ew_sec / 10]; break; // 东西 十位 case 1: P0 SEG_CODE[ew_sec % 10]; break; // 东西 个位 case 2: P0 SEG_CODE[nb_sec / 10]; break; // 南北 十位 case 3: P0 SEG_CODE[nb_sec % 10]; break; default: P0 0xFF; break; // 未接的位保持全灭 } pos (pos 1) 0x07; // 8 位循环 } void t1_isr(void) interrupt 3 { TH1 0xF8; // 2ms 重装 TL1 0x30; seg_scan(); }seg_scan里没有调用任何延时函数位与位之间的时间间隔由 T1 的 2ms 中断保证。这是和资源代码最大的差别——它用的是delay(100)在函数内部死等这样扫描一轮的时间取决于编译器和晶振改个优化等级就变而且期间主循环被完全占死。把节拍交给定时器扫描函数变成被调用时只管输出当前这位稳定性和可维护性都上一个台阶。pos 0x07里那个 0x07 对应八位数码管如果只用了四位改成 0x03一轮 8ms刷新率更高。3.4 主循环只做三件事主循环的结构越简单越好。我的一般做法是按键扫描、消费秒标志、其他全交给中断。资源里的主循环混了显示调用和延时导致按键响应时快时慢这种不确定性在调试阶段最难定位。void main(void) { u8 key_flag 0; load_state(0); timer_init(); while (1) { key_flag key_scan(); // 返回 0 表示无键按下 if (key_flag) { key_dispatch(key_flag); // 按键处理统一走这里 } if (sec_flag) { sec_flag 0; if (mode MODE_RUN) { time_tick(); // 只有正常模式才走倒计时 } } } }mode这个变量是为紧急和设置模式预留的开关。正常运行时MODE_RUN每秒推进一次倒计时进入紧急模式后mode变成MODE_EMGtime_tick不再被调用倒计时自然冻结恢复时从冻结处继续。这比在time_tick内部加一堆 if 判断要清晰得多也是我建议所有状态机都留一个总闸变量的原因。4. 五个按键怎么塞进状态机紧急全红与时间增减的实现边界按键是这套系统里唯一的人机接口也是最容易写出逻辑漏洞的地方。资源把五个键的功能定义得很清楚但附录代码在 K1 的处理上有个典型问题——用按位取反~去翻转一个 u8 标志位结果标志位从 0/1 变成了 0/254后面的if判断全部失效。4.1 K1 紧急全红标志位千万别用按位取反紧急模式的语义是按下 K1四个方向全部红灯数码管关闭倒计时冻结松开 K1恢复原来的相位继续走。实现上需要一个当前是否处于紧急的状态以及一个进入紧急前是什么相位的备份。资源代码里的写法是这样if (K1 0) { delay10ms(); if (K1 0) { if (hong_biao 1) { TR0 1; zhuangtai_xuanze(); } if (hong_biao 0) { TR0 0; DXG1; DXY1; DXR0; NBG1; NBY1; NBR0; } } hong_biao ~hong_biao; // 问题在这里 while (K1 0); }hong_biao声明成u8初值 0。第一次按下~0得到 0xFF是 255 1成立第二次按下~0xFF得到 0x00又回到 0。看起来能工作是因为它刚好在两个极值之间跳。但只要有任何一个中间值进去比如初始化时赋了 1或者被别处改过~1 0xFE之后就永远在 0xFE 和 0x01 之间跳判断逻辑彻底失效。这类 bug 在仿真里不一定暴露在实物上表现为按一次紧急再按就恢复不了。正确的写法是用逻辑非或者显式赋值u8 emg_on 0; // 0 正常1 紧急 u8 saved_state 0; // 备份进入紧急前的相位 void key_k1(void) { if (emg_on 0) { emg_on 1; saved_state state; all_red(); // 六盏灯里只留两盏红 P0 0xFF; // 数码管段码全灭 mode MODE_EMG; // 关掉倒计时推进 } else { emg_on 0; mode MODE_RUN; load_state(saved_state); // 回到原来的相位秒数按备份恢复 } }all_red()里要先把六盏灯全置灭再点亮两个红灯理由和load_state一样防重影。P0 0xFF是共阳接法下的全灭如果是共阴这里应该是 0x00。这一行的取值必须和段码表配套改接法时两处一起改漏一处就是显示乱码。关于按住 K1 期间持续全红还是按一下切换资源正文写的是按下 K1 键四个路口均红灯松开后恢复也就是电平触发。电平触发的实现要小心主循环里key_scan()每几十毫秒跑一次如果按住不放会反复触发进入紧急的分支。所以我更倾向边沿触发——检测到按下动作时切换一次状态之后按住不再响应直到检测到松开。这样逻辑上等价于按一下进再按一下出但避免了长按抖动。4.2 K2 到 K5进入设置、加减、退出的三段式时间调整是这套系统里交互最复杂的一块。资源定义的是K2 进入设置模式K3 加时间K4 减时间K5 恢复显示。这四步构成一个小的子状态机必须和主状态机隔离开否则改时间的时候交通灯还在跑改完发现相位已经错位了。我在实现上用一个三态变量管这件事mode 值含义倒计时是否推进数码管显示MODE_RUN正常运行是实时剩余秒数MODE_SET时间设置否当前设定的时长闪烁提示MODE_EMG紧急全红否关闭进入MODE_SET后倒计时暂停但灯保持当前状态不变数码管显示待调整的时长并做 2Hz 闪烁。K3 每按一次加 1 秒K4 每按一次减 1 秒K5 退出并把新模式写回run_time。u8 mode MODE_RUN; u8 run_time 55; // 绿灯时长可调范围 10~99 u8 set_tmp 55; // 设置模式下的临时值 void key_dispatch(u8 k) { switch (k) { case 1: key_k1(); break; // 紧急 case 2: // K2 进入设置 if (mode MODE_RUN) { mode MODE_SET; set_tmp run_time; } break; case 3: // K3 加 if (mode MODE_SET set_tmp 99) set_tmp; break; case 4: // K4 减 if (mode MODE_SET set_tmp 10) set_tmp--; break; case 5: // K5 退出并生效 if (mode MODE_SET) { run_time set_tmp; mode MODE_RUN; load_state(state); // 按新时长重载当前相位 } break; default: break; } }set_tmp 99和set_tmp 10这两个边界不是随便定的。上限 99 是因为两位数码管最大就显示 99超过就得上三位硬件要改。下限 10 是因为绿灯时间低于 10 秒一个周期内车辆根本来不及起步逻辑上没意义实际路口也不会这么设。资源里写红绿灯时长可设定为 0-99我建议把下限抬到 10把无效配置挡在按键这一层比在运行层做保护要省事。load_state(state)这一句容易漏。改完时长后如果不重载ew_sec还是旧值要等当前相位跑完才生效用户会以为按键没反应。4.3 参数边界与默认值一张表把这套系统里所有可调的量和它们的合理区间列清楚调试的时候对着表打点比瞎猜快得多参数默认值可调范围越界后果绿灯时长 run_time55s10 ~ 99s低于 10s 车辆无法起步高于 99s 数码管溢出黄灯时长5s固定缩短到 3s 以下实车来不及减速倒计时显示位宽2 位固定超过 99 需要扩到三位段码与位选都要改按键消抖时间10ms5 ~ 20ms太短误触发太长按键迟钝黄灯闪烁半周期500ms300 ~ 700ms太快像故障太慢像没在闪黄灯时长这一项在实际工程里是有规范依据的一般按路口限速和停车视距算模拟系统里固定 5 秒够用。但如果有人拿这套代码去做课程演示之外的用途这 5 秒就不该是常量而应该跟着run_time一起进设置模式。改法很简单把YELLOW_SEC从#define改成变量加进 K3/K4 的调整范围代价是多一个显示页面。5. 从仿真到实物必踩的五个坑现象、原因和改法前面几章讲的是应该怎么搭这一章讲实际会怎么坏。这五个坑是我把这份资源照着跑一遍之后真实遇到的每一个都在仿真里表现正常、在实物上原形毕露。它们的共同点是Proteus 的器件模型比真实芯片宽容得多凡是涉及电流、上拉、时序余量的地方仿真都不会报错。5.1 数码管显示乱码段码表和共阳共阴接法对不上现象仿真里八个数码管全亮但显示的不是数字是一堆看不出规律的笔画换成实物后更糟只有个别段微微发亮。原因资源文档写共阳极接法段码表却是共阴的0x3F、0x06、0x5B...。共阳数码管的公共端接 VCC段要输出低电平才亮0x3F送进去会把该亮的段全关掉、该关的全点亮自然乱。解决先确认手里的数码管是共阳还是共阴——万用表二极管档红笔接公共端、黑笔碰任一段亮的是共阴反过来亮的是共阳。确认共阳后段码全部取反或者在代码里统一加一层~。另外检查 74HC138 的位选通路共阳方案下 138 输出低电平有效中间必须有三极管反相驱动公共端直接连会烧 138。5.2 倒计时跑得飞快文档写 50ms代码写的是 10ms现象仿真启动后数码管从 55 飞速往下掉五秒不到就切换相位黄灯闪得像频闪灯。原因文档正文说定时器定时 50ms循环 20 次获得 1S附录代码里TH00xD8; TL00xF0算出来是 10000 个机器周期也就是 10ms。20 次循环只有 200ms秒信号快了 5 倍。解决两条路选一条。要么改初值为TH00x3C; TL00xB050ms循环 20 次要么保留 10ms 初值把循环次数从 20 改成 100。改完用示波器或者拿秒表对着数码管数 60 秒误差在 1 秒以内算合格。这个坑最阴的地方在于仿真里看起来在倒计时不掐表根本发现不了。5.3 数码管一位都不亮P0 忘了上拉现象段码测下来是对的位选也测下来是对的但实物上一个字都不显示把万用表搭在 P0 某脚上量电压高电平只有 1V 出头。原因51 的 P0 口是开漏结构做通用 I/O 时必须外接上拉电阻才能输出高电平。仿真模型里 P0 默认带内部上拉所以看不出来。解决P0 口并一只 10k 排阻到 VCC位置尽量靠近单片机。如果数码管是灌电流驱动共阳理论上低电平不需要上拉但段码切换的瞬间需要拉高关断没上拉会有拖影。这只排阻不到一块钱别省。5.4 信号灯实物不亮P1 高电平点灯带不动现象仿真里红绿灯正常切换焊到板子上六盏灯全不亮或者只有很暗的一点红光。原因资源代码里用DXG1表示点亮是高电平驱动思路。51 的 P1 口输出高电平时是弱上拉拉电流只有几十微安驱动 LED 远远不够输出低电平时灌电流能到十几毫安才是正确用法。解决改接法LED 阳极经 220Ω 电阻接 VCC阴极接 P1 对应脚代码里LED_ON定义成 0。220Ω 是按 (5−1.8)/14.5mA 算的实测亮度够用且不超 P1 单脚 15mA 的灌电流上限。六个脚同时点亮时整口电流接近 90mA超过了 71mA 的整口限制要么把限流电阻加大到 330Ω 降到 10mA 一路要么每路加一只 8550 做驱动。5.5 按键一按倒计时就卡住消抖延时把主循环堵死了现象按住任意按键不放倒计时停住不动松手后过一会儿才继续而且跳了好几秒。原因消抖用的是三层 for 循环的空转延时for(c1;c0;c--) for(b38;b0;b--) for(a130;a0;a--)实际耗时取决于编译器优化等级和晶振-O0下可能十几毫秒-O2下可能只有一两毫秒。更关键的是它阻塞延时期间主循环完全不跑秒标志堆在中断里没人消费。松手后主循环一次性把积压的标志处理掉就出现了跳秒。解决把消抖改成状态机式的非阻塞写法在 20ms 的定时节拍里采样连续两次为低才认按下、连续两次为高才认松开。这样主循环任何时候都不会被堵住倒计时和按键互不干扰。退一步的做法是把消抖延时压到 5ms 以内至少让卡顿短到看不出来但根子上还是非阻塞更靠谱。6. 联调收尾Keil 与 Proteus 对接时我会强制走的三步验证代码编译通过、仿真图连通这两件事同时成立离能用还差一段距离。这份资源里我最想强调的一点是一定要掐着秒表验证定时而不是看数码管在动就算过。前面提到的 50ms 与 10ms 之争靠肉眼是发现本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑