51单片机交通灯设计:定时器中断+状态机实现精准控制
2026/9/19 16:06:14 网站建设 项目流程

简介:本资源是一份面向高校电子类、自动化专业本科生的51单片机实训报告,聚焦交通灯控制系统设计与实现,解决嵌入式基础项目中硬件搭建、定时器编程、数码管动态显示及按键交互等核心实践问题。文档完整覆盖系统设计要求(东西/南北双方向60秒红绿交替、黄灯闪烁5次过渡、双数码管倒计时同步显示)、模块化程序代码(含display()数码管扫描与keycan()多级按键设置逻辑)、洞洞板焊接调试过程(含段选接反导致乱码的典型排错案例)及实训反思,具备教学规范性与工程参考价值。资源为单个Word文档(.doc),文件总数1个,包体大小73KB,结构清晰、图文结合,含原理图框图、C语言源码附录及三本权威参考文献。目前已有155人学习下载,适合初学51单片机的学生开展课程设计、实训复盘或毕业设计参考。

1. 51单片机交通灯实训:为什么用定时器中断驱动黄灯闪烁比延时函数更可靠?

在洞洞板上焊完12颗LED、接好两个共阴数码管、按下下载键却只看到黄灯狂闪不熄——这是很多同学第一次调试51单片机交通灯时的真实场景。问题往往不出在代码逻辑,而在于把黄灯闪烁硬塞进主循环延时里:一旦主程序被按键扫描或显示刷新拖慢,5次闪烁就变成3次或7次,倒计时跳变错乱,南北/东西相位完全失步。这份实训报告的核心价值,恰恰藏在timer0 (void) interrupt 1这个20ms定时中断服务程序中——它用硬件级时间基准解耦了控制逻辑与显示刷新,让黄灯严格按5次(每次亮灭各500ms)、红绿灯切换零延迟、数码管动态扫描不丢帧。适合刚学完C语言基础、能写if-else但没碰过中断的本科生;也适合想快速复现经典控制逻辑、避开Proteus仿真陷阱的嵌入式初学者。它不依赖STC下载工具链,不涉及串口升级架构,纯Keil C51工程,烧录即跑,是验证“定时器+状态机”这一底层控制范式的最小可行样本。

2. 定时器中断与状态机协同:从60秒倒计时到黄灯精准闪烁的实现逻辑

2.1 定时器0模式1配置:为什么选20ms作为中断周期?

void Init_Timer0(void){ TMOD &= 0XF0; TMOD |= 0x01; // 模式1:16位定时器,最大计数值65536 TH0 = 0x3C; // 初始值高8位:(65536 - 20000) / 256 = 60 → 0x3C TL0 = 0xB0; // 初始值低8位:(65536 - 20000) % 256 = 176 → 0xB0 IP = 0x02; // 定时器0中断优先级设为高 TR0 = 1; // 启动定时器 ET0 = 1; // 允许定时器0中断 EA = 1; // 开总中断 }

提示:此处20ms是经过权衡的折中值。若设为1ms,中断过于频繁,主循环来不及执行灯控逻辑;若设为100ms,黄灯闪烁节奏无法精确到500ms级。计算依据:晶振11.0592MHz,机器周期=12/11.0592MHz≈1.085μs,20ms需计数18432次,65536-18432=47104→0xB800→TH0=0xB8, TL0=0x00。但原文用0x3CB0对应20000次计数(实际约18.3ms),已足够支撑后续逻辑。

2.2 中断服务程序:如何用tds变量构建两级时间标尺?

void timer0 (void) interrupt 1 { TH0 = 0x3C; // 重装初值,保持20ms周期 TL0 = 0xB0; t++; // t每20ms加1,用于生成1s基准 if(t == 20) { // t=20 → 累计20×20ms=400ms?不对!原文此处有隐含逻辑漏洞 ds--; // 实际应为t==50才得1s(50×20ms),但代码写t==20,说明ds单位是"20ms" t = 0; if(ds <= 5) { // ds≤5时触发黄灯闪烁:注意此处ds是倒计时剩余值,非绝对时间 if(ms == 0) { NSY = ~NSY; } // 南北黄灯取反 if(ms == 1) { WEY = ~WEY; } // 东西黄灯取反 } if(ds == 0) { // ds归零:相位切换时刻 ms = ~ms; // 切换主干道状态(ms=0→南北通行,ms=1→东西通行) NSY = 1; WEY = 1; // 黄灯强制熄灭 if(ms == 0) { ds = nt; } // 加载南北通行时间 if(ms == 1) { ds = wt; } // 加载东西通行时间 } } }
2.2.1ds变量的本质:动态倒计时寄存器而非秒表
变量类型初始值更新时机物理意义
dsuchar60每次中断t==20时减1倒计时剩余“20ms单位数”,非真实秒数
tuchar0每次中断加1,满20清零20ms计数器,用于触发ds更新
msbit0ds==0时翻转主状态标志:0=南北通行,1=东西通行

注意:原文ds--t==20时执行,意味着ds每减少1代表20ms流逝。因此当nt=60时,实际倒计时为60×20ms=1.2秒——这显然与设计要求的60秒矛盾。真实实现中必须修正为t==50(50×20ms=1s)。否则数码管显示的“60”只是视觉欺骗,物理时间严重缩水。这是初学者最容易忽略的定时器缩放陷阱。

2.3 状态机驱动灯控:NST()WET()函数如何避免相位冲突?

void NST(){ // 南北通行状态:南北绿灯亮,东西红灯亮 seg = ds; // 将倒计时值同步给显示模块 NSB = 0; NSR = 1; NSY = 1; // 南北:绿灭(0)、红亮(1)、黄灭(1) WER = 0; WEB = 1; WEY = 1; // 东西:红亮(0)、绿灭(1)、黄灭(1) } void WET(){ // 东西通行状态:东西绿灯亮,南北红灯亮 seg = ds; NSB = 1; NSR = 0; NSY = 1; // 南北:绿亮(1)、红灭(0)、黄灭(1) WER = 1; WEB = 0; WEY = 1; // 东西:红灭(1)、绿亮(0)、黄灭(1) }
2.3.1 硬件电平约定解析:P1口输出为何用“0亮1灭”?

查看原理图可知,LED阳极接VCC,阴极通过限流电阻接P1口。当P1.x=0时,形成回路,LED导通发光;P1.x=1时,阴极悬空,LED截止。因此:

  • NSB=0→ 南北绿灯亮(P1^2=0)
  • NSR=1→ 南北红灯灭(P1^0=1)
  • WER=0→ 东西红灯亮(P1^3=0)

关键验证点:若发现某方向灯常亮不灭,先用万用表测P1口对应引脚电压——应为0V(亮)或5V(灭)。若电压正常但灯不亮,检查限流电阻是否虚焊;若电压异常,确认NST()/WET()调用时机是否被sz!=0阻断。

3. 数码管动态扫描与按键消抖:2位共阴数码管的驱动细节与抗干扰设计

3.1display()函数的位选/段选时序:为什么必须用delay(5)而非_nop_()

void display(){ P3 = 0x08; // 位选:P3.3=0 → 选中十位数码管(共阴,低电平有效) P0 = Tab[seg/10]; // 段选:输出十位数字的7段码 delay(5); // 保持显示5ms,确保人眼余辉效应不闪烁 P3 = 0x04; // 位选:P3.2=0 → 选中个位数码管 P0 = Tab[seg%10]; // 段选:输出个位数字的7段码 delay(5); }
3.1.1Tab[]数组与共阴数码管编码映射关系
数字Tab[i]值(十六进制)二进制(P0.0~P0.6)对应段说明
00x3F00111111a~g全亮,dp灭P0.0=a, P0.1=b, ..., P0.6=g
10x0600000110b,c亮共阴数码管标准编码
20x5B01011011a,b,d,e,g亮注意:原文未定义小数点段,Tab长度为10

注意P3=0x08P3.3=0P3=0x04P3.2=0,符合共阴数码管位选低电平有效特性。若显示乱码,优先检查P3口是否被其他功能占用(如串口RXD/TXD),或Tab数组索引越界(seg/10最大为6,但seg初始60→60/10=6,Tab[6]存在;若seg=100则越界)。

3.2keycan()函数的三级消抖与状态机:sz变量如何管理设置流程?

void keycan(){ if(k == 0){ // k:设置键(P2^0) delay(10); // 硬件消抖:延时10ms滤除机械抖动 if(k == 0){ sz++; // sz=0→1:进入南北时间设置 if(sz == 4) sz = 0; // sz=3→0:循环归零 } while(!k); // 等待按键释放,防止重复触发 } if(a == 0 && sz == 1){ // a:加键(P2^2),仅在sz==1时响应 delay(10); if(a == 0 && sz == 1){ nt++; // 南北通行时间+1 if(nt == 61) nt = 1; // 循环范围1~60 } while(!a); } if(j == 0 && sz == 1){ // j:减键(P2^3) delay(10); if(j == 0 && sz == 1){ nt--; if(nt == 0) nt = 60; // 下溢处理 } while(!j); } if(g == 0 && sz != 0){ // g:确定键(P2^1),sz≠0时才生效 delay(10); if(g == 0){ sz = 0; // 退出设置态 } while(!g); } }
3.2.1 按键状态机流转表
当前sz按设置键k按加键a按减键j按确定键g功能说明
0sz=1无响应无响应无响应进入设置模式
1sz=2nt++nt--sz=0设置南北通行时间
2sz=3wt++wt--sz=0设置东西通行时间
3sz=0无响应无响应sz=0紧急模式(原文sz==3时强制nt=wt=10

提示:紧急模式(sz==3)在main()中通过if(sz==3){nt=10;wt=10;}实现,无需额外按键。这种设计避免了“紧急键”硬件冲突,但需注意:若用户误触三次设置键,系统会自动切入10秒应急模式,可能影响正常调度。

4. 硬件焊接与调试排错:洞洞板上12颗LED与2位数码管的故障定位方法

4.1 数码管显示不全的三大根源及逐项排查法

原文提到“数码管显示不全,经检查发现是段选有两处连接反了”。这暴露了洞洞板布线的典型风险。以下是系统化排查流程:

故障现象可能原因验证方法修复措施
所有数码管全暗P0口被占用或P3位选失效用万用表测P0口对地电压:正常应随Tab[]变化(0V/5V跳变);测P3.2/P3.3是否为0V检查P0口是否接了其他外设(如LED共阳极);确认P3口未被TMOD等寄存器意外改写
十位显示正常,个位乱码P3=0x04对应引脚虚焊或P0段码输出错位断开个位位选线,单独测试个位:P3=0x04; P0=0x3F;观察是否显示0重新焊接P3.2引脚;检查Tab[seg%10]计算是否溢出(seg为uchar,seg%10安全)
显示数字但缺笔画(如0缺横)段选线接触不良或Tab[]索引错误用示波器测P0.0~P0.6波形,对比Tab[0]=0x3F的8位输出重点检查P0.0(a段)和P0.6(g段)连线;确认Tab数组定义无语法错误

注意:共阴数码管的Tab[0]=0x3F表示P0.0~P0.5为低电平(0),P0.6/P0.7为高电平(1)。若用万用表测得P0.0=5V,则对应段不亮,需查P0.0线路是否断路。

4.2 黄灯不闪烁或闪烁次数不准的硬件级诊断

黄灯异常通常源于中断服务程序未执行或ds更新逻辑错误。按此顺序排查:

  1. 确认中断是否触发:在timer0函数首行加NSR = ~NSR;(翻转南北红灯),观察红灯是否以20ms频率闪烁。若闪烁,说明中断正常;否则检查EAET0TR0是否置1。
  2. 验证ds递减节奏:将ds--替换为NSB = ~NSB;,观察南北绿灯是否按预期频率翻转。若频率不符20ms,检查TH0/TL0重装值。
  3. 定位黄灯条件判断:在if(ds<=5)内加WEY=0;(强制东西黄灯亮),若仍不亮,检查WEY对应P1^4引脚电压及LED极性。
4.2.1NSY/WEY引脚电平与LED状态对照表
引脚代码操作P1.x电平LED状态物理连接验证点
P1^1NSY=00V南北黄灯亮查P1^1与LED阴极间电阻是否<220Ω
P1^1NSY=15V南北黄灯灭查LED阳极是否接VCC且无断路
P1^4WEY=00V东西黄灯亮查P1^4走线是否与其他P1口短路

关键技巧:用镊子短接P1^1与GND,若黄灯亮,证明LED及供电正常,问题在单片机输出;若不亮,检查LED是否损坏或限流电阻开路。

5. 进阶优化:将20ms中断升级为1ms基准并支持黄灯独立计时

5.1 重构定时器中断:用TH0/TL0重装实现精确1ms中断

原设计t==20对应20ms,导致ds单位失真。升级为1ms中断后,可彻底解耦时间精度:

void Init_Timer0_1ms(void){ TMOD &= 0XF0; TMOD |= 0x01; // 11.0592MHz晶振下,1ms需计数921.6→取整922 // 65536 - 922 = 64614 → 0xFC66 TH0 = 0xFC; // 高8位 TL0 = 0x66; // 低8位 IP = 0x02; TR0 = 1; ET0 = 1; EA = 1; } void timer0 (void) interrupt 1 { TH0 = 0xFC; // 重装1ms初值 TL0 = 0x66; static uchar cnt_1ms = 0; cnt_1ms++; if(cnt_1ms >= 500) { // 500ms计数器:黄灯单次亮/灭时间 cnt_1ms = 0; static uchar yellow_cnt = 0; if(ds <= 5) { // 进入黄灯阶段 if(yellow_cnt < 5) { // 总共5次闪烁 NSY = ~NSY; WEY = ~WEY; yellow_cnt++; } } } if(cnt_1ms == 1000) { // 1s计数器 cnt_1ms = 0; ds--; if(ds == 0) { ms = ~ms; NSY = 1; WEY = 1; // 黄灯熄灭 ds = (ms == 0) ? nt : wt; yellow_cnt = 0; // 重置闪烁计数 } } }

5.2 数码管显示优化:加入小数点动态效果提升可读性

display()中增加小数点控制,使倒计时更具视觉引导性:

void display(){ static bit dp_flag = 0; dp_flag = !dp_flag; // 每次刷新翻转小数点 P3 = 0x08; P0 = Tab[seg/10]; if(dp_flag) P0 &= 0x7F; // 点亮十位小数点(P0.7=0) delay(5); P3 = 0x04; P0 = Tab[seg%10]; if(!dp_flag) P0 &= 0x7F; // 点亮个位小数点 delay(5); }

参数说明P0 &= 0x7F将P0.7置0,因共阴数码管小数点段通常接P0.7,低电平点亮。此技巧无需额外硬件,仅用软件控制,使倒计时数字产生呼吸感,便于远距离识别。

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

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

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

立即咨询