零基础入门学用Arduino 第一部分(三)

重要的内容写在前面:

  1. 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。
  2. 个人把这个教程学完之后,整体感觉是很好的,如果有条件的可以先学习一些相关课程,学起来会更加轻松,相关课程有数字电路(强烈推荐先学数电,不然可能会有一些地方理解起来很困难)、模拟电路等,然后就是C++(注意C++是必学的)
  3. 文章中的代码都是跟着老师边学边敲的,不过比起老师的版本我还把注释写得详细了些,并且个人认为重要的地方都有详细的分析。
  4. 一些函数的介绍有参考太极创客官网给出的中文翻译,为了便于现查现用,把个人认为重要的部分粘贴了过来并做了一些修改。
  5. 如有错漏欢迎指正。

视频链接:1-0 教程介绍_哔哩哔哩_bilibili

太极创客官网:太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料

七、模拟输出

1、模拟输出函数analogWrite

(1)analogWrite函数借助PWM调制,可以用于输出模拟信号,它有两个参数,第一个参数是模拟引脚号,第二个参数是0到255之间的PWM频率值,0对应“off”,255对应“on”。

(2)在Arduino UNO控制器中,5号引脚和6号引脚的PWM频率为980Hz。在一些基于ATmega168和ATmega328的Arduino控制器中,analogWrite函数支持引脚 3、5、6、9、10、11(在开发板上的引脚处有“~”标记)。

(3)PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域。

(4)PWM频率值0-255对应占空比0%-100%,呈正比关系,如下图所示。如果有规律地改变PWM频率值,就能产生等效的模拟波形(如上图紫色虚线描绘的波形),当然,PWM频率值固定,也能产生一个等效的模拟电压,其值不局限于高电平或低电平标定的电压,电压值取决于PWM频率值

2、例1——按键控制LED亮度

(1)根据下图所示将电路连接好,其中电阻可选220Ω(总之在确保不宜过小的前提下不要太大即可)。

(2)将下面的程序下载到开发板中,首先LED会获得一个适中的亮度,持续按下按键1,LED的亮度会持续下降直至熄灭,持续按下按键2,LED的亮度会持续上升直至程序设定的最大值。

bool pushButton1;   // 创建布尔型变量用来存储按键开关1的电平状态
bool pushButton2;   // 创建布尔型变量用来存储按键开关2的电平状态
int ledPin = 9;        //LED引脚号
int brightness = 128;  //LED亮度参数(255/2=127.5,一个适中的亮度参数)

void setup() 
{
  pinMode(2, INPUT_PULLUP); //将引脚2设置为输入上拉模式
  pinMode(8, INPUT_PULLUP); //将引脚8设置为输入上拉模式
  pinMode(ledPin, OUTPUT);  //将LED引脚设置为输出模式
  Serial.begin(9600);      //启动串口通讯,波特率为9600
}

void loop() 
{
  pushButton1 = digitalRead(2); //读取引脚2电平状态并将其赋值给布尔变量pushButton1
  pushButton2 = digitalRead(8); //读取引脚8电平状态并将其赋值给布尔变量pushButton2
  
  if (!pushButton1 && brightness > 0)  //当(持续)按下按键开关1并且LED亮度参数大于0
  {
    brightness--;                           //减低LED亮度参数
  } 
  else if (!pushButton2 && brightness < 255) //当(持续)按下按键开关2并且LED亮度参数小于255
  {
    brightness++;                           //增加LED亮度参数
  }
  analogWrite(ledPin, brightness);         //模拟输出控制LED亮度
  Serial.println(brightness);              //将LED亮度参数显示在串口监视器上
  delay(10);
}

①持续按下按键1,引脚2将持续处于低电平,每执行一次loop函数,brightness都进行一次自减,直至按键1被松开或者亮度参数减小至0,同时loop函数会将当前的亮度参数作为模拟量输出到引脚9上,并通过串口将当前的亮度参数输出到监视器上。

②持续按下按键2,引脚8将持续处于低电平,每执行一次loop函数,brightness都进行一次自减,直至按键2被松开或者亮度参数增加至255,同时loop函数会将当前的亮度参数作为模拟量输出到引脚9上,并通过串口将当前的亮度参数输出到监视器上。

③程序没有做异常情况的处理,比如两个按键同时按下,这种情况理论上是不允许的。

④亮度参数的取值范围为0-255,若不将其限定在此范围,那么它作为analogWrite函数的参数,会发生强制类型转换,最终的结果也会在0-255之间,至于强制类型转换的具体过程,这里不再赘述。

3、例2——LED呼吸灯

(1)根据下图所示将电路连接好,其中电阻可选220Ω(总之在确保不宜过小的前提下不要太大即可)。

(2)将下面的程序下载到开发板中,可以发现LED灯的亮度从暗变亮,再从亮变暗,以此往复,做一个周期性的“呼吸”。

void setup() 
{
  pinMode(9, OUTPUT);      //设置9号引脚为输出模式
  Serial.begin(9600);     //启动串口通讯
}

void loop() 
{
  // LED由暗到明
  for (int brightness = 0; brightness <= 255; brightness+=5)
  {
    analogWrite(9, brightness);   
    Serial.println(brightness);
    delay(10);
  }
  // LED由明到暗  
  for (int brightness = 255; brightness >=0 ; brightness-=5)
  {
    analogWrite(9, brightness);
    Serial.println(brightness);
    delay(10);
  }
}

①LED由暗到明的过程由一个for循环控制,首先亮度参数的初始值为0,它会随着时间的流逝而逐渐增加,直至到达最大值255,for循环结束。

②LED由明到暗的过程也由一个for循环控制,首先亮度参数的初始值为255,它会随着时间的流逝而逐渐减小,直至到达最小值0,for循环结束。

③loop函数重复执行上述两个for循环,以此达到呼吸灯的效果。

八、模拟输入

1、模拟输入函数analogRead

(1)analogRead函数仅有一个参数用于指示模拟引脚,该函数用于从Arduino的模拟输入引脚读取模拟电压的数值

(2)Arduino控制器有多个10位数模转换通道,这意味着Arduino可以将0-5V的电压输入信号映射到数值0-1023(2^{10})。关于数模转换这里不进行详细介绍,总之就是将输入的模拟量转换成单片机能“读懂”的数字信号,或者说将模拟量转换为二进制的形式供计算机接收。

(3)引脚的输入范围以及解析度可以使用analogReference指令进行调整。

(4)Arduino控制器读取一次模拟输入需要消耗100微秒的时间(0.0001秒),控制器读取模拟输入的最大频率是每秒10000次。

(5)在模拟输入引脚没有任何连接的情况下,用analogRead指令读取该引脚,这时获得的返回值为不固定的数值,这个数值可能受到多种因素影响,比如将手靠近引脚也可能使得该返回值产生变化。

2、例——电位器控制LED灯亮度

(1)根据下图所示将电路连接好,其中电位器的总电阻为10kΩ,连接LED的电阻可选220Ω(总之在确保不宜过小的前提下不要太大即可)。

(2)将下面的程序下载到开发板中,转动电位器,可以发现LED灯的亮度随之发生变化。

void setup() 
{
  Serial.begin(9600);  //串口通讯初始化(9600 bps)
  pinMode(9, OUTPUT);  //设置9号引脚为输出模式
}
 
void loop() 
{
  int analogInputVal = analogRead(A0);  //读取模拟输入值 
  int brightness = map(analogInputVal, 0, 1023, 0, 255); //将模拟输入数值(0 - 1023)等比映射到模拟输出数值区间(0-255)内,根据该映射关系由analogInputVal得出brightness
  analogWrite(9, brightness);  //根据模拟输入值调节LED亮度

  //将结果通过串口监视器显示
  Serial.print("analogInputVal = ");
  Serial.println(analogInputVal);
   
  Serial.print("brightness = ");
  Serial.println(brightness);
   
  Serial.println("");
}

①电位器的两端引脚分别接5V和GND,中间引脚连接Arduino的引脚A0,转动电位器,引脚A0的电压随之发生改变,程序需要不断地将当前电压记录在变量analogInputVal中,当然,记录的值并不完全等于电压值,只是它与实际电压值有一个映射关系(或者说线性关系)。

②analogRead函数的返回值范围为0-1023(针对本项目而言),而LED灯的亮度参数取值范围为0-255,虽然二者取值范围不同,但是可以为它们构造一个等比映射的关系,如下图所示,这样,引脚A0的电压值就能与LED灯的亮度参数存在一个映射关系(或者说线性关系)。

③工作原理示意图:

九、Arduino内存

1、Arduino内存的存储介质

(1)FLASH(闪存):可用于存储数量较大的静态信息,如Arduino程序,其特点是烧录进开发板后无需做其它修改(而且也不能修改,Arduino程序只能对FLASH进行读操作),另外当Arduino断电后,FALSH中存储的内容不会丢失。

