模数转换 A/D 与数模转换 D/A介绍
A/D 和 D/A 的基本概念
A/D
从模拟量到数字量的转换取决于模数转换器(Analog to Digital Converter),简称ADC
。D/A
从数字量到模拟量的转换取决于数模转换器(Digital to Analog Converter),简称DAC
。它们的道理完全一样,只是转换方向不同,
A/D 的主要指标
- ADC 的位数
一个 n 位的 ADC 表示这个 ADC 共有 2 的 n 第二个刻度 位的 ADC,输出的是从 0~255 一共 256 数字量,也就是 2 的 8 二次数据刻度。
- 基准源
基准源,又称基准电压,是 ADC 的一个重要指标,要想把输入 ADC 如果信号测量准确,则基准源应首先准确,基准源的偏差将直接导致转换结果的偏差。例如,米尺的总长度应该是 1 米,假设米尺被火烤了,实际上变成了 1.2 米,再用这根米尺测物体长度的话自然就有了较大的偏差。假设我们的基准源应该是 5.10V,但它实际上是提供的 4.5V,这样误把 4.5V 当成了 5.10V 如果处理,偏差会比较大。
- 分辨率
当数字量变化最小刻度时,模拟信号的变化量被定义为满刻度量程和 2n-1 假设 5.10V 使用电压系统 8 位的 ADC 测量相当于 0~255 一共 256 个刻度把 5.10V 平均分成了 255 份,那么分辨率就是 5.10/255 = 0.02V。
- INL(积分非线性度)和 DNL(差异非线性度)
初学者最容易混淆的两个概念是分辨率和精度们认为分辨率越高,精度就越高。事实上,两者之间没有必然的联系。分辨率用于描述刻度划分,精度用于描述准确性。同一米尺,刻度相同,分辨率相同,但精度可以大不相同 ADC 精度关系的两个主要指标是 INL(Integral NonLiner)和 DNL(Differencial NonLiner)。INL 指的是 ADC 所有值对应的模拟值和真实值之间最大误差点的误差值是 ADC 最重要的精度指标是单位 LSB。LSB(Least Significant Bit)它意味着最低有效性,所以它实际上对应于 ADC 的分辨率。一个基准为 5.10V 的 8 位 ADC,它的分辨率是 0.02V,用它来测量电压信号,结果是 100意味着它测量的电压值是 100*0.02V=2V,假定它的 INL 是 1LSB,这意味着电压信号的真正准确值是1.98V~2.02V 之间的,根据理想情况对应的数字应该是 测量误差是99~101的最低有效位 1LSB。DNL 表示的是 ADC 相邻两个刻度之间最大的差异,单位也是 LSB。DNL 表示的是 ADC 相邻两个刻度之间最大的差异,单位也是 LSB。分辨率是 1 毫米的尺子,相邻的刻度之间并不都是正确的 1 毫米,总有大大小小的误差。同理,一个 ADC 两个刻度线之间的分辨率并不总是准确的,也有误差。这个误差是 DNL。一个基准为 5.10V 的 8 位 ADC,假定它的 DNL 是 0.5LSB,然后,当其转换结果从 100 增加到 101 理想情况下,实际电压应增加 0.02V,但 DNL 为 0.5LSB 实际电压增加值为 0.01~0.03V 之间。值得一提的是 DNL 不一定小于 1LSB,在很多情况下,它会等于或大于或大于或大于或大于大于大多数时间 1LSB,当实际电压保持不变时,这相当于一定程度的刻度紊乱,ADC 结果可能会在几个值之间跳动,很大程度上是因为这个原因(但并不完全是因为有无处不在的干扰)。
- 转换速率
转换率,是指 ADC 每秒采样转换的最大次数是单位 sps(或 s/s、sa/s,即 samples per second),它与 ADC 从模拟到数字转换所需的时间是倒数关系。ADC 有很多种,包括积分型 ADC 转换时间为毫秒,属于低速 ADC;逐次逼近型 ADC转换时间为微秒,属于中速 ADC;并行/串行 ADC 转换时间可达纳秒级,属于高速 ADC。
ADC 大家先熟悉一下这些主要指标,对于其他的,作为入门级选手, 不要急于深入理解。以后在使用过程中遇到,然后找到相关信息深入学习,目前的重点是思考。 中建立一个 ADC 基本概念。
介绍转换芯片
PCF8591 的硬件接口
PCF8591 是单电源低功耗 8 位 CMOS 拥有数据采集装置 4 路模拟输入,1 路模拟输出和串行 I2C 用于与单片机通信的总线接口。与前面讲过的 24C02 类似,3 个地址引脚 A0、A1、A2 最多允许编程硬件地址 8 连接到个器件 I2C 总线不需要额外的片选电路。通过设备的地址、控制和数据 I2C 让我们先看看总线传输。 PCF8591 原理图,如图所示 17-3 所示。
其中引脚 1、2、3、4 是 4 模拟道路输入,引脚 5、6、7 是 I2C 总线硬件地址,8 脚是数字地 GND,9 脚和 10 脚是 I2C 总线的 SDA 和 SCL。12 脚是引脚的时钟。如果连接高电平,则使用外部时钟输入,如果连接低电平,则使用内部时钟。因此,我们的电路使用内部时钟 12 脚直接接 GND,同时 11 脚悬空。13 脚是模拟地 AGND,在实际开发中,如果有复杂的模拟电路,那么 AGND 部分在布局布线上要特别处理,和 GND 连接的方式有很多,14 脚是基准源,15 脚是 DAC 模拟输出,16 脚是供电电源 VCC。
- PCF8591 的 ADC 转换速率是中速,但其速度瓶颈在 I2C 通信上。由于 I2C 通信速度慢,所以最终 PCF8591 转换速度直接取决于 I2C 通信速率。由于 I2C 速度限制,所以 PCF8591 可以算是低速 AD 和 DA 集成主要用于一些转换速度要求低、成本低的场合,如电池供电设备、测量电池供电电压、电压低于一定值、报警提示更换电池等。Vref 提供基准电压有两种方法。一是采用简易的原则,直接接到 VCC 但是因为 VCC 它会受到整个线路功耗的影响,不准确 5V,实测大多在 4.8V 其次,随着整个系统负载的变化,只能用于简单、精度要求低的场合。方法二是使用专用基准电压器件,如 TL431,它能提供高精度 2.5V 我们通常使用的电压基准。如图 17-4 所示。 图中 J17 是双排插针,可以根据自己的需要选择跳线帽短接,也可以用杜邦线连接其他外部电路。在这个地方,我们直接 J17 的 3 脚和 4 用跳线帽短路,所以现在 Vref 基准源是 2.5V 了。分别把 5 和 6、7 和 8、9 和 10、11 和 12 如果用跳线帽短接,那么我们的 AIN0 测量的是电位器的分压值,AIN1 和 AIN2 测的是 GND 的值,AIN3 测的是 5V 的值。这里需要注意的是,AIN3 虽然测的是 5V 值,但对AD 只要输入信号超过, Vref 基准源,它总是得到最大值,即 也就是说,它实际上不能超过它 Vref 电压信号。需要注意的是,所有输入信号的电压值都不能超过 VCC,即 5V,否则可能会损坏 ADC 芯片。
PCF8591通讯
PCF8591 通信接口是 I2C,那么编程必须符合这个协议。单片机对 PCF8591 初始化,发送三个字节。第一个字节,和 EEPROM 类似地,设备地址字节,其中 7 代表地址,1 位代表读写方向。地址高 4 位固定是 0b1001,低三位是 A2,A1,A0.这三个电路都连接了 GND,因此也就是 0b000,如图 17-5 所示。
- 写地址为:0x阅读地址为:0x91
发送到 PCF8591 控制寄存器将存储第二个字节 PCF8591 功能 3 位和第 7 位是固定的 0,另外 6 每个人都有自己的角色,如图所示 17-6 所示,
控制字节的第 6 位是 DA 使能位,这一位置 1 表示 DA 输出引脚使能,会产生模拟电压输出功能。第 4 位和第 5 位可以实现把 PCF8591 的 4 路模拟输入配置成单端模式和差分模式,单端模式和差分模式的区别,我们在 17.5 节有介绍,这里大家只需要知道这两位是配置 AD输入方式的控制位即可,如图 17-7 所示。
控制字节的第 2 位是自动增量控制位,自动增量的意思就是,比如我们一共有 4 个通道, 当我们全部使用的时候,读完了通道 0,下一次再读,会自动进入通道 1 进行读取,不需要我们指定下一个通道,由于 A/D 每次读到的数据,都是上一次的转换结果,所以同学们在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值。为了保持程序的通用性,我们的代码没有使用这个功能,直接做了一个通用的程序。 控制字节的第 0 位和第 1 位就是通道选择位了,00、01、10、11 代表了从 0 到 3 的一共4 个通道选择。 发送给 PCF8591 的第三个字节 D/A 数据寄存器,表示 D/A 模拟输出的电压值。D/A 模拟我们一会介绍,大家知道这个字节的作用即可。我们如果仅仅使用 A/D 功能的话,就可以不发送第三个字节。
D/A 输出
D/A 是和 A/D 刚好反方向的,一个 8 位的 D/A,从 0~255,代表了 0~2.55V 的话,那么我们用单片机给第三个字节发送 100,D/A 引脚就会输出一个 1V 的电压,发送 200 就输出一个 2V 的电压,很简单,我们用一个简单的程序实现出来,并且通过上、下按键可以增大或减小输出幅度值,每次增加或减小 0.1V。如果有万用表的话,可以直接测试一下板子上 AOUT 点的输出电压,观察它的变化。由于 PCF8591 的 DA 输出偏置误差最大是 50mv(由数据手册提供),所以我们用万用表测到的电压值和理论值之间的误差就应该在 50mV 以内。
示例代码
/* ******************************************************************************* * 《手把手教你学51单片机(C语言版)》 * 配套 KST-51 单片机
开发板 示例源代码 * 描 述:第17章 DA转换例程 * 由按键控制DA输出可调电压值 ******************************************************************************* */ #include <reg52.h> unsigned char T0RH = 0; //T0重载值的高字节 unsigned char T0RL = 0; //T0重载值的低字节 void ConfigTimer0(unsigned int ms); extern void KeyScan(); extern void KeyDriver(); extern void I2CStart(); extern void I2CStop(); extern bit I2CWrite(unsigned char dat); void main() { EA = 1; //开总中断 ConfigTimer0(1); //配置T0定时1ms while (1) { KeyDriver(); //调用按键驱动 } } /* 设置DAC输出值,val-设定值 */ void SetDACOut(unsigned char val) { I2CStart(); if (!I2CWrite(0x48<<1)) //寻址PCF8591,如未应答,则停止操作并返回 { I2CStop(); return; } I2CWrite(0x40); //写入控制字节 I2CWrite(val); //写入DA值 I2CStop(); } /* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */ void KeyAction(unsigned char keycode) { static unsigned char volt = 0; //输出电压值,隐含了一位十进制小数位 if (keycode == 0x26) //向上键,增加0.1V电压值 { if (volt < 25) { volt++; SetDACOut(volt*255/25); //转换为AD输出值 } } else if (keycode == 0x28) //向下键,减小0.1V电压值 { if (volt > 0) { volt--; SetDACOut(volt*255/25); //转换为AD输出值 } } } /* 配置并启动T0,ms-T0定时时间 */ void ConfigTimer0(unsigned int ms) { unsigned long tmp; //临时变量 tmp = 11059200 / 12; //定时器计数频率 tmp = (tmp * ms) / 1000; //计算所需的计数值 tmp = 65536 - tmp; //计算定时器重载值 tmp = tmp + 28; //补偿中断响应延时造成的误差 T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节 T0RL = (unsigned char)tmp; TMOD &= 0xF0; //清零T0的控制位 TMOD |= 0x01; //配置T0为模式1 TH0 = T0RH; //加载T0重载值 TL0 = T0RL; ET0 = 1; //使能T0中断 TR0 = 1; //启动T0 } /* T0中断服务函数,执行按键扫描 */ void InterruptTimer0() interrupt 1 { TH0 = T0RH; //重新加载重载值 TL0 = T0RL; KeyScan(); //按键扫描 }
- 示例以及源码来源于:http://www.qdkingst.com/cn/disc51,提供了视频和资料下载,想离线学习的朋友可以去哪里将资源下载到本地进行学习。