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

2024-02-15 10:12

本文主要是介绍一起玩儿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();

}

}

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

运行结果

这篇关于一起玩儿Proteus仿真(C51)——06. 红绿灯仿真(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711106

相关文章

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

perl的学习记录——仿真regression

1 记录的背景 之前只知道有这个强大语言的存在,但一直侥幸自己应该不会用到它,所以一直没有开始学习。然而人生这么长,怎就确定自己不会用到呢? 这次要搭建一个可以自动跑完所有case并且打印每个case的pass信息到指定的文件中。从而减轻手动跑仿真,手动查看log信息的重复无效低质量的操作。下面简单记录下自己的思路并贴出自己的代码,方便自己以后使用和修正。 2 思路整理 作为一个IC d

前端-06-eslint9大变样后,如何生成旧版本的.eslintrc.cjs配置文件

目录 问题解决办法 问题 最近在写一个vue3+ts的项目,看了尚硅谷的视频,到了配置eslintrc.cjs的时候我犯了难,因为eslint从9.0之后重大更新,跟以前完全不一样,但是我还是想用和老师一样的eslintrc.cjs文件,该怎么做呢? 视频链接:尚硅谷Vue项目实战硅谷甄选,vue3项目+TypeScript前端项目一套通关 解决办法 首先 eslint 要

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑燃料电池和电解槽虚拟惯量支撑的电力系统优化调度方法》

本专栏栏目提供文章与程序复现思路,具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源程序擅长文章解读,论文与完整源程序,等方面的知识,电网论文源程序关注python

跟我一起玩《linux内核设计的艺术》第1章(四)——from setup.s to head.s,这回一定让main滚出来!(已解封)

看到书上1.3的大标题,以为马上就要见着main了,其实啊,还早着呢,光看setup.s和head.s的代码量就知道,跟bootsect.s没有可比性,真多……这确实需要包括我在内的大家多一些耐心,相信见着main后,大家的信心和干劲会上一个台阶,加油! 既然上篇已经玩转gdb,接下来的讲解肯定是边调试边分析书上的内容,纯理论讲解其实我并不在行。 setup.s: 目标:争取把setup.

C++入门(06)安装QT并快速测试体验一个简单的C++GUI项目

文章目录 1. 清华镜像源下载2. 安装3. 开始菜单上的 QT 工具4. 打开 Qt Creator5. 简单的 GUI C++ 项目5.1 打开 Qt Creator 并创建新项目5.2 设计界面5.3 添加按钮的点击事件5.4 编译并运行项目 6. 信号和槽(Signals and Slots) 这里用到了C++类与对象的很多概念 1. 清华镜像源下载 https://

Matlab simulink建模与仿真 第十章(模型扩展功能库)

参考视频:simulink1.1simulink简介_哔哩哔哩_bilibili 一、模型扩展功能库中的模块概览         注:下面不会对Block Support Table模块进行介绍。 二、基于触发的和基于时间的线性化模块 1、Trigger-Based Linearization基于触发的线性化模块 (1)每次当模块受到触发时,都会调用linmod或者dlinmod函数

F12抓包06-4:导出metersphere脚本

metersphere是一站式的开源持续测试平台,我们可以将浏览器请求导出为HAR文件,导入到metersphere,生成接口测试。 metersphere有2种导入入口(方式),导入结果不同:         1.导入到“接口定义”:自动生成接口API和单接口case(接口自动去重;每个请求生成不同case,重复的请求生成重复的case,名称自动加数字后缀,自动与接口关联)。

AMEsim和Simulink联合仿真生成新的.mexw64液压模型文件

AMEsim和Simulink进行联合仿真非常重要的就是AMEsim经过第四阶段Simulation会在相同文件下面生成一个与AMEsim液压模型相同名字的.mexw64文件,在Simulink进行联合仿真的S-Function需要找的也就是这个文件,只不过输入的时候除了液压模型名字之外,后面有一个短下划线。 简而言之: AMEsim和Simulink联合仿真, 首先是需要AMEsim软