摘要:本文介绍如何仿真红绿灯
今天来看一下红绿灯仿真程序的具体实现方法。先来看一下整个程序的原理图。
在这个红绿灯仿真实验中,每个路口需要控制的设备是2位数码管显示倒计时以及红黄绿灯的亮灭。先来看一下数码管的连接方法。
数码管的8根LED显示引脚都连接到了一起,使用了一组单片机端口。另外的公共端则由单片机引脚来单独的控制。这样,在程序中通过数码管公共端引脚循环控制数码管点亮。
下面就来看一下具体的实现方法。首先需要了解一下程序中使用到的全局变量。首先看一下与显示相关的全局变量:
uchar tab[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0xBF}; //显示码值表 uchar dis_buff[4]; // 显示数组 |
其中的tab记录了0~9这些字符对应的码值。在这个示例中,采用的是共阴极数码管,因此是高电平的时候数码管对应的LED亮起,低电平的时候数码管LED熄灭。dis_buf数组记录了当前数码管显示的字符。dis_buf[0]对应东西方向的个位数码管,dis_buf[1]对应东西方向十位数码管。同样的,dis_buf[2]对应南北方向的个位数码管,dis_buf[3]对应南北方向的十位数码管。
只要将dis_buff数组,赋予要显示的字符后,调用display()函数,就可以将字符显示在数码管上了。根据前面的介绍,这个display()函数需要被连续调用,才能让眼睛觉得这些字符一直是亮着的。display()函数的实现方法如下:
void display(void) { P2 = 0x01; P0 = tab[dis_buff[1]]; delay(2); P2 = 0x02; P0 = tab[dis_buff[0]]; delay(2); P2 = 0x04; P0 = tab[dis_buff[3]]; delay(2); P2 = 0x08; P0 = tab[dis_buff[2]]; delay(2); } |
需要提醒的是,本例中所使用的数码管的公共端是接到P2的响应引脚上的。所以通过控制P2的状态就能将输出字符显示在数码管上。
本仿真实验,是将定时器的时间中断设置在了10ms,这样100次的中断就是1分钟,时间中断计数器变量的初值是0xDC00,那么时间中断的初始化方法如下:
void int_init(void) { TMOD = 0x01; TH0 = 0xDC; TL0 = 0x00; TR0 = 1; ET0 = 1; EA = 1; } |
接下来就是本实验的核心环节的实现了。主要包括两个地方,一个是红绿灯的循环倒计时显示,另一个是按键的处理。
先讲解一下红绿灯的循环显示是如何实现的。在这里利用的是一个状态变量status来标记当前红绿灯的运行状态。简单的说,红绿灯包括了以下几个运动状态:
状态0:东西绿灯,南北红灯,两边同时倒计时,以东西绿灯时间为基准,那么计算出来的南北红灯的时间就是:东西绿灯的时间+黄灯的时间。当东西绿灯时间减至0时,进入状态1。
状态1:东西绿灯熄灭,黄灯点亮,并以黄灯的时间开始倒计时。南北的红灯状态不变,南北的倒计时时间与东西的黄灯相同。当两者同时倒计时到0时,进入状态2。
状态2:南北变成绿灯,并开始倒计时。东西变成红灯,也开始倒计时,东西的红灯倒计时时间为南北的绿灯时间+黄灯时间。当南北绿灯倒计时至0时,进入状态3。
状态3:南北变成黄灯,自黄灯闪烁时间开始倒计时,东西延续之前的状态,继续倒计时。当南北黄灯倒计时至0时。放回状态0,依次循环,就是红绿灯的运行过程。
接下来再来看一下按键的处理逻辑,一方面就是通过按键改变预先定义的东西方向绿灯变量的值和南北方向绿灯变量的值。另外,就是改变完成之后,将东西方向的灯显示东西的绿灯时间,南北方向的灯显示南北的绿灯时间。显示的时长默认为5秒(定义了全局变量count,可以随时调整这个时长)。
根据上面这两个要点,来看一下如何实现红绿灯的仿真。先来看一下全局变量的定义:
uchar sec100; // 10ms计数变量 uchar count=0; // 修改时长后的显示时长变量 // 红黄绿灯控制引脚,低电平点亮,高电平熄灭 sbit dr = P1^0; // 东西红灯控制引脚 sbit dy = P1^1; // 东西黄灯控制引脚 sbit dg = P1^2; // 东西绿灯控制引脚 sbit nr = P1^3; // 南北红灯控制引脚 sbit ny = P1^4; // 南北黄灯控制引脚 sbit ng = P1^5; // 南北绿灯控制引脚 uchar dxTotal=10,nbTotal=15; // 定义东西和南北总时间 uchar yellowTime = 3; // 黄灯时间 /** 红绿等状态变量 * 0:东西绿灯,南北红灯 * 1:东西黄灯,南北红灯 * 2:南北绿灯,东西红灯 * 3:南北黄灯,东西红灯 */ uchar status = 0; uchar lastTime = 0; // 倒计时时间 // 按键控制引脚 sbit kd1 = P3^0; //东西绿灯时长增加 sbit kd2 = P3^1; //东西绿灯时长减少 sbit kn1 = P3^2; //南北绿灯时长增加 sbit kn2 = P3^3; //南北绿灯时长减少 |
其中的sec100时中断的计数器,当其累加到100时,表示到达一秒钟时长了,这个时候需要变换红绿灯的显示了。count为使用按键修改绿灯时长后,显示修改结果的计数器,count的值大于0,表示需要显示东西和南北方向的绿灯时长。count为0时,则表示红绿灯正常运行。
后边还定义了三个时间。dxTotal表示东西绿灯的时长,nbTotal表示南北绿灯的时长。yellowTime表示黄灯的时长。
之后是状态变量status的定义,其取值范围是0、1、2和3。代表了红绿灯运行的4个状态。lastTime表示当前状态下绿灯或者黄灯还剩余的时长。下面就是处理逻辑的核心——中断函数的实现方法。
// 定时器中断处理函数 void timer0() interrupt 1 { TH0 = 0xDC; TL0 = 0x00; sec100++; if( sec100>=100 ) // 达到1秒 { sec100 = 0; if(count==0) // 正常运行状态 { if( status==0 || status==2 ) { // 状态0、2 lastTime--; if( status==0 ) { // 状态0:东西为倒计时时间 dis_buff[0] = lastTime%10; dis_buff[1] = lastTime/10%10; dis_buff[2] = (lastTime+yellowTime)%10; // 南北为倒计时时间+黄灯时长 dis_buff[3] = (lastTime+yellowTime)/10%10; } else { dis_buff[0] = (lastTime+yellowTime)%10; // 状态2,与上一种情况东西和南北对调 dis_buff[1] = (lastTime+yellowTime)/10%10; dis_buff[2] = lastTime%10; dis_buff[3] = lastTime/10%10; } if( lastTime==0 ) { // 剩余时间为0,改变LED状态 if(status==0) // 状态0 { dg = 1; // 东西绿灯灭,黄灯量 dy = 0; } else { ng = 1; // 南北绿灯灭,黄灯量 ny = 0; } lastTime = yellowTime; // 进入黄灯状态,倒计时为黄灯时间 status++; } } else if( status==1 || status==3 ) { // 状态1和3 lastTime--; dis_buff[0] = (lastTime)%10; / dis_buff[1] = lastTime/10%10; dis_buff[2] = lastTime%10; dis_buff[3] = lastTime/10%10; if( lastTime==0 ) { // 倒计时为0,切换状态 if( status==1 ) { status = 2; lastTime = nbTotal; ng = 0; nr = 1; dy = 1; dr = 0; } else { status = 0; lastTime = dxTotal; dg = 0; dr = 1; ny = 1; nr = 0; } } } } else { count--; } } } |
下面来看一下按键处理函数。
// 判断按键状态,返回按键值 uchar getkey(void) { if((P3&0x0F)!=0x0F) { uchar kvalue = ~(P3&0x0F); delay(5); if((P3&0x0F)!=0x0F) { while((P3&0x0F)!=0x0F); return kvalue; } } return 0; } // 处理按键 void key(void) { uchar kvalue = getkey(); if(kvalue!=0) { if((kvalue&0x01)!=0) // 东西时间+1 { dxTotal++; } else if ((kvalue&0x02)!=0) { // 东西时间-1 dxTotal--; } else if ((kvalue&0x04)!=0) { // 南北时间+1 nbTotal++; } else { // 南北时间-1 nbTotal--; } count = 5; // 倒计时显示5秒 } } |
getkey()函数用来返回按下的按键。当这个方法返回0x0F时,表示无按键按下,返回值的低4位,任意一位不为1,则表示该位对应的引脚被按下。key()函数根据getkey()函数的返回值,对东西和南北绿灯的时长做相应的修改。并将显示的倒计时时长设置为5秒。
最后来看一下主程序,主程序的作用就是初始化中断和各个变量。然后循环驱动数码管显示(根据count是否大于0,变换显示的内容),并检测看是否有按键按下。代码如下所示:
void main(void) { int_init(); sec100 = 0; status = 0; // 初始化东西绿灯 lastTime = dxTotal; dg = 0; nr = 0; while(1) { if(count>0) { dis_buff[0]=dxTotal%10; dis_buff[1]=dxTotal/10%10; dis_buff[2]=nbTotal%10; dis_buff[3]=nbTotal/10%10; } display(); key(); } } |
整个程序的所有代码都讲解完了,接下来运行看一下结果吧。如下所示:
运行结果