蓝桥杯单片机设计与开发⑨ ---ADDA

A/D(模数转换)

  1. ADC的位数
    一个n位的ADC表示这个ADC共有2的n次方个刻度。8位的ADC输出的是从0~255一共256个数字值,也就是28个数据刻度
  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 得出的结果可能会在几个数值之间跳动,很大程度上就是由于这个原因(但并不完全是,因为还有无时无处不在的干扰的影响)。
  1. 转换速率
    转换速率,是指 ADC 每秒能进行采样转换的最大次数,单位是 sps (或 s/s、sa/s,即 samplesper second),它与 ADC 完成一次从模拟到数字的转换所需要的时间互为倒数关系。ADC 的种类比较多,其中积分型的 ADC 转换时间是毫秒级的,属于低速 ADC;逐次逼近型 ADC转换时间是微秒级的,属于中速 ADC;并行/串行的 ADC 的转换时间可达到纳秒级,属于高速 ADC。

PDF8591硬件接口

PCF8591是一个单电源低功耗的 8 位 CMOS 数据采集器件,具有 4 路模拟输入,1 路模拟输出和一个串行 I 2 C 总线接口用来与单片机通信。与前面讲过的 24C02 类似,3 个地址引脚 A0、A1、A2 用于编程硬件地址,允许最多 8 个器件连接到I2C 总线而不需要额外的片选电路。器件的地址、控制以及数据都是通过 I2C 总线来传输。

蓝桥杯单片机设计与开发⑨ ---ADDA_第1张图片

其中引脚 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上。

PDF8591编程

PCF8591 的通信接口是 i2C,那么编程肯定是要符合这个协议的。单片机对 PCF8591 进行初始化,一共发送三个字节即可。

第一个字节,和E2PROM类似,是器件地址字节,其中7位代表地址,一位代表读写方向。地址高四位固定是 0b1001,低三位是A2、A1、A0,这三位在电路上都接到了GND,因此也就是0b00,如下图所示:
蓝桥杯单片机设计与开发⑨ ---ADDA_第2张图片
第二个字节,将被存储在控制寄存器里,用于控制PCF8591的功能。其中第3位和第7位是固定的0,另外6位各自有各自的作用,如下图所示:
控制字节的第 6 位是 DA 使能位,这一位置 1 表示 DA 输出引脚使能,会产生模拟电压输出功能。
第4位和第5位可以实现把PCF8591的4路模拟输入配置成单端模式和差分模式,是配置 AD输入方式的控制位。单端模式和差分模式的区别。 如下图所示:
蓝桥杯单片机设计与开发⑨ ---ADDA_第3张图片
控制字节的第 2 位是自动增量控制位,自动增量的意思就是,比如一共有 4 个通道,当全部使用的时候,读完了通道 0,下一次再读,会自动进入通道 1 进行读取,不需要我们指定下一个通道。

注意:由于 A/D 每次读到的数据,都是上一次的转换结果,所以在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值。 为了保持程序的通用性,代码没有使用这个功能,而是直接做了一个通用的程序。

控制字节的第 0 位和第 1 位就是通道选择位了,00、01、10、11 代表了从 0 到 3 的一共4 个通道选择。

第三个字节,D/A数据寄存器,表示D/A模拟输出的电压值。如果仅仅使用A/D功能,可不发送第三个字节!

ADC程序

pcf8591.c

#include 
#include "iic.h"
#define PCF8591W 0x90
#define PCF8591R 0x91
/*******************************************************************************
* 函数名	:Read_AIN
* 输入值	:unsigned char chn
* 返回值	:unsigend char dat
* 作者		:guyao
* 时间		:2021/2/22
* 功能描述:读取PCF8591AIN采集数据
* 备注		:chn为PCF8591的通道
*******************************************************************************/
unsigned char Read_AIN(unsigned char chn)
{
		unsigned char dat;
		EA = 0;
		IIC_Start();
		IIC_SendByte(PCF8591W);//PCF8591写地址
		IIC_WaitAck();
		IIC_SendByte(chn);//写入PCF8591控制字节
		IIC_WaitAck();
		IIC_Stop();
		somenop;
		IIC_Start();
		IIC_SendByte(PCF8591R);//PCF8591读地址
		IIC_WaitAck();
		dat = IIC_RecByte();//读取PCF8591通道3的数据
		IIC_Ack(0);
		IIC_Stop();
		EA  = 1;
		return dat;
}
/*******************************************************************************
* 函数名	:ValueToString
* 输入值	:unsigned char *str, unsigned char val
* 返回值	:none
* 作者		:guyao
* 时间		:2021/2/22
* 功能描述:将PCF8591AIN采集的数据转换为字符型
* 备注		:注意这里把电压扩大了10倍
*******************************************************************************/
void ValueToString(unsigned char *str, unsigned char val)
{
		
		val = (val*50)/255;//电压5v,256个刻度分成255份
		
		str[0] = val / 10 + 48; //0~9对应assica表 48~57
		str[1] = '.';
		str[2] = val % 10 + 48;
		str[3] = 'V';
	
}

注意:在程序里我设置开始读ADC值时关闭中断,避免ADC测量程序被中断打断,影响精准度
uart.c - - 串口打印输出从AIN3通道测量的电压值

#include "sys.h"
#define FOCS 11059200L
bit Txdflag;
void Uart_Init(uint baud)
{
	AUXR &= 0XBF; //设置定时器1为12T
	AUXR &= 0XFE; //选择定时器1作为波特率发生器
	SCON = 0X40; 	//工作方式1
	PCON &= 0X7F; //波特率不加倍
	TMOD &= 0X0F; 
	TMOD |= 0x20;
	TH1 = TL1 = 256 - FOCS/32/12/baud;
	ET1 = 0;			//关闭定时器1中断
	ES = 1; //打开串口中断
	TR1 = 1; //启动定时器1
}


