Arduino教程 03 互动交通信号灯「DFR0100」

2014-01-0729.2万
精华项目
AI 快速预览详细 收起
本篇Arduino教程介绍了如何制作一个互动交通信号灯。项目使用了DFR0100套件,包括5个LED灯和1个按键。通过代码实现按键控制交通灯的状态变化,允许行人在按下按键后通过马路。示例代码中使用了unsigned long变量类型来存储更长的时间间隔,避免变量溢出。整个过程涵盖了硬件连接、代码编写及调试技巧,适合零基础新手和STEM教育场景。
【本篇课程内容于 2024年8月20日更新】


有没有试着做【SOS求救信号器】的课后作业呢?做出来的话,说明你已经基本掌握之前所学的东西了,如果不会也没关系,我相信,看完这个章节,前面那个问题就不攻自破了!我们这回就基于课后作业的交通灯来进行一个拓展,增加一种行人按键请求通过马路的功能。当按被按下时,Arduino会自动反应,改变交通灯的状态,让车停下,允许行人通过。

这个项目中,我们开始要实现Arduino的互动了,也会学习在代码如何创建自己的函数。这次的代码相对长一点,耐下心来,等看完这一章,相信你能收获不少!


元件清单


Arduino教程 03 互动交通信号灯「DFR0100」图1


*这里5个LED灯,为什么会用到了6个电阻呢?我们知道5个电阻是LED的限流电阻。还有一个电阻是给按键的,它叫做下拉电阻(我们后面会解释)。

硬件连接

按图1的连线图连接你的电路。特别要注意的是,这次连线比较多,注意不要插错。下图中,面包板上标出淡绿色的不是跳线,只是为了说明纵向的孔导通,避免你插错。给Arduino上电前认真检查你的接线是否正确。在连线时,保持电源是断开的状态,也就是没有插USB线。


Arduino教程 03 互动交通信号灯「DFR0100」图2

1 交通信号灯连接图


示例代码
输入下面的样例代码,这段代码引自《beginning-arduino》一书。
样例代码:


  1. // 项目 - 交通信号灯  
  2. int carRed = 12;    // 汽车红灯引脚  
  3. int carYellow = 11; // 汽车黄灯引脚  
  4. int carGreen = 10;  // 汽车绿灯引脚  
  5. int button = 9;     // 按钮引脚  
  6. int pedRed = 8;     // 行人红灯引脚  
  7. int pedGreen = 7;   // 行人绿灯引脚  
  8. int crossTime = 5000; // 允许行人通过的时间(毫秒)  
  9. unsigned long changeTime; // 按钮按下后的时间  
  10.   
  11. void setup() {  
  12.     // 初始化引脚模式  
  13.     pinMode(carRed, OUTPUT);  
  14.     pinMode(carYellow, OUTPUT);  
  15.     pinMode(carGreen, OUTPUT);  
  16.     pinMode(pedRed, OUTPUT);  
  17.     pinMode(pedGreen, OUTPUT);  
  18.     pinMode(button, INPUT); // 按钮设置为输入模式  
  19.   
  20.     // 初始状态设置  
  21.     digitalWrite(carGreen, HIGH); // 车行绿灯亮  
  22.     digitalWrite(pedRed, HIGH);   // 人行红灯亮  
  23. }  
  24.   
  25. void loop() {  
  26.     int state = digitalRead(button);  
  27.     // 检测按钮状态及时间间隔  
  28.     if (state == HIGH && (millis() - changeTime) > 5000) {  
  29.         // 调用变灯函数  
  30.         changeLights();  
  31.     }  
  32. }  
  33.   
  34. void changeLights() {  
  35.     // 汽车绿灯变黄灯  
  36.     digitalWrite(carGreen, LOW);  
  37.     digitalWrite(carYellow, HIGH);  
  38.     delay(2000); // 等待2秒  
  39.   
  40.     // 汽车黄灯变红灯  
  41.     digitalWrite(carYellow, LOW);  
  42.     digitalWrite(carRed, HIGH);  
  43.     delay(1000); // 为安全考虑等待1秒  
  44.   
  45.     // 行人红灯变绿灯  
  46.     digitalWrite(pedRed, LOW);  
  47.     digitalWrite(pedGreen, HIGH);  
  48.   
  49.     // 行人绿灯亮,持续crossTime毫秒  
  50.     delay(crossTime);  
  51.   
  52.     // 闪烁行人绿灯,提示时间快到  
  53.     for (int x = 0; x < 10; x++) {  
  54.         digitalWrite(pedGreen, HIGH);  
  55.         delay(250);  
  56.         digitalWrite(pedGreen, LOW);  
  57.         delay(250);  
  58.     }  
  59.   
  60.     // 行人红灯再次亮起  
  61.     digitalWrite(pedRed, HIGH);  
  62.     delay(500);  
  63.   
  64.     // 恢复到汽车绿灯亮状态  
  65.     digitalWrite(carRed, LOW);  
  66.     digitalWrite(carYellow, HIGH);  
  67.     delay(1000);  
  68.     digitalWrite(carYellow, LOW);  
  69.     digitalWrite(carGreen, HIGH);  
  70.   
  71.     // 更新changeTime  
  72.     changeTime = millis();  
  73. }
复制代码


下载完成后,可以尝试按下按键。看看是个什么的效果?我们可以看到整个变化过程是这样的——开始时,汽车灯为绿灯,行人灯为红灯,代表车行人停。一旦行人按键被按下,请求过马路,那么汽车灯由绿变黄,变红,接着行人灯就由红变绿。在行人通行的过程中,设置了一个过马路的时间crossTime,一旦到点,行人绿灯开始闪烁,提醒行人快速过马路。闪烁完毕,最终,又回到了开始的状态,汽车灯为绿灯,行人灯为红灯。

整段代码看起来很复杂,其实理清一下思路并不难。如果你还是没有办法理不清里面变化关系的话,可以试着画一个示意图,像项目SOS求救信号器】的课后作业示例图那样,这样一来可能会方便你理解程序。


代码回顾
通过前面两个项目,你应该能够理解这个代码的大部分内容。代码开始是一串的变量的声明,在声明中,出现了一个新名词。这里就解释一下这个新名词:


unsigned long changeTime;


这是一个新的变量类型。我们之前,只创建过int整型变量,它可以存放一个-32768到32767之间的整数。这次要创建的是一个long的变量类型,它可以存放一个-2147483648到2147483647之间的整数。而unsigned long数据类型不存储负数,所以存储的范围就变成了0到4294967295.

如果我们使用一个int型的话,信号灯状态变化的时间,只能存储最大32秒(32768毫秒约为32秒),一旦出现变量溢出就会造成程序运行出现错误,所以为了避免这样的情况,要选用能存储更大数的一个变量类型,并且不为负,我们就可以考虑使用unsigned long型。你可以用笔算下,这个变量最大能存储的数,时间可达49天。

随即进入setup()函数,对LED和按钮进行一些设置,在设置时,需要注意到的是:


pinMode(button, INPUT);


pinMode()函数我们已经很熟悉了,在项目一的时候就介绍过,只是和LED有所不同的是,按要设置为INPUT。

setup()函数中,先给定行人灯和汽车灯的一个初始状态:


digitalWrite(carGreen, HIGH); //开始时,汽车灯绿灯
digitalWrite(pedRed, LOW); //行人灯为红灯


进入到的主程序中的第一句,就是来检测button(引脚9)的状态的:


int state = digitalRead(button);

变量这个盒子无限大吗?

有人会问为什么有些变量类型可以存储很大的数,而有些变量类型不行呢?这是由变量类型所占的存储空间决定的。就拿我们前面讲变量的时候举过的例子,变量好比用来放东西的盒子,把不同类型的变量想象成不同大小的盒子,int的盒子比unsigned long的盒子小,所以放的东西当然少啦!这样解释是不是比较容易理解这个概念呢?

那又有人问,设置不同大小的盒子干嘛呢?一样大不就行啦,都设置的大一点。理论上没有什么不可以的,可是我们不能忽略一个问题,那就是微控制器的内部存储容量是有限定的。电脑有内存,我们的微控制器同样有内存。像Arduino UNO板上的用的主芯片Atmega328最大内存是32K。所以,我们要尽量的少用存储空间,能不用则不用。

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

Arduino教程 03 互动交通信号灯「DFR0100」图12


此时,一个新函数出现了——digitalRead()!

