arduino闪烁的小灯–互动交通灯

之前我们做过交通灯的项目,现在我们要对上一个项目进行拓展,增加一套行人灯和一个行人请求通过马路的按钮。当按钮被按下时,Arduino将有反应,它改变交通灯的状态,使汽车停下来,允许行人安全通过。

这是你第一次与Arduino互动。当Arduino处于等待状态时,改变按钮的状态,使它产生相应的动作。在这个项目中你将学习如何在代码里创建你自己的函数。

从现在开始,在需要的元件列表里我不再列出面包板和跳线,但是记住,项目还是需要这些基本元件的。

需要的元件

2个红色,2个黄色,2个绿色LED灯:led

4个150欧姆电阻:电阻

按钮一个:5385323

 

给项目中使用的LED选择限流电阻。150 电阻是给按钮用的,它叫做下拉电阻(点击这里),有时供应商称按钮为微动开关,微动开关在面包板上使用是十分理想的。

把元件连接起来

像下图那样连接你的电路。给Arduino上电前再检查一遍你的连线。记住,当你连接电路时,要把Arduino电源断开。

0001

点击放大电路图

输入代码

输入以下代码,确定无语法错误并上传代码。运行这个程序,开始时汽车通行灯为绿色允许汽车通过,行人通行灯为红色。

当你按下按钮时,程序判断距最后一次行人通过灯的状态发生变化是否已经超过5秒(允许交通运行)。如果是这样,把代码转移到你建立的叫做changeLights()的函数中执行,在这个函数中汽车通行灯从绿色变为黄色再变为红色,行人通行灯变绿。经过设置在变量crossTime中的时间之后(这段时间应当允许行人安全通过),绿色行人通行灯闪烁,警告行人抓紧时间通过,因为灯要变为红色了。之后行人通行灯变为红色,机动车通行灯从红色变为黄色再变为绿色。机动车继续通行。

// 互动交通信号灯

int carRed = 12; //指定机动车灯

int carYellow = 11;

int carGreen = 10;

int pedRed = 9; //指定行人灯

int pedGreen = 8;

int button = 2; //按钮引脚

int crossTime = 5000; //允许行人通过的时间

unsigned long changeTime; //按钮按下的时间

 

void setup() {

pinMode(carRed, OUTPUT);

pinMode(carYellow, OUTPUT);

pinMode(carGreen, OUTPUT);

pinMode(pedRed, OUTPUT);

pinMode(pedGreen, OUTPUT);

pinMode(button, INPUT); //按钮引脚模式

//变成绿色灯

digitalWrite(carGreen, HIGH);

digitalWrite(pedRed, HIGH);

}

 

void loop() {

int state = digitalRead(button);

/* 检查按钮是否按下,并且是否距上次按下已经超过5秒 */

if (state == HIGH && (millis() - changeTime) > 5000) {

//调用变灯函数

changeLights();

}

}

 

