【任务导航】

1.认识电子元件按键,并掌握其结构
2.会用电子元件按键连接电路
3.认识面包板及其结构,能用面包板搭建简单电路
4.会用编程软件中的串口监视器
5.会编写Arduino串口程序上传数据到串口监视器查看
6.会读取按键电路的信号并传到串口监视器查看
7.能编程识别按键信号并根据信号对其他电路进行控制
8.掌握条件判断语句if的用法
9.会用逻辑非运算符
10.掌握变量相关知识,并会将变量运用到编程中去
11.掌握Mixly编程中相关程序块的使用

【知识加油站】

1.按键

按键开关主要是指轻触式按键开关,也称之为轻触开关。按键开关是一种电子开关,属于电子元器件类。使用时向开关操作方向按压开关功能闭合接通,当撤销压力时开关即断开,其内部结构是靠金属弹片受力变化来实现通断的。如图2.3.1所示,这是常见的按键开关实物图。
第三节 按键点灯 - 图1
图2.3.1 按键开关实物图
常见的按键开关有4个引脚,也有一些其他封装的按键只有2个引脚。其实,4个引脚的按键开关,在使用时也只需要用到2个引脚,其内部结构是4个引脚两两相连的。具体如图2.3.2左边所示。我们将4个引脚分别表上编号1、2、3、4,内部结构如图2.3.2右边所示。当按键开关没有被按下时,按键开关内部已经将引脚1和4,2和3连接了,当按键开关被按下后,中间的金属弹片会将4个引脚连接在一起。仔细观察会发现一个规律,1、2引脚是在按键开关的同一侧,按键内部没有相连,1、4引脚在按键开关的不同侧,但是按键内部是连起来的,所以,我们可以记住一个简单的口诀:相连不同侧,同侧不相连。
按键.jpg
图2.3.2 按键引脚及其内部结构图

2.面包板

面包板是实验室中用于临时搭接电路的重要工具,熟练掌握面包板的使用方法是提高实验效率,减少实验故障几率的重要基础之一。下面就面包板的结构和使用方法做简单介绍。
面包板.jpg
图2.3.3 面包板外观图
常见面包板的外观如图2.3.3所示,面包板分上、中、下三部分,上面和下面部分一般是由一行或两行的插孔构成的窄条,中间部分是由中间一条隔离凹槽和上下各5行的插孔构成的宽条。
对于上面和下面部分的窄条,每一行的所有插孔都是相连的,行与行之间不连通。这部分一般我们用作电源扩展插孔,在市面上有配合面包板使用的电源扩展板,可以直接对应面包板上所标注的电源正极和电源地直接插入,这样一来,面包板上标注红线的两行所有插孔将接通电源正极,标注蓝线的两行所有插孔将接通电源地线。插好电源扩展板的面包板如图2.3.4所示。
第三节 按键点灯 - 图4
图2.3.4 插好电源扩展板的面包板
中间部分宽条是由中间一条隔离凹槽和上下各5行的插孔构成。在同一列中的5个插孔是互相连通的,列和列之间以及凹槽上下部分则是不连通的。面板板中间部分宽条的外观及内部结构如图2.3.5所示。
第三节 按键点灯 - 图5
图2.3.5 面包板中间部分结构图
通常我们在使用的时候是在中间宽条部分拼搭电路的主体部分,上下的窄条部分做电源用。在搭建电路主体部分的时候,确保需要连接在引脚才能连接在一起,跨区域的电子元件之间需要连接的,可以通过面包板专用插针线连接,在电路连接检查无误后再连接电源线或打开电源开关。在任何时候不允许将电源正极和电源地线之间出现短路,如若出现短路,将可能烧毁供电系统、Arduino 板、面包板以及搭建好的电路主体,重则出现火灾等安全事故。

3.Serial.begin()函数

在Arduino IDE编程中用于设置串行通讯接口(串口)的通讯速度的函数,在使用串行通讯接口的程序中,需要在setup()函数中调用此函数。此函数将串行数据传输速率设置为xx位/秒(波特率)。与计算机进行通信时,常用的波特率是:300、1200、2400、4800、9600、14400、19200、28800、38400、57600 、115200。波特率越大,通讯速度越快,但是带来的问题就是可能出现数据丢失的情况,所以在使用时需要选择一个合理的波特率,最常用的波特率是9600。
函数语法:Serial.begin(speed);其中首字母S需要大写,“点”后面的b字母需要小写,小括号内的参数speed就是需要设置的波特率,最常用的波特率是9600。此函数只需要在setup()函数中调用一次后即可,不需要重复调用。

