蓝桥杯之单片机设计与开发(18)——模数转换A/D与数模转换D/A

版权声明:让结局不留遗憾,让过程更加完美。 https://blog.csdn.net/Xiaomo_haa/article/details/87922645

A/D的主要指标

1、ADC的位数

一个n位的ADC表示这个ADC共有2的n次方个刻度。8位的ADC输出的是从0~255一共256个数字值,也就是2^{^{8}}个数据刻度。。

2、基准源

基准源也叫基准电压,是ADC的一个重要指标,要想把输入的ADC的信号测量准确,那么基准源首先要准,基准源的偏差会直接导致转换结果的偏差。

3、分辨率

分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与2^{_{n}}-1的比值。假定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输出值
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Xiaomo_haa/article/details/87922645