PCF8591解读
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
学习情境6-数字电压表的设计
之基于PCF8591设计的数字电压表
☆点名,复习
1、ADC0832的引脚及其功能,以及与单片机的硬件连接?
2、ADC0808的引脚及其功能,以及与单片机的硬件连接?
引言:PCF8591是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。
PCF8591具有4个模拟输入、1个模拟输出和1个串行I²C总线接口。
PCF8591的3个地址引脚A0, A1和A2可用于硬件地址编程,允许在同一个I²C总线上接入8个PCF8591器件,而无需额外的硬件。
在PCF8591器件上输入输出的地址、控制和数据信号都是通过双线双向I²C总线以串行的方式进行传输。
☆新课讲授
6.3 基于PCF8591设计的数字电压表
6.3.1 PCF8591简介
PCF8591的功能包括多路模拟输入、内置跟踪保持、8-bit模数转换和8-bit数模转换。
PCF8591的最大转化速率由I²C总线的最大速率决定。
1、主要技术指标和特性
∙单独供电
∙PCF8591的操作电压范围2.5V-6V
∙低待机电流
∙通过I2C总线串行输入/输出
∙PCF8591通过3个硬件地址引脚寻址
∙PCF8591的采样率由I2C总线速率决定
∙4个模拟输入可编程为单端型或差分输入
∙自动增量频道选择
∙PCF8591的模拟电压范围从V ss到V DD
∙PCF8591内置跟踪保持电路
∙8-bit逐次逼近A/D转换器
∙通过1路模拟输出实现DAC增益
2、ADC0808引脚功能图6-3-1 PCF8591引脚图
AIN0~AIN3:模拟信号输入端。
A0~A3:引脚地址端。
VDD、VSS:电源端。
(2.5~6V)
SDA、SCL:I2C 总线的数据线、时钟线。
OSC:外部时钟输入端,内部时钟输出端。
EXT:内部、外部时钟选择线,使用内部时钟时EXT 接地。
AGND:模拟信号地。
AOUT:D/A 转换输出端。
VREF:基准电源端。
3、ADC0808内部结构图
图6-3-2 PCF8591内部结构图
4 功能描述
(1)地址
IIC总线系统中的每一片PCF8591通过发送有效地址到该地址器件来激活。
该地址包括固定部分和可编程部分。
可编程部分必需根据地址引脚A0、A1和A2来设置。
在IIC总线协议中地址必需是起始条件后作为第一个字节发送。
地址字节的最后一位是用于设置以后数据传输方向的读/写。
固定部分可编程部分
(2)控制字
控制字节用于实现器件的各种功能,如模拟信号由哪几个通道输入等。
控制字节存放在控制寄存器中。
总线操作时为主控器发送的第二字节。
其格式如下所示:
其中:D1、D0两位是A/D通道编号:00通道0,01通道1,10通道2,11通道3 D2自动增益选择(有效位为1)
D5、D4模拟量输入选择:00为四路单数入、01为三路差分输入、10为单端与差分配合输入、11为模拟输出允许有效
当系统为A/D转换时,模拟输出允许为0。
模拟量输入选择位取值由输入方式决定:四路单端输入时取00,三路差分输入时取01,单端与差分输入时取10,二路差分输入时取11。
最低两位时通道编号位,当对0通道的模拟信号进行A/D转换时取00,当对1通道的模拟信号进行A/D转换时取01,当对2通道的模拟信号进行A/D转换时取10,当对3通道的模拟信号进行A/D转换时取11。
在进行数据操作时,首先是主控器发出起始信号,然后发出读寻址字节,被控器做出应答后,主控器从被控器读出第一个数据字节,主控器发出应答,主控器从被控器读出第二个数据字节,主控器发出应答…一直到主控器从被控器中读出第n个数据字节,主控器发出非应答信号,最后主控器发出停止信号。
(3)A/D转换
A/D转换器采用逐次逼近转换技术。
在A/D转换周期将临时使用片上转换器和高增益比较器。
一个A/D转换周期总是开始于发送一个有效读模式地址给PCF8591之后。
A/D转换周期子在应答时钟脉冲的后延被触发。
并在传输前一次转换结果时执行(见图6-3-3)
图6-3-3 A/D 转换
一旦一个转换周期被触发,所选通的输入电压采样将保存到芯片并被转换为对应的8位二进制码,取自差分输入的采样将被转换为8位二进制补码
转换结果被保存在ADC 数据寄存器等待传输。
如果自动增量标志被置1,将选择下一个通道。
在读周期传输的第一个字节包含前一次读周期的转换结果代码,以上电复位之后读取的第一个字节是0x80。
最高A/D 转换速率取决于实际的IIC 总线速度。
(4)D/A 转换
发送给PCF8591的第三个字节被存储到DAC 数据存储器,并使用片内D/A 转换器转换成对应的模拟电压。
这个D/A 转换器由连接到外部参考电压的具有256个接头的电阻分压电路和选择开关组成。
接头译码器切换一个接头至DAC 输出线。
模拟输出电压由自动清零单位增益放大器缓冲。
这个缓冲放大器可通过设置控制寄存器的模拟输出允许标志来打开或关闭。
在激活状态,输出电压将保持到新的数据字节被发送。
提供给模拟输出AOUT 的输出电压由下式给出。
D/A 转换顺序的波形图如图6-3-5
AOUT V =AGND V +256AGND REF V V -∑=⨯702i i i
D
图6-3-4 D/A转换
(5)振荡器
片上振荡器产生A/D转换周期和刷新自动清零缓冲放大器需要的时钟信号。
在使用这个振荡器时EXT引脚必须连接到VSS。
在OSC引脚振荡频率是可用的。
如果EXT引脚被连接到VDD,振荡输出OSC将切换到高阻态以允许用户连接外部时钟信号至OSC。
6 I2C总线特性
I2C总线是不同的IC或模块之间的双向两线通信,这两条线是串行数据线(SDA)和串行时钟线(SCL),这两条线必须通过上拉电路连接至正电源。
数据传输只能是在总线不忙时启动。
(1)位传输
一个数据位在每一个时钟脉冲期间传输。
SDA线上的数据必须在时钟脉冲的高电平期间保持稳定。
这个期间数据线上的改变被当作控制信号。
图6-3-5 位传输
(2)开始或停止条件
数据和时钟线在总不忙时保持高电平。
在时钟为高电平时,数据线上的一个由高到低的变化被定义为开始条件。
时钟为高电平时,数据线上的一个由低到高的变化被定义为停止条件。
图6-3-6 开始和停止条件
(3)系统配置
产生信息的器件称作“发送机”,接收信息的器件称作“接收机”。
控制信息的器件称作“主机”,被控制的器件称作“从机”。
图6-3-7 系统配置
(4)应答
在开始和停止条件之间从发送机传输到接收机的数据字节数是没有限制的。
每个8位数据字节之后紧跟着一个应答位。
应答位是由发送机放在总线的一个高电平,而主机也产生一个额外的与应答有关的时钟脉冲。
地址匹配的从接收机必须在接收每一个字节后产生一个应答。
在应答时钟脉冲期间,应答的器件必须将SDA线拉低。
因此在应答相应的时钟脉冲的高电平期间,SDA线必须保持稳定的低电平。
在由从机终止的最后一个字节,主接收机必须通过产生一个低电平应答向发送机发送一个数据结束信号,这样发送机必须将数据线SDA拉高以允许主机产生停止条件。
图6-3-8 I2C 总线应答
(5)总线协议
在开始条件后一个有效的硬件地址必须发送至PCF8591。
读/写位定义了以后单个或多个字节数据传输的方向。
开始条件、停止条件和应答位的格式应定时参考I2C总线特性。
在写模式数据传输通过发送下一个数据的停止条件或开始条件来约束。
图6-3-9 写模式的总线协议,D/A转换
图6-3-10 读模式的总线协议,A/D转换
6.3.2 系统硬件设计
硬件电路主要包含单片机最小系统、PCF8591电压数据采集模块电路、LCD1602显示模块。
它们之间的具体连接如图6-3-11所示。
图6-3-11 系统硬件连接图
6.2.3 系统软件设计
本项目在软件设计方面主要有三个源程序:PCF8591.C、LCD1602.C和MAIN.C。
在此我们主要介绍PCF8591.C源程序的设计。
根据对PCF8591芯片的详细分析,PCF8591.C源程序由以下8个子程序构成:
1、启动I2C总线子程序
void IIC_Start(void)
{
//EA=0; //时钟保持高,数据线从高到低一次跳变,I2C通信开始
SDA = 1;
SCL = 1;
delay4us(); // 延时5us
SDA = 0;
delay4us();
SCL = 0;
}
2、停止I2C总线子程序
void IIC_Stop(void)
{
SDA = 0; //时钟保持高,数据线从低到高一次跳变,I2C通信停止SCL = 1;
delay4us();
SDA = 1;
delay4us();
SCL = 1;
}
3、从机发送应答子程序
void Slave_ACK(void)
{
SDA = 0;
SCL = 1;
delay4us();
SCL = 0;
SDA = 1;
}
4、从机发送非应答子程序
void Slave_NOACK(void)
{
SDA = 1;
SCL = 1;
delay4us();
SDA = 0;
SCL = 0;
}
5、主机应答位检查子程序
void check_ACK(void)
{
SDA = 1; // 将p1.0设置成输入,必须先向端口写1
SCL = 1;
F0 = 0;
if(SDA == 1) // 若SDA=1表明非应答,置位非应答标志F0
F0 = 1;
SCL = 0;
}
6、发送一个字节子程序
void IIC_SendByte(uchar ch)
{
uchar idata n=8; // 向SDA上发送一位数据字节,共八位while(n--)
{
if((ch&0x80) == 0x80) // 若要发送的数据最高位为1则发送位1 {
SDA = 1; // 传送位1
SCL = 1;
delay4us();
SDA = 0;
SCL = 0;
}
else
{
SDA = 0; // 否则传送位0
SCL = 1;
delay4us();
SCL = 0;
}
ch = ch<<1; // 数据左移一位
}
}
7、接收一个字节子程序
uchar IIC_ReceiveByte(void)
{
uchar i,rd = 0x00;
for(i=0;i<8;i++)
{
SCL = 1;
rd <<= 1;
rd |= SDA;
delay4us();
SCL = 0;
delay4us();
}
//SCL = 1;
delay4us();
return rd;
}
8、A/D转换子程序
void ADC_PCF8591(uchar controlbyte) {
uchar idata receive_data,i=0;
IIC_Start();
IIC_SendByte(PCF8591_WRITE); //控制字check_ACK();
if(F0 == 1)
{
IIC_Error = 1;
return;
}
IIC_SendByte(controlbyte); //控制字check_ACK();
if(F0 == 1)
{
IIC_Error = 1;
return;
}
IIC_Start(); //重新发送开始命令
IIC_SendByte(PCF8591_READ); //控制字
check_ACK();
if(F0 == 1)
{
IIC_Error = 1;
return;
}
while(i<4)
{
receive_data=IIC_ReceiveByte();
receive_buf[i++]=receive_data;
Slave_ACK(); // 收到一个字节后发送一个应答位
}
Slave_NOACK(); // 收到最后一个字节后发送一个非应答位
IIC_Stop();
}
9、D/A转换子程序
void DAC_PCF8591(uchar controlbyte,uchar wdata)
{
IIC_Start(); // 启动I2C
IIC_SendByte(PCF8591_WRITE); // 发送地址位
check_ACK(); // 检查应答位
if(F0 == 1)
{
IIC_Error = 1;
return; // 若非应答表明器件错误或已坏,置错误标志位SystemError
}
IIC_SendByte(controlbyte&0x77); //Control byte
check_ACK(); // 检查应答位
if(F0 == 1)
{
IIC_Error = 1;
return; // 若非应答表明器件错误或已坏,置错误标志位SystemError
}
IIC_SendByte(wdata); //data byte
check_ACK(); // 检查应答位
if(F0 == 1)
{
IIC_Error = 1;
return; // 若非应答表明器件错误或已坏,置错误标志位SystemError
}
IIC_Stop(); // 全部发完则停止
delay4us();
delay4us();
delay4us();
delay4us();
}
需要注意的是,在PROTEUS仿真条件下无法把4路模拟通道的效果仿真出来,只有AINO通道有效。
但做成硬件4个通道都有效。
☆课堂小结
本节课主要详细介绍了pcf8591芯片的有关技术资料和具体用法,重点分析了PCF8591.C源程序的设计。
☆完整程序代码
1、MAIN.C源程序
#include <reg51.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
uint Disp_Voltage[]={'0','0','0'};
uchar LCD_Line1[]={"1- . V 2- . v"};
uchar LCD_Line2[]={"3- . V 4- . v"};
extern uchar idata receive_buf[]; // 数据接收缓冲区
extern void Initialize_LCD1602(); //液晶初始化函数
extern void ADC_PCF8591(uchar controlbyte);//连续读入4路通道的A/D转换结果到receive_buf[] extern void DAC_PCF8591(uchar controlbyte,uchar wdata);
extern void LCD_Display(uchar p,uchar *str);//在LCD上显示字符串
//函数申明
void Convert_To_Voltage(uchar result);// 将模数转换结果得到的数值分解后放入缓冲数组中
void Refesh_LCD(); // 显示刷新函数,把ADC转换结果送入显示缓冲数组
//------------------------------------------------------------
// 函数名称:main
// 函数功能:主程序
//-------------------------------------------------------------
void main()
{
Initialize_LCD1602();
while(1)
{
ADC_PCF8591(0x04);
Refesh_LCD();
//液晶2行显示4个通道的转换结果
LCD_Display(0x00,LCD_Line1);
LCD_Display(0x40,LCD_Line2);
//将0通道模拟转换后的结果重新转换为模拟量并从Aout引脚输出
//结果通过LED的亮度表现出来,在真实的硬件上该函数调用才有效,在Proteus中看不到效果DAC_PCF8591(0x40,receive_buf[0]);
}
}
//-----------------------------------------------------------------
// 函数名称:Convert_To_Voltage()__
// 将模数转换结果得到的数值分解后放入缓冲数组中
//------------------------------------------------------------------
void Convert_To_Voltage(uchar result)
{
uchar temp;
//最大值为255,对应5V,255/5=51
Disp_Voltage[2] =result/51+'0'; //整数部分
temp=result%51*10; //第1位小数
Disp_Voltage[1] =temp/51+'0';
temp=temp%51*10; //第2位小数
Disp_Voltage[0] =temp/51+'0';
}
//---------------------------------------------------------
// 显示刷新函数,把ADC转换结果送入显示缓冲数组
//---------------------------------------------------------
void Refesh_LCD()
{
//将4个模拟通道模数转换结果放入LCD显示缓冲LCD_Line1和LCD_Line2 Convert_To_Voltage(receive_buf[0]);
LCD_Line1[2]=Disp_Voltage[2];
LCD_Line1[4]=Disp_Voltage[1];
LCD_Line1[5]=Disp_Voltage[0];
Convert_To_Voltage(receive_buf[1]);
LCD_Line1[11]=Disp_Voltage[2];
LCD_Line1[13]=Disp_Voltage[1];
LCD_Line1[14]=Disp_Voltage[0];
Convert_To_Voltage(receive_buf[2]);
LCD_Line2[2]=Disp_Voltage[2];
LCD_Line2[4]=Disp_Voltage[1];
LCD_Line2[5]=Disp_Voltage[0];
Convert_To_Voltage(receive_buf[3]);
LCD_Line2[11]=Disp_Voltage[2];
LCD_Line2[13]=Disp_Voltage[1];
LCD_Line2[14]=Disp_Voltage[0];
}
2、PCF8591.C源程序
//----------------------------------------------------------------
// pcf8591驱动程序
//----------------函数声明,变量定义-------------------------------
#include <reg51.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit SDA=P1^1; // 将p1.0口设置为模拟数据口sbit SCL=P1^0; // 将p1.1口设置为模拟时钟口#define delay4us(); {_nop_();_nop_();_nop_();_nop_();_nop_();};
bit bdata IIC_Error; // 从机错误标志位
//PCF8591专用变量定义----------------------------------------
#define PCF8591_WRITE 0x90
#define PCF8591_READ 0x91
#define NUM 4 // 接收和发送缓存区的深度uchar idata receive_buf[NUM]; // 数据接收缓冲区
//-----------------------------------------------------------------
具体函数参见本文正文。
3、LCD1602.C源程序
//液晶控制与显示驱动程序
#include <reg52.h>
#include <intrins.h>
#include <string.h>
#define uchar unsigned char
#define uint unsigned int
#define delay4us() {_nop_();_nop_();_nop_();_nop_();} sbit RS = P2^0;
sbit RW = P2^1;
sbit E = P2^2;
// 延时
void Delay(uint X)
{
uchar i;
while(X--)for(i=0;i<120;i++)delay4us()
}
//---------------忙检查-------------------//
uchar LCD_Busy_Check()
{
uchar LCD_Status;
RS = 0;
RW = 1;
E = 1;
delay4us();
LCD_Status = P0;
E = 0;
return LCD_Status;
}
//--------------向LCD写入命令--------------------//
void Write_LCD_Command(uchar cmd)
{
while((LCD_Busy_Check()& 0x80)==0x80); //忙等待
RS = 0;
RW = 0;
E = 0;
P0 = cmd;
delay4us();
E = 1;
delay4us();
E = 0;
}
//-----------向LCD写入一个字节的数据函数-----------------*/ void Write_LCD_Data(uchar dat)
{
while((LCD_Busy_Check()&0x80)==0x80);
RS = 1;
RW = 0;
E = 0;
P0 = dat;
delay4us();
E = 1;
delay4us();
E = 0;
}
//-----------LCD初始化-----------------*/
void Initialize_LCD1602() //液晶初始化函数
{
Write_LCD_Command(0x38);Delay(5);//功能设置,数据长度为8位,双行显示,5×7点阵字体Write_LCD_Command(0x0C);Delay(5);//字符进入模式:屏幕不动,字符后移
Write_LCD_Command(0x06);Delay(5); // 显示开,关光标
Write_LCD_Command(0x01);Delay(5); //清屏
}
// 设置显示位置
void Set_LCD_Position(uchar pos)
{
Write_LCD_Command(pos|0x80) ;
}
//-----------在LCD上显示字符串-----------------*/ void LCD_Display(uchar p,uchar *str)
{
uchar i;
Set_LCD_Position(p);
for(i=0;i<strlen(str);i++)
{
Write_LCD_Data(str[i]);
}
}。