上期我们学习了DS1302实时时钟的基本使用,现在我们来学习PCF8591A/D&D/A转换芯片的相关内容
PCF8591是具有I2C 总线接口的8 位A/D 及D/A 转换器。具有以下特点:
【1】 单电源供电
【2】 工作电压2.5~6.0V
【3】 低待机电流(低功耗)
【4】 使用IIC通信接口
【5】 具有三个可编程地址引脚
【6】 4个模拟输入通道可控制为单端输入或差分输入
【6】 可控制自动递增通道选择
【7】 模拟输出电压范围VSS~VDD
【8】 八位逐次逼近AD转换
【9】 一路模拟输出DA转换
【1】 AIN0~3 :四路模拟输入,用于AD转换
【2】 A0~A2:可编程地址
【3】 VSS/VDD:负/正电压,采用双电源供电,模拟输出电压范围VSS~VDD
【4】 SDA:IIC接口数据线
【5】 SCL:IIC接口时钟线
【6】 OSC:时钟输入/输出引脚,外部时钟输入,内部时钟输出。
【7】 EXT:时钟输入输出开关,使用内部时钟时接地(为低电平)
【8】AGND:模拟信号的地
【9】VREF:基准电压,原理图上直接接的是VCC(5V)
【10】VOUT:模拟输出引脚
以上是PCF8591的硬件原理图,下面是4个模拟输入和模拟输出的产生方式。
1.AIN0/AOUT:直接接到排针J3上面,可以提供直接提供电压模拟输入&直接测量电压
2.AIN1:通过光敏电阻分压模拟输入
3.AIN2:先做保留,下次介绍
4.AIN3:通过一个继电器分压输入。
PCF8591使用的是IIC通信协议,IIC可用于多机通信,因此在通信之前需要寻址,以下是PCF8591的读写地址。
在通信寻址成功之后,我们需要对PCF8591写入控制指令,设置芯片的工作模式。
其中:
AD转换是将时间连续和幅值连续的模拟量转换为时间离散、幅值也离散的数字量。使输出的数字量与输入的模拟量成正比。模拟量与输出量的关系如下图
举个栗子:
芯片使用通道0进行AD转换,在通道0输入一个1.0V的电压,那么对应的数字量 = 256*(V-AIN - V-AGND)/ (V-REF - V-AGND) = 256 * 1.0/5.0 = 51 = 0x33
特别的:当输入电压>(V-REF - V-AGND)以上时,输出为255不变
DA转换则是将数字量转换为模拟量进行输出,与AD转换相反。
计算公式如图,特别的,当V-AGND = 0V 时,V-AOUT= 256*数字量/V-REF
注意:输入数字量的取值为0~255, 输出模拟量的范围是V-AGND~V-REF。
I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
规定:在传输过程在,在SCL为低电平时,SDA允许改变,在SCL为高电平时读取SDA的电平,SDA不允许改变。
1.起始信号:SCL为高电平时,SDA产生下降沿,为开始信号
2.结束信号,SCL为高电平时,SDA产生上升沿,为结束信号
官方提供代码如下:
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0; //SCL为高电平时,SDA产生下降沿,为开始信号
IIC_Delay(DELAY_TIME);
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 1; //SCL为高电平时,SDA产生上升沿,为结束信号
IIC_Delay(DELAY_TIME);
}
在IIC协议中,主机发送一个字节数据后,从机需要产生应答位(Ack),为0时表示应答成功,此时主机可以选择继续发送或结束本次通信;为1时表示应答失败,此时从机将不再介绍来自主机的数据。
当通信方向为单片机->PCF8591时,单片机每发送一个字节数据,需要等待PCF8591应答,反之则需要单片机发送应答。
应答的规则如下:
【1】 一个字节发送完毕,主机释放数据线(将SDA置为1)
【2】 从机应答,将SDA置为0
【3】 等待SCL为高电平读取应答位,
【4】 读取完毕,从机释放总线
【5】 主机的到应答,选择继续发送数据或结束通信
官方提供代码如下:
//发送应答
void IIC_SendAck(bit ackbit)
{
SCL = 0; //将SCL拉低,写入应答位
SDA = ackbit; // 0:应答,1:非应答
IIC_Delay(DELAY_TIME);
SCL = 1; //将SCL拉高,从机读取应答位
IIC_Delay(DELAY_TIME);
SCL = 0;
SDA = 1; //主机释放SDA,结束应答
IIC_Delay(DELAY_TIME);
}
//等待应答
bit IIC_WaitAck(void)
{
bit ackbit;
SCL = 1; //主机释放SCL,等待从机拉低SCL,发送应答
IIC_Delay(DELAY_TIME);
ackbit = SDA; //从机发送应答后,释放SCL
SCL = 0; //主机再次拉低SCL,为下一次发送数据或者结束通信做准备
IIC_Delay(DELAY_TIME);
return ackbit;
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++)
{
SCL = 0; //SCL拉低
IIC_Delay(DELAY_TIME);
if(byt & 0x80) SDA = 1; //主机SDA写入数据位1/0
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1; //SCL拉高,从机读取SDA电平
byt <<= 1;
IIC_Delay(DELAY_TIME);
} //重复八次,完成一个字节的发送
SCL = 0; //将SCL拉低
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char i, da;
for(i=0; i<8; i++) //
{
SCL = 1; //将SCL拉高,
IIC_Delay(DELAY_TIME);
da <<= 1;
if(SDA) da |= 1; //主机读取SDA的状态
SCL = 0; //将SDA拉低,从机写入SDA状态
IIC_Delay(DELAY_TIME);
} //循环八次,完成一个字节数据的读取
return da; //返回读取的字节
}
1.AD转换的具体流程
【1】 单片机发送开始通信信号
【2】 单片机寻址,与PCF8591通信(方向:单片机-> PCF8591)
【3】 等待PCF8591应答,
【4】 应答成功,发送控制指令(AD转换,单端输入,通道为channel)
【5】 等待PCF8591应答,
【6】 应答成功,单片机结束通信
【7】 单片机发送开始通信信号
【8】 单片机寻址,与PCF8591通信(方向: PCF8591->单片机)
【9】 等待PCF8591应答,
【10】 PCF8591发送AD转换后的数字量
【11】 单片机读取数据,并且不应答
【12】 单片机结束通信
【13】 单片机对读取的数据进行处理,
代码如下:
//PCF8591AD转换函数,channel为模拟输入的通道
unsigned char PCF8591_AD_Conversion(unsigned char channel )
{
unsigned char dat = 0;
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Write_Addr); //写入从机PCF8591的写地址,
if(IIC_WaitAck()) //等待从机应答,为1表示不应答,为0表示应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
IIC_SendByte(0x00 | channel); //写入控制指令,单端输入,AD转换,选择通道channel
IIC_WaitAck(); //这一步不能少,否则读出来一直是255
IIC_Stop(); //结束本次通信,改变收发方向
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Read_Addr); //写入从机PCF8591的读地址,
if(IIC_WaitAck()) //等待应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
dat = IIC_RecByte(); //读取AD转换出的数字量
IIC_SendAck(1); //不应答
IIC_Stop(); //结束通信
SEG_Arr[0] = dat/100%10; //计算数字量的百位
SEG_Arr[1] = dat/10%10; //计算数字量的十位
SEG_Arr[2] = dat%10; //计算数字量的各位
return dat;
}
2.DA转换的具体流程
【1】 单片机发送开始通信信号
【2】 单片机寻址,与PCF8591通信(方向:单片机-> PCF8591)
【3】 等待PCF8591应答,
【4】 应答成功,发送控制指令(DA转换)
【5】 等待PCF8591应答,
【6】 应答成功,单片机继续发送DA转换的数字量
【7】 等待PCF8591应答,
【8】 应答成功,PCF8591进行DA转换
【9】 单片机结束通信
代码如下:
//PCF8591DA转换函数,dat为输入的数字量
void PCF8591_DA_Conversion(unsigned char dat)
{
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Write_Addr); //写入PCF8591的写地址
if(IIC_WaitAck()) //等待应答
{
IIC_Stop(); //如果不应答,就结束通信
}
else
{
IIC_SendByte(0x40); //写入控制指令,DA转换
IIC_WaitAck(); //等待应答
IIC_SendByte(dat); //写入数字量
IIC_WaitAck(); //等待应答
IIC_Stop(); //结束通信
}
}
通过官方提供的iic代码,完成与PCF8591之间的通信,具体要求如下:
#include
#include "LS138.h"
#include "iic.h"
#define PCF8591_Write_Addr 0x90 //PCF8591写地址
#define PCF8591_Read_Addr 0x91 //PCF8591读地址
unsigned char SEG_Arr[3]; //数码管显示数字
unsigned char dat = 0; //存放PCF8591读取的内容
unsigned int INT0_Count = 0;
unsigned char channel = 3;
void IT0_Init()
{
IT0= 1;
EX0 =1;
EA =1;
}
//PCF8591AD转换函数,channel为模拟输入的通道
unsigned char PCF8591_AD_Conversion(unsigned char channel )
{
unsigned char dat = 0;
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Write_Addr); //写入从机PCF8591的写地址,
if(IIC_WaitAck()) //等待从机应答,为1表示不应答,为0表示应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
IIC_SendByte(0x00 | channel); //写入控制指令,单端输入,AD转换,选择通道channel
IIC_WaitAck(); //这一步不能少,否则读出来一直是255
IIC_Stop(); //结束本次通信,改变收发方向
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Read_Addr); //写入从机PCF8591的读地址,
if(IIC_WaitAck()) //等待应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
dat = IIC_RecByte(); //读取AD转换出的数字量
IIC_SendAck(1); //不应答
IIC_Stop(); //结束通信
SEG_Arr[0] = dat/100%10; //计算数字量的百位
SEG_Arr[1] = dat/10%10; //计算数字量的十位
SEG_Arr[2] = dat%10; //计算数字量的各位
return dat;
}
//PCF8591DA转换函数,dat为输入的数字量
void PCF8591_DA_Conversion(unsigned char dat)
{
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Write_Addr); //写入PCF8591的写地址
if(IIC_WaitAck()) //等待应答
{
IIC_Stop(); //如果不应答,就结束通信
}
else
{
IIC_SendByte(0x40); //写入控制指令,DA转换
IIC_WaitAck(); //等待应答
IIC_SendByte(dat); //写入数字量
IIC_WaitAck(); //等待应答
IIC_Stop(); //结束通信
}
}
void SEG_Show()
{
SEG_Write(0,10);
SEG_Write(1,channel);
SEG_Write(2,10);
SEG_Write(5,SEG_Arr[0]);
SEG_Write(6,SEG_Arr[1]);
SEG_Write(7,SEG_Arr[2]);
}
void main()
{
LS138_Init(); //LS138初始化
IT0_Init();
while(1)
{
dat = PCF8591_AD_Conversion(channel); //选择通道3输入,通过电阻分压,模拟输入
PCF8591_DA_Conversion(dat); //将读取的数字量通过DA转换,输出
SEG_Show();
}
}
void External_Hander0() interrupt 0
{
INT0_Count++;
if(INT0_Count % 2)
{
channel =3;
}
else
{
channel =1;
}
}
/*0 1 2 3 4 5 6 7 8 9 */
unsigned char code SEG_index[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
0xBF }; //写0点亮
void SEG_Write(unsigned char pos, unsigned char dat)
{
LS138_Clear();
P0 = 0x00;
LS138_Set(7);
P0 = SEG_index[dat];
LS138_Clear();
P0 = 0x00;
LS138_Set(6);
P0 = 0x01 << pos;
LS138_Clear();
Delayxms(1);
}
void SEG_Show()
{
SEG_Write(0,10);
SEG_Write(1,channel);
SEG_Write(2,10);
SEG_Write(5,SEG_Arr[0]);
SEG_Write(6,SEG_Arr[1]);
SEG_Write(7,SEG_Arr[2]);
}
/*
程序说明: IIC总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台 8051,12MHz
日 期: 2011-8-9
*/
#include "iic.h"
#define DELAY_TIME 5
#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
void IIC_Delay(unsigned char i)
{
do{_nop_();}
while(i--);
}
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0; //SCL为高电平时,SDA产生下降沿,为开始信号
IIC_Delay(DELAY_TIME);
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 1; //SCL为高电平时,SDA产生上升沿,为结束信号
IIC_Delay(DELAY_TIME);
}
//发送应答
void IIC_SendAck(bit ackbit)
{
SCL = 0; //将SCL拉低,写入应答位
SDA = ackbit; // 0:应答,1:非应答
IIC_Delay(DELAY_TIME);
SCL = 1; //将SCL拉高,从机读取应答位
IIC_Delay(DELAY_TIME);
SCL = 0;
SDA = 1; //主机释放SDA,结束应答
IIC_Delay(DELAY_TIME);
}
//等待应答
bit IIC_WaitAck(void)
{
bit ackbit;
SCL = 1; //主机释放SCL,等待从机拉低SCL,发送应答
IIC_Delay(DELAY_TIME);
ackbit = SDA; //从机发送应答后,释放SCL
SCL = 0; //主机再次拉低SCL,为下一次发送数据或者结束通信做准备
IIC_Delay(DELAY_TIME);
return ackbit;
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++)
{
SCL = 0; //SCL拉低
IIC_Delay(DELAY_TIME);
if(byt & 0x80) SDA = 1; //主机SDA写入数据位1/0
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1; //SCL拉高,从机读取SDA电平
byt <<= 1;
IIC_Delay(DELAY_TIME);
} //重复八次,完成一个字节的发送
SCL = 0; //将SCL拉低
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char i, da;
for(i=0; i<8; i++) //
{
SCL = 1; //将SCL拉高,
IIC_Delay(DELAY_TIME);
da <<= 1;
if(SDA) da |= 1; //主机读取SDA的状态
SCL = 0; //将SDA拉低,从机写入SDA状态
IIC_Delay(DELAY_TIME);
} //循环八次,完成一个字节数据的读取
return da; //返回读取的字节
}
在蓝桥杯单片机的板子上,PCF8591的基准电压为VCC,理论上是5V,但实际上会与5V存在差距,在本次实验中,我测得的VCC电压为4.53V,那么,如果假设输入数字量为100,进行DA转换之后的电压,就不是5100/255 = 1.96V 而因该是 4.53100/255 = 1.77V这一点需要注意。
如图: