一起玩儿Proteus仿真(C51)——06. 红绿灯仿真(二)

摘要:本文介绍如何仿真红绿灯

今天来看一下红绿灯仿真程序的具体实现方法。先来看一下整个程序的原理图。

在这个红绿灯仿真实验中,每个路口需要控制的设备是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();

}

}

整个程序的所有代码都讲解完了,接下来运行看一下结果吧。如下所示:

运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/469342.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是编程?

如果你已经有了一定的编程经验,本篇文章可以跳过。这篇文章是面向编程初学者的。 编程是什么 编程,字面意思即编写程序,即通过既定的关键字,来描述你的想法,并让计算机的各个部件按照你的想法来做事。 这里计算机的…

黄金交易策略(Nerve Nnife.mql4):利用锁定单消除保留单

完整EA: Nerve Knife.ex4黄金交易策略_黄金趋势ea-CSDN博客 趋势突然转变有大约30%的概率会产生一张锁定单,反复转变之后难免就会形成几个保留单了,可以选择一张与保留单同向同大小(接近也行)的单,去消除这…

ChatGPT高效提问—prompt实践(法律助手)

ChatGPT高效提问—prompt实践(法律助手) ​ 作为现代法治国家的公民,无论我们是否从事法律相关的工作,都难免会遇到法律问题,那么如何争取自身合法利益最大化呢?很多人大概率会第一时间查询相关的法律知识…

ClickHouse--05--MergeTree 表引擎

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 MergeTree 系列表引擎前言MergeTree 系列表引擎 --功能MergeTree 系列表引擎 --种类 1.MergeTree1.1MergeTree 建表语句:1.2 MergeTree 引擎表目录解析查…

数据库被人破解,删除数据,勒索

事情是这样的,我买了一台服务器自己部署项目玩儿玩儿,我的数据库运行在3306端口,密码没改,就是默认的123456,诡异的事情发生了,用了一段时间之后,数据库突然连接不上了,我一通操作猛…

权限提升:利用Linux错配提权

目录 Linux权限基础 Linux用户权限 Linux文件权限 特殊的Linux文件权限 Linux本机信息收集 Linux错配提权 crontab计划任务提权 SUID提权 Linux权限基础 Linux用户权限 在Linux中,根据权限的不同,大致可以分为三种:超级用户&#x…

QQ利用KEY漏洞上号登录空间邮箱网盘群管等-详细讲解

QQ利用KEY漏洞上号登录空间邮箱网盘群管等-详细讲解 QQ通过key登录空间邮箱等的原理是使用了数字签名技术。在用户登录时,QQ服务器会生成一个Key(密钥),并将该Key发送给用户的QQ软件。用户的QQ软件将密钥与其私钥进行加密&#x…

如何在Django中使用分布式定时任务并结合消息队列

如何在Django中使用分布式定时任务并结合消息队列 如何在Django中使用分布式定时任务并结合消息队列项目背景与意义实现步骤1. 安装Celery和Django-celery-beat2. 配置Celery3. 配置Django-celery-beat4. 定义定时任务5. 启动Celery worker 和 beat6. Celery 指令7. 对接消息队…

maven创建webapp+Freemarker组件的实现

下载安装配置maven Maven官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘123云盘为您提供Maven最新版正式版官方版绿色版下载,Maven安卓版手机版apk免费下载安装到手机,支持电脑端一键快捷安装https://www.123pan.com/s/9QRqVv-TcUY.html链接为3.6.2-3.6.3的版本 下载解…

代码+视频基于R语言进行K折交叉验证

我们在建立数据模型后通常希望在外部数据验证模型的检验能力。然而当没有外部数据可以验证的时候,交叉验证也不失为一种方法。交叉验验证(交叉验证,CV)则是一种评估模型泛化能力的方法,广泛应用…

tee漏洞学习-翻译-3:TrustZone exploit for MSM8974

原文:http://bits-please.blogspot.com/2015/08/full-trustzone-exploit-for-msm8974.html 在这篇博文中,我们将介绍利用上一篇文章中描述的 TrustZone 漏洞的完整过程。 在开发此漏洞时,我只使用了我值得信赖的(个人&#xff0…

电脑上用什么软件恢复数据?2024年受欢迎的恢复软件推荐

在当今数字化的时代,电脑已经成为我们生活中不可或缺的工具。然而,由于各种原因,我们的电脑可能会出现数据丢失的情况。这时,一款好的数据恢复软件就显得尤为重要。本文将为大家介绍一款在2024年备受推崇的数据恢复软件&#xff0…