Arduino教程 03 互动交通信号灯「DFR0100」图3
这个函数是用来读取数字引脚状态的,HIGH还是LOW(其实HIGH还有一种表达就是“1”,LOW是“0”,只是HIGH/LOW更直观)。函数需要一个传递参数——pin,pin代表对应的引脚号。这里需要读取得是按键信号,按键所在引脚是数字引脚9,由于前面做了声明,所以这里用button来代替。并且把读到的信号传递给变量state,用于后面进行判断。state为 HIGH或者说为1时,说明按键被按下了。state为LOW或者0,表明按键没被按下。

所以,可以直接检查state的值来判断按键是否被按下:

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

这里涉及新的语句-- if语句:

if(表达式){
         语句;
}

if语句是一种条件判断的语句,判断是否满足括号内的条件,如满足则执行花括号内的语句,如不满足则跳出if语句。
表达式是指我们的判断条件,通常为一些关系式或逻辑式,也可是直接表示某一数值。如果if表达式条件为真则执行if中的语句。表达式条件为假,则跳出if语句。
我们代码中,第一个条件是state变量为HIGH。如果按键被按下,state就会变为HIGH。第二个条件是millis()的值减changeTime的值大于5000。这两个条件之间有个“&&”符号。这是一种逻辑运算符,表示的含义是两者同时满足。

(millis() - changeTime)> 5000)

millis()是一个函数,该函数是Arduino语言自有的函数,它返回值是一个时间,是Arduino开始运行到执行到当前的时间,也称之为机器时间,就像一个隐形时钟,从控制器开始运行的那一刻起开始计时(单位为毫秒)。
变量changeTime初始化时,不存储任何数值,只有在Arduino运行之后,将millis() 值赋给它,它才开始有数值,并且随着millis() 值变化而变化。通过millis() 函数不断记录时间,判断两次按键之间的时间是不是大于5秒,大于五秒则执行if花括号内的语句,如果在5秒之内则不执行。这样做的目的是,防止重复按键而导致的运行错误。

if语句内只有一个函数:

changeLights();


这是一个函数调用的例子。该函数单独写在了loop()函数之外。我们需要使用时,直接写出函数名就可以实现调用了。该函数是void型,所以是无返回值、无传递参数的函数。当函数被调用时,程序也就自动跳到它的函数中运行。运行完之后,再跳回主函数。需要特别注意的:函数调用时,函数名后面的括号不能省,要和所写的函数保持一致。changeLights() 函数内部就不做说明了。


逻辑运算符

前面说到的&&是一个逻辑运算符,常用的逻辑运算符有:


&& —— 逻辑与(两者同时满足)
|| —— 逻辑或(两者其中一个满足)
!—— 逻辑非(取反,相反的情况)




硬件回顾

按键开关

按键一共有4个引脚,图2分别显示了正面与背面。而图3则说明了按键的工作原理。一旦按下后,左右两侧就被导通了,而上下两端始终导通。

Arduino教程 03 互动交通信号灯「DFR0100」图4

图 2 按键结构图


Arduino教程 03 互动交通信号灯「DFR0100」图5

图 3 按键原理图


在图4中,按键仅起到一个通断的作用。但在本项目中,按键要能被读取状态,所以除了要将按键的1、2(或者3、4;1、4;2、3)引脚连入电路中,还需要接入信号口。按下的话,数字引脚9就能检测到为高电平。否则就是保持一个低电平的状态。

Arduino教程 03 互动交通信号灯「DFR0100」图6

图 4 按键示意图



下拉电阻

下拉电阻这个名词可能比较抽象,从字的含义着手,“下拉”我们就理解为把电压往下拉,降低电压。按键作为开关,当输入电路状态为高电平(high)的时候,电压要尽可能接近5V;输入电路状态为低电平(low)的时候,电压要尽可能接近0V。为了避免状态偏离所需电压造成的电压浮动现象,在按键电路中,我们将一个电阻串联在输入(input)引脚和接地(ground)之间,将按键未被按下的输入状态固定在低电平,这个电阻就是所谓下拉电阻。
可以从下面两张图看到,图5是未接下拉电阻的电路,按键未被按下时,输入引脚就处于一个悬空状态。空气会使该引脚电压产生浮动,不能确保是0V。然而图6是接了下拉电阻的电路,当按键未被按下时,输入引脚通过电阻接地,确保为0V,不会产生电压浮动现象。