(2)SRAM(静态随机存储器):可用于存储数量较小的动态信息,如程序的变量,Arduino程序可对SARM中存储的内容进行修改,另外当Arduino断电后,SARM中存储的内容会随之丢失。

(3)EEPROM(电可擦除可编程只读存储器):可用于存储断电后需要保持的程序变量,其存储的信息可通过电信号擦除,而且还可通过电信号将信息写入其中,但是程序只能读取其中的信息,而不能写入或修改其中的信息,当Arduino断电后,EEPROM中存储的内容不会丢失

2、EEPROM的读写

(1)EEPROM的主要操作为读取和写入。对于最基本的读写操作,可以通过EEPROM.read()以及EEPROM.write()来完成,但是这两个函数具有局限性,EEPROM的每一个地址可以存储的信息为1字节,这就限制了EEPROM的每一个地址内所能单独存储的整数数值为0~255区间。由于EEPROM.read()以及EEPROM.write()每一次只能读或写一个字节的数据,假如需要存储超出0~255范围的整数数值或者带有小数点的浮点数,就需要用多个EEPROM协作存储来完成,好在Arduino库还配有EEPROM.put()和EEPROM.get()这两个函数。

(2)例——向EEPROM存储数据:

#include <EEPROM.h>  //使用EEPROM库需包含其头文件

int addr = 0;  //被写入数据的EEPROM地址编号(即哪一个存储地址将要被写入数据)
 
void setup() 
{
  /** setup内无内容 **/
}

void loop() 
{
  int val = 123;  //将要存储于EEPROM的整数数值
  EEPROM.write(addr, val);  //将数值val写入EEPROM,地址为addr
  addr = addr + 1;  //转入下一存储地址

  if (addr == EEPROM.length()) 
  {
    addr = 0;  //当存储地址序列号达到EEPROM的存储空间结尾,重新返回到EEPROM的开始地址
  }
 
  delay(100);
}

①EEPROM.write(addr, val):将1字节大小的数值val(取值范围0-255)写入EEPROM,地址为addr。

②EEPROM.length():返回EEPROM的总存储空间大小。(不同型号Arduino开发板具有不同大小的EEPROM存储空间,对于Arduino Uno,其EEPROM共有1kb存储空间,即允许使用的EEPROM地址序列号为0-1023)

(3)例——基于上例,从EEPROM读取数据:

#include <EEPROM.h>  //使用EEPROM库需包含其头文件

int address = 0;  //从EEPROM的第一个字节(地址序号0)开始读取
byte value;
  
void setup() 
{
  //初始化串口通讯并等待初始化完成
  Serial.begin(9600);
  while (!Serial);  //等待初始化串口通讯初始化完成
}
  
void loop() 
{
  value = EEPROM.read(address);  //从EEPROM存储地址为address的存储单元中读取数据
  Serial.print(address);
  Serial.print("\t");
  Serial.print(value, DEC);
  Serial.println();
  address = address + 1;

  if (address == EEPROM.length()) 
  {
    address = 0;//当存储地址序列号达到EEPROM的存储空间结尾,重新返回到EEPROM的开始地址
  }
  
  delay(500);
}

①EEPROM.read(address):从EEPROM存储地址为address的存储单元中读取1字节的数据。

②EEPROM.length():返回EEPROM的总存储空间大小。(不同型号Arduino开发板具有不同大小的EEPROM存储空间,对于Arduino Uno,其EEPROM共有1kb存储空间,即允许使用的EEPROM地址序列号为0-1023)

(4)EEPROM.put(Addr, Var):将Var写入EEPROM,Var的大小可以不止1字节,那么它在EEPROM中可以占用不止1字节的空间,其起始地址为Addr。

①例——向EEPROM存储浮点型数据:每一个浮点型变量所占的内存大小是4个字节,假如选择从EEPROM地址序号0的存储单元开始来存储变量floatVar1,那么Arduino将会把序号0-3的存储单元都分配给变量floatVar1,所以变量floatVar2的存储起始地址序号为4。

#include <EEPROM.h>
  
void setup() 
{
  Serial.begin(9600);
  
  float floatVar1 = 123.456;         //将要存储入EEPROM的浮点型数据1
  float floatVar2 = 234.567;         //将要存储入EEPROM的浮点型数据2
   
  int fVar1Addr = 0;                 //存储floatVar1的EEPROM地址
  int fVar2Addr = 4;                 //存储floatVar2的EEPROM地址
  
  EEPROM.put(fVar1Addr, floatVar1);  //将floatVar1存入EEPROM地址1
  delay(10);
  EEPROM.put(fVar2Addr, floatVar2);  //将floatVar2存入EEPROM地址2
  delay(10);   

  Serial.println("Finished writing float data!");
}
  
