上期我们学习了DS1302实时时钟的基本使用,现在我们来学习PCF8591A/D&D/A转换芯片的相关内容
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的读写地址
PCF8591使用的是IIC通信协议,IIC可用于多机通信,因此在通信之前需要寻址,以下是PCF8591的读写地址。
- 高四位在出厂之前就已经确定,属于不可修改部分
- A0~A2是可编程部分,通过电路将对应的引脚拉高/拉低来确定。
- 第八位是读写控制位,为1时表示通信方向为,PCF写数据,单片机读数据。为0时则相反。
控制指令
在通信寻址成功之后,我们需要对PCF8591写入控制指令,设置芯片的工作模式。
其中:
- 第4位和第八位固定为0,不可更改。
- 第7位为模拟输出使能位,为1时芯片工作为DA转换模式,通过IIC接口写入0~255之间的值,可以输出对应的模拟电压值。(具体计算公式在后面介绍)
- 第5、6位可以选择模拟输入的方式,单端输入、双端输入、单端+双端输入
- 第3位为自动增量标志,为1时,每完成一次AD转换,输入通道自动切换到下一个通道(0->1->2->3->0往复循环),为0时不会自增
- 第1、2位共同控制AD转换的通道,具体对应关系如上图。
AD转换(模拟量->数字量)
AD转换是将时间连续和幅值连续的模拟量转换为时间离散、幅值也离散的数字量。使输出的数字量与输入的模拟量成正比。模拟量与输出量的关系如下图
1.单端输入
其中:
- V-REF为基准电压,电路图上连接到VCC,理论值为5V,
- V-AGND为模拟地,电路图上连接到GND,电压值为0V,
- V-LSB为微分非线性dnl,即芯片可以区分最小模拟电压值,
举个栗子:
芯片使用通道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不变
2.差分输入
原理和单端输入相似,不做介绍。
DA转换
DA转换则是将数字量转换为模拟量进行输出,与AD转换相反。
计算公式如图,特别的,当V-AGND = 0V 时,V-AOUT= 256*数字量/V-REF
注意:输入数字量的取值为0~255, 输出模拟量的范围是V-AGND~V-REF。
IIC协议
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; //返回读取的字节
}
PCF8591A/D&D/A的基本控制方法&代码
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(); //结束通信
}
}
实践部分
1.任务要求
通过官方提供的iic代码,完成与PCF8591之间的通信,具体要求如下:
- 使用通道0和通道3模拟输入,进行AD转换,并将输出的数字量通过数码管显示出来
- 通过按键S5控制模拟输入的通道,每次按下按键,通道在0和3之间跳变,并通过数码管显示当前模拟输入的通道数
- 将输出的数字量进行DA转换,模拟输出
- 测量OUT引脚的电压,判断模拟输出是否正确
数码管显示格式如下 - 通道 - [空] [空] 数字量
2.实现思路
3.代码展示
3.1.main.c
#include <STC15F2K60S2.H>
#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;
}
}
3.2.数码管显示函数
/*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]);
}
3.3.官方提供的iic文件
/*
程序说明: 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这一点需要注意。
如图:
文章来源:https://www.toymoban.com/news/detail-404569.html
文章来源地址https://www.toymoban.com/news/detail-404569.html
到了这里,关于蓝桥杯单片机学习11——PCF8591A/D&D/A转换芯片的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!