4.8 PCF8591(ADC/DAC)转换芯片
4.8.1 原理图
当前实验板上没有PCF8591芯片,这里采用外接模块的形式使用。
通过原理图得到的重要信息: PCF8591芯片地址线全部接GND。也就是当前模块的地址固定为: 1001000
4.8.2 PCF8591模块功能介绍
在一个完整的单片机系统中,A/D转换芯片往往是必不可少的;PCF8591是一款具有I2C总线接口的A/D转换芯片。在与CPU的信息传输过程中仅靠时钟线SCL和数据线SDA就可以实现。
PCF8591具有8位AD/DA转换器,有4个模拟输入口、1个模拟输出口和1个串行I²C总线接口。PCF8591的3个地址引脚A0, A1和A2可用于硬件地址编程,飞利浦公司规定A/D转换芯片的内部器件地址为1001,这样就允许在同个I2C总线上接入8个PCF8591器件,而无需额外的硬件。在PCF8591器件上输入输出的地址、控制和数据信号都是通过双线双向I2C总线以串行的方式进行传输。
I2C总线是Philips(飞利浦)公司推出的串行总线,整个系统仅靠数据线(SDA)和时钟线(SCL)实现完善的全双工数据传输,即CPU与各个外围器件仅靠这两条线实现信息交换。I2C总线系统与传统的并行总线系统相比具有结构简单、可维护性好、易实现系统扩展、易实现模块化标准化设计、可靠性高等优点。
PCF8591的电源电压典型值为5V,所有输入信号的电压值都不能超过 VCC,即+5V,否则可能会损坏 ADC 芯片。
引脚功能介绍:
(1)、AIN0~AIN3:模拟信号输入端。
(2)、A0~A3:引脚地址端。
(3)、VDD、VSS:电源端。(2.5~6V)
(4)、SDA、SCL:I2C总线的数据线、时钟线。
(5)、OSC:外部时钟输入端,内部时钟输出端。
(6)、EXT:内部、外部时钟选择线,使用内部时钟时EXT接地。
(7)、AGND:模拟信号地。
(8)、AOUT:D/A转换输出端。
(9)、VREF:基准电源端
PCF8591的ADC是逐次逼近型的,转换速率算是中速,它的速度瓶颈在 I2C 通信上。由于 I2C 通信速度较慢,所以最终的 PCF8591 的转换速度,直接取决于 I2C 的通信速率。由于 I2C 速度的限制,所以 PCF8591 算是个低速的 AD 和 DA 的集成,主要应用在一些转换速度要求不高,希望成本较低的场合,比如电池供电设备,测量电池的供电电压,电压低于某一个值,报警提示更换电池等类似场合。
4.8.3 PCF8591寄存器介绍
PCF8591遵循标准IIC协议,编程肯定符合这个协议的。
单片机对 PCF8591 进行初始化,一共发送三个字节即可;第一个字节和 EEPROM 类似,是器件地址字节,其中 7 位代表地址, 1 位代表读写方向。地址高 4位固定是 0b1001,低三位是 A2, A1, A0,这三位在模块电路上都接了 GND,因此也就是 0b000 (这里0b是二进制的前缀)。
(1). PCF8591的设备地址
其中:
高位是器件固定的地址:1001
低位A2 A1 A0 由硬件电路决定
R/W是读写位:1表示读 0表示写
本模块的硬件地址固定为: 0 0 0 ,得出PCF8591的地址:0x91(读方向) 0x90(写方向)
(2). PCF8591的控制字节
发送到 PCF8591 的第二个字节将被存储在控制寄存器,用于控制 PCF8591 的功能。
其中第 3 位和第 7 位是固定的 0,另外 6 位各自有各自的作用。
控制字节的第6位是DAC使能位,这一位置1表示 DAC 输出引脚使能,会产生模拟电压输出功能。
第 4位和第 5位可以实现把 PCF8591的 4路模拟输入配置成单端模式和差分模式,具体的配置可以看下面图片。
控制字节的第 2 位是自动增量控制位,如果此位为1,就使用自动增量;自动增量的意思就是,比如一共有 4 个通道,当4个通道全部使用的时候,读完了通道 0,下一次再读,会自动进入通道 1 进行读取,不需要指定下一个通道,由于 A/D 每次读到的数据,都是上一次的转换结果,所以在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值。
控制字节的第 0 位和第 1 位是通道选择位, 00、 01、 10、 11 代表了从 0 到 3 的一共4 个通道选择。
如果使用通道0为单端输入,采集ADC转换的数据,那么控制字节就为: 0000 0000 -->0x00
如果使用通道1为单端输入,采集ADC转换的数据,那么控制字节就为: 0000 0001 -->0x01
如果使用通道2为单端输入,采集ADC转换的数据,那么控制字节就为: 0000 0010 -->0x02
如果使用通道3为单端输入,采集ADC转换的数据,那么控制字节就为: 0000 0011 -->0x03
如果使用DAC输出功能,那么控制字节就为: 0100 0000 -->0x40
4.8.4 读写时序介绍
(1) 设置PCF8591转换通道读取ADC转换数据的时序
示例代码:
/* 函数功能: 设置ADC转换通道,并返回采集的数据值 ch的范围:0x00 0x01 0x02 0x03 分别代表通道0~3 */ u8 PCF8591_GetADC_CHx(u8 ch) { u8 dat; IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_WRITE_ADDR);//发送设备地址 IIC_GetAck();//获取应答 IIC_SendOneByte(ch); //发送控制字节 IIC_GetAck();//获取应答 IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_READ_ADDR);//发送设备地址 IIC_GetAck();//获取应答 dat=IIC_RecvOneByte();//读取数据 IIC_SendAck(1); //发送非应答 IIC_SendStop(); //停止信号 return dat; }
(2). 设置DAC通道输出数据时序
示例代码:
/* 函数功能:设置DAC通道输出的值 */ void PCF8591_SetDAC_Data(u8 val) { IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_WRITE_ADDR);//发送设备地址 IIC_GetAck();//获取应答 IIC_SendOneByte(0x40); //发送控制字节 IIC_GetAck();//获取应答 IIC_SendOneByte(val); //设置AD值 IIC_GetAck();//获取应答 IIC_SendStop();//停止信号 }
4.8.9 PCF8591完整示例代码
PCF8591模块接在实验板P2.1(SCL)和P2.0(SDA)口上,在主函数里按照顺序读取4个通道ADC值,打印到串口终端,并且使用其中一个ADC通道检测的输入值,作为DAC通道的输出值,用以测试DAC通道功能。
(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)
示例代码:
#include <reg51.h> /* 硬件连接: SCL---P2.1 SDA---P2.0 */ //数据线 sbit IIC_SDA=P2^0; //时钟线 sbit IIC_SCL=P2^1; /* 函数功能: 发送起始信号 当时钟线为高电平的时候,数据线由高电平变为低电平的过程 */ void IIC_SendStart(void) { u8 i=0; IIC_SCL=1; IIC_SDA=1; IIC_SDA=0; IIC_SCL=0; //总线开始工作、开始读写数据 } /* 函数功能: 停止信号 当时钟线为高电平的时候,数据线由低电平变为高电平的过程 */ void IIC_SendStop(void) { u8 i=0; IIC_SCL=0; IIC_SDA=0; IIC_SCL=1; IIC_SDA=1; } /* 函数功能: 获取应答信号 返 回 值: 0表示获取到应答 1表示没有获取到应答 */ u8 IIC_GetAck(void) { u8 i=0; IIC_SDA=1; //上拉 IIC_SCL=0; IIC_SCL=1; while(IIC_SDA) { i++; if(i>250)return 1; //获取不到应答 } IIC_SCL=0; return 0; } /* 函数功能: 发送应答信号 函数参数:0表示应答 1表示非应答 */ void IIC_SendAck(u8 ack) { u8 i=0; IIC_SCL=0; if(ack)IIC_SDA=1; //发送非应答 else IIC_SDA=0; //发送应答 IIC_SCL=1; IIC_SCL=0; } /* 函数功能: 发送一个字节数据 */ void IIC_SendOneByte(u8 dat) { u8 j=0,i=0; for(j=0;j<8;j++) { IIC_SCL=0; if(dat&0x80)IIC_SDA=1; else IIC_SDA=0; IIC_SCL=1; dat<<=1; } IIC_SCL=0; } /* 函数功能: 接收一个字节数据 */ u8 IIC_RecvOneByte(void) { u8 i=0,j=0; u8 dat=0; for(j=0;j<8;j++) { IIC_SCL=0; IIC_SCL=1; dat<<=1; //表示默认收到0 if(IIC_SDA)dat|=0x01; } IIC_SCL=0; return dat; } /* 函数功能: 设置ADC转换通道,并返回采集的数据值 ch的范围:0x00 0x01 0x02 0x03 分别代表通道0~3 */ u8 PCF8591_GetADC_CHx(u8 ch) { u8 dat; IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_WRITE_ADDR);//发送设备地址 IIC_GetAck();//获取应答 IIC_SendOneByte(ch); //发送控制字节 IIC_GetAck();//获取应答 IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_READ_ADDR);//发送设备地址 IIC_GetAck();//获取应答 dat=IIC_RecvOneByte();//读取数据 IIC_SendAck(1); //发送非应答 IIC_SendStop(); //停止信号 return dat; } /* 函数功能:设置DAC通道输出的值 */ void PCF8591_SetDAC_Data(u8 val) { IIC_SendStart();//起始信号 IIC_SendOneByte(PCF8591_WRITE_ADDR);//发送设备地址 IIC_GetAck();//获取应答 IIC_SendOneByte(0x40); //发送控制字节 IIC_GetAck();//获取应答 IIC_SendOneByte(val); //设置AD值 IIC_GetAck();//获取应答 IIC_SendStop();//停止信号 } int main() { u8 ch0,ch1,ch2,ch3; //存放ADC通道检测的值 u8 dac_val; //存放DAC输出的值 UART_Init(); //初始化串口波特率为4800 while(1) { /*1. 转换并读取通道0的AD值: 模块上通道0默认接可调0-5v的可变电阻*/ ch0=PCF8591_GetADC_CHx(0x00);//模块上标注是AIN3 printf("(CH0)可变电阻=%d\r\n",(int)ch0); /*2. 转换并读取通道1的AD值: 模块上通道1默认接光敏电阻*/ ch1=PCF8591_GetADC_CHx(0x01); //模块上标注是AIN0 printf("(CH1)光敏电阻=%d\r\n",(int)ch1); /*3. 转换并读取通道2的AD值: 模块上通道2默认接热敏电阻*/ ch2=PCF8591_GetADC_CHx(0x02);//模块上标注是AIN1 printf("(CH2)热敏电阻=%d\r\n",(int)ch2); /*4. 转换并读取通道3的AD值: 模块上通道3默认悬空没有接检测点*/ ch3=PCF8591_GetADC_CHx(0x03);//模块上标注是AIN2 printf("(CH3)=%d\r\n",(int)ch3); printf("---------------------\r\n"); /*5. 设置DAC输出值*/ PCF8591_SetDAC_Data(ch0); //使用通道1测量的可变电阻值传给DAC输出 DelayMs(1000); //延时一段时间 } }