A/D的主要指标
1、ADC的位数
一个n位的ADC表示这个ADC共有2的n次方个刻度。8位的ADC输出的是从0~255一共256个数字值,也就是个数据刻度。。
2、基准源
基准源也叫基准电压,是ADC的一个重要指标,要想把输入的ADC的信号测量准确,那么基准源首先要准,基准源的偏差会直接导致转换结果的偏差。
3、分辨率
分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与的比值。假定5.10V的电压系统,使用8位的ADC进行测量,那么相当于0~255一共256个刻度把5.10V平均分成了255份,那么分辨率就是5.10/255=0.02V。
4、INL(积分非线性度)和 DNL(差分非线性度)
ADC精度关系重大的两个指标是INL(IntegralNonLiner)和 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。一把分辨率是 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 得出的结果可能会在几个数值之间跳动,很大程度上就是由于这个原因(但并不完全是,因为还有无时无处不在的干扰的影响)。
5、转换速率
转换速率,是指 ADC 每秒能进行采样转换的最大次数,单位是 sps (或 s/s、sa/s,即 samplesper second),它与 ADC 完成一次从模拟到数字的转换所需要的时间互为倒数关系。ADC 的种类比较多,其中积分型的 ADC 转换时间是毫秒级的,属于低速 ADC;逐次逼近型 ADC转换时间是微秒级的,属于中速 ADC;并行/串行的 ADC 的转换时间可达到纳秒级,属于高速 ADC。
PCF8591的硬件接口
PCF8591是一个单电源低功耗的 8 位 CMOS 数据采集器件,具有 4 路模拟输入,1 路模拟输出和一个串行 I 2 C 总线接口用来与单片机通信。与前面讲过的 24C02 类似,3 个地址引脚 A0、A1、A2 用于编程硬件地址,允许最多 8 个器件连接到I2C 总线而不需要额外的片选电路。器件的地址、控制以及数据都是通过 I2C 总线来传输。
其中引脚 1、2、3、4 是 4 路模拟输入,引脚 5、6、7 是 I 2 C 总线的硬件地址,8 脚是数字地 GND,9 脚和 10 脚是 I 2 C 总线的 SDA 和 SCL。12 脚是时钟选择引脚,如果接高电平表示用外部时钟输入,接低电平则用内部时钟,我们这套电路用的是内部时钟,因此 12 脚直接接 GND,同时 11 脚悬空。13 脚是模拟地 AGND,在实际开发中,如果有比较复杂的模拟电路,那么 AGND 部分在布局布线上要特别处理,而且和 GND 的连接也有多种方式,这个板子上没有复杂的模拟部分电路,所以我们把 AGND 和 GND 接到一起。14 脚是基准源,15 脚是 DAC 的模拟输出,16 脚是供电电源 VCC。
PCF8591 的 ADC 是逐次逼近型的,转换速率算是中速,但是它的速度瓶颈在 I 2 C 通信上。由于 I 2 C 通信速度较慢,所以最终的 PCF8591 的转换速度,直接取决于 I 2 C 的通信速率。由于 I 2 C 速度的限制,所以 PCF8591 得算是个低速的 AD 和 DA 的集成,主要应用在一些转换速度要求不高,希望成本较低的场合,比如电池供电设备,测量电池的供电电压,电压低于某一个值,报警提示更换电池等类似场合。
Vref 基准电压的提供有两种方法。一是采用简易的原则,直接接到 VCC 上去,但是由于 VCC 会受到整个线路的用电功耗情况影响,一来不是准确的 5V,实测大多在 4.8V 左右,二来随着整个系统负载情况的变化会产生波动,所以只能用在简易的、对精度要求不高的场合。方法二是使用专门的基准电压器件,比如 TL431,它可以提供一个精度很高的 2.5V 的电压基准。
对于AD 来说,只要输入信号超过 Vref 基准源,它得到的始终都是最大值,即 255,也就是说它实际上无法测量超过其 Vref 的电压信号的。需要注意的是,所有输入信号的电压值都不能超过 VCC,即+5V,否则可能会损坏 ADC 芯片。
在CT107D开发板上,Vref是直接接到了VCC上。
PCF8591编程
PCF8591 的通信接口是 I 2 C,那么编程肯定是要符合这个协议的。单片机对 PCF8591 进行初始化,一共发送三个字节即可。
第一个字节,和E2PROM类似,是器件地址字节,其中7位代表地址,一位代表读写方向。地址高四位固定是0b1001,低三位是A2、A1、A0,这三位在电路上都接到了GND,因此也就是0b00,如下图所示:
第二个字节,将被存储在控制寄存器里,用于控制PCF8591的功能。其中第3位和第7位是固定的0,另外66位各自有各自的作用,如下图所示:
控制字节的第 6 位是 DA 使能位,这一位置 1 表示 DA 输出引脚使能,会产生模拟电压输出功能。
第4位和第5位可以实现把PCF8591的4路模拟输入配置成单端模式和差分模式,是配置 AD输入方式的控制位。单端模式和差分模式的区别。 如下图所示:
控制字节的第 2 位是自动增量控制位,自动增量的意思就是,比如一共有 4 个通道,当全部使用的时候,读完了通道 0,下一次再读,会自动进入通道 1 进行读取,不需要我们指定下一个通道。
注意:由于 A/D 每次读到的数据,都是上一次的转换结果,所以在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值。 为了保持程序的通用性,代码没有使用这个功能,而是直接做了一个通用的程序。
控制字节的第 0 位和第 1 位就是通道选择位了,00、01、10、11 代表了从 0 到 3 的一共4 个通道选择。
第三个字节,D/A数据寄存器,表示D/A模拟输出的电压值。如果仅仅使用A/D功能,可不发送第三个字节!
ADC程序
/*******************************************************************************
* 函数名 :Read_AIN
* 输入值 :unsigned char chn
* 返回值 :unsigend char dat
* 作者 :小默haa
* 时间 :2019年2月25日
* 功能描述:读取PCF8591AIN采集数据
* 备注 :chn为PCF8591的通道
*******************************************************************************/
unsigned char Read_AIN(unsigned char chn)
{
unsigned char dat;
EA = 0;
IIC_Start(); //IIC总线起始信号
IIC_SendByte(0x90); //PCF8591的写设备地址
IIC_WaitAck(); //等待从机应答
IIC_SendByte(chn); //写入PCF8591的控制字节
IIC_WaitAck(); //等待从机应答
IIC_Stop(); //IIC总线停止信号
IIC_Start(); //IIC总线起始信号
IIC_SendByte(0x91); //PCF8591的读设备地址
IIC_WaitAck(); //等待从机应答
dat = IIC_RecByte(); //读取PCF8591通道3的数据
IIC_Ack(0); //产生非应答信号
IIC_Stop(); //IIC总线停止信号
EA = 1;
return dat;
}
/*******************************************************************************
* 函数名 :ValueToString
* 输入值 :unsigned char *str, unsigned char val
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月25日
* 功能描述:将PCF8591AIN采集的数据转换为字符型
* 备注 :注意这里把电压扩大了10倍
*******************************************************************************/
void ValueToString(unsigned char *str, unsigned char val)
{
val = (val * 50) / 255; //电压5V,256个刻度分成255份!
str[0] = val / 10;
str[1] = ‘.’;
str[2] = val % 10;
str[3] = ‘V’;
}
注意:在程序里我设置开始读ADC值时关闭中断,避免ADC测量程序被中断打断,影响精准度。
D/A输出
/*******************************************************************************
* 函数名 :SetDACOut
* 输入值 :unsigned char val
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月25日
* 功能描述:输入电压值,设置DAC输出值
* 备注 :val为设定的电压值
*******************************************************************************/
void SetDACOut(unsigned char val)
{
IIC_Start();
if(IIC_WaitAck() == 1)
{
IIC_Stop();
return;
}
IIC_SendByte(0x40);
IIC_SendByte(val);
IIC_Stop();
}
/*******************************************************************************
* 函数名 :KeyAction
* 输入值 :unsigned char keycode
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月25日
* 功能描述:通过按键控制输出电压值
* 备注 :
*******************************************************************************/
void KeyAction(unsigned char keycode)
{
static unsigned char volt = 0;
if(keycode == 0x26) //向上键,增加0.1V电压值
{
if(volt < 50)
{
volt++;
SetDACOut((volt * 255) / 50); //转换为AD输出值
}
}
else if(keycode == 0x28) //向下键,减小0.1V电压值
{
if(volt > 0)
{
volt--;
SetDACOut((volt * 255) / 50); //转换为AD输出值
}
}
}