在我们学习51单片机过程中,模拟交通灯都是一个经典的设计任务。交通灯模拟涉及到的硬件都是比较简单的,LED灯、主控芯片、两位数码管,这些东西依靠普通51单片机就可以完成驱动,部分数码管需要电流大的话可以适当加一些驱动芯片以达到更好的显示效果,如74HC245芯片。这个设计的难度往往在于南北、东西两路的配合以及按键改变工作模式如何完善切换而不冲突。
下面我将就本次设计的硬件与编程思想进行介绍。
文末附Proteus仿真与keil源工程以及测试视频,欢迎大家下载测试。
注:本次仿真基于proteus8.15,低于本版本将无法打开仿真。
一、硬件
(1)双位七段数码管
本次设计采用共阴七段数码管,A-H分别对应其中七段显示和小数点,因交通灯设计不涉及小数点,H口未接,其余接至主控芯片51单片机P0口。所谓共阴数码管就是共地,所以A-H接口给1就亮了。共阴段码如下:
u8 code SMG_NODOT[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//无小数点
另外,二位数码管有两个控制位,1拉低即第一位有效,2拉低即第二位有效。
(2)LED
我们都知道51单片机上电端口默认高电平,因此我们可以将LED灯进行共阳接法。
(3)按键
普通按键,共阴接法。
图比较小,建议下载proteus工程看。
总体设计图如下:
二、软件
1.设计思路
这个是整个设计最有意思的地方,我们想下日常生活中见到的交通灯,一侧红灯,一侧就为绿灯,绿灯切红灯中间有短暂黄灯。我第一想法是设置三个变量,红灯时间、绿灯时间、黄灯时间,按照顺序进行控制就OK,可惜想法美好但现实残酷,这实现普通的可以,但实现调节时间时出bug了,增加红灯时间后,有一部分时间南北、东西都是红灯状态,不太合理啊,而且这样的话调节时间的意义就没了,因此我最终设计了六个变量,分别为南北红灯、绿灯、黄灯时间和东西红灯、绿灯、黄灯时间。
关于顺序控制,学过PLC的话,应该很清楚,关注切换条件就好,下面我们借助一个图更好的理解思路。
图中NS表示南北方向、WE表示东西方向。我们可以将交通灯运行分为四个阶段:
(1)南北红东西绿
(2)南北红东西黄
(3)南北绿东西红
(4)南北黄东西红
阶段一切阶段二标志,东西绿灯时间为0。阶段二切阶段三,东西黄灯时间为0。阶段三切阶段四,南北绿灯为0,阶段四结束,重装值,进行循环。
本次我定义了一个状态变量,采用switch结构选择
程序设计如下:
1)数码管变量
void isr_timer0() interrupt 1
{
count_time++;
TL0 = 0xc8; //设置定时0初始值
TH0 = 0x38; //设置定时0初始值
if(count_time == 20)
{
count_time = 0;//1s时间到
switch(state)
{
case 0://阶段一:南北红,东西绿
{
ns_red_time --;
we_green_time --;
ns_time = ns_red_time;
we_time = we_green_time;
if(we_green_time == 0&&ns_red_time>=0)//切换条件
{
state = 1;
}
break;
}
case 1://阶段二:南北红,东西黄
{
ns_red_time --;
ns_time = ns_red_time;
we_yellow_time --;
we_time = we_yellow_time;
if(we_yellow_time==0)//切换条件
{
state = 2;
}
break;
}
case 2://阶段三:南北绿,东西红
{
ns_green_time --;
we_red_time --;
we_time = we_red_time;
ns_time = ns_green_time;
if(ns_green_time == 0&&we_red_time>=0)//切换条件
{
state = 3;
}
break;
}
case 3://阶段四:南北黄,东西红
{
we_red_time --;
we_time = we_red_time;
ns_yellow_time --;
ns_time = ns_yellow_time;
if(ns_yellow_time == 0)//切换条件
{
state = 0;
we_red_time = we_red_param;
we_green_time = we_green_param;
we_yellow_time = 3;
ns_red_time = ns_red_param;
ns_green_time = ns_green_param;
we_yellow_time = 3;
}
break;
}
}
}
}
2)LED灯
/*********************************************************
函数名: dis_led
功能:正常模式下LED显示
形参:无
返回值:无
*********************************************************/
void dis_led()
{
if(!mode)
{
switch(state)
{
case 0://阶段一:南北红,东西绿
{
P2 = 0xeb;
break;
}
case 1://阶段二:南北红,东西黄
{
P2 = 0xf3;
break;
}
case 2://阶段三:南北绿,东西红
{
P2 = 0xdd;
break;
}
case 3://阶段四:南北黄,东西红
{
P2 = 0xbe;
break;
}
default:break;
}
}
}
正常模式的讲完了,下面我们进入按键部分,这里写的比较繁琐,因为要保证其按下后连续执行,所以定义了一个变量mode,对应夜间模式、禁行模式、加时间、减时间等操作,根据不同mode值执行不同操作,确保了响应的及时性,同时任意情况下都可以丝滑切换,设计如下:
void key_scan()
{
if(!K1)//夜间模式
{
delay(10);
if(!K1)
{
while(!K1);
mode = 1;
}
}
if(!K2 )//南北禁行
{
delay(10);
if(!K2)
{
while(!K2);
mode = 2;
}
}
if(!K3 )//东西禁行
{
delay(10);
if(!K3)
{
while(!K3);
mode = 3;
}
}
if(!K4)//加时间
{
delay(10);
if(!K4)
{
while(!K4);
mode = 4;
ns_red_param++;
we_green_param++;
ns_green_param--;
we_red_param--;
if(ns_red_param==55)
{
ns_red_param = 30;
ns_green_param = 27;
we_red_param = 30;
we_green_param = 27;
}
}
}
if(!K5)//减时间
{
delay(10);
if(!K5)
{
while(!K5);
mode = 5;
ns_red_param--;
ns_green_param++;
we_green_param--;
we_red_param++;
if(we_red_param==55)
{
ns_red_param = 30;
ns_green_param = 27;
we_red_param = 30;
we_green_param = 27;
}
}
}
if(!K6)//确认
{
delay(10);
if(!K6)
{
while(!K6);
mode = 6;
}
}
}
/*********************************************************
函数名:key_pron
功能:按键处理函数,根据mode值调用函数
形参:无
返回值:无
*********************************************************/
void key_pron()
{
switch(mode)
{
case 1:
{
K1_pron();
break;
}
case 2:
{
K2_pron();
break;
}
case 3:
{
K3_pron();
break;
}
case 4:
{
K4_pron();
break;
}
case 5:
{
K5_pron();
break;
}
case 6:
{
K6_pron();
break;
}
}
}
OK,本次设计大体已经结束。
下面是完整代码
(1)main.c
/*********************************************
Author:sakura
Time:2023/5/16
funcation:模拟日常生活中的交通灯,拥有正常模式
、夜间模式、禁行模式、调节时间灯等
功能,通过按键进行切换
copyright:版权所有,借鉴请注明
*********************************************/
#include "main.h"//系统头文件
#include "seg.h"
#include "led.h"
#include "key.h"
/*********************************************************
函数名:main
功能:主函数入口
形参:无
返回值:无
*********************************************************/
void main()
{
sys_Init();
while(1)
{
dis_seg();
dis_led();
key_pron();
key_scan();
}
}
(2)main.h
#ifndef __MAIN_H
#define __MAIN_H
#define u8 unsigned char
#define u16 unsigned int
#include <reg51.h>
#endif
(3)seg.c
#include "seg.h"
//段码0-9
u8 code SMG_NODOT[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//无小数点
u8 ns_red_time = 0;//南北红灯时间
u8 ns_green_time = 0;//南北绿灯时间
u8 ns_yellow_time = 3;//南北黄灯时间
u8 we_red_time = 0;//东西红灯时间
u8 we_green_time = 0;//东西绿灯时间
u8 we_yellow_time = 3;//东西黄灯时间
u8 ns_red_param = 30;//南北红灯时间参数,后期修改时间使用
u8 ns_green_param = 27;//南北绿灯时间参数,后期修改时间使用
u8 we_red_param = 30;//东西红灯时间参数,后期修改时间使用
u8 we_green_param = 27;//南北绿灯时间参数,后期修改时间使用
u8 ns_time = 30;//数码管南北向变量
u8 we_time = 27;//数码管东西向变量
u8 count_time = 0;//定时器计数变量
u8 state = 0;//状态变量
/*********************************************************
函数名:delay
功能:延时函数,延时n*1ms
形参:n,范围0-255
返回值:无
*********************************************************/
void delay(u8 n)//1ms
{
while(n--)
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
/*********************************************************
函数名:seg_Init
功能:数码管显示初始化
形参:pos:0-3,显示位 value:段码
返回值:无
*********************************************************/
void seg_Init(u8 pos,u8 value)
{
P3 = ~(0x01 << pos);//选择位
P0 = value;//数据
delay(5);
}
/*********************************************************
函数名:dis_seg
功能:数码管显示函数
形参:无
返回值:无
*********************************************************/
void dis_seg()
{
if(mode == 0)//正常模式
{
seg_Init(0,SMG_NODOT[ns_time/10]);
seg_Init(1,SMG_NODOT[ns_time%10]);
seg_Init(2,SMG_NODOT[we_time/10]);
seg_Init(3,SMG_NODOT[we_time%10]);
}
}
/*********************************************************
函数名:sys_Init
功能:系统初始化,定时器初值,数码管数值重装
形参:无
返回值:无
*********************************************************/
void sys_Init()
{
TMOD = 0x01;
TL0 = 0xc8; //设置定时0初始值50ms
TH0 = 0x38; //设置定时0初始值
ET0 = 1;
EA = 1;
ns_red_time = ns_red_param;//数值重装(初始化)
ns_green_time = ns_green_param;
we_red_time = we_red_param;
we_green_time =we_green_param;
TR0 = 1;
}
/*********************************************************
函数名:isr_timer0
功能:定时器0中断处理函数,每过1s,数码管值减一
形参:无
返回值:无
*********************************************************/
void isr_timer0() interrupt 1
{
count_time++;
TL0 = 0xc8; //设置定时0初始值
TH0 = 0x38; //设置定时0初始值
if(count_time == 20)
{
count_time = 0;//1s时间到
switch(state)
{
case 0://阶段一:南北红,东西绿
{
ns_red_time --;
we_green_time --;
ns_time = ns_red_time;
we_time = we_green_time;
if(we_green_time == 0&&ns_red_time>=0)//切换条件
{
state = 1;
}
break;
}
case 1://阶段二:南北红,东西黄
{
ns_red_time --;
ns_time = ns_red_time;
we_yellow_time --;
we_time = we_yellow_time;
if(we_yellow_time==0)//切换条件
{
state = 2;
}
break;
}
case 2://阶段三:南北绿,东西红
{
ns_green_time --;
we_red_time --;
we_time = we_red_time;
ns_time = ns_green_time;
if(ns_green_time == 0&&we_red_time>=0)//切换条件
{
state = 3;
}
break;
}
case 3://阶段四:南北黄,东西红
{
we_red_time --;
we_time = we_red_time;
ns_yellow_time --;
ns_time = ns_yellow_time;
if(ns_yellow_time == 0)//切换条件
{
state = 0;
we_red_time = we_red_param;
we_green_time = we_green_param;
we_yellow_time = 3;
ns_red_time = ns_red_param;
ns_green_time = ns_green_param;
ns_yellow_time = 3;
}
break;
}
}
}
}
(4)seg.h
#ifndef __SEG_H
#define __SEG_H
#include "main.h"
void delay(u8 n);//1ms
void seg_Init(u8 pos,u8 value);
void dis_seg();
void sys_Init();
extern u8 mode;
#endif
(5)led.c
#include "led.h"
extern u8 state;
extern u8 mode;
/*********************************************************
函数名: dis_led
功能:正常模式下LED显示
形参:无
返回值:无
*********************************************************/
void dis_led()
{
if(!mode)
{
switch(state)
{
case 0://阶段一:南北红,东西绿
{
P2 = 0xeb;
break;
}
case 1://阶段二:南北红,东西黄
{
P2 = 0xf3;
break;
}
case 2://阶段三:南北绿,东西红
{
P2 = 0xdd;
break;
}
case 3://阶段四:南北黄,东西红
{
P2 = 0xbe;
break;
}
default:break;
}
}
}
(6)led.h
#ifndef __LED_H
#define __LED_H
#include "main.h"
void dis_led();
#endif
(7)key.c
#include "key.h"
u8 mode = 0;
/*********************************************************
函数名:key_scan
功能:按键扫描函数,mode表示按键切换的功能
形参:无
返回值:无
*********************************************************/
void key_scan()
{
if(!K1)//夜间模式
{
delay(10);
if(!K1)
{
while(!K1);
mode = 1;
}
}
if(!K2 )//南北禁行
{
delay(10);
if(!K2)
{
while(!K2);
mode = 2;
}
}
if(!K3 )//东西禁行
{
delay(10);
if(!K3)
{
while(!K3);
mode = 3;
}
}
if(!K4)//加时间
{
delay(10);
if(!K4)
{
while(!K4);
mode = 4;
ns_red_param++;
we_green_param++;
ns_green_param--;
we_red_param--;
if(ns_red_param==55)
{
ns_red_param = 30;
ns_green_param = 27;
we_red_param = 30;
we_green_param = 27;
}
}
}
if(!K5)//减时间
{
delay(10);
if(!K5)
{
while(!K5);
mode = 5;
ns_red_param--;
ns_green_param++;
we_green_param--;
we_red_param++;
if(we_red_param==55)
{
ns_red_param = 30;
ns_green_param = 27;
we_red_param = 30;
we_green_param = 27;
}
}
}
if(!K6)//确认
{
delay(10);
if(!K6)
{
while(!K6);
mode = 6;
}
}
}
/*********************************************************
函数名:key_pron
功能:按键处理函数,根据mode值调用函数
形参:无
返回值:无
*********************************************************/
void key_pron()
{
switch(mode)
{
case 1:
{
K1_pron();
break;
}
case 2:
{
K2_pron();
break;
}
case 3:
{
K3_pron();
break;
}
case 4:
{
K4_pron();
break;
}
case 5:
{
K5_pron();
break;
}
case 6:
{
K6_pron();
break;
}
}
}
/*********************************************************
函数名:K1_pron
功能:夜间模式函数
形参:无
返回值:无
*********************************************************/
void K1_pron()//夜间
{
TR0 = 0;
seg_Init(0,SMG_NODOT[0]);
seg_Init(1,SMG_NODOT[0]);
seg_Init(2,SMG_NODOT[0]);
seg_Init(3,SMG_NODOT[0]);
P2 = 0xf6;
delay(10);
P2 = 0xff;
delay(10);
}
/*********************************************************
函数名:K2_pron
功能:南北禁行处理函数
形参:无
返回值:无
*********************************************************/
void K2_pron()//南北禁行
{
TR0 = 0;
seg_Init(0,SMG_NODOT[9]);
seg_Init(1,SMG_NODOT[9]);
seg_Init(2,SMG_NODOT[0]);
seg_Init(3,SMG_NODOT[0]);
P2 = 0xeb;
}
/*********************************************************
函数名:K3_pron
功能:东西禁行处理函数
形参:无
返回值:无
*********************************************************/
void K3_pron()//东西禁行
{
TR0 = 0;
seg_Init(0,SMG_NODOT[0]);
seg_Init(1,SMG_NODOT[0]);
seg_Init(2,SMG_NODOT[9]);
seg_Init(3,SMG_NODOT[9]);
P2 = 0xdd;
}
/*********************************************************
函数名: K4_pron
功能:加时间处理函数
形参:无
返回值:无
*********************************************************/
void K4_pron()//加时间
{
TR0 = 0;
seg_Init(0,SMG_NODOT[ns_red_param/10]);
seg_Init(1,SMG_NODOT[ns_red_param%10]);
seg_Init(2,SMG_NODOT[we_green_param/10]);
seg_Init(3,SMG_NODOT[we_green_param%10]);
}
/*********************************************************
函数名: K5_pron
功能:减时间处理函数
形参:无
返回值:无
*********************************************************/
void K5_pron()//减时间
{
TR0 = 0;
seg_Init(0,SMG_NODOT[ns_red_param/10]);
seg_Init(1,SMG_NODOT[ns_red_param%10]);
seg_Init(2,SMG_NODOT[we_green_param/10]);
seg_Init(3,SMG_NODOT[we_green_param%10]);
}
/*********************************************************
函数名:K6_pron
功能:确认时间调整函数
形参:无
返回值:无
*********************************************************/
void K6_pron()//确认/退出
{
TR0 = 1;
mode = 0;
ns_red_time = ns_red_param;
ns_green_time = ns_green_param;
we_red_time = we_red_param;
we_green_time =we_green_param;
ns_time = ns_red_time;
we_time = we_green_time;
}
(8)key.h
#ifndef __KEY_H
#define __KEY_H
#include "main.h"
sbit K1 = P1^0;
sbit K2 = P1^1;
sbit K3 = P1^2;
sbit K4 = P1^3;
sbit K5 = P1^4;
sbit K6 = P1^5;
extern void delay(u8 n);//1ms
extern void seg_Init(u8 pos,u8 value);
extern u8 code SMG_NODOT[10];
extern u8 ns_red_time;//南北红灯
extern u8 ns_green_time;
extern u8 we_red_time ;
extern u8 we_green_time;
extern u8 ns_red_param;
extern u8 ns_green_param;
extern u8 we_red_param;
extern u8 we_green_param;
extern u8 ns_time;
extern u8 we_time;
void key_scan();
void key_pron();
void K1_pron();//夜间
void K2_pron();//南北禁行
void K3_pron();//东西禁行
void K4_pron();//加时间
void K5_pron();//减时间
void K6_pron();//确认/退出
#endif
5.测试视频
交通灯运行(源码与仿真搜作者:sakura(划水))
6.资源
链接:https://pan.baidu.com/s/1uqDNAAb8k1cpAkT_IHSAfw?pwd=nazb
提取码:nazb