重要的内容写在前面:
- 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。
- 个人把这个教程学完之后,整体感觉是很好的,如果有条件的可以先学习一些相关课程,学起来会更加轻松,相关课程有数字电路(强烈推荐先学数电,不然可能会有一些地方理解起来很困难)、模拟电路等,然后就是C++(注意C++是必学的)。
- 文章中的代码都是跟着老师边学边敲的,不过比起老师的版本我还把注释写得详细了些,并且个人认为重要的地方都有详细的分析。
- 一些函数的介绍有参考太极创客官网给出的中文翻译,为了便于现查现用,把个人认为重要的部分粘贴了过来并做了一些修改。
- 如有错漏欢迎指正。
视频链接:1-0 教程介绍_哔哩哔哩_bilibili
太极创客官网:太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料
四、数字输出
1、认识LED与电阻
(1)LED即发光二极管,当它工作(即发光)时,LED产生2伏特左右的压降,阳极的电位要高于阴极。
(2)一般LED都需要串联一个限流电阻,否则如果流过LED的电流过大可能会烧坏LED。
(3)色环电阻分为四环电阻和五环电阻,其色环指示了电阻的阻值,具体规则如下图所示。
2、数字输出函数digitalWrite
(1)digitalWrite函数有两个参数,第一个参数用于指示数字引脚,第二个参数用于指示高电平/低电平,该函数的作用是将数字引脚写为HIGH(高电平)或LOW(低电平),前提是该引脚需要通过pinMode函数设置为输出模式(OUTPUT)。
(2)例——Arduino点亮LED:
①按下图所示将电路接好,电阻的阻值不宜过小(220Ω是一个可选值,只要LED流过的电流不超过20mA即算安全,当然,若电阻阻值过大LED也不会亮)。
②将下面的程序下载到开发板中,可发现外接的LED灯被点亮,能被点亮的原因是程序将引脚5设置为输出高电平,这样LED就有一个正向压降,从而被点亮。
void setup()
{
pinMode(5, OUTPUT); //初始化引脚5为输出模式
digitalWrite(5, HIGH); //将引脚5设置为高电平
}
void loop()
{
}
五、数字输入
1、认识按键开关
如下图所示,当按键未按下时A、D与B、C不导通,当按键按下时A、D与B、C导通。
2、数字输入函数digitalRead
(1)digitalRead函数有一个参数用于指示数字引脚,该函数的作用是读取数字引脚的当前电平,即HIGH(高电平)或LOW(低电平)。
(2)例——按键开关控制电路:
①按下图所示将电路接好,其中电阻的阻值不宜过小,可以选择10kΩ。(实际上这个电路对于引脚2来说像是配置成了上拉输入模式,这是在把上拉电阻R1部分的电路视作Arduino的一部分的前提下而言的,当按键开关不导通时引脚2的输入为高电平,当按键开关导通时引脚2的输入取决于按键开关另一侧的电平高低,在本例中开关的另一侧为地,由于上拉电阻R1分压,引脚2处的电压为零,即输入为低电平)
②将下面的程序下载到开发板中。
int pushButton = 2; //用变量pushButton指示引脚2
void setup()
{
Serial.begin(9600); //串口通讯初始化,波特率为9600
pinMode(pushButton, INPUT); //配置按键引脚为输入模式
}
void loop()
{
int buttonState = digitalRead(pushButton); //读取引脚2的当前输入,记录在变量buttonState中
Serial.println(buttonState); //通过串口传输当前按键状态(0表示按下,1表示松开)
delay(1); //为确保程序稳定运行进行短暂停止
}
③接着使用串口助手打开串口(之前选择端口时选择哪个端口就打开哪个串口即可),当前按键开关未按下,也就是按键不导通,开发板的引脚2通过上拉电阻R1与5V电源相连,处于一个高电平的状态,在程序中对应着数字1,程序通过digitalRead函数读取到此状态记录在变量buttonState中,接着通过串口将“1”传输到电脑上。
④按下按键开关(不松开),此时按键导通,开发板的引脚2通过按键开关导通的电路与地相连,由于上拉电阻R1将5V的电压全部分走,所以引脚2处的电压为零,即处于一个低电平的状态,在程序中对应着数字0,程序通过digitalRead函数读取到此状态记录在变量buttonState中,接着通过串口将“0”传输到电脑上。
六、逻辑控制
1、C++相关语法回顾
(1)关系运算符和关系表达式:
①C++提供了六种关系运算符(如下图所示),它们都是双目运算符(也就是有两个运算分量),运算结果为逻辑型值true(对应整数1)或false(对应整数0)。
②由一个关系运算符连接前后两个数值表达式而构成的式子称为关系表达式,简称关系式,当关系式成立时,其计算结果为逻辑值真(true),否则为逻辑值假(false)。
(2)逻辑运算符和逻辑表达式:
①C++提供了三种逻辑运算符,其中“!”是单目运算符(也就是只有一个运算分量),“&&”和“||”是双目运算符。
②逻辑运算的运算分量是逻辑型数据(逻辑常量、逻辑变量、关系表达式等都是逻辑型数据),由逻辑型数据和逻辑运算符连接而成的式子称为逻辑表达式,简称逻辑式。一个数值表达式也可以作为逻辑型数据使用,当值为0时则默认是逻辑值false,当值为非0时则默认为是逻辑值true。
(3)if语句:
①基本if语句:根据给定条件是否成立来决定要不要执行一条语句或语句块。
if(条件)
{
条件满足执行的语句/语句块
}
②if…else语句:根据给定条件是否成立来决定执行两部分语句中的哪一部分。
if(条件)
{
条件满足执行的语句
}
else
{
条件不满足执行的语句
}
③if语句的嵌套:
if(条件1)
{
条件1满足执行的语句
}
else if(条件2)
{
条件1不满足,条件2满足执行的语句
}
else
{
条件1、条件2不满足执行的语句
}
(4)switch语句:
switch(表达式)
{
case 结果1:执行语句; break;
case 结果2:执行语句; break;
...
case 结果n:执行语句; break;
default:执行语句; break; //最后这个break可有可无
}
①首先计算出switch后面的表达式的值,设此值为E。
②case后面跟着的结果如果是表达式,先计算出它们的值,设它们分别为C1、C2、…、Cn。
③将E依次与C1、C2、…、Cn进行比较,如果E与某个值相等,则从该值所在的case标号语句开始执行各个语句序列,直至遇到break语句(在不出现break语句的情况下,将一直执行到switch语句结束)。
④如果E与所有值都不相等且存在default标号,则从default标号语句起开始向下执行,在不出现跳转语句的情况下,一直执行到switch语句结束。
⑤如果E与所有值都不相等且不存在default标号,则switch语句不会执行任何操作。
(5)for语句:
①for语句的语法格式:
for(起始表达式; 条件表达式; 末尾循环体)
{
循环语句
}
②for语句的执行过程:
[1]运行起始表达式。
[2]计算条件表达式的值,如果此值不等于0(即循环条件为“真”)则转向下一步,如果此值等于0(即循环条件为“假”)则结束for循环。
[3]执行一遍循环体。
[4]运行末尾循环体,然后转向第二步。
(6)while语句:
①while语句的语法格式:
while(循环条件)
{
循环语句
}
②while语句的执行过程:
[1]判断循环条件是否为真,如果为真(值为非0)则转向下一步,如果为假(值为0)则结束while循环。
[2]运行循环体,接着转回上一步。
(7)do…while语句:
①do…while语句的语法格式:
do
{
循环语句
} while(循环条件);
②do…while语句的执行过程:
[1]执行一遍循环体。
[2]判断循环条件是否为真,如果为真(值为非0)则转向上一步,如果为假(值为0)则结束do…while循环。
(8)break语句:
①break语句又称为跳出语句,由关键字break加上一个分号构成。
②break语句只能用在switch语句和循环语句中。
[1]在switch语句中,break用来使执行流程跳出switch语句,而继续执行switch语句之后的语句。
[2]在循环语句中,break用来使执行流程跳出本层循环体(一个break语句只能跳出一层)。
(9)continue语句:
①continue语句又称为继续语句,由关键字continue加上一个分号构成。
②continue语句只能用在循环语句中,它的功能是跳过循环体中尚未执行的语句并结束本次循环,然后进行下一次是否执行循环的条件判定。
[1]在while和do…while循环中,continue语句将使执行流程直接跳转到循环条件的判定部分,然后决定循环是否继续进行。
[2]在for循环中,当遇到continue时,执行流程将跳过循环体中余下的语句,而专区执行for语句中的“末尾循环体”,再根据循环条件的判断以决定循环是否继续进行。
2、例1——按键开关控制LED
(1)通过面包板,将按键开关的一侧与开发板的引脚2相连,将按键开关的另一侧与开发板的GND相连。
(2)将下面的程序下载到开发板中。
bool pushButton; //记录按键是否按下的变量(布尔类型)
void setup()
{
Serial.begin(9600); //串口通讯初始化,波特率为9600
pinMode(2, INPUT_PULLUP); //将引脚2设置为输入上拉模式
pinMode(13, OUTPUT); //将引脚13设置为输出模式
}
void loop()
{
pushButton = digitalRead(2); //将开关状态数值读取到变量sensorVal中
Serial.println(pushButton); //串口输出开关状态数值
if (pushButton)
{ //如果按钮没有按下
digitalWrite(13, LOW); //引脚13输出低电平,熄灭LED
}
else
{ //否则
digitalWrite(13, HIGH); //引脚13输出高电平,点亮LED
}
}
(3)观察现象:
①当按键未按下时,引脚2没有任何外界输入,但由于将其配置为输入上拉模式,所以引脚2此时的状态为高电平,digitalRead将引脚2此时的状态“1”记录在布尔类型变量pushButton中,由if语句对其进行判断,由于“1”代表true,即满足条件,执行熄灭LED的操作。
②当按键按下时,引脚2通过按键开关与GND直接相连,所以引脚2此时的状态为低电平,digitalRead将引脚2此时的状态“0”记录在布尔类型变量pushButton中,由if语句对其进行判断,由于“0”代表false,即不满足条件,执行点亮LED的操作。
3、例2——MC猜数字
(1)认识数码管:
①LED数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件(也就是说数码管显示数字的原理只是点亮LED灯原理的plus版而已)。
②数码管分为共阳极数码管和共阴极数码管,原理图如下所示。对于共阴极数码管,如果想让其显示数字,首先要将其公共阴极置为低电平(否则永远不可能产生正向电流),对于各个LED的阳极,需要点亮哪个LED灯,就将哪个LED灯的阳极置为高电平;对于共阳极数码管,如果想让其显示数字,首先要将其公共阳极置为高电平(否则永远不可能产生正向电流),对于各个LED的阴极,需要点亮哪个LED灯,就将哪个LED灯的阴极置为低电平。
(2)按下图所示将电路接好(图中的数码管为共阴极数码管)。
(3)将下面的程序下载到开发板中,可以发现数码管每隔1000毫秒显示一个数字,显示时长为500毫秒。
void displayClear() //清理显示内容并停顿0.5s的函数
{
digitalWrite(3, LOW); digitalWrite(4, LOW); digitalWrite(5, LOW);
digitalWrite(6, LOW); digitalWrite(7, LOW); digitalWrite(8, LOW);
digitalWrite(9, LOW);
delay(500);
}
void setup()
{
pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT);
pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT);
}
void loop()
{
//显示数字1并停顿0.5s
digitalWrite(4, HIGH); digitalWrite(7, HIGH);
delay(500);
displayClear(); //清理显示内容并停顿0.5s
//显示数字2并停顿0.5s
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(8, HIGH); digitalWrite(9, HIGH);
delay(500);
displayClear(); //清理显示内容并停顿0.5s
//显示数字3并停顿0.5s
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(7, HIGH); digitalWrite(8, HIGH);
delay(500);
displayClear(); //清理显示内容并停顿0.5s
}
①当显示一个数字后,对应LED的阳极被置为高电平,如果需要将显示的数字换成另一个数字,那么在此之前需要先将之前被点亮的LED全部熄灭,否则先前的数字显示没有取消的同时再显示另一个数字,就会造成应熄灭的LED被点亮的现象,从而引起显示错乱。
②由于清除显示的步骤对每个数字显示而言都如出一辙,故可将其封装成一个子函数clean,需要清除显示时调用该子函数即可。
(4)完整程序代码:
①初始化工作:引脚2连接按键开关,加上没有外接上拉电阻,故应配置为上拉输入模式;引脚3-9连接数码管中LED灯管的阳极,应配置为输出模式;随机数(伪随机数)的生成由随机数种子决定,考虑到空引脚A0的取值不定,故以引脚A0在板子通电时刻的信号作为随机数种子。
int myNumber; //记录随机抽选的数,程序编译时初始化为0
void setup()
{
pinMode(2, INPUT_PULLUP); //引脚2配置为上拉输入模式
Serial.begin(9600); //配置串口的波特率为9600
int pinNumber = 3; //引脚3-9均配置为输出模式
while(pinNumber <= 9)
{
pinMode(pinNumber, OUTPUT);
pinNumber = pinNumber + 1;
}
randomSeed(analogRead(A0)); //用引脚A0的信号做随机数种子
}
②将清空显示操作封装为一个函数:将各个LED管的阳极置为低电平,也就是将引脚3-9均置为低电平。
void displayClear()
{
digitalWrite(3, LOW); //熄灭引脚3连接的LED灯
digitalWrite(4, LOW); //熄灭引脚4连接的LED灯
digitalWrite(5, LOW); //熄灭引脚5连接的LED灯
digitalWrite(6, LOW); //熄灭引脚6连接的LED灯
digitalWrite(7, LOW); //熄灭引脚7连接的LED灯
digitalWrite(8, LOW); //熄灭引脚8连接的LED灯
digitalWrite(9, LOW); //熄灭引脚9连接的LED灯
}
③将显示数字操作封装为一个函数:函数参数为需要显示的数字,通过switch语句分情况进行处理。
void displayNumber(int ledNumber)
{
displayClear(); //清除上一次的显示(其实这一步在本项目中算多此一举,但还是建议加上)
switch(ledNumber)
{
case 1: //显示数字1
digitalWrite(4, HIGH); digitalWrite(7, HIGH); break;
case 2: //显示数字2
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(8, HIGH); digitalWrite(9, HIGH); break;
case 3: //显示数字3
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(7, HIGH); digitalWrite(8, HIGH); break;
case 4: //显示数字4
digitalWrite(4, HIGH); digitalWrite(5, HIGH); digitalWrite(6, HIGH);
digitalWrite(7, HIGH); break;
case 5: //显示数字5
digitalWrite(3, HIGH); digitalWrite(5, HIGH); digitalWrite(6, HIGH);
digitalWrite(7, HIGH); digitalWrite(8, HIGH); break;
case 6: //显示数字6
digitalWrite(3, HIGH); digitalWrite(5, HIGH); digitalWrite(6, HIGH);
digitalWrite(7, HIGH); digitalWrite(8, HIGH); digitalWrite(9, HIGH); break;
case 7: //显示数字7
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(7, HIGH); break;
case 8: //显示数字8
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(6, HIGH); digitalWrite(7, HIGH); digitalWrite(8, HIGH);
digitalWrite(9, HIGH); break;
case 9: //显示数字9
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(5, HIGH);
digitalWrite(6, HIGH); digitalWrite(7, HIGH); digitalWrite(8, HIGH); break;
case 0: //显示数字0
digitalWrite(3, HIGH); digitalWrite(4, HIGH); digitalWrite(6, HIGH);
digitalWrite(7, HIGH); digitalWrite(8, HIGH); digitalWrite(9, HIGH); break;
default: //程序有误时会显示非数字图形
digitalWrite(4, HIGH); digitalWrite(5, HIGH); digitalWrite(7, HIGH);
digitalWrite(8, HIGH); digitalWrite(9, HIGH);
}
}
④将生成随机数的操作封装为两个函数:
[1]displayRandom函数的作用:在引脚3-9中随机选取一个引脚进行点亮,
[2]getRandomNumber函数的作用:在minNumber-maxNumber中随机选取一个整数并返回,同时附带有选取随机数过程的动画(类似于转盘抽奖,也会有个抽奖的动画过程)。
void displayRandom()
{
int randomPin = random(3,10); //在引脚3-9随机选取一个引脚
digitalWrite(randomPin, HIGH); //点亮该引脚
}
int getRandomNumber(int minNumber, int maxNumber)
{
int randomNumber;
int i = 0; //局部变量可能不会自动初始化为0,“= 0”不能省略
while(i < 15)//正在随机选取数字的“过场动画”,随机选取的动画为随机单个灯管闪烁,共15次
{
i = i + 1;
randomNumber = random(minNumber,maxNumber); //在[minNumber,maxNumber)内生成一个随机整数
displayRandom(); //在引脚3-9中随机选取一个引脚对应的LED灯管进行点亮
delay(100); //暂停0.1秒
displayClear(); //熄灭刚刚点亮的LED灯管
delay(100); //暂停0.1秒
//通过串口输出观察随机数的选取情况
Serial.print("i = ");
Serial.println(i);
Serial.print("randomNumber = ");
Serial.println(randomNumber);
Serial.println(""); //串口输出换行
}
return randomNumber; //灯管闪烁过程中共选取了15个随机数,将最后一次选取的随机数返回
}
⑤程序主循环部分:当按键未被按下时,引脚2处于高电平状态,此时仅不断重复显示数myNumber;当按键被按下时,引脚2处于低电平状态,此时会调用生成新随机数的函数(同时会附有生成随机数过程的动画效果),接着再显示新随机数。(需要注意的是,一般来讲非持续按下的按键模块在大型项目中都需要消抖,但是在本小项目中并不需要)
void loop()
{
if (!digitalRead(2)) //如果按键被按下
{
myNumber = getRandomNumber(0, 10); //在0-9中选取一个随机数
}
displayNumber(myNumber); //显示当前的数
}