简介:这是一个面向单片机课程设计与综合实验的十字路口交通灯控制系统完整方案,基于51单片机和Proteus实现。资源压缩包共21个文件,大小约358KB,内含Proteus仿真工程、Keil工程文件、C语言源码、hex烧录文件以及Word实验报告,仿真、代码、报告齐全,可直接打开仿真工程运行,也可将hex下载到单片机验证。目前已有453人学习,适合正在完成交通灯课设或需要学习Proteus仿真的读者参考。方案使用P1.0-P1.5控制四个方向红黄绿灯,P0口驱动两位共阳数码管倒计时,P2.0-P2.3作为位选,定时中断50ms产生秒跳变;报告涵盖实验目的、仪器材料、内容要求与原理分析,结合源码和仿真图可以清晰理解模块化编程思路,也便于修改交通灯配时或扩展为更复杂的控制逻辑。
1. 十字路口四向红绿灯仿真:四组灯和两位数码管的“同步”才是重点
四组红黄绿对应十字路口的四个方向,用两位数码管显示剩余时间,再用 Proteus 把仿真图画出来——这个题目在单片机课程设计和 proteus 仿真作业里出现频率很高。不少人拿到题先开 Proteus 拖元件,拖完才发现四组灯怎么接、数码管显示谁的剩余时间、红灯倒计时从几秒开始,这些关键点还悬着。其实灯虽然摆了十二只,逻辑上只需要按南北、东西两个车道方向去切换;两位数码管也就是一只给南北方向、一只给东西方向,各自显示本方向下一次变绿灯或当前绿灯还剩多少秒。下面从状态表、C51 代码、Proteus 连线到报告整理,按可复现的顺序走一遍。
2. 系统构成与状态切换:灯组怎么接、剩余时间怎么算
2.1 四个方向四套灯,先简化成两组时序
十字路口四个方向各有一组红黄绿,但真正独立的只有两组:南北向车道同步放行,东西向车道同步放行。东、西两个方向的灯要同时变,因为它们在同一个相位里;南、北也一样。所以代码里只需要维护两套输出,四个方向的灯组共用这两套驱动。
先画一张四状态表,后面写代码、画 Proteus 图、写报告都用它:
| 状态编号 | 南北向灯组 | 东西向灯组 | 持续时间 | 说明 |
|---|---|---|---|---|
| S0 | 绿灯 | 红灯 | 30s | 南北放行,东西禁行 |
| S1 | 黄灯 | 红灯 | 3s | 南北清空路口 |
| S2 | 红灯 | 绿灯 | 30s | 东西放行,南北禁行 |
| S3 | 红灯 | 黄灯 | 3s | 东西清空路口 |
注意 S1 和 S3 是短状态,只用于黄灯清空。很多人做到这里会直接写成“南北绿30秒,然后南北黄3秒,然后南北红3秒,再东西绿30秒”,结果东西向红灯切绿灯的时机不对,路口出现四个方向同时绿灯的瞬间。用上面的状态表就不会,因为东西向红灯在 S0、S1 两个状态里一直保持,S1 结束才切换到绿灯。
两位数码管的显示值这样约定:南北向数码管显示南北方向的剩余时间,东西向数码管显示东西方向的剩余时间。一个方向绿灯时显示本相位剩余秒数,黄灯时显示黄灯剩余秒数,红灯时显示“离下一次本方向绿灯亮起还剩多少秒”。
这个定义决定了一件事:红灯显示不是从 30 开始,而是从 33 开始。因为南北红灯期间,还要等东西绿灯 30 秒和东西黄灯 3 秒,才能重新轮到南北绿灯。从 33 倒计时到 1,期间数字连续变化,不会在状态切换时跳回 33。
2.2 主控与 IO 分配:AT89C51 在 Proteus 里的最小方案
主控选 AT89C51,原因是 Proteus 元件库里直接搜“AT89C51”就能放进去,不需要额外导入模型。双击元件把时钟频率设为 12MHz,这和代码里定时器初值计算一致。
用 Proteus 8 Professional 做仿真时,P0 口需要注意:AT89C51 的 P0 是开漏输出,段码输出必须接上拉电阻,否则两位数码管亮度很暗或者显示不稳定。我一般接一个 RESPACK-8 排阻,一端接 P0,另一端统一接 +5V。
引脚分配如下:
| 引脚 | 连接对象 | 说明 |
|---|---|---|
| P1.0 | 东西向绿 | LED 正极经 220Ω 接 +5V |
| P1.1 | 东西向黄 | 同上 |
| P1.2 | 东西向红 | 同上 |
| P1.3 | 南北向绿 | 同上 |
| P1.4 | 南北向黄 | 同上 |
| P1.5 | 南北向红 | 同上 |
| P0.0 – P0.7 | 两位数码管段码 a – g、dp | 两个模块并联到同一组段码线 |
| P2.0 – P2.3 | 四位数码管位选 | 低电平选中对应位 |
这里说的“两个两位数码管”,实际是两颗 7SEG-MPX2-CA。每颗内部有两位,外部引出两个位选脚 dig1、dig2。南北方向用一颗,东西方向用一颗,合起来四位数码管,最大能显示 99,而我们最大只显示 33,两位足够。
2.3 共阳数码管动态扫描:段码数组和位选时序
7SEG-MPX2-CA 是共阳数码管,段码低电平有效。按 P0.0 接 a、P0.1 接 b、P0.2 接 c、P0.3 接 d、P0.4 接 e、P0.5 接 f、P0.6 接 g、P0.7 接 dp 的顺序接线,数字 0 到 9 的段码是:
| 数字 | 段码 | 数字 | 段码 |
|---|---|---|---|
| 0 | 0xC0 | 5 | 0x92 |
| 1 | 0xF9 | 6 | 0x82 |
| 2 | 0xA4 | 7 | 0xF8 |
| 3 | 0xB0 | 8 | 0x80 |
| 4 | 0x99 | 9 | 0x90 |
动态扫描的核心是“同一时刻只点亮一位”:
sbit DISP_NS_T = P2^0; // 南北十位 sbit DISP_NS_D = P2^1; // 南北个位 sbit DISP_EW_T = P2^2; // 东西十位 sbit DISP_EW_D = P2^3; // 东西个位每次刷新先送段码,再选通位选,最后延时 2ms。四个位轮流扫一遍约 8ms,刷新频率 125Hz,人眼看到的就是四个数字同时稳定显示。如果延时调到 5ms 以上,会看到明显闪烁;小于 1ms,亮度又不够。2ms 是仿真和实物都比较稳的经验值。
3. 定时器中断、倒计时与显示刷新:C51 代码分段拆解
3.1 用定时器0产生 1 秒时基,重载值怎么算
12MHz 晶振下,51 单片机机器周期是 12 ÷ 12MHz = 1µs。定时器0工作在方式1,是 16 位加 1 计数器,最大计到 65536 溢出。要产生 50ms 中断,需要计数 50000 次,初值是 65536 - 50000 = 15536,十六进制是 0x3CB0。
定时器初始化和中断服务程序这样写:
#include <reg51.h> #define uchar unsigned char #define uint unsigned int volatile uchar tick_50ms = 0; volatile uchar sec_flag = 0; void Timer0_Init(void) { TMOD &= 0xF0; /* 只修改定时器0配置 */ TMOD |= 0x01; /* 方式1:16位定时器 */ TH0 = 0x3C; TL0 = 0xB0; /* 初值:50000次 = 50ms */ ET0 = 1; EA = 1; TR0 = 1; } void Timer0_ISR(void) interrupt 1 { TH0 = 0x3C; TL0 = 0xB0; /* 重装初值 */ tick_50ms++; if (tick_50ms >= 20) { tick_50ms = 0; sec_flag = 1; /* 累计到1秒,置位标志 */ } }中断里只置标志,不在中断里做灯切换和显示刷新,避免主循环和中断争用变量。主循环检测到 sec_flag 后再处理倒计时,这样秒脉冲误差只来自 50ms 计数的累计,代码也容易排查。
3.2 状态机主循环:切灯、算剩余秒数、刷新显示
灯的控制状态用数组保存,每个状态持续时间放在表里:
#define GREEN_TIME 30 #define YELLOW_TIME 3 code uchar state_time[4] = {GREEN_TIME, YELLOW_TIME, GREEN_TIME, YELLOW_TIME}; void Light_Set(uchar st) { switch (st) { case 0: /* 南北绿,东西红 */ NS_GREEN = 1; NS_YELLOW = 0; NS_RED = 0; EW_GREEN = 0; EW_YELLOW = 0; EW_RED = 1; break; case 1: /* 南北黄,东西红 */ NS_GREEN = 0; NS_YELLOW = 1; NS_RED = 0; EW_GREEN = 0; EW_YELLOW = 0; EW_RED = 1; break; case 2: /* 南北红,东西绿 */ NS_GREEN = 0; NS_YELLOW = 0; NS_RED = 1; EW_GREEN = 1; EW_YELLOW = 0; EW_RED = 0; break; case 3: /* 南北红,东西黄 */ NS_GREEN = 0; NS_YELLOW = 0; NS_RED = 1; EW_GREEN = 0; EW_YELLOW = 1; EW_RED = 0; break; } }两个数码管显示值用独立函数计算:
uchar Calc_NS_Display(uchar st, uchar remain) { switch (st) { case 0: case 1: return remain; /* 南北绿或黄:显示本状态剩余 */ case 2: return remain + YELLOW_TIME; /* 南北红:剩余 + 后续黄灯3秒 */ default: return remain; } } uchar Calc_EW_Display(uchar st, uchar remain) { switch (st) { case 0: return remain + YELLOW_TIME; /* 东西红:显示33到4 */ case 1: case 2: case 3: return remain; /* 东西红尾段、绿、黄 */ default: return remain; } }主循环里每收到一个秒脉冲,当前状态剩余秒数减 1,减到 0 就切到下一个状态:
void main(void) { uchar state = 0; uchar remain_sec = state_time[0]; Timer0_Init(); Light_Set(state); while (1) { if (sec_flag) { sec_flag = 0; remain_sec--; if (remain_sec == 0) { state = (state + 1) % 4; remain_sec = state_time[state]; Light_Set(state); } } Display_Refresh(Calc_NS_Display(state, remain_sec), Calc_EW_Display(state, remain_sec)); } }为什么 state 2 的南北红要加YELLOW_TIME?因为南北红灯期间还剩“东西黄灯3秒 + 东西绿灯30秒”,融成一个连续倒计时,显示从 33 一路走到 1,中间不会跳变。
3.3 绿信比调整:把 30 秒和 3 秒做成可配置参数
交通灯设计中绿灯和黄灯时长叫绿信比。很多作业只要求固定 30 秒,但报告中通常要写“可调”。用宏定义后,修改只需要改两个数字:
| 参数 | 现在值 | 调整示例 | 作用 |
|---|---|---|---|
| GREEN_TIME | 30 | 25 | 调整通行相位总时长 |
| YELLOW_TIME | 3 | 5 | 调整清空路口的缓冲时间 |
| state_time[0] | GREEN_TIME | 随宏变化 | 南北绿灯 |
| state_time[1] | YELLOW_TIME | 随宏变化 | 南北黄灯 |
如果直接写死30和3,显示函数里加YELLOW_TIME的位置也要同步改,很容易漏。抽出宏之后,报告里可以写“改变宏定义即可改变配时”,这句话在答辩时很加印象分。周期总长为两个绿灯加两个黄灯,也就是2 * (GREEN_TIME + YELLOW_TIME)秒。
4. Proteus 仿真搭建:元件布置、连线与运行
4.1 Proteus 元件清单与搜索关键词
打开 Proteus 元件库后,按下面表格逐个搜索放入:
| 元件 | Proteus 搜索关键词 | 数量 | 备注 |
|---|---|---|---|
| 主控芯片 | AT89C51 | 1 | 双击改频率 12MHz |
| 两位共阳数码管 | 7SEG-MPX2-CA | 2 | 南北、东西各一颗 |
| 红色 LED | LED-RED | 4 | 四个方向的红灯 |
| 黄色 LED | LED-YELLOW | 4 | 四个方向的黄灯 |
| 绿色 LED | LED-GREEN | 4 | 四个方向的绿灯 |
| 排阻 | RESPACK-8 | 1 | P0 上拉 |
| 电阻 | RES | 12 | 220Ω LED 限流 |
| 晶振 | CRYSTAL | 1 | 12MHz |
| 电容 | CAP | 4 | 22pF 晶振电容 |
| 电解电容 | CAP-ELEC | 1 | 10µF 复位电路 |
| 按键 | BUTTON | 1 | 手动复位 |
Proteus 元件库里“7SEG-MPX2-CA”是两位共阳数码管的标准模型,不要再用两个 7SEG-COM-CAT-8 拼,拼出来的引脚和位选逻辑跟代码对不上。排阻注意选 RESPACK-8,不是 RESPACK-7,后者少一位。
4.2 连线顺序与网络标号:先电源和晶振,再排 IO
打开 Proteus 后先放 AT89C51,把 VCC 和 GND 接好,再放晶振电路。晶振两端各接一个 22pF 电容到 GND,复位电路用 10µF 电解电容接到 VCC、再串联 10kΩ 电阻到 GND,RST 引脚接在电容和电阻中间。这个复位电路在仿真里也能用,按一次 BUTTON 相当于硬件复位。
然后按下面的顺序分组连线:
- P0 接 RESPACK-8,排阻公共端接 +5V,另一侧接 P0。再把两个 7SEG-MPX2-CA 的八段引脚 a 到 dp 分别接到 P0 的同一组网络上。
- P2.0 接南北模块 dig1,P2.1 接南北模块 dig2,P2.2 接东西模块 dig1,P2.3 接东西模块 dig2。
- 十二只 LED 分成四组,每组一只红、一只黄、一只绿。东西向红灯接 P1.0,东西向黄灯接 P1.1,东西向绿灯接 P1.2;南北向按 P1.3、P1.4、P1.5 对应绿、黄、红。
- 每只 LED 正极接 220Ω 电阻到对应 IO,负极统一接地。这样 IO 输出高电平就点亮 LED。
连线时可以用网络标号替代实际走线,P0 的八根段码线加上排阻和两次数码管,用鼠标拉线会非常乱。给每个网络起名 SEG_A 到 SEG_DP,数码管和排阻的同名引脚直接标网络号,仿真图整洁,报告里截图也好看。
4.3 加载 HEX 文件与运行前检查
Keil 工程里把 main.c 加入,然后在 Options for Target 的 Output 选项卡勾选 “Create HEX File”,编译后在工作目录生成 hex 文件。回到 Proteus 双击 AT89C51,在 Program File 一栏选择这个 hex,晶振频率填写 12MHz,点运行。
如果只想在命令行确认 hex 是否生成,可以用:
if exist .\Objects\*.hex ( echo HEX 文件已生成 ) else ( echo 请先在 Keil 中勾选 Create HEX File )运行后首先看南北绿灯和东西红灯是否同时保持,再看两个数码管是不是从 30 和 33 开始倒数。南北绿灯 30 秒结束后,南北黄灯亮 3 秒,数码管显示 3 到 1;然后南北红灯亮,南北数码管从 33 重新倒数,东西数码管从 30 开始倒数。
4.4 Proteus 仿真不动的三排错法
仿真最常见的现象是灯不闪、数码管全灭或者数字乱跳,按顺序排查:
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 运行后灯和数码管全不动 | 没加载 hex 或频率不对 | 检查 AT89C51 的 Program File 和 Clock Frequency |
| 数码管乱码 | 段码线接错或共阳共阴选错 | 核对 P0 到 a–dp 的顺序 |
| 倒计时比实际快一倍 | Keil 和 Proteus 晶振不统一 | 两处都设为 12MHz |
| 数字闪烁 | 数码管延时太长 | 把延时降到 2ms |
| 四方向绿灯同时亮 | 状态表写错 | 回到第 2 章的状态表逐行核对 |
Proteus 和 Keil 都是默认 12MHz 的话,这三排错基本覆盖 80% 的问题。
5. 验收清单与报告素材整理:仿真图、时序表、参数调优记录
5.1 验收时盯住的四个观察点
仿真通过后,按下面这张表逐项打勾再截图:
| 观察点 | 通过标准 |
|---|---|
| 相位互斥 | 任意时刻只有南北或东西一个方向是绿灯 |
| 黄灯顺序 | 绿灯熄灭后黄灯亮 3 秒,再切红灯 |
| 倒计时连续性 | 状态切换瞬间数码管数字不跳变、不回弹 |
| 刷新稳定 | 四位数码管亮度均匀,无闪烁 |
截图为证时,分别截一张南北绿灯、南北黄灯、东西绿灯、东西黄灯的图,四张图能完整表达整个周期。
5.2 报告里需要体现的三类素材
标题里带“报告”,说明最终交付不只有代码和仿真图。报告结构里最少要有三块:状态时序表、Proteus 原理图、关键代码段。状态时序表直接复用第 2 章的四状态表;原理图用 4.2 节完成后的干净截图,不要带交叉杂乱的总线;代码段只要贴定时器初始化、状态切换、显示刷新三块,并配上每段的功能说明。
报告中建议另外画一个简易的系统框体:AT89C51 在中间,左边是晶振复位电路,右边是六只灯的输出,下边是两位数码管显示模块。这个框图在答辩时能让老师一眼看出你理解了系统结构。
5.3 用参数表做绿信比验证
最后一步是验证参数可调。把宏定义改成一组不同值,重新编译并加载 hex:
#define GREEN_TIME 20 #define YELLOW_TIME 5重新运行仿真,记录如下表:
| 测试序号 | 南北绿灯 | 南北黄灯 | 东西绿灯 | 东西黄灯 | 周期总长 |
|---|---|---|---|---|---|
| 1 | 30s | 3s | 30s | 3s | 66s |
| 2 | 20s | 3s | 30s | 3s | 56s |
| 3 | 20s | 5s | 25s | 3s | 53s |
每组参数跑完一轮,把数码管开始倒计时的画面和灯组切换画面截下来,放进报告的“参数分析”小节。最后在报告里写一句结论:绿灯时长调整只需修改GREEN_TIME和YELLOW_TIME两个宏,重新编译即可生效。保存这两次修改后的仿真截图,插入到报告参数分析栏目,整套作业从仿真、代码到报告就闭环了。
本文还有配套的精品资源,点击获取