ADC 简介
ADC(analog to digital converter)也称为模数转换器,是指一个将模拟 信号转变为数字信号。单片机在采集模拟信号时,通常都需要在前端加上 A/D 芯 片。下面我们看下 ADC 的主要技术指标:
分辨率
ADC 的分辨率是指对于允许范围内的模拟信号,它能输出离散数字信号值的 个数。这些信号值通常用二进制数来存储,因此分辨率经常用比特作为单位,且 这些离散值的个数是 2 的幂指数。 例如:12 位 ADC 的分辨率就是 12 位,或者说分辨率为满刻度的 1/(2^12)。 一个 10V 满刻度的 12 位 ADC 能分辨输入电压变化最小值是
10 V × 1 / ( 2^ 12 ) = 2.4 m V
转换误差
转换误差通常是以输出误差的最大值形式给出。它表示 A/D 转换器实际输出 的数字量和理论上的输出数字量之间的差别。常用最低有效位的倍数表示。例如 给出相对误差≤±LSB/2,这就表明实际输出的数字量和理论上应得到的输出数 字量之间的误差小于最低位的半个字。
转换速率
ADC 的转换速率是能够重复进行数据转换的速度,即每秒转换的次数。而完 成一次 A/D 转换所需的时间(包括稳定时间),则是转换速率的倒数。
ADC 转换原理
AD 转换器(ADC)将模拟量转换为数字量通常要经过 4 个步骤:采样、保持、 量化和编码。所谓采样即是将一个时间上连续变化的模拟量转换为时间上离散变化的模拟量。如下图所示:
直接比较型:就是将输入模拟信号直接与标准的参考电压比较,从而得到数字量。常见的有并行 ADC 和逐次比较型 ADC。
间接比较型:输入模拟量不是直接与参考电压比较,而是将二者变为中间 的某种物理量在进行比较,然后将比较所得的结果进行数字编码。常见的有双积分型 ADC。
下面就以常用的逐次比较型 ADC 和双积分型 ADC 介绍其工作原理。
逐次逼近型 ADC
采用逐次逼近法的 AD 转换器是有一个比较器、DA 转换器、缓冲寄存器和控 制逻辑电路组成,如下图所示:
基本原理是:从高位到低位逐次试探比较,就像用天平秤物体,从重到轻逐级增减砝码进行试探。逐次逼近法的转换过程是:初始化时将逐次逼近寄存器各位清零,转换开始时,先将逐次逼近寄存器最高位置 1,送入 DA 转换器,经 DA 转换后生成的模拟量送入比较器,称为 U0,与送入比较器的待转换的模拟量 Ux 进行比较,若 U0<Ux,该位 1 被保留,否则被清除。然后再将逐次逼近寄存器次 高位置 1,将寄存器中新的数字量送 DA 转换器,输出的 U0 再与 Ux 比较,若 U0<Ux, 该位 1 被保留,否则被清除。重复此过程,直至逼近寄存器最低位。转换结束后, 将逐次逼近寄存器中的数字量送入缓冲寄存器,得到数字量的输出。逐次逼近的 操作过程是在一个控制电路的控制下进行的
双积分型 ADC
采用双积分法的 AD 转换器由电子开关、积分器、比较器和控制逻辑等部件组成。如下图所示:
其基本原理:将输入电压变换成与其平均值成正比的时间间隔,再把此时间 间隔转换成数字量,属于间接转换。双积分法 AD 转换的过程是:先将开关接通 待转换的模拟量 Vi,Vi 采样输入到积分器,积分器从零开始进行固定时间 T 的 正向积分,时间 T 到后,开关再接通与 Vi 极性相反的基准电压 Vref,将 Vref 输入到积分器,进行反向积分,直到输出为 0V 时停止积分。Vi 越大,积分器输 出电压越大,反向积分时间也越长。计数器在反向积分时间内所计的数值,就是 输入模拟电压 Vi 所对应的数字量,实现了 AD 转换。
常见的AD转换芯片有ADC0808,ADC0809,XPT2046,本次实验我们将使用XPT2046进行讲解。
XPT2046 的介绍
1.功能介绍
XPT2046 是一个内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。XPT2046 支持从 1.5V 到 5.25V 的低电压 I/O 接口。
2.主要特性
工作电压范围为 1.5V~5.25V
支持 1.5V~5.25V 的数字 I/O 口
内建 2.5V 参考电压源
电源电压测量(0V~6V)
内建结温测量功能
触摸压力测量
采用 3 线制 SPI 通信接口
具有自动省电功能
了解完引脚之后,就要看怎么去使用和控制这些引脚来达成目标,所以接下来要看的就是时序工作图,要了解芯片是如何工作,如何完成采样 保持 量化 编码这几个步骤的。
完整时钟时序如下:
如图所示, 一次完整的转换需要 24 个串行同步时钟(DCLK)来完成。(看DCLK时序那,一共出现了3次8, 3*8=24)
前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。3个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。接着的 12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式(SER/DFR’=0),驱动器在转换过程中将一直工作,第 13 个时钟将输出转换结果的最后一位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)。
控制字节由 DIN 输入的控制字如表 5 所示,它用来启动转换,寻址,设置 ADC 分辨率,配置和对XPT2046 进行掉电控制。图 12、表 5 和表 6 给出控制字的各控制位的详细说明。
起始位——第一位,即 S 位。控制字的首位必须是 1,即 S=1。在 XPT2046 的 DIN 引脚检测到起始位前,所有的输入将被忽略。
地址——接下来的 3 位(A2、A1 和 A0)选择多路选择器的现行通道(见表 3、表 4 和图 6),触摸屏驱动和参考源输入。
MODE——模式选择位,用于设置 ADC 的分辨率。MODE=0,下一次的转换将是 12 位模式;MODE=1,下一次的转换将是 8 位模式。
SER/DFR’——SER/DFR’位控制参考源模式,选择单端模式(SER/DFR’=1),或者差分模式(SER/DFR’=0)。在X坐标、Y坐标和触摸压力测量中,为达到最佳性能,首选差分工作模式。参考电压来自开关驱动器的电压。在单端模式下,转换器的参考电压固定为VREF相对于GND引脚的电压。
PD0 和 PD1——表 5 展示了掉电和内部参考电压配置的关系。ADC 的内部参考电压可以单独关闭或者打开,但是,在转换前,需要额外的时间让内部参考电压稳定到最终稳定值;如果内部参考源处于掉电状态,还要确保有足够的唤醒时间。ADC 要求是即时使用,无唤醒时间的。另外还得注意,当 BUSY 是高电平的时候,内部参考源禁止进入掉电模式。XPT2046 的通道改变后,如果要关闭参考源,则要重新对 XPT2046 写入命令。
通过上图和开发板AD模块原理图可以得到,我们如果使用12位分辨率单端模式测量AIN1(可调电阻),那么我们输入DIN端就要控制字命令寄存器值为0x94(1001 1000)或0xB4(1011 1000)。同理如果要检测转换热敏电阻模拟信号,控制字命令寄存器值为0XD4;要检测转换光敏电阻模拟信号,控制字命令寄存器值为0XA4;测转换AIN3通道上模拟信号,控制字命令寄存器值为0XE4.
故一次完整的转换有以下步骤:
(我们如果使用12位分辨率单端模式测量AIN1)
1.先将CS和CLK拉低。
2.利用DIN发送8位控制命令符设置工作模式,且每次发送一位数据时,需将CLK拉高后再拉低。
3.接着利用DOUT接收16位温度数据,且每次接收前,需要将CLK拉高后再拉低,才能接收到数据。(分辨率为12位时,16位接收数据前12位位有效数据,剩下的忽略)
4.将CS拉高,结束一次时钟周期。
示例程序:
ZPT2046读取温度值
//引脚定义
sbit XPY2046_DIN=P3^4; //输入
sbit XPY2046_CS=P3^5; //片选
sbit XPY2046_DCLK=P3^6; //时钟
sbit XPY2046_DOUT=P3^7; //输出
/**
* @brief ZPT2046读取AD值
* @param Command 命令字,范围:头文件内定义的宏,结尾的数字表示转换的位数
* @retval AD转换后的数字量,范围:8位为0~255,12位为0~4095
*/
unsigned int XPT2046_ReadAD(unsigned char Command)
{
unsigned char i;
unsigned int Data=0;
XPY2046_DCLK=0; //拉低时钟
XPY2046_CS=0; //拉低片选,开启周期
for(i=0;i<8;i++) //控制字符命令输入
{
XPY2046_DIN=Command&(0x80>>i);
XPY2046_DCLK=1;
XPY2046_DCLK=0;
}
for(i=0;i<16;i++) //接收转换温度数据
{
XPY2046_DCLK=1;
XPY2046_DCLK=0;
if(XPY2046_DOUT){Data|=(0x8000>>i);}
}
XPY2046_CS=1; //拉高片选,周期结束
return Data>>8; //高位到地位输出8位转换值
}
完整示例:
用LCD1602显示转换值;
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "XPT2046.h"
unsigned int ADValue;
void main(void)
{
LCD_Init();
LCD_ShowString(1,1,"ADJ NTC GR");
while(1)
{
ADValue=XPT2046_ReadAD(XPT2046_XP); //读取AIN0,可调电阻
LCD_ShowNum(2,1,ADValue,3); //显示AIN0
ADValue=XPT2046_ReadAD(XPT2046_YP); //读取AIN1,热敏电阻
LCD_ShowNum(2,6,ADValue,3); //显示AIN1
ADValue=XPT2046_ReadAD(XPT2046_VBAT); //读取AIN2,光敏电阻
LCD_ShowNum(2,11,ADValue,3); //显示AIN2
Delay(100);
}
}
XPT2046.c
#include <REGX52.H>
#include <INTRINS.H>
//引脚定义
sbit XPY2046_DIN=P3^4;
sbit XPY2046_CS=P3^5;
sbit XPY2046_DCLK=P3^6;
sbit XPY2046_DOUT=P3^7;
/**
* @brief ZPT2046读取AD值
* @param Command 命令字,范围:头文件内定义的宏,结尾的数字表示转换的位数
* @retval AD转换后的数字量,范围:8位为0~255,12位为0~4095
*/
unsigned int XPT2046_ReadAD(unsigned char Command)
{
unsigned char i;
unsigned int Data=0;
XPY2046_DCLK=0;
XPY2046_CS=0;
for(i=0;i<8;i++)
{
XPY2046_DIN=Command&(0x80>>i);
XPY2046_DCLK=1;
XPY2046_DCLK=0;
}
for(i=0;i<16;i++)
{
XPY2046_DCLK=1;
XPY2046_DCLK=0;
if(XPY2046_DOUT){Data|=(0x8000>>i);}
}
XPY2046_CS=1;
return Data>>8;
}
XPT2046.h
#ifndef __XPT2046_H__
#define __XPT2046_H__
#define XPT2046_VBAT 0xAC
#define XPT2046_AUX 0xEC
#define XPT2046_XP 0x9C //0xBC
#define XPT2046_YP 0xDC
unsigned int XPT2046_ReadAD(unsigned char Command);
#endif
Delay.c
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
LCD1602.c
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
LCD1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif