这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 【Arduino教程】06 数模信号的转换机制与编程

共9条 1/1 1 跳转至

【Arduino教程】06 数模信号的转换机制与编程

工程师
2021-08-14 09:18:00     打赏

--------------------------关于讲法的简介-----------------------

近期开始要在Arduino的讲解上渐渐进入正题了,在具体应该怎么讲才能让更多人懂得更全面,学的更快。在上一讲之中提到了Arduino的写法基本上是基于C++的一种写法。网络上有不少资料讲解Arduino的语法和写法一类的资料,所以笔者认为或者无需更进一步进行相应的语法或写法讲解。然而在笔者参阅了不少阅读资料后发现,不少现有的Arduino语法的相关讲解是比较有局限性的,所以笔者认为还是有必要对相关的语法问题进行讲解,会提供给大家相关阅读资料并且在阅读时进行解说。

此外,目前网络上有很多的Arduino相关的讲解材料,而这些材料之中有些特别偏重于理论,有些特别偏重于做项目,所以在剩余系列的博客撰写当中,笔者将要践行一条项目和实践并重的道路。所以,笔者要先讲例子,然后再讲例子背后的原理,然后如何去通过这些原理实现一些好玩的项目。

所以,讲解的路线就是例子+原理+好玩的项目+相关语法。

--------------------------------------------------------------------

从一些基础的程序讲起(digital/ analogWrite的区别):

Arduino的一个最基础程序,就是Blink。控制一个LED灯的闪烁。而这个程序是所有的初学者学习的第一个程序。所以,就从这个程序开始讲解Arduino的基础语法和逻辑语句。

在图一和图二当中,分别为Blink的电路搭建以及代码实现。

image.png

图一,Blink的电路搭建

image.png

图二,Blink的基础代码

首先,可以看到这段代码当中有类似于C语言中两个类似函数的程序(红色框和橘色框)。第一个是 void setup() { },第二个是void loop() { }。在这两个程序中,setup部分的程序只会运行一次,loop部分的程序会反复不断的运行。几乎所有的Arduino程序中,都需要先进行setup的程序编写,然后进行loop的程序编写。Setup之中所有的程序类似于C之中的initialize一样,而在loop之中,则是严格的按照代码给电子器件下达的指令不断的运行。就算把和电脑连接的数据线拔出,只要有一个外接电源,复制进控制器的这段代码依然会不断的运行。

在setup函数之中,pinMode(pin, mode); pinMode是指引脚的配置模式。在括号中,pin是指定输出的引脚编号 (基本适用于所有带pin的指令) ,参数mode是配置模式。在这段指令中的pin为LED_BUILTIN,这个是在不同的Arduino控制器中默认的一个信号输出。而在Mega2560之中,它是指代引脚D13。

配置模式一般有三种,如下表(表一)所示。

image.png

表一:模式名称表

输入输出模式都比较好理解,输入相当于是默认给控制器下一道指令让它去处理,而输出是让控制器输出一道指令于相连接的部件。输入上拉模式比较特殊,会在最后做详细的讲解。 

在loop函数之中,digitalWrite (pin, value); 参数value是指要输出的电平,使用HIGH是输出高电平,使用LOW是输出低电平。低电平是0V,高电平是5V。低电平的含义就是将输出电源关闭,而高电平是输出给所连接的电子器件一个高电压使其运作。而delay(1000),是指延迟1000微秒(1秒)。所以就是让这个灯开一秒,关一秒。

而在另外一种情况下,在pinMode指令中,其mode除了OUTPUT和INPUT外也可以被定义为1和0, 在digitalWrite指令中,HIGH和LOW也可以被定义为1和0。以上两个指令可以被改写为:

在第一个函数中,

pinMode (LED_BUILTIN, 1) ;

在第二个函数中,  

digitalWrite (LED_BUILTIN, 1) ;
    delay (1000) ;
digitalWrite (LED_BUILTIN, 0) ;
    delay (1000) ;

LED_BUILTIN直接写13也可以。

以上是一个使用数字引脚去控制LED灯的绝佳案例。

而现在要举一个模拟信号引脚输出的例子,去实现同样的功能,以便大家了解两种Arduino输出端口的基本形式。现在可以称这个例子为模拟输出闪烁。

        image.png

图三,Analog_Blink的基础代码

