简介:面向嵌入式课程设计与单片机入门的智能交通灯控制器项目包,围绕 Keil 与 Proteus 的联合开发流程,给出可编译程序、虚拟仿真电路与使用说明,适合需要完成交通灯课题、练习 C 语言控制逻辑与电路联调的学生和自学者,覆盖传感器数据处理、模式判断、实时时序与硬件接口等知识点。压缩包共 17 个文件、约 86KB,包含 main.c 源码、uvproj/uvopt 工程配置、hex 烧录文件、dsn/pwi 仿真工程,以及 txt 使用说明和成果记录,m51、lst、obj 等编译中间文件也一并保留,便于对照工程结构排查问题。已有 4704 人学习,说明该案例在同类课程设计中具有一定参考面。读者可据此复现正常、紧急、智能三种模式切换,理解按键交互、时序控制与车流检测思路,并通过 Keil 编译与 Proteus 联合仿真验证程序,减少实物搭建前的试错成本,也能更直接地看到程序逻辑与电路模块如何对应。
1. 需求先落地:智能交通灯控制器到底要解决哪些现场问题
做十字路口智能交通灯控制器,最怕的是只有一张仿真图,没有 Keil 程序代码和使用说明,灯一亮就不知道下一步该改哪。这套方案把路口时序、单片机资源分配、Keil 程序代码、Proteus 仿真图和配套使用说明串成一条线,目标不是做一个“会亮灯”的 Demo,而是做一个能讲清状态切换、倒计时显示、按键干预和夜间模式的完整控制器。它适合正在做单片机课设、毕设的学生,也适合需要快速搭一套交通灯仿真原型的开发者。核心需求通常包括东西/南北方向红绿黄切换、两位数码管倒计时、紧急通行按键、夜间黄闪,以及复位后回到安全初始状态。方案不追求外设堆料,而是把定时器、状态机、显示刷新和按键扫描这四件事做稳,让读者拿到后能对照仿真图逐段改代码,也能在答辩时把设计依据讲明白。
2. 总体方案设计:从路口时序到单片机资源分配
智能交通灯控制器的难点不在“点亮一个 LED”,而在“让多个方向、多个灯组、倒计时和按键在同一个时间轴上不打架”。总体方案通常采用“单片机 + 定时器时基 + 状态机 + 数码管动态刷新 + 独立按键”的结构:定时器负责产生稳定节拍,状态机负责决定当前哪个方向放行、哪个方向等待,显示模块只负责把当前倒计时送到数码管,按键模块只负责产生紧急或夜间模式请求。这样分工之后,每个模块都能单独测试,Proteus 仿真图也不会因为一根线接错就整屏失效。
2.1 路口时序与状态划分:四个相位如何覆盖直行、黄灯和全红
最常见的十字路口简化模型是东西直行、南北直行两个主方向,每个方向配红、黄、绿三组灯。为了让切换过程安全,通常把完整周期拆成四个相位:东西绿灯放行、东西黄灯缓冲、南北绿灯放行、南北黄灯缓冲。黄灯之后不要立刻把另一方向切绿,通常插入 1 到 2 秒全红,给路口一个清空时间。这个全红时间在仿真图里看起来只是“所有灯短暂熄灭或红灯保持”,但在方案里它是防止抢跑的关键设计点。
以典型课设参数为例,东西绿灯 20 秒、东西黄灯 3 秒、南北绿灯 20 秒、南北黄灯 3 秒,全红 2 秒。倒计时数码管显示当前相位剩余时间,绿灯和黄灯阶段显示倒计时,全红阶段可以显示 00 或保持上一相位末值,具体看使用说明里的约定。参数不是固定死的,实际方案里通常把时长写成宏或常量表,改时间只需要改表,不需要在状态机里到处改数字。
| 相位 | 东西方向灯态 | 南北方向灯态 | 典型时长 | 设计目的 |
|---|---|---|---|---|
| S0 | 绿灯亮 | 红灯亮 | 20 s | 东西直行放行 |
| S1 | 黄灯亮 | 红灯亮 | 3 s | 东西方向缓冲 |
| S2 | 红灯亮 | 绿灯亮 | 20 s | 南北直行放行 |
| S3 | 红灯亮 | 黄灯亮 | 3 s | 南北方向缓冲 |
| S4 | 红灯亮 | 红灯亮 | 2 s | 全红清空路口 |
状态划分确定后,程序里就不要用“延时 20 秒再换灯”的写法。阻塞式延时会让按键扫描和数码管刷新全部停摆,仿真里看起来灯会亮,但按键按下去没反应,倒计时也会跳变。通常的做法是用一个 1 秒时基去递减当前相位剩余时间,减到 0 再进入下一个状态。这样主循环始终有空处理按键和显示,Proteus 仿真里的观感也更接近真实控制器。
注意:全红相位不要省略。很多仿真图为了“看起来切换快”把全红压成 0 秒,结果黄灯刚灭另一方向就绿了,答辩时容易被问“路口清空时间在哪里”。
2.2 硬件资源与Proteus元件选型:AT89C51、数码管、按键和信号灯
这套方案的核心控制器通常选 AT89C51 或同系列 51 单片机,原因是 Keil C51 开发链路成熟,Proteus 元件库里也容易找到对应模型,课程设计场景下资料多、排查成本低。显示部分用两位共阴或共阳数码管,分别显示东西和南北方向的倒计时;如果只做一套倒计时,也可以只用一个两位数码管分时显示当前放行方向剩余时间。信号灯用 6 个 LED 表示东西红黄绿和南北红黄绿,按键至少保留 2 个:一个紧急通行,一个夜间模式,复位按键用单片机复位电路实现。
在 Proteus 仿真图里,元件选型要和代码里的端口映射严格对应。比如 P1 口低四位驱动东西方向灯组,高四位驱动南北方向灯组;P0 口接数码管段码,P2 口做位选;P3 口接按键。P0 口作为开漏输出,通常要加上拉排阻,否则仿真里数码管可能偏暗或段码乱跳。LED 要串限流电阻,Proteus 里不串电阻虽然有时也能亮,但电流参数不真实,换到实物就容易烧灯或亮度不均。
| 元件 | 作用 | 选型/连接注意 |
|---|---|---|
| AT89C51 | 主控制器 | 晶振、复位、电源引脚不能悬空 |
| 两位数码管 | 显示倒计时 | 共阴/共阳要与段码表一致 |
| 红黄绿 LED 各 2 组 | 指示方向灯态 | 每组串限流电阻,端口电平要与驱动方式匹配 |
| 独立按键 | 紧急/夜间模式 | 一端接地,另一端接 IO,软件消抖 |
| 晶振与电容 | 提供时钟 | 常用 12 MHz 或 11.0592 MHz,代码时基要对应 |
| 复位电路 | 上电复位 | 阻容复位参数要保证复位时间足够 |
这里容易踩坑的地方是“仿真能亮,实物不亮”。Proteus 对端口驱动能力比较宽容,实物里 LED 灌电流和拉电流差别很明显。通常的做法是先确定 LED 是低电平点亮还是高电平点亮,再统一写端口宏,不要把高低电平散落在状态机里。这样后面改电路时只改宏定义,不用满工程搜P1 = 0x...。
2.3 软件分层与主循环职责:定时器、按键扫描、显示刷新和状态机
软件分层不需要搞得很复杂,但职责要清楚。初始化层负责端口方向、定时器模式、中断开关和初始状态;时基层负责每 50 ms 进一次中断,累计到 1 秒给出一个tick_1s标志;状态机层只在tick_1s有效时递减倒计时并判断是否换相位;显示层每 5 到 10 ms 刷新一位数码管,保证亮度稳定;按键层每 10 到 20 ms 扫描一次,做软件消抖和模式切换。主循环里不要写长延时,所有等待都用标志位和状态变量解决。
主循环的典型顺序是:先扫按键,再根据模式更新状态机,最后刷新显示。紧急模式优先级最高,按下后所有方向转红,倒计时暂停或显示固定值;夜间模式下次干道黄灯闪烁,另一方向红灯保持。模式退出后不要直接跳回绿灯,通常先回到全红,再按正常相位重新开始,避免从紧急状态直接放行造成时序错乱。
提示:状态机里只保存“当前相位”和“剩余秒数”两个核心变量,其他灯态由相位查表得出。这样调试时看两个变量就能还原现场,不用在十几个布尔变量之间猜。
3. Keil程序实现:定时器中断、倒计时与按键处理的完整骨架
Keil 程序代码是这套方案里最容易被反复修改的部分。很多同学一开始把红灯、绿灯、倒计时、按键全写在一个main函数里,仿真时看起来能跑,但只要按一下按键,倒计时就停住,或者数码管开始闪。问题不在 Proteus 仿真图,而在程序结构:延时函数占住了 CPU,中断和显示没有机会执行。下面按“时基—状态机—按键—显示”四块给出可复用的骨架,代码按 51 单片机 C 语言写,端口映射可以按自己的仿真图调整。
3.1 定时器初值与1秒时基:如何从晶振算出中断重装值
定时器通常用模式 1,也就是 16 位定时器。假设晶振 12 MHz,机器周期为 1 μs,想要 50 ms 中断一次,计数值是 50000,初值就是65536 - 50000 = 15536,换成十六进制是0x3CB0。中断里重装TH0和TL0,再用一个计数器累计 20 次,得到 1 秒时基。如果晶振换成 11.0592 MHz,机器周期不再是整 1 μs,初值要重新算,不能直接照搬。
#include <reg51.h> volatile unsigned char tick_50ms = 0; // 50 ms 中断计数 volatile bit flag_1s = 0; // 1 秒标志,主循环消费 void Timer0_Init(void) { TMOD &= 0xF0; // 清除定时器0模式位 TMOD |= 0x01; // 定时器0,模式1,16位 TH0 = 0x3C; // 12 MHz 晶振,50 ms 初值高字节 TL0 = 0xB0; // 50 ms 初值低字节 ET0 = 1; // 允许定时器0中断 EA = 1; // 开总中断 TR0 = 1; // 启动定时器0 } void Timer0_ISR(void) interrupt 1 { TH0 = 0x3C; // 重新装载高字节 TL0 = 0xB0; // 重新装载低字节 tick_50ms++; if (tick_50ms >= 20) // 20 × 50 ms = 1 s { tick_50ms = 0; flag_1s = 1; // 只置标志,不在中断里做长任务 } }这段代码的关键点是中断服务程序尽量短。中断里只重装初值、累计计数、置 1 秒标志,状态切换和显示刷新都放到主循环。参数说明:TH0/TL0对应 12 MHz 晶振下的 50 ms;如果仿真图里晶振属性改成 11.0592 MHz,需要把初值改成对应值,否则倒计时会偏快或偏慢。flag_1s用volatile或bit修饰,避免编译器优化后主循环读不到变化。
3.2 状态机与倒计时联动:绿灯、黄灯、全红怎么切
状态机建议用枚举表示相位,用一张常量表保存每个相位的灯态和时长。主循环检测到flag_1s后,先清标志,再判断当前是否处于紧急或夜间模式;正常模式下剩余秒数减 1,减到 0 就切换到下一个相位。切换时不要直接改灯,而是先更新相位变量,再由灯态表统一输出,这样灯态和倒计时不会出现“灯已经变了、数码管还在显示上一个相位”的错位。
typedef enum { PHASE_EW_GREEN = 0, // 东西绿,南北红 PHASE_EW_YELLOW, // 东西黄,南北红 PHASE_NS_GREEN, // 东西红,南北绿 PHASE_NS_YELLOW, // 东西红,南北黄 PHASE_ALL_RED // 全红清空 } Phase_t; typedef struct { unsigned char ew_lamp; // 东西灯态编码 unsigned char ns_lamp; // 南北灯态编码 unsigned char seconds; // 该相位时长 } PhaseCfg_t; code PhaseCfg_t phase_cfg[5] = { {0x01, 0x04, 20}, // 东西绿,南北红 {0x02, 0x04, 3}, // 东西黄,南北红 {0x04, 0x01, 20}, // 东西红,南北绿 {0x04, 0x02, 3}, // 东西红,南北黄 {0x04, 0x04, 2} // 全红 }; Phase_t phase = PHASE_EW_GREEN; unsigned char remain_sec = 20; void Traffic_Update_1s(void) { if (flag_1s == 0) return; flag_1s = 0; if (mode_emergency || mode_night) return; // 特殊模式暂停正常倒计时 if (remain_sec > 0) { remain_sec--; } if (remain_sec == 0) { phase = (Phase_t)((phase + 1) % 5); // 五个相位循环 remain_sec = phase_cfg[phase].seconds; // 装载新相位时长 } } void Traffic_Output(void) { P1 = (phase_cfg[phase].ew_lamp << 4) | phase_cfg[phase].ns_lamp; }这里用phase_cfg把灯态和时长集中管理,改绿灯时长只改表里的20,不用在多个if里找数字。remain_sec是当前相位剩余秒数,初始值要和phase_cfg[0].seconds保持一致。Traffic_Output负责把灯态送到 P1 口,端口高低位对应关系要和 Proteus 仿真图一致;如果仿真图里东西灯接低四位,就把移位方向反过来,不要只改代码不改图。
3.3 按键消抖与紧急模式:外部中断还是轮询
按键用轮询就够了,不必一上来就上外部中断。交通灯控制器按键数量少,主循环每 10 到 20 ms 扫一次,配合状态机已经足够。外部中断虽然响应快,但按键抖动会在中断里产生多次触发,反而要加更复杂的消抖逻辑。轮询式消抖的思路是:检测到按下后不立即生效,连续两次或多次采样一致才确认;确认后等待按键释放,避免长按连续触发。
#define KEY_EMERGENCY 0x01 // P3.0 紧急按键 #define KEY_NIGHT 0x02 // P3.1 夜间按键 unsigned char key_old = 0; bit mode_emergency = 0; bit mode_night = 0; void Key_Scan_10ms(void) { unsigned char key_now = P3 & 0x03; // 只取低两位 unsigned char key_press = key_old & (~key_now); // 下降沿 key_old = key_now; if (key_press & KEY_EMERGENCY) { mode_emergency = !mode_emergency; // 按一次进入,再按退出 mode_night = 0; // 互斥,避免模式叠加 } if (key_press & KEY_NIGHT) { mode_night = !mode_night; mode_emergency = 0; } }这段代码假设按键接 P3.0 和 P3.1,按下为低电平。key_press取的是“上一次为高、这一次为低”的下降沿,所以按住不放只触发一次。实际工程里通常还会加 10 ms 定时调用,或者在主循环里用flag_10ms控制调用节奏,不要在主循环里裸写while等待释放。紧急模式和夜间模式做成互斥,避免两个模式同时打开后灯态冲突。
注意:如果仿真图里按键没有上拉电阻,P3 口内部上拉可能让按下电平不明显。先确认按键另一端接的是地还是电源,再决定代码里判断高电平还是低电平。
3.4 数码管动态刷新与亮度稳定:显示缓存与位选顺序
数码管动态刷新最忌讳在main里用长延时切换位选。正确做法是用一个显示缓存保存两个方向的倒计时,定时器或主循环每 5 ms 左右刷新一位,先送段码再开位选,短暂延时后关位选,再切下一位。段码表要和共阴/共阳一致,位选顺序要和 Proteus 图里的三极管或直接驱动方式一致。如果数码管出现重影,通常是位选没关干净就换了段码;如果亮度不均,通常是刷新节拍被其他任务拖长。
code unsigned char seg_code[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F // 9 }; unsigned char disp_buf[2] = {0, 0}; // 两位显示缓存 void Display_Refresh(void) { static unsigned char pos = 0; P0 = 0x00; // 先关段码,防止重影 P2 = (pos == 0) ? 0x01 : 0x02; // 位选:先第一位,再第二位 P0 = seg_code[disp_buf[pos]]; // 送段码 pos++; if (pos >= 2) pos = 0; }seg_code是共阴数码管 0 到 9 的段码,如果仿真图用共阳数码管,要把表取反。disp_buf里放的是要显示的数字,不要直接拿remain_sec去做除法取位,先把十位和个位拆好放缓存,显示函数只负责刷新。刷新函数可以放在定时器中断里每 5 ms 调用一次,也可以在主循环里由flag_5ms触发,但不要在刷新函数里加delay_ms(5)这种阻塞延时,否则按键扫描会被拖慢。
4. Proteus仿真搭建:从元件放置到联调可复现的步骤
Proteus 仿真图不是“把元件拖到一起”就完事,真正影响联调的是网络标号、端口映射和 HEX 文件加载顺序。建议先画最小系统,再挂显示和灯组,最后加按键。每加一部分就单独测试,不要等整张图画完再一次性点运行仿真。这样一旦灯不亮,能快速判断是单片机没跑起来、端口接错,还是代码里灯态表写反了。
4.1 仿真图搭建顺序:电源、复位、晶振、数码管和灯组
搭建顺序通常按“最小系统—显示—灯组—按键”推进。第一步放 AT89C51,接好 VCC、GND、晶振和两个谐振电容,复位引脚接阻容复位电路。第二步放两位数码管,段码线接 P0,位选线接 P2,P0 加上拉排阻。第三步放 6 个 LED,按东西红黄绿、南北红黄绿分组,阳极或阴极根据驱动方式接限流电阻。第四步放两个按键到 P3.0、P3.1,另一端接地。最后用网络标号把同名端口连起来,减少飞线。
| 步骤 | 操作 | 检查点 |
|---|---|---|
| 1 | 放置单片机并接晶振、复位 | 晶振属性与代码时基一致 |
| 2 | 连接数码管段码和位选 | 共阴/共阳与段码表一致 |
| 3 | 连接 6 个 LED 与限流电阻 | 高低电平点亮方式与代码一致 |
| 4 | 连接按键到 P3 口 | 按下电平与消抖代码一致 |
| 5 | 加载 HEX 并运行仿真 | 复位后灯态符合初始相位 |
仿真图里建议给关键网络加标号,比如EW_G、EW_Y、EW_R、NS_G、NS_Y、NS_R,这样代码里写端口宏时不容易错位。LED 的限流电阻不要省略,Proteus 虽然允许理想器件,但保留电阻更接近实物,后面换实物板时不用重新算电流。
4.2 Keil输出HEX与Proteus加载:编译配置和常见路径问题
Keil 工程里要确认两件事:晶振频率设置和 HEX 输出开关。晶振频率影响软件延时和定时器初值,如果工程里设 12 MHz、仿真图里放 11.0592 MHz,倒计时就会偏。HEX 输出在 Target Options 的 Output 页勾选 Create HEX File,编译后会在工程目录下生成 HEX 文件。Proteus 里双击单片机,在 Program File 栏选择这个 HEX,Clock Frequency 填和代码一致的晶振频率。路径里尽量不用中文和空格,避免加载失败。
// 端口映射建议集中定义,和 Proteus 网络标号一一对应 #define PORT_LAMP P1 #define PORT_SEG P0 #define PORT_SEL P2 #define PORT_KEY P3 // 灯态编码:bit0 东西绿,bit1 东西黄,bit2 东西红 // bit3 南北绿,bit4 南北黄,bit5 南北红(按实际接线调整) #define EW_GREEN 0x01 #define EW_YELLOW 0x02 #define EW_RED 0x04 #define NS_GREEN 0x08 #define NS_YELLOW 0x10 #define NS_RED 0x20把端口和灯态编码集中定义,仿真图改线时只改宏,不用去状态机里逐行改。参数说明:如果 LED 是低电平点亮,输出时要对灯态取反;如果高电平点亮,直接输出。这个宏定义不解决硬件驱动问题,但能避免“代码里写的是 P1.0,图里接的是 P1.4”这类低级错误。
4.3 联调顺序与观测点:先灯后数码管,再按键和夜间模式
联调不要一上来就看完整周期。先把程序改成固定输出一个相位,比如东西绿灯、南北红灯,运行仿真看 6 个 LED 是否按预期亮灭;确认灯组无误后,再打开倒计时,看数码管是否从 20 递减;最后加按键,测试紧急模式下是否全红、夜间模式下是否黄闪。每一步只验证一个观测点,出问题时范围小,排查快。
| 联调阶段 | 观测点 | 通过标准 |
|---|---|---|
| 灯组测试 | 6 个 LED 是否按相位亮灭 | 无串亮、无暗亮、方向不反 |
| 倒计时测试 | 数码管是否每秒减 1 | 20 秒相位能完整走完 |
| 相位切换 | 黄灯和全红是否出现 | 切换时没有两个方向同时绿 |
| 按键测试 | 紧急/夜间是否生效 | 按下响应,松开不连续触发 |
| 复位测试 | 复位后初始状态 | 回到安全相位,倒计时重新装载 |
联调时如果灯态对、数码管不对,先查段码表和位选;如果数码管对、按键不对,先查按键电平和消抖节拍。Proteus 仿真可以暂停后看引脚电平,比盯着灯猜原因快。血泪经验是:先把最小系统跑通,再往上堆功能,别在一张全连接的图上同时改代码和改线。
5. 常见问题排查:仿真能跑但实物不亮时先查哪几处
智能交通灯控制器的排查要按“电源与复位—端口映射—显示刷新—时序逻辑”的顺序走,不要一上来就怀疑代码全错。Proteus 仿真能跑但实物不亮,最常见的原因不是单片机坏了,而是驱动电平、限流电阻、共阴共阳接法或晶振频率不一致。下面四类问题在课设和毕设里出现频率很高,每条按现象、原因、解决三步写,方便对照。
5.1 信号灯不亮或乱闪:先查端口映射与限流
现象是仿真里某个方向的红灯不亮,或者绿灯和黄灯一起微亮,换一个相位又正常。原因通常有三类:端口宏和仿真图网络标号错位;LED 高低电平点亮方式写反;限流电阻过大或直接省掉导致电流不足。解决时先把程序固定输出一个灯态,用万用表或 Proteus 探针看对应引脚电平,再对照仿真图确认线接到哪个引脚。如果是低电平点亮,代码里输出0才亮,状态表里却按1写,灯态就会整体反相。限流电阻一般按 LED 电流和电源电压估算,仿真里可以保留 220 Ω 到 1 kΩ 之间的值,实物不要直接拿端口灌电流驱动多组灯。
还有一种情况是“乱闪”,往往不是灯的问题,而是主循环里既有数码管刷新又有长延时,端口被不同函数反复改写。解决方法是把灯态输出集中到一个函数,其他函数只改变量,不直接写P1。这样任何时刻只有一个地方决定灯态,排查时也只需要看状态表和输出函数。
5.2 数码管缺段、重影或跳变:先查位选与刷新节拍
现象是数码管少一段、两位数字重叠,或者倒计时跳变时亮度忽然变暗。原因通常是段码表与共阴/共阳不匹配、位选没有先关断再换段码、刷新节拍被其他任务拖长。解决时先单独测试段码表:依次显示 0 到 9,看哪一段不亮,对照硬件连线查 A 到 G 段。重影问题要把刷新顺序改成“关段码—换位选—送段码—短延时—关位选”,不要先送段码再换位选。跳变问题要检查刷新函数是不是放在 1 秒任务里,显示刷新应该是毫秒级,不是秒级。
如果数码管在按键按下时变暗,说明按键扫描里有阻塞延时,把刷新节拍拉长了。通常的做法是把按键扫描改成每 10 ms 调用一次,不在按键处理里等释放。显示缓存也要在状态机更新后统一刷新,避免主循环和中断同时改同一个显示变量。
5.3 按键无响应或误触发:先查消抖与中断标志
现象是按下紧急按键没反应,或者手一碰就连续切换模式。原因可能是按键接法判断反了、消抖节拍太快或太慢、按键释放没有处理。解决时先确认按键另一端接地还是接电源,代码里判断低电平还是高电平要跟电路一致。消抖不要用delay_ms(20)硬等,改成定时扫描加下降沿检测,按下只触发一次。如果用了外部中断,要在中断里清标志并做简单消抖,否则抖动会进多次中断。
还有一种误触发是模式变量没有互斥,紧急模式和夜间模式同时为 1,灯态输出冲突。解决方法是进入一个模式时强制清另一个模式,退出时回到全红再恢复正序。按键响应不要求微秒级,10 到 20 ms 的扫描节拍对交通灯控制器足够。
5.4 时序错乱与倒计时卡死:先查中断重装与状态变量
现象是倒计时走到 0 不切换,或者黄灯停留时间明显不对,甚至两个方向同时绿灯。原因常见于定时器中断里没有重装初值、flag_1s没有清零、状态变量在中断和主循环里同时被改写。解决时先看中断服务程序是否重装TH0/TL0,再看主循环消费标志后有没有清标志。状态变量尽量只在主循环里改,中断只置标志,避免竞态。两个方向同时绿灯通常是灯态表编码错位或输出函数把两个相位叠加了,先把输出固定到全红,再逐相位测试。
如果倒计时偶尔跳字,检查晶振频率和定时器初值是否匹配。12 MHz 和 11.0592 MHz 的初值不能混用,仿真图里的单片机属性也要和 Keil 工程设置一致。时序问题不要靠“多跑几次看看”解决,先把 1 秒时基单独测出来,再往上查状态机。
6. 验收与答辩:用一组可复现的测试用例把方案讲清楚
验收时不要只说“仿真能跑”,要拿测试用例说话。通常准备一张 5 到 6 行的验收表,每行写操作、预期现象和实际结果。比如复位后是否回到东西绿、南北红;正常倒计时 20 秒后是否进黄灯;黄灯结束是否出现全红;紧急按键按下是否全红;夜间模式是否黄闪;再按一次是否能恢复。表格比口头描述更有说服力,答辩时顺着表格讲,老师或评审也能快速抓住设计点。
| 用例 | 操作 | 预期现象 | 检查点 |
|---|---|---|---|
| 1 | 上电/复位 | 东西绿、南北红,倒计时装载 | 初始相位安全 |
| 2 | 等待 20 s | 东西黄灯 3 s 后全红 | 黄灯与全红存在 |
| 3 | 继续等待 | 南北绿 20 s 后转黄 | 相位顺序正确 |
| 4 | 按紧急键 | 所有方向红灯,倒计时暂停 | 紧急模式优先 |
| 5 | 再按紧急键 | 回到全红后恢复正序 | 退出不直接放行 |
| 6 | 按夜间键 | 次干道黄闪,另一方向红 | 夜间模式可恢复 |
答辩讲述顺序建议按“需求—方案—实现—验证”走:先说路口为什么需要四相位和全红,再说单片机资源怎么分配,接着拿 Keil 程序里的状态表和定时器初值说明实现依据,最后用验收表证明每个需求都有对应现象。不要一上来念代码,也不要把 Proteus 仿真图从头讲到尾,挑三个关键点:时基怎么来、灯态怎么切、按键怎么不打架。被问到参数时,直接说清晶振频率、定时器初值、相位时长和端口映射,比背整段程序更稳。
从那以后我每次验收这类控制器,都强制走一遍“复位—正常切换—紧急—夜间—恢复”的闭环,不跳过全红,也不靠肉眼猜时序。希望帮到你。
本文还有配套的精品资源,点击获取