void loop() 
{
  /* 无内容 */
}

②例——向EEPROM存储整型数据:每一个整型变量所占的内存大小是2个字节(对于Arduino Uno来说是这样),假如选择从EEPROM地址序号1的存储单元开始来存储变量i,那么Arduino将会把序号1-2的存储单元都分配给变量i。

#include <EEPROM.h>
 
void setup() 
{
 
  Serial.begin(9600);
 
  int i = 9999;               //将要存储入EEPROM的整型数据
  int address = 1;            //EEPROM存储地址
 
  EEPROM.put(address, i);    //将整型变量i存入EEPROM
 
  Serial.println("Finished writing int data type!");
}
 
void loop() 
{
  /* 无内容 */
}

(5)EEPROM.get(Addr, Var):以Addr为起始地址,从EEPROM的相应存储单元开始读取数据,具体读取多少字节由Var的变量类型决定。

①例——从EEPROM读取浮点型数据:在例“向EEPROM存储浮点型数据”的基础上,确保EEPROM中已经写入可以获取的浮点型数据了,由于floatVar1的变量类型为浮点型,占用4个字节,所以Arduino会从EEPROM的0号存储单元开始读取4字节数据返回到floatVar1,于是下一个浮点数据需从4号存储单元开始读取。

#include <EEPROM.h>
   
void setup() 
{
   
  float floatVar1;   //此变量用于存储获取到的EEPROM浮点型数据1
  float floatVar2;   //此变量用于存储获取到的EEPROM浮点型数据2
   
  int fVar1Addr = 0;  //存储floatVar1的EEPROM地址
  int fVar2Addr = 4;  //存储floatVar2的EEPROM地址
   
  Serial.begin(9600);
  Serial.println("Get float from EEPROM: ");
   
  //从EEPROM中获取浮点型数据
  EEPROM.get(fVar1Addr, floatVar1);
  delay(10);
  EEPROM.get(fVar2Addr, floatVar2);
  delay(10);
    
  Serial.print("floatVar1 = ");   
  Serial.println(floatVar1, 3);  
  Serial.print("floatVar2 = ");     
  Serial.println(floatVar2, 3);  
}

②例——从EEPROM读取整型数据:在例“向EEPROM存储整型数据”的基础上,确保EEPROM中已经写入可以获取的整型数据了,由于i的变量类型为整型,占用2个字节,所以Arduino会从EEPROM的1号存储单元开始读取2字节数据返回到i。

#include <EEPROM.h>
   
void setup() 
{
   
  int i;             //此变量用于存储获取到的EEPROM整数型数据
  int eeAddress = 1; //起始获取信息的EEPROM地址
   
  Serial.begin(9600);
  Serial.print("Get int from EEPROM: ");
  
  EEPROM.get(eeAddress, i);  //从eeAddress地址获取整数型数据
  Serial.println(i);    

}
   
void loop() 
{
  /* Empty loop */
}

 

(6)EEPROM.update()与EEPROM.write()类似,同样可以用来向EEPROM写入数据,但是与EEPROM.write()不同的是,EEPROM.update()只会更新EEPROM中的数据,也就是说,只有在将要写入EEPROM的数据与EEPROM内现存的数据不同时,EEPROM.update()才会将这一数据写入EEPROM。

3、内存优化

(1)使用串口监视器输出一个字符串常量时,这个字符串常量存储在SARM中,但实际上这个字符串常量在程序编译后并不需要改变,它完全可以存储在FALSH中,于是可以将这个字符串常量用“F()”包含,这样,这个字符串常量就会存储在FALSH中,从而节省SARM的空间,更合理地利用内存。

(2)在全局区建立的常变量原本也默认存放在SARM中,但全局常变量本身就不允许被改变,而且它也不像局部变量那样生命周期有限,程序只要运行,全局(常)变量就一直存在,所以它也可存放在FALSH中,具体声明方式为在类型声明后添加“PROGMEM”。

(3)在能实现需求的前提下,应尽可能地使用占用内存少的数据类型,从而减少内存的浪费。

(4)在能实现需求的前提下,必须删除无用的代码,另外仅用于调试的代码,在产品上市前也应将其删除(或将其注释,或用条件编译区分也可)。

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值