一、方案设计
1. 系统需求分析
1.1 功能需求
设计一个能够模拟实际交通灯运行的系统,包括四个路口红灯、黄灯、绿灯的状态转换,以及数码管的倒计时功能。除此之外,能够通过接收串口数据,人为地修改红绿灯状态、每个灯点亮的时长,修改成功后串口回传应答信号表示修改成功。
1.2 性能需求
系统应能稳定运行,交通灯转换逻辑清晰,响应时间短。接收到串口数据后能迅速做出响应,以解决实际生活中突发的紧急状况,确保交通流畅。
2. 系统设计
2.1 硬件设计
使用Proteus软件(Proteus 8.16 P3版本)绘制电路图,主要包括AT89C51单片机(一个用来控制红绿灯系统,另一个用来模拟上位机进行串口通讯)、交通灯、两位共阴极数码管(显示倒计时)、LED灯(调试串口数据是否发送/接收成功)、按钮(触发串口数据发送事件)。
2.2 软件设计
使用Keil C51 (uVision V5.38.0.0版本)编写汇编代码控制51单片机(两个单片机分别控制),实现交通灯的基本逻辑控制,包括状态转换、动态数码管的刷新,以及串口数据收发、处理紧急状况的额外功能。
3. 各功能实现思路
3.1 定时
使用定时器0进行读秒,由于定时时间较长,采用定时器工作方式1的16位计数,由TH0和TL0各提供8位计数初值,是几种方式中计数值最大的方式。
使用6MHz的晶振频率,单片机的机器周期MC = 2us,16位计数方式最大可计65536个脉冲,也就是最大能计131ms的时长, 为了实现读秒,将定时器周期设置为100ms,这样每触发10次定时器溢出中断就能说明经过了1秒。根据公式计算出计数初值为10000H - C350H = 3CB0H,TH0应设置为高八位3CH,TL0设置为低八位0B0H。在定时器0的中断服务函数中用寄存器R7作为计数器,每触发一次定时器溢出中断R7自增1,当R7 = 10,就说明经过了10个定时器周期,也就是1秒。最后使能定时器中断,应注意采用方式1还需要在中断服务函数中手动重装载定时器初值。
3.2 红绿灯状态切换
使用Proteus中的交通灯器件模拟实际红绿灯,当一个引脚给高电平,另外两个引脚给低电平时,高电平对应的灯点亮。
将四个这样的交通灯摆在每个路口处,由于相对的交通灯亮灭状况相同,只需6个IO口进行控制。
四个交通灯总共有四种状态:
状态0:南北向红灯、东西向绿灯。
状态1:南北向红灯、东西向黄灯。
状态2:南北向绿灯、东西向红灯。
状态3:南北向黄灯、东西向红灯。
每种状态都对应了P2口不同的输出信号,可对这四种状态进行宏定义编码:
STATE0 EQU 21H //状态0:南北向红灯、东西向绿灯
STATE1 EQU 11H //状态1:南北向红灯、东西向黄灯
STATE2 EQU 0CH //状态2:南北向绿灯、东西向红灯
STATE3 EQU 0AH //状态3:南北向黄灯、东西向红灯
当想要切换到某个状态时,只需执行以下代码:
MOV P2, #STATEx (x = 0, 1, 2, 3)
为了能实现与上位机串口通信随时改变红绿灯的四种状态,用寄存器R5保存当前状态0-3。
STATE_REG EQU R5 //状态寄存器
通过调用SET_STATE子函数,可以根据寄存器R5的值切换状态,子函数初步设计如下:
SET_STATE: //状态切换函数
CJNE STATE_REG, #00H, NEXT1 // 状态0
MOV P2, #STATE0
RET
NEXT1: CJNE STATE_REG, #01H, NEXT2 // 状态1
MOV P2, #STATE1
RET
NEXT2: CJNE STATE_REG, #02H, NEXT3 // 状态2
MOV P2, #STATE2
RET
NEXT3: CJNE STATE_REG, #03H, END1 // 状态3
MOV P2, #STATE3
END1:
RET
3.3 动态数码管显示
实现倒计时功能,需要用到四个这样的2位数码管(共阴极),一共要控制八位数码管,而由于IO口数量有限,只能采用动态扫描的方式。
将八位数码管的A-G、DP都连到P0口(作为IO口使用,要外加上拉电阻),东西、南北相对的数码管显示相同,因此阴极只需4个IO口,S0为南北向低位,S1为南北向高位,S2为东西向低位,S3为东西向高位。用寄存器R3、R4分别存储东西向、南北向的倒计时时长,在主循环中经过十进制转换,实现数码管动态扫描显示功能,代码设计如下:
LOOP: //主循环 动态数码管刷新
//16进制转10进制(南北向数码管R4)
MOV P1, #0FFH
MOV A, R4
MOV B, #0AH
DIV AB //A(除以10后的商)对应高位,B(除以10后的余数)对应低位
//设置S1数码管
MOVC A, @A+DPTR
MOV P1, #0FDH
MOV P0, A
ACALL DELAY1
//设置S0数码管
MOV A, B
MOVC A, @A+DPTR
MOV P1, #0FEH
MOV P0, A
ACALL DELAY1
//16进制转10进制(东西向数码管R3)
MOV P1, #0FFH
MOV A, R3
MOV B, #0AH
DIV AB //A(除以10后的商)对应高位,B(除以10后的余数)对应低位
//设置S4数码管
MOVC A, @A+DPTR
MOV P1, #07H
MOV P0, A
ACALL DELAY1
//设置S3数码管
MOV A, B
MOVC A, @A+DPTR
MOV P1, #0BH
MOV P0, A
ACALL DELAY1
SJMP LOOP
定时器每经过1秒(10次中断)都需要更新数码管倒计时R3、R4的值。当R3、R4任意一个为0的时候需要切换下一状态,代码设计如下:
T0IN: //定时器0中断服务函数
INC R7 //R7-计数器
CJNE R7, #0AH, NEXT5
//每10次中断经过1秒,更新数码管显示数字
MOV R7, #00H
DEC R3