在这段代码之中,实现的最终功能是和Blink一样的。唯一的区别就是将之前插入数字引脚13的线重新插入模拟引脚A0当中(如图四所示)。并且将第二个函数中digitalWrite(LED_BUILTIN, 1)的指令改写为analogWrite(A0, 255), 以及digitalWrite(LED_BUILTIN, 0)改写为analogWrite(A0, 0)。

Analog/DigitalRead() ,以及Analog/DigitalWrite() 作为Arduino的两种基础输出模式,会在各种不同类型的场景下应用。而在有些情况下,他们也是有想通之处的,就如同我在上面所举的例子一样。而为什么要在analogWrite() 指令的后半段中写255,要先在这卖个关子,留到下一讲具体解释。

image.png

图四,Analog_Blink的电路搭建

数字和模拟信号的进一步详解(digital/ analogRead的区别):

除了上文中所叙述的analog/digitalWrite以外的指令,也需要用digital/analogRead相关的指令进行不同的传感器状态的指令采集。

首先来讲解digitalRead的指令。数字信号的读取,就跟数字信号的写入一样。也只有high, low两种数值。如图五所示,在这个项目中,我们搭建一个电路,这个电路有一个输入端,同数字引脚7相连接,连接一个按钮开关。有一个输出端,同数字引脚13相连接,连接一个电阻和一个LED显示灯。

image.png

图五,digital_Read的电路搭建

这个项目的代码,则如图六所示。初始化函数(setup function)如同上文所示(将LED的模式设为输出,按钮开关的模式设为输入)。

在初始化函数之前提前定义三个变量值,在第5,6,7行中,将引脚13定义为led灯连接的引脚;将按钮开关定义为引脚7;以及定义一个储存输入引脚变量值的数字0。

在循环函数中,让储存变量的值去读取按钮开关的状态(16行黄线)。然后将读取的开关状态重新写入LED灯的数值状态。在这种情况下,按钮开关的默认状态是1,闭合状态是0。

所以,这段代码在运行的时候,就是让按钮在按下的时候,直接让LED灯从点亮状态转为熄灭状态。

image.png 

图六,Digital_Read的基础代码

为了更直观的理解这种机制,笔者加了一行代码,Serial.println(val),有了这行代码,就可以直接让所显示的代码打印在输出串口上。所以在运行代码的时候打开输出串口。当按住按钮开关的时候,就一定会发现InputState显示为0。而在不按住按钮开关的时候,则会发现InputState在按一定的规律在0和1之间转换(这就是Mega2560控制器输出给LED灯的信号,而这种输出本身就不稳定所以造成了其在0和1之间转换,这种机制同三种配置模式有关,会一起在文末讲到) 。

有一点需要注意一下,我在这个代码之中添加了一行delay(1000),这个语句会让整个循环语句当中的更新速度变慢1秒。所以打印在输出串口上的语句就会一秒更新一次,Arduino的LED灯闪烁的变化也会变慢不少。这样做是为了让大家能更加清晰的看到这个过程。

image.png

图七,Digital_Read显示的串口输出状态

image.png

图八,analog_Read的电路搭建

在analogRead的指令中,如图八所示,搭建一个专为analog_Read所设计的电路。在这个电路中,我们只需要连接一个电位计(如图八所示,这个电位计是在Fritzing上面的一个例子,可以根据自己所有的电位计型号进行调整,在这个例子当中,电位计的三个引脚一个接5V电源,一个接模拟输出的A3引脚,还有一端接地)。

这个项目的代码如图九所示,在这段代码中,依然需要从头定义接入引脚的号码,并且设置一个储值变量。在循环函数当中,分配一个变量名去读取这个值。

image.png

图九,analog_Read的基础代码

而大家可以猜测一下,读取出来的值是什么呢?大家可以参看图十,随着电位计被旋转,串口读取出来的值是0到1024。

这段代码的本质就是在用模拟信号读取控制器所输出的5V电压。以一个10位的数字模拟转换系统计算,这个数据在0到1023之间分布。后文会详述这种情况。

image.png

图十,analog_Read在串口的显示数据

----------------------------------------------------

原理部分:

模拟数字信号的转换(analog-digital Converters):