void changeLights() {

digitalWrite(carGreen, LOW); //绿灯灭

digitalWrite(carYellow, HIGH); //黄灯亮

delay(2000); //等待2秒

 

digitalWrite(carYellow, LOW); //黄灯灭

digitalWrite(carRed, HIGH); //红灯亮

delay(1000); //为安全再等待1秒

 

digitalWrite(pedRed, LOW); //行人红灯灭

digitalWrite(pedGreen, HIGH); //行人绿灯亮

delay(crossTime); //等待一个通过时间

 

//闪烁行人绿灯

for (int x=0; x<10; x++) {

digitalWrite(pedGreen, HIGH);

delay(250);

digitalWrite(pedGreen, LOW);

delay(250);

}

//行人红灯亮

digitalWrite(pedRed, HIGH);

delay(500);

 

digitalWrite(carYellow, HIGH); //黄灯亮

digitalWrite(carRed, LOW); //红灯灭

delay(1000);

digitalWrite(carGreen, HIGH);

digitalWrite(carYellow, LOW); //黄灯灭

 

//记录自上一次变灯的时间

changeTime = millis();

//返回到主循环当中

 

代码回顾(1)

通过前面项目的学习,你应该能够理解这个项目中的大部分代码。我只解释一下新的关键词和概念:

unsigned long changeTime;

这是一个新的变量数据类型。之前,你曾创建整型数,它可以存储一个 32 768到32 767之间的整数。这次你创建了一个数据类型为long的数,它可以存储一个从 2 147 483 648到2 147 483 647之间的整数。然而,对于指定的unsigned long数据类型,它不存储负数,所以它的存储范围是从0到4 294 967 295。如果你使用一个整型变量存储信号灯状态变化的间隔时间,它能存储的最大值为32秒,这是整型变量能存储数据的上限。

行人通过马路的请求不可能每次间隔都在32秒内,你也不希望变量存储超过数据类型范围的数据,因为这会使变量溢出而造成系统崩溃。所以你使用一个unsigned long 数据类型的变量来存储行人通过按钮两次被按下之间最大的时间长度:

4294967295 * 1毫秒 = 4294967秒

4294967秒 = 71582分钟

71582分钟 = 1193小时

1193小时 = 49天

在49天内按钮不可避免地会被行人按下一次,所以你使用这个数据类型没有问题。

那么,为什么只有一种数据类型可以存储大数?因为变量的内容要占用一定的空间。数越大需要越多的空间存储变量。在PC或笔记本电脑上,这没有什么问题,但是对于一个小的微控制器,像Arduino的Atemga 32,使用能够满足程序需要的最小的数据类型是必要的。

下表列出了在程序中可能用到的变量数据类型。

下表  数据类型

数据类型
RAM
范围
void keyword
N/A
N/A
boolean
1byte
0到1(True或False)
byte
1byte
0到255
char
1byte
-128到127
unsigned char
1byte
0到255
int
2byte
-32 768到32 767
unsigned int
2byte
0到65 535
word
2byte
0到65 535
long
4byte
-2 147 483 648到2 147 483 647
unsigned long
4byte
0到4 294 967 295
float
4byte
-3.4028235E38到3.4028235E38
double
4byte
-3.4028235E38到3.4028235E38
string
1byte+x
字符数组
array
1byte+x
变量集合

每一种数据类型使用确定的内存。有些变量只使用1 Byte的存储空间,而其他的变量可能用到4 Byte或更多(不用着急知道什么是1 Byte,我将在后面讨论)。注意,你不可以复制一个数字从一种数据类型到另一种数据类型。如果x是一个整型数,y是一个字符串,程序语句x=y不会工作,因为x和y是两种不同数据类型的数据。

Atmega 168有1kB(1000 Byte),Atemga 328有2kB(2000 Byte)的内存,这不是一个很大的存储空间。在大的程序里会有很多变量,如果在程序里不优先使用正确的数据类型,容易超出最大存储空间。如果使用整型(这种数据类型使用2 Byte存储空间,可存储的数字最大为32 767)存储引脚号码,Arduino的引脚号最大才到13(Arduino Mega到54),你使用了比实际需要更多的存储空间。你完全可以使用Byte类型存储引脚号码到内存中,它的存储范围在0到255之间,足够用来存储I/O引脚号码。

接下来给出以下代码:

pinMode(button, INPUT);

本语句告诉Arduino使用数字引脚2(button=2)作为Input模式,你要使用数字引脚2来接收按钮的按下信号,因此它的模式需要设置为输入模式。

在程序主循环里用如下语句检查引脚2的状态:

int state =  digitalRead (button);

初始化一个整数(是的,这有些浪费,你应该使用一个布尔型变量)state,并且设置state的数值为数字引脚2的状态。digitalRead语句是读括号内的引脚状态,并且返回给已经指定的整数,因此可以通过检查state中的数值来判断按钮是否被按下:

if (state== HIGH && (millis() - changeTime) > 5000) 
{
 	//调用函数变换灯的状态
 	changeLights()
}

if语句是程序控制结构的一种,它的目的是检查一个确定的条件是否达到。如果达到,执行代码块中的代码。例如,如果你想实现当变量x大于数值500时点亮一个LED,可以用如下的代码实现:

if(x>500)
{
  digitalWrite(ledPin, HIGH);
}

当用digitalRead函数读引脚状态时,引脚状态要么是HIGH,要么是LOW,所以在程序中if语句可写成如下形式:

if (state == HIGH && (millis() - changeTime) > 5000)

在这里需要做的是检查两个条件是否符合。第一个条件是state变量为HIGH。如果按钮被按下,state将是HIGH,因为已经设置从数字引脚2中读取它的数值。第二个条件是millis()的值减changeTime大于5000,然后两个条件进行与运算(使用逻辑与操作符&&)。millis()函数是Arduino语言中自有的函数,它返回以毫秒为单位的从Arduino开始执行到运行到当前的时间。Changetime变量初始化不存储任何数值,除非在changeLights函数运行之后设置ChangTime为这个函数结束时的millis()函数返回的数值。

通过从当前的millis()数值中减去changeTime中的数值,你可以判断自从changeTime最后一次设置以来是否已经过去了至少5秒。millis() changeTime计算放在括号里,保证比较的是state与本次计算结果,而不是millis()中的数值。

在state==HIGH与后面的计算之间有符号&&,&&符号是一个布尔计算符号,这个符号的意思是进行逻辑与运算。要想更清楚地了解逻辑与运算,先看一下全部的布尔运算符:

  • &&–逻辑与
  • ||–逻辑或
  • !–逻辑非

这些用于逻辑计算,并且可以检查if语句中的各种条件。

&&的意思是当两个操作数都是真时,计算结果为真,因此当x为5、y为10时下面这个if语句内的代码将运行:

if(x==5&&y==10){…

||表示两个操作数中只要一个为真,值为真,例如,当x为5或y为10时下面这个if语句中的代码将运行:

if(x==5||y==10){…

!是非运算符,如果操作数为假,运算后的值为真,因此如果x是假,下面这个if语句内的代码将运行:

  1. if(!x){…

可以将操作符放在括号中使用,例如:

if(x==5&&(y==10||z==25)){…

在这个例子中,括号中的条件独立处理,当成一个单独的条件对待,并且与第二个条件比较。因此,如果你为这个语句画一个真值表(见表2-3),就可以看到条件是如何工作的。

下表为 条件(x==5&&(y==10||z==25))的真值表

x
y
z
真/假
4
9
25
5
10
24
7
10
25
5
10
25

if语句内的函数是:

changeLights();

这是一个函数调用的例子。函数是一段有名称的独立代码。可以给函数传递参数,函数也可以返回数值。但在这个例子里,你没有传递给函数任何参数,也没有让函数返回任何数值。我将在后面更进一步详细地讲解函数的参数传递和返回值的内容。

当changeLights()被调用时,程序的执行从当前行跳转到函数中,执行函数内的代码,然后返回到程序中调用函数的下一行代码。

在这里,如果满足if语句中的条件,程序执行函数中的代码,函数运行完成之后,返回if语句中changeLights()下一行代码继续执行。

函数中的代码仅仅改变交通灯的颜色,从红色变为黄色,变为绿色,之后点亮绿色行人通过灯。在变量crossTime中设置的时间过后,灯开始闪烁一小段时间以提示行人,允许他通过的时间要结束了。之后,行人通行灯变红,汽车通行灯从红色变为黄色,再变为绿色,这样返回交通灯的正常工作状态。

主程序loop只是不断地检查行人按钮是否被按下。如果被按下,并且(&&)距最后一次按下时间大于5秒,则再次调用changeLights()。

在这个程序里,把代码放入它自己的函数中除了使代码看得更清楚并解释函数的概念外没有什么好处。只有当一个函数传递了参数,并且/或者返回数值,它的优点才能真正凸显出来。当你再次使用函数时,会看到这一点。

0 Comments
Leave a Reply