首先对系统的设计,自然想到软件部分与硬件部分,软件部分根据所要实现的功能进行编写,与硬件要进行对应。本文所介绍的是用keil进行c语言编写的,用STC-ISP进行烧录到板子上,在烧录之前,最好先用Proteus进行仿真,如果实现其所有功能后,再进行烧录,然后在板子上看实际实现的效果。以下我写的顺序是层层递进,展示如何一步步进行思考。可能有点长,但是包看懂的,无废话。
交通灯按以下时序循环:
交通信号灯变化表 |
||||
东西 |
绿灯亮 |
黄灯闪烁 |
红灯亮 |
|
20S |
5S |
25S |
||
南北 |
红灯亮 |
绿灯亮 |
黄灯闪烁 |
|
25S |
20S |
5S |
a.手动调时:通过按键设置绿灯时长。
b.紧急模式:一键切换为全红/全黄闪烁/红绿灯的转换。
思路:需要进行倒计时,所以1秒执行一次,然后每次都遵循红灯时间=绿灯时间+黄灯时间,当倒计时为0时候,则说明东西南北方向的红绿灯需要进行互换了。
void jiaotongdeng_dis()
{
if (flag_1s == 1) //1秒执行一次里面的程序
{
flag_1s = 0;
if (flag_dx_nb == 0) //南北绿灯时间
{
dx_s--; //东西倒计时时间减1
nb_s--; //南北倒计时时间减1
if (dx_s == 5) //东西方向剩余5秒时,南北绿灯0
nb_s = 5; //南北置5秒进行黄灯闪烁
}
if (flag_dx_nb == 1) //东西绿灯时间
{
dx_s--; //南北倒计时时间减1
nb_s--; //东西倒计时时间减1
if (nb_s == 5)
dx_s = 5;
}
if (dx_s <= 0) //倒计时时间为0切换东西 南北绿灯时间
{
flag_dx_nb = ~flag_dx_nb;
if (flag_dx_nb == 1) //东西时间
{
dx_s = dx_time;
nb_s = dx_time + 5;
}
else //南北时间
{
dx_s = nb_time + 5;
nb_s = nb_time;
}
}
}
}
思路:由于1秒执行一次,所以就想到定时与中断。
TH0:给定时器 0 的高 8 位赋初值。前面设置定时器 0 工作在方式 1,是 16 位定时器,计数值从初值开始向上累加,当计数值达到65536(
时产生溢出,触发中断。这里要实现 50ms 定时,已知单片机晶振频率(假设为 12MHz,机器周期为 1μs),定时器 0 每经过一个机器周期计数值加 1。50ms 内包含的机器周期数为50×1000=50000个。为了实现 50ms 定时,需要给定时器 0 设置一个初值,使得从该初值开始计数到65536溢出的时间为 50ms。65536−50000=15536,将这个值的高 8 位(15536÷256)赋给TH0。TL0同理。)
/*********************定时器0初始化******************/
void time0_init()
{
EA = 1; //开总中断
TMOD = 0X01; //定时器0工作方式1,00000001
ET0 = 1; //开定时器0中断
TR0 = 1; //允许定时器0定时
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256; //50ms
}
/*********************定时器0中断服务程序************************/
void time0_int() interrupt 1
{
static uchar value;
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256; //50ms
value++;
if (value % 10 == 0)
flag_500ms = ~flag_500ms; //定时产生一个500毫秒的变量
if (value >= 20) //
{
value = 0;
flag_1s = 1; //定时产生一个1秒的变量
}
}
思路:在完成倒计时的设置后,接下来需要根据倒计时的时间来控制交通灯的亮灭和闪烁状态。
/***********************南北时间绿灯时间*********************************/
if (flag_dx_nb == 0)
{
if (dx_s > 5) //当东西红灯倒计时大于5时
{
dx_red = 0; //东西红灯亮
dx_green = 1; //东西绿灯灭
dx_yellow = 1; //东西黄灯灭
nb_red = 1; //南北红灯灭
nb_green = 0; //南北绿灯亮
nb_yellow = 1; //南北黄灯灭
}
else if (dx_s <= 5) //当小于5秒时 黄灯要闪了
{
dx_red = 0; //东西红灯灭
dx_green = 1; //东西绿灯灭
dx_yellow = 1; //东西黄灯灭
nb_red = 1; //南北红灯灭
nb_green = 1; //南北绿灯灭
if (flag_500ms == 0) //黄灯闪烁
{
nb_yellow = 0; //亮
}
else
{
nb_yellow = 1; //灭
}
}
}
/***********************东西时间绿灯时间*********************************/
if (flag_dx_nb == 1)
{
if (nb_s > 5) //当南北红灯倒计时大于5时
{
dx_red = 1; //东西红灯灭
dx_green = 0; //东西绿灯亮
dx_yellow = 1; //东西黄灯灭
nb_red = 0; //南北红灯亮
nb_green = 1; //南北绿灯灭
nb_yellow = 1; //南北黄灯灭
}
else if (nb_s <= 5) //当小于5秒时 黄灯要闪了
{
dx_red = 1; //东西红灯灭
dx_green = 1; //东西绿灯灭
nb_red = 0; //南北红灯亮
nb_green = 1; //南北绿灯灭
nb_yellow = 1; //南北黄灯灭
if (flag_500ms == 0) //黄灯闪烁
{
dx_yellow = 0;//东西黄灯亮
}
else
{
dx_yellow = 1;//东西黄灯灭
}
}
}
}
思路:由于需要观察到倒计时,所以想到需要进行显示出来。一想到显示,则需要进行接口的定义,然后设置高低电平进行显示。因此,先定义,再设置显示。
a.引脚的定义:数码管的编码有共阳极与共阴极,共阳极是位选为高电平(即1)选中数码管,各段选为低电平(即0)选中各数码段,共阴极与此相反。在这里,我选用的是共阳极,例如用显示0来讲,oxC0(1100 0000)(小数点 g f e d c b a)。
由于要与引脚对应,所以这里最好结合仿真图(已放在第三部分),对于位选,就是选择个位还是十位,定义与仿真图结合看,有电源电路原因是需要进行驱动。由于需要进行控制方向的灯的亮灭,所以对于东西南北LED灯的显示进行定义。
//数码管段选定义 0 1 2 3 4 5 6 7 8 9 A B
uchar smg_du[] = { 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83 }; //段码
uchar dis_smg[4] = { 0 }; //数码管显示数组的缓冲区,初值为0确保启动时候都不亮
//数码管位选定义
sbit smg_we1 = P2 ^ 0; //数码管位选IO口定义
sbit smg_we2 = P2 ^ 1; //数码管位选IO口定义
sbit smg_we3 = P3 ^ 6; //数码管位选IO口定义
sbit smg_we4 = P3 ^ 7; //数码管位选IO口定义
sbit dx_red = P2 ^ 7; //东西红灯IO口定义
sbit dx_green = P2 ^ 6; //东西绿灯IO口定义
sbit dx_yellow = P2 ^ 5; //东西黄灯IO口定义
sbit nb_red = P2 ^ 4; //南北红灯IO口定义
sbit nb_green = P2 ^ 3; //南北绿灯IO口定义
sbit nb_yellow = P2 ^ 2; //南北黄灯IO口定义
b.数码管显示:
void smg_we_switch(uchar i)//数码管位选函数
{
switch (i)
{
case 0: smg_we1 = 0; smg_we2 = 1; smg_we3 = 1; smg_we4 = 1; break;
case 1: smg_we1 = 1; smg_we2 = 0; smg_we3 = 1; smg_we4 = 1; break;
case 2: smg_we1 = 1; smg_we2 = 1; smg_we3 = 0; smg_we4 = 1; break;
case 3: smg_we1 = 1; smg_we2 = 1; smg_we3 = 1; smg_we4 = 0; break;
}
}
void delay_1ms(uint q)//延时函数
{
uint i, j;
for (i = 0; i < q; i++)
for (j = 0; j < 120; j++);
}
void display()//数码管显示函数
{
for (i = 0; i < 4; i++)
{
P0 = 0xff; //消隐
smg_we_switch(i); //位选
P0 = dis_smg[i]; //段选
delay_1ms(3); //延时
}
}
思路:由于需要扩展的功能是可以自己设置时间,所以用按键进行控制时间的加与减,以及模式的切换(东西南北全部亮红灯,东西红灯南北绿灯,南北红灯东西绿灯,南北黄灯闪东西黄灯闪)。
/********************设置函数*****************/
void key_with()
{
if (key_can == 4) //交通管制按键 紧急模式
{
flag_jdgz++;
if (flag_jdgz > 4)
flag_jdgz = 0;
if (flag_jdgz == 1) // 全部亮红灯
{
dx_red = 0; //东西红灯亮
dx_green = 1; //东西绿灯灭
dx_yellow = 1; //东西黄灯灭
nb_red = 0; //南北红灯亮
nb_green = 1; //南北绿灯灭
nb_yellow = 1; //南北黄灯灭
}
if (flag_jdgz == 2) // 东西红灯 南北绿灯
{
dx_red = 0; //东西红灯亮
dx_green = 1; //东西绿灯灭
dx_yellow = 1; //东西黄灯灭
nb_red = 1; //南北红灯灭
nb_green = 0; //南北绿灯亮
nb_yellow = 1; //南北黄灯灭
}
if (flag_jdgz == 3) // 南北红灯 东西绿灯
{
dx_red = 1; //东西红灯灭
dx_green = 0; //东西绿灯亮
dx_yellow = 1; //东西黄灯灭
nb_red = 0; //南北红灯亮
nb_green = 1; //南北绿灯灭
nb_yellow = 1; //南北黄灯灭
}
if (flag_jdgz == 4) // 南北黄灯闪 东西黄灯闪
{
dx_red = 1; //东西红灯灭
dx_green = 1; //东西绿灯灭
nb_red = 1; //南北红灯灭
nb_green = 1; //南北绿灯灭
}
}
if (key_can == 1) //设置键
{
menu_1++;
if (menu_1 >= 3)
{
menu_1 = 0; //menu_1 = 0 退出设置,是在正常显示界面下
}
}
if (menu_1 == 1) //设置东西绿灯的时间
{
if (key_can == 2) //加键
{
dx_time++; //设置东西绿灯的时间 加1
if (dx_time > 99)
dx_time = 99;
}
if (key_can == 3) //减键
{
dx_time--; //设置东西绿灯的时间 减1
if (dx_time <= 5)
dx_time = 5;
}
dis_smg[0] = smg_du[dx_time % 10]; //显示设置的东西绿灯的时间 个位数
dis_smg[1] = smg_du[dx_time / 10]; //显示设置的东西绿灯的时间 十位数
dis_smg[2] = smg_du[10]; //显示为A
dis_smg[3] = smg_du[10]; //显示为A
}
if (menu_1 == 2) //设置南北绿灯的时间
{
if (key_can == 2) //加键
{
nb_time++; //设置南北绿灯的时间 加1
if (nb_time > 99)
nb_time = 99;
}
if (key_can == 3) //减键
{
nb_time--; //设置南北绿灯的时间 减1
if (nb_time <= 5)
nb_time = 5;
}
dis_smg[0] = smg_du[11]; //显示为B
dis_smg[1] = smg_du[11]; //显示为B
dis_smg[2] = smg_du[nb_time % 10]; //显示设置的南北绿灯的时间 个位数
dis_smg[3] = smg_du[nb_time / 10]; //显示设置的南北绿灯的时间 十位数
}
}
思路:前面第5步已经设好按键的功能,所以接下来就需要知道什么时候按键按下。
/********************独立按键程序*****************/
uchar key_can; //按键值
void key() //独立按键程序
{
static uchar key_new; //key_new 这个变量的功能是做按键松手检测的
key_can = 0; //按键值还原成0
if (key1 == 0 || key2 == 0 || key3 == 0 || key4 == 0) //有按键按下
{
delay_1ms(1); //按键延时消抖动
if (key_new == 1)
{
key_new = 0; //key_new = 0 说明按键已按下
if (key1 == 0) //确认是按键按下
key_can = 1; //得到按键值
if (key2 == 0) //确认是按键按下
key_can = 2; //得到按键值
if (key3 == 0) //确认是按键按下
key_can = 3; //得到按键值
if (key4 == 0) //确认是按键按下
key_can = 4; //得到按键值
}
}
else
key_new = 1; //key_new = 1 说明按键已经松开了
}
流程是先设定好初始时间,再检测是否有按键按下,有则进行对时间和模式的设置,没有则进行交通灯的处理,之后就是进行显示。
void main()
{
time0_init(); //定时器初始化程序
dx_s = nb_time + 5; //南北时间
nb_s = nb_time; //东西时间
while (1)
{
key(); //按键函数
if (key_can > 0) //有按键按下
key_with(); //按键处理设置函数
if ((menu_1 == 0) && (flag_jdgz == 0))
jiaotongdeng_dis(); //交通灯处理函数
if (flag_jdgz == 4) //夜间模式
if (flag_500ms == 1) // 南北黄灯闪 东西黄灯闪
{
flag_500ms = 0;
nb_yellow = ~nb_yellow; // 南北黄灯闪
dx_yellow = ~dx_yellow; // 东西黄灯闪
}
display(); //数码管显示函数
}
}
每个连接的都与程序代码中的定义相关联。
a.打开keil后,导出.hex文件。
b.打开Proteus,双击芯片(AT89C51),选择刚才导出的.hex文件,然后观察仿真效果。
c.进行仿真验证
在烧录之前,确保你的板子已经连接好(与仿真图对应)。具体操作如图。