AT89C51交通灯系统:定时器中断与数码管动态扫描实战
2026/9/17 16:57:40 网站建设 项目流程

简介:本资源是一份面向电子类专业初学者与单片机课程设计学生的完整教学文档,聚焦基于AT89C51单片机的交通灯模拟控制系统实现,解决嵌入式系统中典型时序控制与人机交互设计问题。文档为单个225KB的Word文件(.docx),内容详实覆盖硬件选型依据(AT89C51核心控制器、LED红绿灯模块、七段数码管倒计时显示)、软件模块化设计(主循环状态机、1秒精确计时子程序、外部中断响应逻辑)及系统联调方法,含摘要、关键词、中英文目录、四章正文(含系统时钟/复位电路设计、南北/东西通行时序配置、汇编程序HEX烧录说明)与仿真验证结论。已有143人学习下载,适合课程设计参考、课设报告撰写及51单片机入门实践,结构清晰、图文结合、可直接用于答辩与复现。

1. 为什么用 AT89C51 做交通灯控制,比直接上 PLC 或 Arduino 更能练透底层时序与硬件协同?

很多刚学单片机的人一上来就想做“智能红绿灯”——加摄像头识别车流、用 WiFi 推送状态、接云平台。但真正卡住工程能力跃迁的,从来不是功能堆砌,而是对最小系统里定时、中断、IO 驱动、数码管动态扫描这四层耦合关系的理解深度。本设计用 AT89C51(经典 51 单片机型号,兼容 MCS-51 指令集)搭建一套可完整运行的交通灯模拟系统,核心不在于“灯怎么亮”,而在于:黄灯倒计时必须精确到 0.1 秒级抖动容限;东西/南北两组灯状态切换需严格同步;数码管显示倒计时不能有闪烁或丢位;所有逻辑必须在无操作系统、无库函数依赖下,靠寄存器直驱实现。它不是教学 Demo,而是嵌入式工程师验证自己是否真正“看得见硬件”的标尺——当你能手动写出TH0=0x3C; TL0=0xB0;这组初值并解释为何对应 50ms 定时,你就跨过了 51 单片机的第一道真实门槛。适合电子类本科课程设计、Keil+Proteus 联调入门者,以及想补足定时器配置与数码管驱动细节的转岗工程师。

2. 用 AT89C51 在 Proteus 中跑通交通灯最小系统:从原理图连线到 Keil 编译烧录

2.1 为什么选 AT89C51 而非 STC89C52 或新型 51 兼容芯片?

AT89C51 是工业级 Flash 型 51 单片机的基准型号,其内部结构(128B RAM、4KB Flash、2 个 16 位定时器、4×8 位并行口)恰好覆盖交通灯控制所需的全部资源边界:无需外扩存储器即可存放完整状态机代码;两个定时器分别用于系统主循环节拍(如 100ms)和数码管动态扫描刷新(如 2ms);P0/P1 口足够驱动 6 位共阴数码管 + 6 路 LED 灯。更重要的是,Proteus 元件库中 AT89C51 的 VSM 模型最成熟,仿真时定时器溢出、中断响应、IO 电平翻转行为与真实芯片误差 < 0.3%,避免新手因仿真失真误判代码逻辑。而 STC 系列虽支持 ISP 下载,但在 Proteus 中需额外加载.hex模型文件,且部分型号的定时器模式仿真存在偏差,对初学者排查“为什么仿真不亮灯”反而增加干扰。

2.2 Proteus 原理图关键连接:数码管与 LED 灯的 IO 分配策略

提示:数码管位选线必须接 P2 口(非 P0),否则与地址锁存冲突;LED 灯阴极共地,阳极经 220Ω 电阻接单片机 IO,确保灌电流 ≤ 15mA/引脚。

在 Proteus 中新建AT89C51元件,按以下方式分配 IO:

功能引脚接法说明
东西向红灯P1.0阳极接 P1.0,阴极接地,串 220Ω 限流电阻
东西向黄灯P1.1同上
东西向绿灯P1.2同上
南北向红灯P1.3同上
南北向黄灯P1.4同上
南北向绿灯P1.5同上
数码管段码P0直接连 74HC245(增强驱动能力),输出 a~g+dp 段码
数码管位选P2.0~P2.5共阴数码管 6 位,每位独立接 P2.0~P2.5,低电平选通
晶振X1/X2接 11.0592MHz 晶振 + 30pF 电容,保证波特率精度(后续扩展串口调试用)
复位电路RST10kΩ 上拉 + 10μF 电容 + 按键,高电平复位
# Proteus 中关键元件搜索关键词(避免拼写错误): # AT89C51 → 元件库搜索 "AT89C51" # 共阴数码管 → 搜索 "7SEG-MPX6-CC"(6 位共阴) # 74HC245 → 搜索 "74HC245" # 220Ω 电阻 → 搜索 "RESISTOR" 后双击修改值为 220

2.3 Keil C51 工程配置:生成 HEX 文件并关联 Proteus

  1. 新建 Keil uVision5 工程,CPU 选择Atmel → AT89C51
  2. 添加main.c源文件,设置Output → Create HEX File勾选
  3. Target → Xtal (MHz)设为11.0592(与 Proteus 晶振一致)
  4. C51 → Code Rom SizeLarge(启用全部 4KB Flash)
  5. 编译后生成traffic.hex,在 Proteus 中双击 AT89C51 元件 →Program File栏填入该路径

注意:若 Proteus 报错 “No hex file loaded”,检查 Keil 是否成功编译(Build Output 窗口末尾显示0 Error(s), 0 Warning(s)),且 HEX 文件路径不含中文或空格。

3. 实现交通灯状态机与数码管动态扫描:手写定时器中断与位操作逻辑

3.1 主状态机设计:用枚举定义 4 种合法灯态,禁止非法跳转

交通灯逻辑本质是有限状态机(FSM)。本设计定义 4 个核心状态,每个状态含固定持续时间与灯组输出组合:

状态枚举值持续时间东西向南北向数码管显示
STATE_EW_RED30s绿30→0
STATE_NS_YELLOW3s3→0
STATE_NS_RED30s绿30→0
STATE_EW_YELLOW3s3→0
// main.c 关键状态机结构体(精简版) typedef enum { STATE_EW_RED, STATE_NS_YELLOW, STATE_NS_RED, STATE_EW_YELLOW } TrafficState; TrafficState curr_state = STATE_EW_RED; unsigned char countdown = 30; // 当前倒计时秒数 bit timer_flag = 0; // 100ms 定时标志位 void main() { init_timer0(); // 初始化定时器0(100ms中断) EA = 1; // 开总中断 while(1) { if(timer_flag) { timer_flag = 0; update_countdown(); // 每100ms减1,到0则切换状态 update_leds(); // 根据当前状态设置P1口电平 refresh_display(); // 刷新数码管显示 } } }

3.2 定时器0配置:用 TH0/TL0 计算 100ms 定时初值

AT89C51 定时器工作在方式1(16位定时),机器周期 = 12 / 晶振频率 = 12 / 11.0592MHz ≈ 1.085μs。要实现 100ms 定时,需计数次数 = 100ms / 1.085μs ≈ 92167。因定时器最大计数值为 65536,故需设置初值 = 65536 - 92167 = -26631 → 补码为0x9A9F(高位 TH0=0x9A,低位 TL0=0x9F)。

void init_timer0() { TMOD = 0x01; // 定时器0,方式1(16位) TH0 = 0x9A; // 100ms 定时初值高位 TL0 = 0x9F; // 100ms 定时初值低位 ET0 = 1; // 开定时器0中断 TR0 = 1; // 启动定时器0 } void timer0_isr() interrupt 1 { TH0 = 0x9A; // 重装初值(自动重载需用方式2) TL0 = 0x9F; timer_flag = 1; // 置位100ms标志 }

说明:此处未用自动重载方式2(TMOD=0x02),因方式2仅 8 位,无法满足 100ms 精度;手动重装 TH0/TL0 是方式1的标准做法,确保每次中断后定时器从同一初值开始计数。

3.3 数码管动态扫描:用 P2 口位选 + P0 口段码,实现无闪烁显示

6 位数码管需分时轮流点亮,每位点亮时间约 1~2ms,人眼视觉暂留形成“同时显示”。关键点:

  • 位选信号(P2.0~P2.5)必须先置高(关闭所有位)→ 再置低(选通当前位),避免位选信号切换瞬间出现鬼影
  • 段码输出(P0)必须在位选稳定后立即写入,且保持 ≥ 500μs
  • 扫描周期 ≤ 10ms(即每位 ≤ 1.67ms),否则肉眼可见闪烁
code unsigned char seg_code[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; // 共阴段码表 void refresh_display() { static unsigned char pos = 0; unsigned char digit; // 关闭所有位(P2高电平) P2 = 0xFF; // 根据pos确定当前要显示的数字位 switch(pos) { case 0: digit = countdown / 10; break; // 十位 case 1: digit = countdown % 10; break; // 个位 default: digit = 0; break; } // 输出段码(P0) P0 = seg_code[digit]; // 选通当前位(P2低电平有效) P2 = ~(1 << pos); // 延时确保点亮时间 ≥ 500μs(实际用空指令延时) _nop_(); _nop_(); _nop_(); _nop_(); pos = (pos + 1) % 2; // 仅扫描十位和个位(2位) }

4. 调试与排错:用 Proteus 虚拟逻辑分析仪定位常见硬件级问题

4.1 灯不亮?三步定位法:测 IO 电平、查驱动电阻、验共地连接

当 Proteus 中 LED 不亮,不要立刻改代码,按顺序验证硬件链路:

  1. 右键点击 LED 元件 → Properties → 查看Voltage:若阳极电压 < 2.5V,说明单片机 IO 未输出高电平,检查P1 = 0xFF是否被意外覆盖
  2. 双击 220Ω 电阻 → 查看Current:正常应为 10~15mA;若为 0,检查 LED 阴极是否真正接地(Proteus 中接地符号必须用GROUND,不可用POWER
  3. 用虚拟逻辑分析仪(Virtual Instruments → Logic Analyzer)接 P1.0~P1.5:设置采样率 1kHz,运行仿真,观察波形是否按预期高低电平切换——若全为高电平,说明状态机未进入STATE_EW_RED,检查countdown初始化值或update_leds()函数逻辑

4.2 数码管显示错乱?重点检查段码表与位选时序配合

常见现象:显示数字跳变、某位常亮、所有位全暗。对应排查:

  • 段码表错误:共阴数码管段码0xC0对应数字 0,若用共阳段码表(如0x3F)会导致全黑。确认 Proteus 中选用7SEG-MPX6-CC(CC=Common Cathode)
  • 位选信号竞争:若P2 = 0xFFP2 = ~(1<<pos)之间无延时,Proteus 会仿真出瞬态毛刺。必须在P2 = 0xFF后加delay_us(1)或至少 3 个_nop_()
  • 扫描频率过低:若refresh_display()被调用间隔 > 5ms,人眼可见闪烁。确保该函数在main()循环中被高频调用,或改用定时器1专做扫描(推荐)

4.3 定时不准?验证晶振频率与定时器初值的数学一致性

若倒计时 30 秒实际耗时 32 秒,问题必在定时器配置:

  1. 在 Proteus 中双击 AT89C51 →Clock Frequency必须为11.0592MHz(与 Keil 中Xtal设置一致)
  2. 用公式复算初值:初值 = 65536 - (定时时间 × 晶振频率) / 12
    代入:65536 - (0.1 × 11059200) / 12 = 65536 - 92160 = 3376→ 十六进制0x0D30
    但此前用0x9A9F是因计算了 100ms 的实际计数值(92167),差异源于浮点舍入。精确解应为TH0=0x0D, TL0=0x30
// 修正后的 100ms 定时器初始化(更精准) void init_timer0_precise() { TMOD = 0x01; TH0 = 0x0D; // 65536 - 92160 = 3376 → 0x0D30 TL0 = 0x30; ET0 = 1; TR0 = 1; }

5. 进阶技巧:用单按键实现时间参数设置与黄灯闪烁控制

5.1 按键消抖与长按识别:区分短按(切换模式)与长按(修改时间)

交通灯实际部署需现场调整红灯时长。本设计用 P3.2(INT0 引脚)接独立按键,通过检测按键持续时间实现双功能:

  • 短按(< 500ms):进入参数设置模式,数码管显示当前东西向红灯时间(如30
  • 长按(≥ 1s):每 200ms 加 1 秒,最大设为 99 秒,避免溢出
bit key_press = 0; unsigned int key_time = 0; void external_int0() interrupt 0 { EX0 = 0; // 关外部中断,防重复触发 key_time = 0; while(!P3_2) { // 等待按键释放 key_time++; if(key_time > 5000) break; // 约1s(假设100ms定时器已运行) } if(key_time > 5000) { // 长按:增加红灯时间 if(east_red_time < 99) east_red_time++; } else { // 短按:切换到设置模式 in_setup_mode = 1; display_mode = DISPLAY_EAST_TIME; } EX0 = 1; // 重新开中断 }

5.2 黄灯闪烁控制:用独立定时器1实现 0.5s 闪烁节拍

黄灯需在状态切换前闪烁 5 次(每次亮 0.5s、灭 0.5s)。若用主定时器0 分时处理,会干扰倒计时精度。正确做法是启用定时器1 作独立闪烁源:

void init_timer1_flash() { TMOD |= 0x10; // 定时器1,方式1 TH1 = 0x3C; // 50ms 初值(11.0592MHz下) TL1 = 0xB0; ET1 = 1; TR1 = 1; } void timer1_isr() interrupt 3 { static unsigned char flash_cnt = 0; TH1 = 0x3C; TL1 = 0xB0; flash_cnt++; if(flash_cnt >= 10) { // 10×50ms = 0.5s flash_cnt = 0; P1_1 = ~P1_1; // 翻转黄灯电平 } }

说明:定时器1 中断频率设为 50ms,通过计数 10 次实现 0.5s 周期。P1_1是东西向黄灯 IO,在STATE_NS_YELLOWSTATE_EW_YELLOW状态中启用该中断,其他状态关闭TR1=0,确保闪烁仅在黄灯时段生效。

5.3 Proteus 中验证按键与闪烁效果:用虚拟示波器抓取 P1.1 波形

在 Proteus 中添加OSCILLOSCOPE(虚拟示波器),通道 A 接 P1.1(黄灯引脚):

  • 正常黄灯状态:示波器显示方波,周期 1s(高电平 0.5s + 低电平 0.5s)
  • 若波形占空比异常(如高电平 0.8s),检查flash_cnt是否被其他中断打断,或TH1/TL1重装位置是否在中断入口处
  • 按键按下时,观察 P3.2 引脚电平是否从高变低,并持续 ≥1s —— 这是长按识别的物理依据

最终效果:东西向红灯亮 30 秒(数码管倒计时),随后黄灯以 1Hz 频率闪烁 5 次,接着南北向红灯亮 30 秒……整个过程无需外部传感器,纯靠定时器与状态机驱动,代码量控制在 300 行内,却完整覆盖 51 单片机最核心的硬件控制能力。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询