4.Serial.print()函数

在Arduino IDE编程中以人类可读的ASCII码的文本形式打印数据到串口输出。此命令可以采取多种形式。每个数字的打印输出都是ASCII 字符。Serial.print()函数打印输出数据不换行,Serial.println()函数打印输出数据自动换行处理。例如:

  1. Serial.print(78); 输出为“78
  2. Serial.print(1.23456); 输出为“1.23
  3. Serial.print(“N”); 输出为“N
  4. Serial.print(“Hello world.”); 输出为“Hello world.”

同时,此函数也可以自己定义输出数据的格式。比如:
BIN 二进制
OCT 八进制
DEC 十进制
HEX 十六进制
对于浮点型数字,可以指定输出的小数位数
例如:

  1. Serial.print(78,BIN); 输出为“1001110
  2. Serial.print(78,OCT); 输出为“116
  3. Serial.print(78,DEC); 输出为“78
  4. Serial.print(78,HEX); 输出为“4E
  5. Serial.println(1.23456,0); 输出为“1
  6. Serial.println(1.23456,2); 输出为“1.23
  7. Serial.println(1.23456,4); 输出为“1.2346

该函数数以ASIIC码形式发送数据到串口,如果需要发送单字节数据或者以字节形式发送数据到串口,需要使用Serial.write()函数。此外调用Serial.print()函数后将会返回发送的字节数,但是,是否使用(读取)这个返回数据可以根据程序的需要自行设定,通常情况下都不会使用这个返回值。

5.digitalRead()函数

该函数是通过数字的方式读取指定引脚的值,HIGH(高电平) 或 LOW(低电平)。调用此函数后,将会返回一个值(HIGH或LOW),所以一般在使用时,需要创建一个变量来存储读取得到的值。
函数语法:digitalRead(pin); 其中Read中的“R”需要大写,括号中的pin是需要读取的引脚编号。如果该函数读取的引脚悬空,那么digitalRead()的返回值将会是随机变化的HIGH 或 LOW。在调用digitalRead()之前,需要通过pinMode()函数把需要读取的引脚设为带上拉电阻的输入模式(INTPUT_PULLUP),或者在需要读取的引脚上外接上拉电阻并通过pinMode()函数设置引脚为输入模式。
例程:

  1. //将 13 引脚设置为输入引脚7脚的值。
  2. int inPin = 7; // 按钮连接到数字引脚 7
  3. int val = 0; //定义变量存以储读值
  4. void setup()
  5. {
  6. pinMode(inPin, INPUT); // 将 7 脚设置为输入
  7. }
  8. void loop()
  9. {
  10. val = digitalRead(inPin); // 读取输入脚
  11. digitalWrite(ledPin, val); //将 LED 值设置为按钮的值
  12. }

6.if条件判断语句

if 语句和 ==、!=、<、>(比较运算符)一起用于检测某个条件是否达成,比如:某个输入值是否在特定值之上。通过if语句,你可以让Arduino判断某一个条件是否达到,并且根据这一判断结果执行相应的程序。
if语句的语法结构如下:

  1. if(测试表达式){
  2. //测试表达式成立执行的语句块
  3. }

例如:

  1. if(someVariable > 50){
  2. // 执行某些语句
  3. }

在上面的例子中,Arduino去判断变量someVariable的值是否大于 50,从而决定是否执行“某些语句”。当大于50时,执行大括号内的“某些语句”,否则Arduino将跳过“某些语句”。换句话说,只要if后面小括号里的结果(称之为测试表达式)为真,则执行大括号中的语句(称之为执行语句块);若为假,则跳过大括号中的语句。
第三节 按键点灯 - 图6
图2.3.6 if语句程序流程图
上述结构表示:如果“表达式”的条件得到满足(真)则执行语句块。否则Arduino将不执行该语句块。
在编程时if 语句后的大括号可以省略。当省略大括号时,则只有紧随其后的一条语句(以分号结尾)成为执行语句。
if……else 是比 if 更为高级的流程控制语句,通过if…else语句,你可以让Arduino判断某一个条件是否成立,并且根据这一判断结果执行相应的程序,而且可以进行多次条件判断。比如,检测模拟输入的值,当它小于 500 时该执行A操作,大于或等于500 时执行B操作。代码如下:

  1. if (pinFiveInput < 500){
  2. // 执行 A 操作
  3. }
  4. else{
  5. // 执行 B 操作
  6. }

第三节 按键点灯 - 图7
图2.3.7 if…else语句的程序流程图

在else中还可以进行额外的if…else判断,这叫做if语句的嵌套。程序被执行时将会一个一个进行判断,直到某个条件结果为真,此时该条件下的执行语句块将会被执行,然后程序就跳过剩下的条件判断,直接执行到if……else 的下一条语句。当所有检测都为假时,若存在 else 语句块,将执行 else 语句块。在以后的项目中,我们将会时常用到if…else的语法结构。

7.逻辑非运算(!)

如果运算对象为“假”,则为“真”,例如:

  1. if (!x) {
  2. // 满足条件执行的语句
  3. }

如果 x 为“假”,则为真(即如果 x 等于 0)。

8.变量

变量是相对于常量的,顾名思义,变量是会变的量,就是在程序的运行过程中会改变的量,而常量是在整个程序运行过程中不会改变的量。变量和常量其实质是Arduino主板上的芯片对数据的存储,我们在使用变量时,就是在芯片内部开辟一个空间存储一个会变的数据。变量有两个非常重要的属性,分别是变量的数据类型和变量的作用范围。
变量的数据类型确定了在存储器中占用多少空间以及该变量可以表达的数据范围。在Arduino编程中常用的数据类型如表2.3.1所示。
表2.3.1常用数据类型表

数据类型 数据范围 字节数
boolean true/false (1/0) 1字节
char 一个字符值 1字节
unsigned char 0-255 1字节
byte 0-255 1字节
int -32768-32767 2字节
unsigned int 0-65535 2字节
word 0-65535 2/4字节
long -2147483648-2147483647 4字节
unsigned long 0-4294967295 4字节
short -32768-32767 2字节
float -3.4028235E+38-
3.4028235E+38
4字节

为了编程的统一,8位的变量一般就定义为byte型而不是unsigned char型。在创建变量时,能用2字节的变量就不用4字节的,毕竟Arduino的资源有限。ArduinoUNO/NANO属于8位的CPU,处理和存储2字节变量和4字节变量时候,都是转换为8位的单字节数据处理和保存的,所以,4字节变量比2字节变量会增大Arduino的程序运行时间和存储空间,所以在创建变量时候需要考虑变量数据的取值范围,根据取值范围确定变量的类型。
Arduino使用的C语言中的变量具有作用范围的属性。作用范围其实是程序的一个区域,在Arduino编程中有三个地方可以声明变量。它们是:
在函数或代码块内部,称为局部变量。
在函数参数的定义中,称为形式参数。
在所有函数之外,称为全局变量。
局部变量
在函数或代码块中声明的变量是局部变量。它们只能由该函数或代码块中的语句使用。局部变量不能在它们自己之外运行。
全局变量
全局变量在所有函数之外定义,通常位于程序的顶部。全局变量可以被任何函数访问。也就是说,一个全局变量可以在整个程序中声明后使用。
例程:

  1. int t, s;
  2. float c = 0;
  3. void setup () {
  4. }
  5. void loop () {
  6. int x, y;
  7. int z;
  8. x = 0;
  9. y = 0;
  10. z = 10;
  11. }

【自己动手】

任务1:实现串口通讯

1.任务要求
①通过Serial.bedin()函数设置串口通讯波特率为9600,通过Serial.print()函数实现每1秒钟上传一次某个数字到的串口监视器,并查看数据是否正确。尝试用Serial.print()函数和Serial.println()函数发送数据查看两个函数的区别,并控制上传数据的速度为1秒上传一次。
②实现每50ms上传一次数据,上传数据范围是1-100,并且每上传一次后数据自动加1,直到数据到达100后返回。

2.所需硬件
Arduino NANO(UNO)主板,USB数据线。

3.参考程序
①:Arduino IDE参考程序

  1. /*******************
  2. * Arduino串口每1s发送一次数字4到串口监视器
  3. *******************/
  4. void setup()
  5. {
  6. Serial.bedin(9600); //设置串口波特率为9600
  7. }
  8. void loop()
  9. {
  10. Serial.print(4); //串口发送数据4
  11. delay(1000); //等待1000ms
  12. }