花了这么大的篇幅讲述了这么多analog|digital-Write-Read相关的指令,是为什么呢?因为这是Arduino入门最重要的基础指令集之一。所以对于这个问题,务必要刨根问底讲清楚,才便于更深入的学习其他在Arduino中更复杂的应用机制。

所以,这其中有两个机制需要详细叙述,第一就是模拟数字信号转换的基础原理,第二就是持续性估读数字模拟信号的转换机制。

1. 模拟数字信号转换的基础原理:

在Arduino中,之前提到过它的核心控制器是ATmega2560,在这个控制器当中,如果把一个模拟信号转化为数字信号,就是将一个连续性的信号转化为离散信号。在Arduino中,模拟数字信号转化器是10比特。以二进制的机制转化为一个十进制的数字,则其ADC信号的比例为ADC=210=1024。基于此,求其ADC信号的比值公式。

ADC=(Vin*1024)/Vref

在这个公式中,Vin是指输入电压(在这里就是实际通过analog读取的值);Vref是指代模拟数字信号转化器中的参考限定值(就是Arduino那个常见的5V电源,在这里是指它的最大值)。从新排列这个公式,就会得到:

Vin=( Vref/1024)*ADC

举个例子,就以analogRead的代码为例(图九),如果目前的analogRead的值为614.4,那么对应的:

Vin= (5V/1024)*614.4

Vin = 3V

所以,analogRead的具体值虽然没有给出,但是根据这个0到1023之间,就能了解实际的输出电压是大概是多少。

2.持续性估读模拟数字信号转换(Successive Approximation ADC):

使用模拟数字信号转换最终的目的,就是使用一种二进制搜索在每一次转化中被最终聚合为一个数字输出之前所有可能的量子化等级,将一个连续性的模拟信号波形转化为一个离散的数字信号的代表波形(这句话听起来很拗口,是直接从官网翻译过来的,有兴趣的可以通过下文中的链接去读一读,懂信号学的朋友应该明白它的意思,简而言之,就是将一个输入电压转化为对应的二进制码)。

在这里简述一下其机制,在如图十一所示的模拟信号转化数字信号的图示中,如果给其一个输入电压值Vin=3V,那么最终输出的ADC应该是多少呢?

为了回答这个问题,就应该首先了解清楚持续性估读模拟数字信号转换的机制。如图十一所示,如果给定一个任意的输入信号Vin,从红色箭头所指的方向输入这个持续性估读模拟数字信号转换系统之后,它会持续性的和系统中默认的参考电压值作比较,直到估读出它所有的二进制代码为止。

image.png

图十一,模拟转数字信号转换图示(图源: Arduino官网)

所以,来回答上面的问题,如果Vin=3V,那么如何进行比较呢?在这里详细叙述一下这个流程。

首先,这个输入的值会和最低电压(0V)和最高电压(5V)的中间值(2.5V)进行比较。如果这个值高于这个中间值,那么在二进制码中将其记为1(如下图左所示)。如果这个值低于这个中间值,那么在二进制码中将被记为0(因为给定的Vin是3V,在这次循环中不为0)。所以这个数的第一次最高有效位(Most Significant Bit)是1。

        image.pngimage.png...

图十二,第一次循环估值

在下一次循环中,最低位的值就会被舍去,在2.5到5V之间进行继续估值。因为3V小于3.75V,所以在这次循环中,这两个值的中间位是(2.5V+5V)/2=3.75V。

image.pngimage.png ...

图十三,第二次循环估值

再下一次循环中,因为这次的值低于3.75V,将在2.5到3.75V之间进行继续估值。在3.75V到5V之间的值会被舍去。在这次循环中,这两个值的中间位是(2.5V+3.75V)/2=3.125V。

image.pngimage.png

图十四,第三次循环估值

所以,按照这种循环方式一直进行下去,那么最终当它估读到D9~D0位的时候(因为上文提到Arduino数字模拟转化器为10比特),共10比特,这个值最终就是MSB[1001100110]LSB(最左边为最高有效位,最右边为最低有效位)。

那么,以二进制转十进制,这串数字是多少呢?

ADC=1x29+1x26+1x25+1x22+1x21=614!

所以,这和Vin=3V所对应的ADC是不是相等呢?

这就是持续估读模拟数字信号转化的机制,在一个模拟信号到数字信号转化的过程中,所有的值就是这样被记录的。

--------------------------------------------------------------

