模拟量:自然界连续变化的物理量。所谓连续,包含两个方面的含义; 一方面从时间上来说,它是随时间连续变化的; 另一方面从数值上来说,它的数值也是连续变化的。这种连续变化的物理量通常称为模拟量。
数字量:计算机中处理的是不连续变化的量,离散性的数字量。 当计算机用于数据采集和过程控制时,采集的对象往往是连续变化的物理量(模拟信号)如温度、压力、摄像头采集图像、照度等,但计算机处理的是离散的数字量,因此需要对连续变化的物理量进行A/D转换为不连续的数字量交给计算机处理,保存等。计算机输出的数字量有时需要通过D/A转换为模拟量去控制某些执行元件。A/D转换器完成模拟量至数字量的转换,D/A转换器完成数字量至模拟量的转换。
PCF8591
单电源,低功耗8 位CMOS 数据采集器件,具有4 个模拟输入、一个输出和一个串行I2C 总线接口。3 个地址引脚A0、A1 和A2 用于编程硬件地址,允许将最多8 个器件连接至I2C总线而不需要额外硬件。PCF8591由于其使用的简单方便和集成度高,在单片机应用系统中得到了广泛的应用。
特点:
1 单电源供电
2 工作电压:2.5 V ~ 6 V
3 I2C 总线串行输入/输出
4 通过 3 个硬件地址引脚编址
5 采样速率取决于 I2C 总线传输速率决定
6 4 个模拟输入可编程为单端或差分输入
7 自动增量通道选择 8
8 位逐次比较型A/D 转换
AOUT是模拟输出,通过数字量,改变模拟量输出,控制LED10的亮度
8 位逐次比较型A/D 转换
逐次比较型A/D转换器的工作原理可用天平秤重过程作比喻来说明。若四个砝码共重15克,每个重量分别为8、4、2、1克。设待秤重量Wx = 13克,可以用下表步骤来秤量:
顺序 砝码重 比较判断 暂时结果
1 8g 8g<13g 保留 8g
2 8+4g 12g<13g 保留 12g
3 8+4+2g 14g>13g 撤消 12g
4 8+4+1g 13g=13g 保留 13g
AD转换器的主要技术指标概述:
分辨率
分辩率指数字量变化一个最小量时模拟信号的变化量,定义为满刻度与2^n的比值(n为AD器件的位数)。对于5V的满刻度,采用8位的AD时,分辨率为5V/256≈0.01953V;当采用12位的AD时,分辨率则为5V/4096≈0.00122V。显然,位数越多分辨率就越高。
转换速率
转换速率是指完成一次从模拟转换到数字的AD转换所需的时间的倒数。积分型AD的转换时间是毫秒级属低速AD,逐次比较型AD是微秒级属中速AD,全并行/串并行型AD可达到纳秒级。采样时间则是另外一个概念,是指两次转换的间隔。为了保证转换的正确完成,采样速率必须小于或等于转换速率。
量化误差
由于AD的有限分辩率而引起的误差,即有限分辩率AD的阶梯状转换特性曲线与无限分辩率AD(理想AD)的转换特性曲线(直线)之间的最大偏差。通常是1 个或半个最小数字量的模拟变化量,表示为1LSB、1/2LSB。(尺子量出来的1CM不一定是1CM)
零值误差
满刻度误差
线性误差
绝对精度
在一个转换器中,任何数码所对应的实际模拟量输入与理论模拟输入之差的最大值,称为绝对精度。
AD的差分输入与单端输入:
单端输入,输入信号均以共同的地线为基准.这种输入方法主要应用于输入信号电压较高(高于1 V),信号源到模拟输入硬件的导线较短,且所有的输入信号共用一个基准地线.如果信号达不到这些标准,此时应该用差分输入.单端输入时, 是判断信号与 GND 的电压差.
差分输入,每一个输入信号都有自有的基准地线;由于共模噪声可以被导线所消除,从而减小了噪声误差. 差分输入时, 是判断两个信号线的电压差. 信号受干扰时, 差分的两线会同时受影响, 但电压差变化不大. (抗干扰性较佳) 而单端输入的一线变化时, GND 不变, 所以电压差变化较大. (抗干扰性较差)
PCF8591地址:
I2C 总线系统中的每一片PCF8591 通过发送有效地址到该器件来激活。该地址包括固定部分和可编程部分。可编程部分必须根据地址引脚A0、A1 和A2 来设置,因此I2C系统中最多可接 =8个PCF8591。在I2C 总线协议中地址必须是起始条件后作为第一个字节发送。地址字节的最后一位是用于设置以后数据传输方向的读/写位1为读操作,0为写操作。
PCF8591控制字节:
发送到 PCF8591 的第二个字节将被存储在控制寄存器,用于控制器件功能。控制寄存器的高半字节用于允许模拟输出,和将模拟输入编程为单端或差分输入。低半字节选择一个由高半字节定义的模拟输入通道。如果自动增量(auto-increment)标志置1,每次A/D 转换后通道号将自动增加。
#include <reg52.h> #include <intrins.h> #define MAIN_Fosc 11059200UL //宏定义主时钟HZ #define PCF8591_ADDR 0x90 //PCF8591地址 #define DACOUT_EN 0x40 //DAC输出使能 sbit BEEP = P2^3; //蜂鸣器 /*==================================== 自定义类型名 ====================================*/ typedef unsigned char INT8U; typedef unsigned char uchar; typedef unsigned int INT16U; typedef unsigned int uint; /*==================================== 硬件接口位声明 ====================================*/ sbit SDA = P2^0; //I2C串行数据 sbit SCL = P2^1; //I2C串行时钟 sbit DU = P2^6; //数码管段选 sbit WE = P2^7; //数码管位选 sbit LED1= P1^0; //ADC读取失败指示灯(亮成功,灭失败) sbit LED2= P1^1; //DAC转换失败指示灯(亮成功,灭失败) /*==================================== 共阴极数码管段选码 ====================================*/ uchar code table[]={ //0 1 2 3 4 5 6 7 8 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, //9 A B C D E F - . 关显示 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40, 0x80, 0x00 }; /*==================================== 数码管位选码 ====================================*/ //第1位 2位 3位 4位 5位 6位 7位 8位 uchar code T_COM[] = {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f};//数码管位码 /*==================================== 函数:void Delay_Ms(INT16U ms) 参数:ms,毫秒延时形参 描述:12T 51单片机自适应主时钟毫秒级延时函数 ====================================*/ void Delay_Ms(INT16U ms) { INT16U i; do{ i = MAIN_Fosc / 96000; while(--i); //96T per loop }while(--ms); } /*==================================== 函数:void Delay5us() 描述:12T 51单片机5微秒延时函数自适应时钟(11.0592M,12M,22.1184M) ====================================*/ void Delay5us() { #if MAIN_Fosc == 11059200 _nop_(); #elif MAIN_Fosc == 12000000 _nop_() #elif MAIN_Fosc == 22118400 _nop_(); _nop_(); _nop_(); #endif } /*==================================== 函数:void Display(INT8U Value) 参数:Value,显示值 取值0-255 描述:共阴极数码管显示函数可显示一个字节的数 ====================================*/ void Display(INT8U Value) { //------------------------------ DU = 1; P0 = table[Value/100]; //管显示百位 DU = 0; P0 = 0xff; //清除断码 WE = 1; P0 = T_COM[0]; //第一位数码管 WE = 0; Delay_Ms(5); //------------------------------- DU = 1; P0 = table[Value%100/10]; //显示十位 DU = 0; P0 = 0xff; //清除断码 WE = 1; P0 = T_COM[1]; //第二位数码管 WE = 0; Delay_Ms(5); //------------------------------- DU = 1; P0 = table[Value%10]; //显示个位 DU = 0; P0 = 0xff; //清除断码 WE = 1; P0 = T_COM[2]; //第三位数码管 WE = 0; Delay_Ms(5); } /*==================================== 函数:I2C_init() 描述:I2C总线初始化 ====================================*/ void I2C_init() { SDA = 1; //数据总线高 _nop_(); SCL = 1; //时钟总线高 _nop_(); } /*==================================== 函数:I2C_Start() 描述:I2C起始信号 ====================================*/ void I2C_Start() { SCL = 1; _nop_(); SDA = 1; Delay5us(); SDA = 0; Delay5us(); } /*==================================== 函数:I2C_Stop() 描述:I2C停止信号 ====================================*/ void I2C_Stop() { SDA = 0; _nop_(); SCL = 1; Delay5us(); SDA = 1; Delay5us(); } /*==================================== 函数:Master_ACK(bit i) 参数:i 为0时发送非应答 为1时发送应答 描述:I2C主机发送应答 ====================================*/ void Master_ACK(bit i) { SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化 _nop_(); // 让总线稳定 if (i) //如果i = 1 那么拉低数据总线 表示主机应答 { SDA = 0; } else { SDA = 1; //发送非应答 } _nop_();//让总线稳定 SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号 _nop_(); SCL = 0;//拉低时钟总线, 占用总线继续通信 _nop_(); SDA = 1;//释放SDA数据总线。 _nop_(); } /*==================================== 函数:Test_ACK() 返回:0为不应答 1为应答 描述:I2C检测从机应答 ====================================*/ bit Test_ACK() // 检测从机应答 { SCL = 1;//时钟总线为高电平期间可以读取从机应答信号 Delay5us(); if (SDA) { SCL = 0; I2C_Stop(); return(0); } else { SCL = 0; return(1); } } /*==================================== 函数:I2C_send_byte(uchar byte) 参数:byte 要发送的字节 描述:I2C发送一个字节 ====================================*/ void I2C_send_byte(uchar byte) { uchar i; for(i = 0 ; i < 8 ; i++) { SCL = 0; _nop_(); if (byte & 0x80) // { SDA = 1; _nop_(); } else { SDA = 0; _nop_(); } SCL = 1; _nop_(); byte <<= 1; } SCL = 0; _nop_(); SDA = 1; _nop_(); } /*==================================== 函数:I2C_read_byte() 返回:读取的字节 描述:I2C读一个字节 ====================================*/ uchar I2C_read_byte() { uchar i, dat; SCL = 0 ; _nop_(); SDA = 1; _nop_(); for(i = 0 ; i < 8 ; i++) { SCL = 1; _nop_(); dat <<= 1; if (SDA) { dat |= 0x01; } _nop_(); SCL = 0; _nop_(); } return(dat); } /*==================================== 函数:DAC_OUT(uchar DAT) 参数:DAT,发送给PCF8591转换的数字量 返回:返回1执行成功,0失败 描述:主机发送数字量交由PCF8591转换为模拟量 ====================================*/ bit DAC_OUT(uchar DAT) { I2C_Start(); //I2C总线起始 I2C_send_byte(PCF8591_ADDR+0);//发送PCF8591地址加读写方向位0(写) if (!Test_ACK()) //检测是否发送成功(应答) { return(0); } I2C_send_byte(DACOUT_EN); //发送控制字节DAC输出使能 if (!Test_ACK()) //检测是否发送成功(应答) { return(0); } I2C_send_byte(DAT); //发送数字量交由PCF8591转为模拟量AOUT脚输出 if (!Test_ACK()) //检测是否发送成功(应答) { return(0); } I2C_Stop(); //I2C停止信号 return(1); } /*==================================== 函数:bit ADC_Read(uchar CON, uchar *DAT) 参数:CON,ADC控制字节 *DAT指针变量,用于存放带入形参变量的地址 返回:返回1执行成功,0失败 描述:读取PCF8591的转换回的值 ====================================*/ bit ADC_Read(uchar CON, uchar *DAT) { I2C_Start(); //I2C起始信号 I2C_send_byte(PCF8591_ADDR+0); //发送PCF8591地址加读写方向位0(写) if (!Test_ACK()) //检测是否发送成功(应答) { return(0); } I2C_send_byte(CON); //发送控制字节 Master_ACK(0); //发送非应答 I2C_Start(); //重发起始信号 I2C_send_byte(PCF8591_ADDR+1); //改变读写方向(读) if (!Test_ACK()) //检测是否发送成功(应答) { return(0); } *DAT = I2C_read_byte(); //把读取的值赋给形参 Master_ACK(0); //主机发送非应答 I2C_Stop(); //I2C停止信号 return(1); //成功返回1 } /*==================================== 函数:void main() 描述:主函数 ====================================*/ void main() { INT8U ADC_Value; //存放ADC的值 I2C_init(); while(1) { //读取通道2转换的值,单端输入。 if(!ADC_Read(0x02, &ADC_Value)) LED1 = 1; else; LED1 = 0; //把AD转换得的数量在送给PCF8591转为模拟量控制模拟量输出口的小灯 if(!DAC_OUT(ADC_Value)) LED2 = 1; else LED2 = 0; //显示读出的模拟量 Display(ADC_Value); if (ADC_Value > 150) BEEP = 0; else BEEP = 1; Delay_Ms(5); } }