Arduino教程 03 互动交通信号灯「DFR0100」图7

图 5 未接下拉电阻


Arduino教程 03 互动交通信号灯「DFR0100」图8

图 6 有下拉电阻


课后练习
1、选择任意颜色LED 6个,做一个流水灯的效果,6盏灯从左至右依次点亮,重复循环这个过程。

Arduino教程 03 互动交通信号灯「DFR0100」图9


2、如果上面那个你已经完成了的话,可以尝试一下,先从中间的灯开始亮起,依次向两边扩开。下图是个变换过程的示意图。

Arduino教程 03 互动交通信号灯「DFR0100」图10


3、再比如,从左至右,依次亮起1 个,2个,3个……



Arduino教程 03 互动交通信号灯「DFR0100」图11



下载附件示例代码.rar


下一篇教程:Arduino教程 04 呼吸灯

DF创客社区版权所有,欢迎转载。
转载请务必标注来源: DF创客社区+作者姓名+原文网址。

创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(61)
B5oi0T0lyJ7m
B5oi0T0lyJ7m
[code]// 项目 - 交通信号灯
int carRed = 13; // 汽车红灯引脚
int carYellow = 12; // 汽车黄灯引脚
int carGreen = 8; // 汽车绿灯引脚
int button = 2; // 按钮引脚
int pedRed = 7; // 行人红灯引脚
int pedGreen = 4; // 行人绿灯引脚
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(carRed, HIGH); // 车行红灯亮
digitalWrite(pedGreen, HIGH); // 人行绿灯亮
}
void loop() {
int state = digitalRead(button); //state状态=digitaLRead函数 读取状态(botton按钮)
// 检测按钮状态及时间间隔
if (state == HIGH && (millis() - changeTime) > 5000) { //if判断(state状态==高电平HIGH 与&& (系统运行时间millis() 减- 按钮按下去的时间chuangeTime) 小于> 5秒 5000
// 调用变灯函数
changeLights();
}
}
void changeLights() { //这里其实我目前还不是很理解.前面那个调用变灯函数和这里的关系.
//给初始化的行人绿灯闪烁
for (int x = 0; x < 3; x++) { //循环指令
digitalWrite(pedGreen, HIGH); //打开行人绿灯
delay(400); //等待0.4秒
digitalWrite(pedGreen, LOW); //关闭行人绿灯
delay(400); //等待0.4秒
} //循环3次结束
digitalWrite(pedGreen,LOW); //关闭行人绿灯
delay(500); //等待0.5秒
digitalWrite(pedRed,HIGH); //开启行人红灯
delay(1000); //等待1秒,显得自然
for (int i = 0; i < 3; i++){ //循环指令
digitalWrite(carRed,LOW); //关闭车辆红灯
delay(400); //等待0.4秒
digitalWrite(carRed,HIGH); //打开车辆红灯
delay(400); //等待0.4秒
} //循环3次结束
digitalWrite(carRed,LOW); //关闭车辆红灯
delay(500); //等待0.5秒
digitalWrite(carYellow,HIGH); //打开车辆黄灯
delay(1000); //等待1秒
for (int o = 0; o < 3; o++){ //循环指令
digitalWrite(carYellow,LOW); //车辆黄灯关闭
delay(400); //等待0.4秒
digitalWrite(carYellow,HIGH); //车辆黄灯打开
delay(400); //等待0.4秒
} //循环3次结束
digitalWrite(carYellow,LOW); //车辆黄灯关闭
delay(500); //等待0.5秒
digitalWrite(carGreen,HIGH); //打开车辆绿灯
delay(10000); //等待10秒
for (int y = 0; y < 3; y++){ //循环指令
digitalWrite(carGreen,LOW); //关闭车辆绿灯
delay(400); //等待0.4秒
digitalWrite(carGreen,HIGH); //打开车辆绿灯
delay(400); //等待0.4秒
} //循环3次结束
digitalWrite(carGreen,LOW); //关闭车辆绿灯
digitalWrite(pedRed,LOW); //关闭行人红灯
delay(500); //等待0.5秒
digitalWrite(carRed,HIGH); //打开车辆红灯,回到初始状态
digitalWrite(pedGreen,HIGH); //打开行人绿灯.回到初始状态
// 更新changeTime
changeTime = millis();
}[/code]
liuchang
liuchang
还是2014年的教程,11年了...
liuchang
liuchang
微型舵机不能调角度呀,SG90就不行是不是~
IOHDHQ
IOHDHQ
这是入门套件的器材吗?
王小羊
王小羊
博主,为什么有个LED灯不亮了?搞的不少人嘎了,换个LED灯就好了。
JOVI
JOVI
学习学习
花生编程
花生编程
赞!!!
花生编程
花生编程
厉害