void Uart_Interrupt() interrupt 4
{
		if(TI)//发送数据完毕
		{
					TI = 0;
					Txdflag = 1;
		}
	
}

void Uart_Print(unsigned char *str)
{
	unsigned char len;
	len = sizeof(str)+ 1;
	while(len != 0)
	{		
			Txdflag = 0;
			SBUF = *str++;
			while(!Txdflag);
			len--;
			if(len == 0)
			{
				SBUF = '\t';
				while(!Txdflag);
			}
	}
	
}

nixie.c - - 数码管显示电压值

#include "sys.h"

					// 0    1    2    3    4    5    6    7
uchar code nixie[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
				    // 8	9    a    b    c    d     e    f	u
					  0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xc1};	//共阳数码管码字

uchar NixieBuff[] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};

uchar smg1,smg2,smg3,smg4,smg5,smg6,smg7,smg8;
uchar code Symbol[] = {0xff,0xbf};	//全灭,-
void Nixie_Scan()
{
		static uchar index=0;
		P2 = (P2 & 0X1F)|0xe0;
		P0 = 0XFF;	//消影
	
		P2 = (P2 & 0X1F)|0xc0; 
		P0 = 0x01<<index;
	
		P2 = (P2 & 0X1F)|0xe0;
		P0 = NixieBuff[index];
		
		P2 &= 0X1F;
		index++;
		index &= 0x07;
}

void Nixie_Show()
{
		NixieBuff[0] = nixie[smg1]&0x7f;
		NixieBuff[1] = nixie[smg2];
		NixieBuff[2] = nixie[smg3];
		NixieBuff[3] = Symbol[smg4];
		NixieBuff[4] = Symbol[smg5];
		NixieBuff[5] = Symbol[smg6];
		NixieBuff[6] = Symbol[smg7];
		NixieBuff[7] = Symbol[smg8];
		
}

void Nixie_Drive(unsigned char val)
{
	val = (val*50)/255;
	smg1 = val/10;
	smg2 = val%10;
	smg3 = 16;
	smg4= smg5= smg6 = smg7= 0;
}

sys.c

#include "sys.h"

extern bit Read_AIN_Falg100ms;
extern bit Uart_Print_Flag1s;
/**
*@brief     外设初始化  
*@param[in] none
*@return    none
**/
void ALL_Init()
{
	P2 = (P2&0x1f)|0xa0;	//打开Y5C
	P0 = 0x00;				//关闭蜂鸣器&继电器
	P2 = (P2&0x1f)|0xe0;	//打开Y7C
	P0 = 0xff;				//关闭数码管
	P2 = (P2&0x1f)|0x80;	//打开Y4C
	P0 = 0xff;				//关闭LED
	P2 = P2&0x1f;			//关闭所用使能
}
/**
*@brief     延时函数
*@param[in] 延时多少ms(0~65535)
*@return    none
**/
void Operate_Delay(u16 ms)
{
	u16 i;
	for(ms;ms>0;ms--)
		for(i=921;i>0;i--);
}
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xCD;			//设置定时初值
	TH0 = 0xD4;			//设置定时初值
	TF0 = 0;			//清除TF0标志
	TR0 = 1;			//定时器0开始计时
	ET0 = 1;
	EA = 1;
}

void Timer0() interrupt 1
{
	static uint i,n;
	i++;
	n++;
	if(n==100)
	{
		Read_AIN_Falg100ms = 1;
		n = 0;
	}
	if(i==1000)
	{
		i = 0;
		Uart_Print_Flag1s = 1;
	}
	Nixie_Show();
	Nixie_Scan();
	
}

main.c

#include "sys.h"

unsigned char buf[4] = {0};
bit Read_AIN_Falg100ms = 0;
bit Uart_Print_Flag1s = 0;
void main()
{
	unsigned char val;
	ALL_Init();
	Timer0Init();
	Uart_Init(9600);
	while(1)
	{
		//100ms读取一次电压值
		if(Read_AIN_Falg100ms)
		{
			val = Read_AIN(0x03);
			Read_AIN_Falg100ms = 0;
		}
		//串口每1s打印一次电压值
		if(Uart_Print_Flag1s)
		{
			Uart_Print(buf);
			Uart_Print_Flag1s = 0;			
		}
		ValueToString(buf,val);
		Nixie_Drive(val);
	}
}

蓝桥杯单片机设计与开发⑨ ---ADDA_第4张图片
蓝桥杯单片机设计与开发⑨ ---ADDA_第5张图片

D/A(数字量转模拟量输出)

蓝桥杯单片机设计与开发⑨ ---ADDA_第6张图片

根据原理图,j3这排针脚中的第19个引脚为模拟量输出引脚,输出电压量程为0~5v

需要测量模拟输出的电压,得另外使用测量仪器(例如万用表)这里直接给出驱动程序

对于 val 的取值,应该在0~255 当val = 255时 对应P19口输出5V电压

/*******************************************************************************
* 函数名	:SetDACOut
* 输入值	:unsigned char val
* 返回值	:none
* 作者		:guyao
* 时间		:2021/2/23
* 功能描述:输入电压值,设置DAC输出值
* 备注		:val为设定的电压值
*******************************************************************************/

void SetDACOut(unsigned char val)
{
	IIC_Start();
	if(IIC_WaitAck())
	{
		IIC_Stop();
		return;
	}
	IIC_SendByte(0x40);//使能AD输出
	IIC_SendByte(val);//输出值
	IIC_Stop();
}

你可能感兴趣的:(蓝桥杯,STC15,单片机)