Mixly参考程序
第三节 按键点灯 - 图8

②:Arduino IDE参考程序

  1. /*******************
  2. * Arduino串口每50ms发送一次数据到串口监视器
  3. * 发送数据在1-100之间循环
  4. ********************/
  5. byte rec_dat=1;
  6. void setup()
  7. {
  8. Serial.bedin(9600); //设置串口波特率为9600
  9. }
  10. void loop()
  11. {
  12. Serial.print(rec_dat); //串口发送数据
  13. delay(50); //等待50ms
  14. rsc_dat=rsc_dat+1;
  15. if(rsc_dat==100){
  16. rsc_dat=1;
  17. }
  18. }

Mixly参考程序
第三节 按键点灯 - 图9

任务2:读取按键状态并串口发送状态

1.任务要求
通过面包板连接简单的按键电路并编写程序检测按键是否按下,将按键的状态发送到Arduino IDE的串口监视器上查看。

2.所需硬件
Arduino NANO/UNO主板、面包板、USB下载线、直插封装开关按键、面包板专用连接线若干。

3.参考电路
选取按键没有相连的两个引脚,分别连接到电源GND和Arduino的D2引脚,如图2.3.8所示。
第三节 按键点灯 - 图10
图2.3.8 面包板搭建按键电路
4.参考程序
Arduino IDE参考程序

  1. /*******************
  2. * Arduino读取按键的状态信息,发送到串口监视器查看
  3. *******************/
  4. byte m = 1;
  5. void setup() {
  6. Serial.begin(9600);
  7. pinMode(2,INPUT_PULLUP); //设置按键引脚为输入上拉模式
  8. }
  9. void loop() {
  10. m=digitalRead(2); //读取按键状态
  11. Serial.println(m); //发送状态
  12. }

Mixly参考程序
第三节 按键点灯 - 图11

任务3:读取按键状态直接驱动LED灯

1.任务要求
通过面包板搭建按键电路和LED电路,编写程序,实现通过按键的状态值去直接控制LED灯。

2.所需硬件
Arduino NANO/UNO主板、面包板、USB下载线、直插封装开关按键、直插封装LED灯(颜色不限)、面包板专用连接线若干。

3.参考电路
按键电路同任务2,LED阳极(长引脚)连接Arduino的D3引脚,阴极(短引脚)连接Arduino的GND,如图2.3.9所示。

第三节 按键点灯 - 图12
图2.3.9 面包板搭建按键及其LED灯电路
4.参考程序
Arduino IDE参考程序

  1. /*******************
  2. * Arduino读取按键的状态信息,直接驱动LED
  3. *******************/
  4. byte m = 1;
  5. void setup() {
  6. Serial.begin(9600);
  7. pinMode(2,INPUT_PULLUP); //按键连接引脚
  8. pinMode(3,OUTPUT); //LED灯连接引脚
  9. }
  10. void loop() {
  11. m=digitalRead(2); //读取案件状态
  12. digitalWrite(3,m); //按键状态驱动LED
  13. Serial.println(m); //发送状态
  14. }

Mixly参考程序
第三节 按键点灯 - 图13
思考:如何实现让按键按下后LED灯亮起,按键释放后LED熄灭?

任务4:用按键控制LED的亮灭

1.任务要求
通过编程程序,实现用按键去控制LED灯的亮灭。完成按键按下放开一次,LED灯亮起,按键第二次按下放开,LED灯熄灭,如此循环。

2.所需硬件
同任务3。

3.参考电路
同任务3。

4.参考程序
Arduino参考程序

  1. /*******************
  2. * Arduino读取按键的按下并释放的状态,完成后驱动LED实现亮灭状态
  3. *的切换
  4. *******************/
  5. byte m = 1;
  6. byte LED_state=0; //LED的状态
  7. void setup() {
  8. Serial.begin(9600);
  9. pinMode(2,INPUT_PULLUP); //按键连接引脚
  10. pinMode(3,OUTPUT); //LED灯连接引脚
  11. }
  12. void loop() {
  13. m=digitalRead(2); //读取案件状态
  14. if(m==0){
  15. LED_state=!LED_state; //LED状态改变一次
  16. }
  17. digitalWrite(3,LED_state);
  18. Serial.println(m); //发送状态
  19. delay(200);
  20. }