三春牛-创客
三春牛-创客
不错的教程!
三春牛-创客
三春牛-创客
赞一个!
猪爱吃面
猪爱吃面
楼主 我按你的程序做出来的灯 指挥交通把人撞死了
开始汽车通过时 行人灯不亮 很多乱穿马路的
后来按了按钮 汽车不能走路 行人灯也不能走
然后三次准备后 行人汽车都是绿灯 大家同时抢行 压死不少
现在正在处理交通事故 一会律师会给您来电 请解释下
miner233-
miner233-回复猪爱吃面
你这是真的吗???
一开始写反了
`setup()` 中的 `pedRed` 应被设置为 HIGH
雨峰
雨峰
C:\Program Files (x86)\Mind+\Arduino\hardware\tools\avr\bin\avr-g++ -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10804 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR -I C:\Program Files (x86)\Mind+\Arduino\hardware\arduino\avr\cores\arduino -I C:\Program Files (x86)\Mind+\Arduino\hardware\arduino\avr\variants\eightanaloginputs -I C:\Program Files (x86)\Mind+\Arduino\hardware\arduino\avr\cores\arduino -I C:\Program Files (x86)\Mind+\Arduino\hardware\arduino\avr\variants\standard C:\Users\Administrator\AppData\Local\DFScratch\cache\dfrobot.ino.cpp -o C:\Users\Administrator\AppData\Local\DFScratch\cache\dfrobot.ino.cpp.o
C:\Users\Administrator\AppData\Local\DFScratch\cache\dfrobot.ino.cpp: In function 'void loop()':
C:\Users\Administrator\AppData\Local\DFScratch\cache\dfrobot.ino.cpp:29:24: error: 'changeLights' was not declared in this scope
changeLights();
^
编译失败
楼主帮看一下,我直接复制代码,就有错误,不知道哪里有问题
茴香豆
茴香豆回复雨峰
楼主代码顺序出错了,需要手动改一下。
即声明函数 void changeLights() 要放在主函数调用之前,
可以将声明函数void changeLights() 与主函数void loop()调换顺序,就能解决问题
fanfanfan
fanfanfan
楼主好,想请教一下:
等待5秒那个问题。
程序里汽车灯灭灯亮2秒,又灯亮灭1秒了,再人行道人通过5秒,,,总之,程序里执行完一个循环,就已经大于5秒了。因为行人通过就是5秒时间。
所以按键时间差不应该把这些时间加起来吗:2+1+5+...
如果只是相差5秒判断,可能程序还没执行完一个循环呀,还在本次循环里。
комалов
комалов
楼主,我编译显示...7more是怎么回事?
怎么回事
怎么回事
数据线连接之后不能识别端口是什么情况
bGgiKyoFoicM
bGgiKyoFoicM
老师你那个线路连接图是用什么软件画的?我用fritzing找不到扩展板等其他元器件
新来的
新来的
这为什么要加个按钮啊
莫莫
莫莫
这个changetime 一开始还没有被赋值 ,那么怎么参与if后面的条件判定啊,不能参与条件判断,if不执行,不调用函数,那么changetime还是没被赋值,这不是死循环了吗??有没有大神解释一下啊
Bromine
Bromine
这里应该是HIGH吧?
globerman
globerman
请教一下,在
void loop() {
int state = digitalRead(button);
这段,如果把int state = digitalRead(button);这行文字放到void loop() {这行的前面去可以吗?
莫莫
莫莫回复globerman
不能,因为loop是循环函数,把这个语句放在loop里是为了不停的读取按钮,放在外面就只能读取不了按钮,因为程序在loop函数里循环执行呢