附录(Appendix):配置模式的详解

在上文中提到过如果设定一个数字输出的话,在setup函数中有三种配置模式(可以参加图二表一),分别是输入模式(input),输出模式(output),输入上拉模式(input_pullup)。在数字输出中,前两种模式很好理解,pull_up模式可能用的不是很频繁,可以通过几个小实验来着重理解pull_up模式。在理解清楚pull_up模式后,也有助于从侧面厘清上文中的digital/analog_Read指令。

首先来看这个电路搭建以及代码,如图A-1所示;其对应的代码如图A-2所示。在运行这段代码之后,打开工具,串口绘图(Serial Plotter),或者直接按ctrl+shift+L。可以看到如图A-3所示的波动图像。该图像显示,如果不按下如图A-1所示的按钮的话,该波形图会一直进行高频率不规则的快速波动(上限是纵轴的1),而按下按钮之后,这个图像的波形会变成0。

image.png

图A-1, 输入上拉测试电路1

image.png

图A-2, 输入上拉测试代码1

而这个图像波动的本质,就是它在0V和5V之间浮动,如果在5V的位置,那么它在loop函数中被读出的值就是1,如果小于特定电压,那么它的读数就是0V。这个pin_Mode发出的波是未按按钮那种随机产生的上下浮动,这就是它的默认状态,而在按钮按下接通电路之后,就是它默认状态的相反状态。

image.png  

图A-3, 输入上拉测试电路串口绘图1

在下一段代码中,把图A-2中的代码的input模式改为input_pullup模式(如图A-4所示)。而在随后的绘图中(如图A-5所示),会发现在使用了input_pullup模式之后,电压的默认输出是一个稳定值,loop函数中的读值为1。而按下按钮之后,依然变为0。

image.png

图A-4, 输入上拉测试代码2

 image.png

图A-5, 输入上拉测试电路串口绘图2

如果在不使用pull_up模式的情况下,如何得到和pull_up模式一样的效果呢?现在将代码改回图A-2的形式,将input_pullup模式改回input模式。按图A-6的形式搭建电路。

image.png

图A-6, 上拉电阻输出 

这次输出的波形是什么样呢?如图A-7所示,当该按钮处于断开状态时,会持续输出数值1,而当按钮按下时,则会持续输出数值0。这和图4中的pullup模式所产生的效果是不是一样呢?

image.png

图A-7, 上拉电阻输出绘图

还有一种方法,如图A-8所示(注意电路的区别,需要从新连接蓝线以及电阻的位置),在这种情况下,电路在默认状态会一直输出0,而当按下按钮的时候则会为1。

image.png

图A-8, 下拉电阻输出

 image.png

图A-9, 下拉电阻输出绘图

在电路图A-6之中,5V电源输出的电压直接被数字引脚读取,在这种情况下默认状态为0,而在A-8之中,电路一直处于断路状态,直到按下按钮开关(接通)才能被数字引脚读取,它的默认状态为1。

------------------------------------后记------------------------------------

在写这篇博客的时候一直在思考在这个话题的内容框架下写什么,写多少,怎么写才比较合适。虽然笔者对不少内容十分了解,但是真正在动笔的时候才发现把什么东西都给读者讲清楚还是需要不少文字的。所以为了呼应一开始的内容,笔者还是决定另外写一篇关于本篇的相关课题给大家列举一些比较好玩的例子,然后再给大家一些作业,可以尝试着做一下,做出来的会有礼品相赠!

-----------------------------------------------------------------------------

文末附有一份英文版的语法手册,欢迎阅读学习!
点击下载:Arduino Language Reference.pdf




关键词: Arduino     Mega2560    

专家
2021-08-14 14:47:16     打赏
2楼

学习学习


专家
2021-08-15 00:02:47     打赏
3楼

感谢楼主的分享,很实用了。


工程师
2021-08-15 23:40:57     打赏
4楼

感谢您的分享


高工
2021-08-15 23:56:03     打赏
5楼

感谢分享


管理员
2021-08-16 17:29:27     打赏
6楼

更新啦~


工程师
2021-08-16 23:49:01     打赏
7楼

总结的蛮好的


工程师
2021-08-18 23:50:51     打赏
8楼

讲解的非常到位


高工
2021-09-07 14:30:28     打赏
9楼

感谢分享!


共9条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]