Mixly参考程序
第三节 按键点灯 - 图14
思考:如何提高按键的准确性?

任务5:发送按键按下次数数据到串口监视器查看

1.任务要求
在完成任务4的基础上,编程实现每按下一次按键,通过串口发出一次按键按下的次数数据到Arduino IDE的串口监视器,并查看。

2.所需硬件
同任务3。

3.参考电路
同任务3。

4.参考程序
Arduino IDE参考程序

  1. /*******************
  2. * Arduino读取按键的按下并释放次数并发送到串口监视器查看
  3. *******************/
  4. byte m = 1;
  5. byte LED_state=0; //LED的状态
  6. byte KEY_rec=0; //记录按键按下次数
  7. void setup() {
  8. Serial.begin(9600);
  9. pinMode(2,INPUT_PULLUP); //按键连接引脚
  10. pinMode(3,OUTPUT); //LED灯连接引脚
  11. }
  12. void loop() {
  13. m=digitalRead(2); //读取案件状态
  14. if(m==0){
  15. LED_state=!LED_state; //LED状态改变一次
  16. KEY_rec++; //记录按键按下次数
  17. Serial.println(KEY_rec); //发送次数
  18. }
  19. digitalWrite(3,LED_state); //改变LED状态
  20. delay(200);
  21. }

Mixly参考程序
第三节 按键点灯 - 图15
思考:按键不灵敏的原因是什么?如何提高准确性的前提下提高程序效率和改善灵敏性?

【拓展学习】

按键的抖动处理

以上的任务中,我们加入了延时函数delay()来降低按键检测的速度以达到更好的按键效果,但是会出现一个问题,那就是你写的程序并不一定适合我来使用,当这个时间不匹配时,按键还是会出现不灵敏或失灵的状态,为了解决这个问题,我们首先了解两个时间概念。第一,我们完成一次按键的按下—放开这个动作,所需要的时间是25-200ms,第二,我们的Arduino运行程序的速度,每运行一条语句所需要的时间是微妙级别,也就是说Arduino执行完if()判断语句只需要几微秒,或者十几微妙。这样就会出现按键按下一次,但是程序执行了多次判断的情况。在按键检测的时候我们加入了delay()函数,降低了检测的速度,但是,延时时间做不到和每个人的按键速度相匹配,还是会出现问题。
第三节 按键点灯 - 图16
图2.3.10 按键按下释放波形图
如图2.3.10所示,是按键按下到释放的电压变化波形图。按键没按下时是高电平,按下的瞬间会有10-20ms的前沿抖动信号,这段时间内读取得到的结果是不确定的。然后才是一个较长的稳定信号。当按键被释放后,还会有一个10-20ms的不确定后沿抖动信号,最后变为高电平。
在之前的程序编写过程中,我们只是考虑了按键信号从高电平变为低电平这个过程,也就是上图中的前半部分,后半部分的按键释放都是没有考虑到的。要想得到确定的按键处理程序,必须要考虑到按键释放这个过程,在按键释放后立刻去对按键程序进行处理。在任务5的基础上我们加入按键释放的检测处理,将能得到确定的按键按下次数,在以后的按键程序编写中,如果需要确定的按键按下次数的处理,必须考虑按键释放过程。加入按键释放处理后的任务5的参考程序如下所示。

  1. /*******************
  2. * Arduino读取按键的按下并释放次数并发送到串口监视器查看
  3. *******************/
  4. byte m = 1;
  5. byte LED_state=0; //LED的状态
  6. byte KEY_rec=0; //记录按键按下次数
  7. void setup() {
  8. Serial.begin(9600);
  9. pinMode(2,INPUT_PULLUP); //按键连接引脚
  10. pinMode(3,OUTPUT); //LED灯连接引脚
  11. }
  12. void loop() {
  13. m=digitalRead(2); //读取案件状态
  14. if(m==0){
  15. while(!m){ m=digitalRead(2); }
  16. LED_state=!LED_state; //LED状态改变一次
  17. KEY_rec++; //记录按键按下次数
  18. Serial.println(KEY_rec); //发送次数
  19. }
  20. digitalWrite(3,LED_state); //改变LED状态
  21. delay(10);
  22. }

第三节 按键点灯 - 图17

【思考与讨论】

1.尝试采用两个按键对数据进行加一和减一的设置处理。
2.如果需要多个按键工作时,按键电路该如何设计,程序该如何设计?