第5章 Arduino便捷的模拟/数字转换 自然界所有的信号都是模拟的,例如温度、光照强度、压力等。单片机只能处理数字离散信号,因而需要一种将模拟信号转化为数字信号形式的方法。将模拟信号转换成数字信号的电路,称为模数转换器,简称A/D转换器或ADC(Analog to Digital Converte),A/D转换的作用是将时间连续、幅值也连续的模拟量转换为时间离散、幅值也离散的数字信号。 标准MCS51单片机芯片内部不具备A/D转换功能,需要外接AD0809等A/D转换芯片,而AVR系列单片机都配备A/D转换子系统。 Arduino将复杂的AVR单片机A/D转换过程做了很好的封装,只要使用analogRead()即能便捷地进行A/D转换。 5.1Arduino UNO板上的A/D转换 在Arduino UNO开发板上,有一排标着A0~A5的引脚具有模拟信号输入的功能。Arduino中的片内 A/D转换设备通过逐次逼近法测量输入电压的大小,得到输入电压,转换成0~1023的数字值。 5.1.1逐次逼近模数转换技术 将模拟信号转换为数字信号一般分为4个步骤,即取样、保持、量化和编码。逐次逼近转换过程和用天平称物重非常相似,从最重的砝码开始试放,与被称物体进行比较,若物体重于砝码,则该砝码保留,否则移去。再加上第二个次重砝码,由物体的重量是否大于砝码的重量决定第二个砝码是留下还是移去。照此一直加到最小一个砝码为止。将所有留下的砝码重量相加,就得到该物体的重量。 ATmega328采用逐次逼近技术,包括一个数模转换器、一个控制器和一个比较器来执行模数转换。从最高有效位开始向下到最低有效位,控制器逐个开启每一位并且生成一个模拟信号,在数模转换器的帮助下,将生成的模拟信号与原始输入模拟信号进行对比。基于对比结果,控制器更换或者离开当前位并且使能下一个最高有效位。该过程一直持续直到生成所有有效位的对比结果。 5.1.2Arduino UNO上的A/D引脚 Arduino UNO板包含一个6通道(Mini和Nano有8个通道,Mega有16个通道)、10位模拟/数字转换器, 它将0~5V的输入电压映射为0~1023的整数值。读数之间的关系是: 5V/1024单位,或0.0049V(4.9mV)每单位。输入范围和精度可以使用 analogReference()改变。它需要大约100μs(0.0001s)来读取模拟输入,所以最大的阅读速度是 10000次每秒,或4.88mV每单位。 在Arduino UNO板下方ANALOG IN 区域有6个A/D转换输入引脚,分别标记为A0、A1、A2、A3、A4、A5,这些模拟引脚 可作为数字引脚D14~D19使用,如图51所示。 图51Arduino UNO的6个A/D转换引脚 5.1.3Arduino中的A/D转换语句 Arduino把ATmega单片机中的10位A/D转换时需要的复杂寄存器设置做了很好的封装,用户只需使用analogRead(0)、analogRead(1)命令就可以读出A0、A1引脚的模拟电平值了。 1. analogRead(pin) 从指定的模拟引脚读取数据值。 返回值: 整数型int(0~1023)。 参数: pin为A/D引脚标号,Arduino UNO为0~5,Arduino nano为0~7,Mega为0~15。 2. analogReference (uint8_t type) 配置模式引脚的参考电压。输入范围和精度可以使用analogReference()改变,配置用于模拟输入的基准电压(即输入范围的最大值)。函数analogRead()在读取模拟值之后,根据参考电压将模拟值转换到[0,1023]区间。 参数type有如下几种选择。 (1) DEFAULT: 基准电压,默认为5V(Arduino UNO板为5V)或3.3V(Arduino Nano板为3.3V)。 (2) INTERNAL: 低功耗模式。在ATmega168和ATmega328上以1.1V为基准电压,在ATmega8上以2.56V为基准电压。 INTERNAL1V1: 以1.1V为基准电压(Arduino Mega)。 INTERNAL2V56: 以2.56V为基准电压(Arduino Mega)。 (3) EXTERNAL: 以AREF引脚(0~5V)的电压作为基准电压,但是要低于5V。 如果模拟输入引脚没有连入电路,则由analogRead()返回的值将根据多项因素(例如,你的手靠近电路板等)而产生波动。 注意: 如果使用AREF引脚上的电压作为基准电压,那么在调用analogRead()前,必须先设置参考类型为EXTERNAL; 否则,外接的参考电压将会损坏内部基准电压源,这可能会损坏Arduino板上的单片机。 不要在AREF引脚上使用任何小于0V或超过5V的外部电压,以免损坏单片机。 5.1.4A/D转换器主要技术参数 1. 分辨率 A/D转换器的分辨率以输出二进制(或十进制)数的位数来表示,它表明A/D转换器对输入信号的分辨能力。 分辨率=参考电压/(总元素-1),总元素=2n,n为位数。 假设用4位(0~15)来表示量化等级,分辨率=5V/(16-1)=333mV,对于Arduino中的ATmega328来说,为10位(0~1023)转换精度,分辨率=5V/1023=4.88mV。 量化级越高,模拟信号转换时的精度就越高,在将模拟信号转化为数字信号时,为什么我们不使用当前技术为数字系统提供的最大比特数呢?因为必须考虑到该数据系统的计算能力以及所需要的系统分辨率。 ATmega328的A/D系统配备一个10位分辨率的逐步逼近式转换器,在给定时间只能对一个A/D转换器通道进行转换。 2. 转换误差 转换误差通常是以输出误差的最大值形式给出。它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别,常用LSB(最低有效位)的倍数表示。例如,给出相对误差≤±LSB/2,这就表明实际输出的数字量和理论上应得到的输出数字量之间的误差小于最低位的半个字。 ATmega328的转换误差是±2最低有效位(LSB)绝对精度,即意味着±9.76mV的分辨率。 3. 转换时间 转换时间是指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号为止所经过的时间。 ATmega328的A/D转换需要13个A/D时钟转换时间,最高采样速率为76.9kSPS。Arduino系统封装采用了较高的分频比,使最高采样速度降为10kSPS。当应用需要高速取样时,可以采取直接操控寄存器方式实现,详见第11章。 5.2A/D转换基本实验 5.2.1读取模拟引脚上的模拟值并显示出来 1. 电路原理 微课视频2 读取模拟引 脚上的模拟 值并显示 出来 通常一个电位器有3个引脚,其中两个连接到电阻材料,第三个引脚(通常在中间)被连接到可旋转并接触电阻材料 的任何位置的滑动触点。当电位器旋转时,滑动触点和一个引脚之间的电阻增加,而和另一个 引脚间的电阻减小。当滑动触点移向底端时,滑动触点(图52中带箭头的线)和地之间具有较低的电阻,而连接到5V的一端有更高的电阻。随着滑动触点向下移动,模拟引脚上的电压将减小(最小为0V)。滑动触点向上移动将产生相反的效果,引脚上的电压将增加(最大为5V)。电路图及实物连接图如图52所示。 图52电路图及实物连接图 将电位器中间滑动端接至Arduino UNO板的A0端,使用analogRead()语句就可以读出模拟口的值,Arduino UNO 控制器 进行的是10位的A/D采集,所以读取的模拟值范围是0~1023。 2. 程序说明 首先,在 void setup()中设置波特率。在Arduino软件的串口工具监视窗口右下角有一个可以设置波特率的按钮,选中与程序中设置的波特率相同的波特率,Serial.begin()的括号中为波特率的值。 串口监视器中显示的数值来源于Arduino与PC的通信,因此 Arduino的波特率应与PC软件设置的波特率一致,才能显示正确的数值,否则将会显示乱码或者不显示。 1/******************************************************** 2* 程序5-1: 读取模拟引脚上的模拟值并显示 3********************************************************/ 4int potpin = 0 ; //定义模拟接口 0 5int ledpin = 13 ; //定义数字接口 13 6int val = 0 ; //定义变量val,并赋初值 0 7void setup() 8{ 9//设置数字 13 引脚为输出模式 10pinMode(ledpin,OUTPUT); 11Serial.begin(9600); //设置波特率 9600b/s 12} 13void loop() 14{ 15digitalWrite(ledpin,HIGH); //点亮数字接口13的LED 16delay(50); //延时 0.05s 17digitalWrite(ledpin,LOW); //熄灭数字接口13的LED 18delay(50); //延时0.05s 19//读取模拟接口0的值,并将其赋给val 20val = analogRead(potpin); 21Serial.println(val); //显示val的值 22} 3. 运行结果 每读取一次值,Arduino自带的 LED 小灯就会闪烁一下,可以通过旋转来改变读数。 通过Arduino IDE中的“工具”菜单打开串口监视器,读取模拟量值,如图53所示。 图53串口监视器的输出 5.2.2使用A/D转换器进行按键输入判别 1. 实验电路原理 电路与实物连接图如图54和图55所示。 图54A/D转换器用作按键判别的电路连接 图55实物连接图 电路连接关系是 Arduino UNO上的 A0接按键开关; LED接Arduino UNO上的DT。 2. 程序说明 使用A/D转换器作为按键输入具有一定的抗干扰性。当按键没有被按下时,模拟口电压为0V,LED灯熄灭; 当按键 被按下时,模拟口电压值为5V,所以只要判断电压值是否大于2.5V,如果大于2.5V, 则可以知道按键被按下,LED灯点亮。 1/******************************************************** 2* 程序5-2: A/D转换器用于按键输入判别 3********************************************************/ 4int analogPin = 0; 5int val = 0; 6int led = 7; 7void setup() 8{ 9pinMode(led,OUTPUT); 10pinMode(14,INPUT); 11Serial.begin(9600); 12} 13void loop() 14{ 15val = analogRead(analogPin); 16Serial.println(val); 17if(val>127) 18digitalWrite(led,HIGH); 19else 20digitalWrite(led,LOW); 21delay(200); 22} 3. 运行结果 将程序下载到Arduino UNO开发实验板后,当按下按键时,LED熄灭; 松开按键,LED灯点亮,串口监视显示如图56所示,软件施密特触发器实物实现如图57(a)、(b)所示。 图56串口监视器的输出 图57施密特触发器实物 5.2.3使用A/D转换器读取键盘值的抢答器 1. 抢答器实验要求 两名选手各有一个按键,比赛开始后进行抢答,谁先按下按键,对应的灯就会亮起来。裁判可根据亮灯情况提示参赛选手答题,本次结束后,裁判按下按键3清除亮灯(使亮灯都熄灭)。 这个实验要求使用analogRead()判定按键是否按下。 2. 电路原理 按键1 ->模拟2口(A2) 按键2 ->模拟3口(A3) 按键3 ->模拟4口(A4) 红LED ->数字第8引脚(D8) 绿LED ->数字第7引脚(D7) 蜂鸣器 ->数字第5引脚(D5) 电路原理图如图58所示,实物连接图如图59所示。 图58抢答器的电路原理图 图59抢答器的实物接线图 3. 程序说明 按键在保持按下和保持弹起这两种状态下,按键引脚电压值是有变化的。因此,我们可以依次读取模拟口1~模拟口3的电压值,根据读出的电压值来判断按键是否被按下。本实验采用高电平有效方式连接。 用万用表测量可知,当没有按键按下时,模拟口电压值为0.0V左右。当有按键按下时,模拟口的电压值为5.0V左右。所以,我们可以认为当模拟口的电压值小于1V(数字二进制表示为204)时,没有按键按下,当模拟口的电压值大于4V(数字二进制表示为818)时,有按键按下。 1/******************************************************** 2* 程序5-3: 使用A/D读取键盘值的抢答器 3********************************************************/ 4int RedLed=8; //定义第8引脚连接红灯 5int GreenLed=7; //定义第7引脚连接绿灯 6int i; //定义变量i 7int j=0; //定义变量j 8void buzzer() //蜂鸣器发出"嘀"声音子程序 9{ 10for(i=0; i<80; i++) 11{ 12digitalWrite(5,HIGH); //发声音 13delay(1); //延时1ms 14digitalWrite(5,LOW); //不发声音 15delay(1); //延时ms 16} 17} 18void key_scan() //按键扫描子程序 19{ 20int key_1,key_2,key_3; //定义变量 21key_1=analogRead(2); //读模拟引脚1电压值 22key_2=analogRead(3); //读模拟引脚2电压值 23key_3=analogRead(4); //读模拟引脚3电压值 24//Serial.println(key_1,key_2,key_3); 25Serial.println(key_1); 26Serial.println(key_2); 27Serial.println(key_3); 28/*如果各按键电压值都小于204(即模拟值1V),判断没有按键按下*/ 29if(key_1<204&&key_2<204&&key_3<204) 30{ 31return; //跳出本子函数 32} 33/*如果按键1电压值都大于818(即模拟值4V),判断按键1被按下*/ 34if(key_1>818) 35{ 36delay(10); //由于有抖动,所以延时100ms再一次判断 37/*如果按键1电压值都大于818(即模拟值4V),判断按键1确实被按下*/ 38if(key_1>818) 39{ 40buzzer(); //蜂鸣器发出声音 41digitalWrite(RedLed,HIGH); //红灯亮 42digitalWrite(GreenLed,LOW); //绿灯灭 43} 44else //否则认为是抖动干扰,不做任何动作 45{ 46return; //跳出本子函数 47} 48} 49/*如果按键2电压值都大于818(即模拟值4V),判断按键2被按下*/ 50if(key_2>818) 51{ 52delay(10); //存在抖动,延时100ms再一次判断 53/*如果按键2电压值都大于818(即模拟值4V),判断按键2确实被按下*/ 54if(key_2>818) 55{ 56buzzer(); //蜂鸣器发出声音 57digitalWrite(RedLed,LOW); //红灯灭 58digitalWrite(GreenLed,HIGH); //绿灯亮 59} 60else //否则认为是抖动干扰,不做任何动作 61{ 62return; //跳出本子函数 63} 64} 65/*如果按键3电压值都大于818(即模拟值4V),则可以判断按键3被按下*/ 66if(key_3>818) 67{ 68delay(10); //存在抖动,延时100ms再一次判断 69/*如果按键3电压值都大于818(即模拟值4V),判断按键3确实被按下*/ 70if(key_3>818) 71{ 72buzzer(); //蜂鸣器发出声音 73digitalWrite(RedLed,LOW); //红灯灭 74digitalWrite(GreenLed,LOW); //绿灯灭 75} 76else //否则认为是抖动干扰,不做任何动作 77{ 78return; //跳出本子函数 79} 80} 81} 82void setup() 83{ 84Serial.begin(9600); 85for(i=5; i<=8; i++) 86{ 87pinMode(i,OUTPUT); //引脚5~8设置为输出模式 88} 89} 90void loop() 91{ 92key_scan(); //循环扫描按键 93} 4. 运行结果 按键1和按键2是抢答按键,按键3是清除按键。如果按键1先被按下,则蜂鸣器发出提示音,红灯亮,绿灯灭; 如果按键2先被按下,则蜂鸣器发出提示音,绿灯亮,红灯灭; 如果按键3被按下,则蜂鸣器发出提示音,将红灯和绿灯都熄灭。实物效果如图510所示。 图510抢答器的实现 需要注意的是,按键可能存在抖动干扰,为了更加准确地判断是否有按键被按下,在第一次判断有按键按下之后,延时10ms躲避抖动,然后进行第二次判断。另外,从模拟口读出的电压值是用二进制表示的。