18-PCF8591模块的基本使用
PCF8591的基本应用
1.引言
\(I^2C\)总线是Philips公司推出的串行总线,整个系统仅靠数据线(SDA)和时钟线(SCL)实现完善的全双工数据传输,即CPU与各个外围器件仅靠这两条线实现信息交换,\(I^2C\)总线系统与传统的并线总线系统相比具有结构简单、可维护性好、易实现系统扩展、易实现模块化标准化设计,可靠性高等优点
在一个完整的单片机系统中,A/D转换芯片往往是必不可少的,PCF8591是一种具有\(I^2C\)总线接口的A/D转换芯片,在与CPU的信息传输过程中仅靠SCL和数据线SDA就可以实现
2.芯片介绍
PCF8591是具有\(I^2C\)总线的接口的8位A/D及D/A转换器,有4路A/D转换输入,1路D/A模拟输出,也就是说,它既可以用作A/D转换,也可以用作D/A转换,A/D转换为逐次比较型,引脚如图1所示:
AIN0~AIN3:模拟信号输入端
A0~A2:引脚地址端
\(V\tiny DD\)、\(V\tiny SS\):电源端(2.5~6v)
SDA、SCL:\(I^2C\)总线的数据端、时钟线
OSC:外部时钟输入端,内部时钟输出端
EXT:内部、外部时钟选择线,使用内部时钟时EXT接地
AGND:模拟信号地
AOUT:D/A转换输出端
\(V\tiny REF\):基准电源端
1.特性
- 单电源供电
- 工作电压:2.5V~6V
- 待机电流低
- \(I^2C\)总线串行输入/输出
- 通过三个硬件地址引脚编址
- 采样速率取决于\(I^2C\)总线速度
- 4个模拟输入可编程为单端或差分输入
- 自动增量通道选择
- 模拟电压范围:VSS~VDD
- 片上跟踪与保持电路
- 8位逐次逼近式A/D转换
- 带一个模拟输出的乘法DAC
2.应用
- 闭环控制系统
- 用于远程数据采集的低功耗转换器
- 电池供电设备
- 在汽车、音响和TV应用方面的模拟数据采集
概述
PCF8591是单片、单电源低功耗8位CMOS数据采集器件,具有4个模拟输入、一个输出和一个串行\(I^2C\)总线接口,3个地址引脚A0、A1、A2用于编程硬件地址,允许将最多8个器件连接至\(I^2C\)总线而不需要额外硬件,器件的地址、控制和数据通过两线双向\(I^2C\)总线传输
器件功能包括多路复用模拟输入、片上跟踪和保持功能、8位模数转换和8位数模转换,最大转换速率取决于\(I^2C\)总线的最高速率
结构框图
标志 | 引脚 | 描述 |
---|---|---|
AIN0 | 1 | 模拟输入通道1(A/D转换器) |
AIN1 | 2 | 模拟输入通道2(A/D转换器) |
AIN2 | 3 | 模拟输入通道3(A/D转换器) |
AIN3 | 4 | 模拟输入通道4(A/D转换器) |
A0 | 5 | 硬件地址 |
A1 | 6 | 硬件地址 |
A2 | 7 | 硬件地址 |
VSS | 8 | 负电源电压 |
SDA | 9 | IIC数据输入/输出 |
SCL | 10 | IIC时钟 |
OSC | 11 | 振荡器input/output |
EXT | 12 | 振荡器输入的外部和内部开关 |
AGUD | 13 | 模拟地 |
Vref | 14 | 参考电压输入 |
AOUT | 15 | 模拟输出(D/A转换器) |
原理图如下所示:
由上可知:AIN1、AIN3分别接入RD1光敏电阻、和Rb2滑动变阻器进行A/D、D/A转换,由A0、A1、A2接地可知,地址线分别为000,
地址
\(I^2C\)总线系统中的每一片PCF8591通过发送有效地址到该器件来激活,该地址包括固定部分和可编程部分。可编程部分必须根据地址引脚A0、A1、A2来设置,在\(I^2C\)总线协议中地址必须式起始条件后作为第一个字节发送,地址字节的最后一位是用于设置以后数据传输方向的读/写位,如下图所示
控制字节
控制字节用于实现器件的各种功能,如模拟信号由哪几个通道输入等,控制字节存放在控制寄存器中,总线操作时为主控器发送的第二字节,其格式如下:
当系统为A/D转换时,模拟输出允许为1,模拟量输入选择位取值由模拟输入方式决定:
由上图所示第四位和第五位所决定
在进行数据操作时,首先是主控器发出起始信号,然后发出读寻址字节,被控器做出应答后,主控器从被控器读出第一个数据字节,主控器发出应答,主控器从被控器读出第二个数据字节,主控器发出应答........一直到主控器从被控器中读出第n个数据字节,主控器发出非应答信号,最后主控器发出停止信号
IIC简介
IIC(Inter-integrated cirulit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备,多用于主控制器和从器件间的主从通信,在小数据量场和使用,传输距离短,任意时刻只能有应该主机等特性。
在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
PS:这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输率比不上SPI
IIC的物理层
IIC一共有只有两个总线:一条是双向的串行数据SDA,一条是串行时钟线SCL
- SDA(Serial data)是数据线:用来传输数据
- SCL(serial clock line)是时钟线:控制数据发送的时序
所有接到\(I^2C\)总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上,\(I^2C\)总线上的每个设备都有自己唯一的地址,来确保不同设备之间访问的准确性
IIC主要特点:
通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备
- IIC主设备功能:主要产生时钟,产生起始信号和停止信号
- IIC从设备功能:可编程的IIC地址检测,停止位检测
- IIC的优点是它支持多主控(multimastering)其中任何一个能够发送和接收的设备都可以成为主总线,一个主控能够控制信号的传输和时钟频率,当然,在任何时间点上只能有一个主控
- 支持不同速率的通讯速度,标准速度(最高速度100kHZ)快速(最高400kHz)
- SCL和SDA都需要接上拉电阻(大小由速度和容性负载决定一般在3.3K-10K之间)保证数据的稳定性,减少干扰
- IIC是半双工,而不是全双工,同一时间只能单向通信
- 为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出
IIC的协议层
I2C总线在传输数据过程中共有三种类型信号,他们分别是开始信号、结束信号和应答信号
- 开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据
- 结束信号:SCL为高电平时,SDA由电平向高电平跳变,结束传送数据
- 应答信号:接收数据的IC在接收到8bit的数据后,向发送数据的IC发出特定的低电平脉冲,表示以收到数据,CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断,若未收到应答信号,由此判断为受控单元出现故障
这些信号,起始信号是必须的,结束引号和应答信号都可以不要
IIC总线时序图
代码如下:
// 总线启动条件
void IIC_Start() {
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 0;
}
// 总线停止条件
void IIC_Stop() {
SDA = 0;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 1;
IIC_Delay(DELAY_TIME);
}
应答信号
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否接收到数据
- 应答信号为低电平时,规定为有效应答位(ACK,简称应答位)表示接收器已经成功接收了该字节
- 应答信号为高电平时,规定为非应答位(NACK)一般表示接收器接收该字节没有成功
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答
发送应答:发送应答时,先把时钟线拉低,放入数据,再把时钟线拉高,延迟再把时钟线拉低,拉低后再把地址线拉低
等待应答:先把总线拉高,拉高后延迟接收应答,再拉低
代码如下:
// 发送应答
void IIC_SendAck(bit ackbit) {
SCL = 0;
SDA = ackbit; // 0为应答,1为非应答
IIC_Delay(DELAY_TIME);
SCL = 1;
IIC_Delay(DELAY_TIME);
SCL = 0;
SDA = 1;
IIC_Delay(DELAY_TIME);
}
// 等待接收应答
bit IIC_WaitAck(void) {
bit ackbit;
SCL = 1;
IIC_Delay(DELAY_TIME);
ackbit = SDA;
SCL = 0;
IIC_Delay(DELAY_TIME);
return ackbit;
}
发送和接收数据时序如下:
发送字节:先将总线拉低,判断该高位是否为1,为1把数据线拉高,否则拉低,然后延迟把时钟拉高,移动到下一位,重复此操作
读取数据:先把总线拉高,将数据移位,如果数据线SDA为高电平,将该为置一,然后将总线拉低
代码如下:
// 通过I2C总线发送数据
void IIC_SendByte(unsigned char byte) {
unsigned char i;
for(i=0;i<8;i++) {
SCL = 0;
IIC_Delay(DELAY_TIME);
if(byte & 0x80)
SDA = 1;
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1;
byte <<= 1;
IIC_Delay(DELAY_TIME);
}
SCL = 0;
}
// 从I2C总线上接收数据
unsigned char IIC_RecByte(void) {
unsigned char i,dat;
for(i=0;i<8;i++) {
SCL = 1;
IIC_Delay(DELAY_TIME);
dat <<=1;
if(SDA) dat |= 1;
SCL = 0;
IIC_Delay(DELAY_TIME);
}
return dat;
}
A/D转换(模数转换)
A/D转换器采用逐次逼近型转换技术。
在A/D转换期间内临时使用片上D/A转换器和高增益比较器
A/D转换周期总是在发送一个有效的读模式地址到PCF8591设备后开始
A/D转换周期在应答时钟脉冲的后缘触发
一旦转换周期被触发,所选通道的输入电压样本被存储在芯片上转换成相应的8位二进制码
从差分输入中提取的样本被转换成一个8位的转换结果存储在ADC数据寄存器并等待传输
如果选择了自动增量,则选择下一个通道
在一个读周期中传输的第一个字节包含前一个读周期的转换结果代码
在上电复位条件后,第一个读取字节为16进制80
最大AD转换速率是由\(I^2C\)总线的实际速度给的
意思就是说,在开始读后,读的是上一次转换的结果
流程为:IIC开始信号--> 地址读--> 等待PCF8591回应--> 读PCF8591->主机回应-->继续读->主机回应-->直到想停止AD转换了-> 不回应了->直接停止信号
传输数据时序如下
S代表\(I^2C\)传输开始,P表示传输停止
- \(I^2C\)传输开始信号
- 写PCF8591地址
- 等待PCF8591回应
- 读PCF8591
- 等待主机回应(4到5步可以一直循环进行,直到不需要AD转换)
- \(I^2C\)传输结束
代码如下:
unsigned char Ad_Read(unsigned char addr) {
unsigned char temp;
// 时序开始
IIC_Start();
// 发送写地址
IIC_SendByte(0x90);
// 等待应答
IIC_WaitAck();
// 写入控制字
// 由上图可知滑动变阻器为通道3,光敏电阻为通道1
// 所以分别对应0x43 和0x41
IIC_SendByte(addr);
// 等待应答
IIC_WaitAck();
// 写时序结束
// 开始第二轮读时序
IIC_Start();
// 发送读地址
IIC_SendByte(0x91);
// 应答
IIC_WaitAck();
// 接收数据
temp = IIC_RecByte();
// 发送非应答信号 告诉从机不再读取数据
IIC_SendAck(1);
// 结束
IIC_Stop();
return temp;
}
D/A转换(数模转换)
发送到PCF8591设备的第三个字节存储在DAC数据请求器中,并使用片上D/A转换器转换为相应的模拟电压
这个D/A转换器由一个电阻分频链组成
该分屏链连接到具有256个分屏点和选择开关的外部参考电压tap-decoder 将其中一个tap切换到DAC输出线
模拟输出电压由一个自动归零的放大器缓冲
这个缓冲放大器可以通过设置模拟输出使能标志
来打开或关闭控制寄存器
在使能状态下,输出电压被保持直到进一步的数据字节被发送,片上D/A转换器也用于逐次逼近A/D转换,为了释放DAC进行A/D转换循环,单元增益放大器配备了跟踪和保持电路
这个电路在执行A/D转换时保持输出电压
- \(I^2C\)传输开始信号
- 写PCF8591地址
- 等待PCF8591回应
- 写控制字节
- 等待PCF8591回应
- 写DAC的值
- 等待PCF8591回应
(6-7步可以一直循环进行,且DAC的值可以一直改变,只要没有重新\(I^2C\)开始信号,或者结束信号,DAC输出)就一直时最后一个输出的值
- \(I^2C\)传输结束
void Da_Write(unsigned char addr) {
//传输开始
IIC_Start();
//写地址
IIC_SendByte(0x90);
//应答
IIC_WaitAck();
// 写控制字
IIC_SendByte(0x41);
//应答
IIC_WaitAck();
//写DAC值
IIC_SendByte(addr);
//等待回应
IIC_WaitAck();
//传输结束
IIC_Stop();
}
main函数代码如下
#include <REGX52.H>
#include "I2C.h"
unsigned char code DMG_NODOT[16] = {
0xc0, 0xf9,
0xa4, 0xb0,
0x99, 0x92,
0x82, 0xf8,
0x80, 0x90,
0x88, 0x83,
0xc6, 0xa1,
0x86, 0x8e
};
unsigned int dat2,dat;
void Delay_ms(unsigned int xms) {
unsigned int i,j;
for(i=0;i<xms;i++) {
for(j=0;j< 299;j++);
}
}
void _74HC138(unsigned char n) {
switch(n) {
case 4:
P2 = (P2 & 0x1f) | 0x80;
break;
case 5:
P2 = (P2 & 0x1f) | 0xa0;
break;
case 6:
P2 = (P2 & 0x1f) | 0xc0;
break;
case 7:
P2 = (P2 & 0x1f) | 0xe0;
break;
}
}
void System_Init(void) {
_74HC138(4);
P0 = 0xff;
_74HC138(5);
P0 = 0x00;
}
void SMG_Display_Bit(unsigned char pos,unsigned char dat) {
_74HC138(7);
P0 = 0xff;
_74HC138(6);
P0 = 0x01 << pos;
_74HC138(7);
P0 = dat;
}
void SMG_Display(void) {
// 读取的是上一次的数据
dat2 = Ad_Read(0x41);// 光敏电阻
dat = Ad_Read(0x43);// 滑动变阻器
SMG_Display_Bit(0,DMG_NODOT[(dat/100)%10]); // 百
Delay_ms(10);
SMG_Display_Bit(1,DMG_NODOT[(dat/10)%10]); // 10
Delay_ms(10);
SMG_Display_Bit(2,DMG_NODOT[dat%10]);// 个位
Delay_ms(10);
SMG_Display_Bit(4,DMG_NODOT[(dat2/100)%10]);
Delay_ms(10);
SMG_Display_Bit(5,DMG_NODOT[(dat2/10)%10]);
Delay_ms(10);
SMG_Display_Bit(6,DMG_NODOT[dat2 %10]);
Delay_ms(10);
}
void main(void) {
System_Init();
while(1) {
SMG_Display();
}
}
I2C时序代码如下:
#include <REGX52.H>
#include "intrins.h"
sbit SDA = P2^1;
sbit SCL = P2^0;
#define DELAY_TIME 5
// 5us
void IIC_Delay(unsigned char i) {
while(i--);
}
// 总线启动条件
void IIC_Start() {
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 0;
}
// 总线停止条件
void IIC_Stop() {
SDA = 0;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 1;
IIC_Delay(DELAY_TIME);
}
// 发送应答
void IIC_SendAck(bit ackbit) {
SCL = 0;
SDA = ackbit; // 0为应答,1为非应答
IIC_Delay(DELAY_TIME);
SCL = 1;
IIC_Delay(DELAY_TIME);
SCL = 0;
SDA = 1;
IIC_Delay(DELAY_TIME);
}
// 等待接收应答
bit IIC_WaitAck(void) {
bit ackbit;
SCL = 1;
IIC_Delay(DELAY_TIME);
ackbit = SDA;
SCL = 0;
IIC_Delay(DELAY_TIME);
return ackbit;
}
// 通过I2C总线发送数据
void IIC_SendByte(unsigned char byte) {
unsigned char i;
for(i=0;i<8;i++) {
SCL = 0;
IIC_Delay(DELAY_TIME);
if(byte & 0x80)SDA = 1;
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1;
byte <<= 1;
IIC_Delay(DELAY_TIME);
}
SCL = 0;
}
// 从I2C总线上接收数据
unsigned char IIC_RecByte(void) {
unsigned char i,dat;
for(i=0;i<8;i++) {
SCL = 1;
IIC_Delay(DELAY_TIME);
dat <<=1;
if(SDA) dat |= 1;
SCL = 0;
IIC_Delay(DELAY_TIME);
}
return dat;
}
unsigned char Ad_Read(unsigned char addr) {
unsigned char temp;
// 时序开始
IIC_Start();
// 发送写地址
IIC_SendByte(0x90);
// 等待应答
IIC_WaitAck();
// 写入控制字
// 由上图可知滑动变阻器为通道3,光敏电阻为通道1
// 所以分别对应0x43 和0x41
IIC_SendByte(addr);
// 等待应答
IIC_WaitAck();
// 写时序结束
// 开始第二轮读时序
IIC_Start();
// 发送读地址
IIC_SendByte(0x91);
// 应答
IIC_WaitAck();
// 接收数据
temp = IIC_RecByte();
// 发送非应答信号
IIC_SendAck(1);
// 结束
IIC_Stop();
return temp;
}
void Da_Write(unsigned char addr) {
//传输开始
IIC_Start();
//写地址
IIC_SendByte(0x90);
//应答
IIC_WaitAck();
// 写控制字
IIC_SendByte(0x41);
//应答
IIC_WaitAck();
//写DAC值
IIC_SendByte(addr);
//等待回应
IIC_WaitAck();
//传输结束
IIC_Stop();
}