水电电工知识
电工文库|PCF8591的软件编程
2016-05-16  浏览:53
水电之家讯:PCF8591的通信接口是I2C,那么编程肯定是符合这个协议的。单片机对PCF8591进行初始化,一共发送三个字节即可。第一个字节,和EEPROM类似,第一个字节是地址字节,其中7位代表地址,1位代表读写方向。地址高4位固定是1001,低三位是A2,A1,A0,这三位我们电路上都接了GND,因此也就是000,如图1所示。

图1PCF8591地址字节

发送到PCF8591的第二个字节将被存储在控制寄存器,用于控制PCF8591的功能。其中第3位和第7位是固定的0,另外6位各自有各自的作用,如图2所示,我逐一介绍。

图2PCF8591控制字节

控制字节的第6位是DA使能位,这一位置1表示DA输出引脚使能,会产生模拟电压输出功能。第4位和第5位可以实现把PCF8591的4路模拟输入配置成单端模式和差分模式,单端模式和差分模式的区别,我们17.4章节有介绍,这里大家只需要知道这两位是配置AD输入方式的控制位即可,如图3所示。

图3PCF8591模拟输入配置方式

控制字节的第2位是自动增量控制位,自动增量的意思就是,比如我们一共有4个通道,当我们全部使用的时候,读完了通道0,下一次再读,会自动进入通道1进行读取,不需要我们指定下一个通道,由于A/D每次读到的数据,都是上一次的转换结果,所以同学们在使用自动增量功能的时候,要特别注意,当前读到的是上一个通道的值。为了保持程序的通用性,我们的代码没有使用这个功能,直接做了一个通用的程序。

控制字节的第0位和第1位就是通道选择位了,00、01、10、11代表了从0到3的一共4个通道选择。

发送给PCF8591的第三个字节D/A数据寄存器,表示D/A模拟输出的电压值。D/A模拟我们一会介绍,大家知道这个字节的作用即可。我们如果仅仅使用A/D功能的话,就可以不发送第三个字节。

下面我们用一个程序,把AIN0、AIN1、AIN3测到的电压值显示在液晶上,同时大家可以转动电位器,会发现AIN0的值发生变化。



#include<reg52.h>

#defineLCD1602_DBP0

sbitLCD1602_RS=P1^0;

sbitLCD1602_RW=P1^1;

sbitLCD1602_E=P1^5;

voidLcdWaitReady()//等待液晶准备好

{

unsignedcharsta;

LCD1602_DB=0xFF;

LCD1602_RS=0;

LCD1602_RW=1;

do

{

LCD1602_E=1;

sta=LCD1602_DB;//读取状态字

LCD1602_E=0;

}while(sta&0x80);//bit7等于1表示液晶正忙,重复检测直到其等于0为止

}

voidLcdWriteCmd(unsignedcharcmd)//写入命令函数

{

LcdWaitReady();

LCD1602_RS=0;

LCD1602_RW=0;

LCD1602_DB=cmd;

LCD1602_E=1;

LCD1602_E=0;

}

voidLcdWriteDat(unsignedchardat)//写入数据函数

{

LcdWaitReady();

LCD1602_RS=1;

LCD1602_RW=0;

LCD1602_DB=dat;

LCD1602_E=1;

LCD1602_E=0;

}

voidLcdShowStr(unsignedcharx,unsignedchary,constunsignedchar*str)//显示字符串,屏幕起始坐标(x,y),字符串指针str

{

unsignedcharaddr;

//由输入的显示坐标计算显示RAM的地址

if(y==0)

addr=0x00+x;//第一行字符地址从0x00起始

else

addr=0x40+x;//第二行字符地址从0x40起始

//由起始显示RAM地址连续写入字符串

LcdWriteCmd(addr|0x80);//写入起始地址

while(*str!='\0')//连续写入字符串数据,直到检测到结束符

{

LcdWriteDat(*str);

str++;

}

}

voidLcdInit()//液晶初始化函数

{

LcdWriteCmd(0x38);//16*2显示,5*7点阵,8位数据接口

LcdWriteCmd(0x0C);//显示器开,光标关闭

LcdWriteCmd(0x06);//文字不动,地址自动+1

LcdWriteCmd(0x01);//清屏

}



#include<reg52.h>

#include<intrins.h>

#defineI2CDelay(){_nop_();_nop_();_nop_();_nop_();}

sbitI2C_SCL=P3^7;

sbitI2C_SDA=P3^6;

voidI2CStart()//产生总线起始信号

{

I2C_SDA=1;//首先确保SDA、SCL都是高电平

I2C_SCL=1;

I2CDelay();

I2C_SDA=0;//先拉低SDA

I2CDelay();

I2C_SCL=0;//再拉低SCL

}

voidI2CStop()//产生总线停止信号

{

I2C_SCL=0;//首先确保SDA、SCL都是低电平

I2C_SDA=0;

I2CDelay();

I2C_SCL=1;//先拉高SCL

I2CDelay();

I2C_SDA=1;//再拉高SDA

I2CDelay();

}

bitI2CWrite(unsignedchardat)//I2C总线写操作,待写入字节dat,返回值为应答状态

{

bitack;//用于暂存应答位的值

unsignedcharmask;//用于探测字节内某一位值的掩码变量

for(mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行

{

if((mask&dat)==0)//该位的值输出到SDA上

I2C_SDA=0;

else

I2C_SDA=1;

I2CDelay();

I2C_SCL=1;//拉高SCL

I2CDelay();

I2C_SCL=0;//再拉低SCL,完成一个位周期

}

I2C_SDA=1;//8位数据发送完后,主机释放SDA,以检测从机应答

I2CDelay();

I2C_SCL=1;//拉高SCL

ack=I2C_SDA;//读取此时的SDA值,即为从机的应答值

I2CDelay();

I2C_SCL=0;//再拉低SCL完成应答位,并保持住总线

return(~ack);//应答值取反以符合通常的逻辑:0=不存在或忙或写入失败,1=存在且空闲或写入成功

}

unsignedcharI2CReadNAK()//I2C总线读操作,并发送非应答信号,返回值为读到的字节

{

unsignedcharmask;

unsignedchardat;

I2C_SDA=1;//首先确保主机释放SDA

for(mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行

{

I2CDelay();

I2C_SCL=1;//拉高SCL

if(I2C_SDA==0)//读取SDA的值

dat&=~mask;//为0时,dat中对应位清零

else

dat|=mask;//为1时,dat中对应位置1

I2CDelay();

I2C_SCL=0;//再拉低SCL,以使从机发送出下一位

}

I2C_SDA=1;//8位数据发送完后,拉高SDA,发送非应答信号

I2CDelay();

I2C_SCL=1;//拉高SCL

I2CDelay();

I2C_SCL=0;//再拉低SCL完成非应答位,并保持住总线

returndat;

}

unsignedcharI2CReadACK()//I2C总线读操作,并发送应答信号,返回值为读到的字节

{

unsignedcharmask;

unsignedchardat;

I2C_SDA=1;//首先确保主机释放SDA

for(mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行

{

I2CDelay();

I2C_SCL=1;//拉高SCL

if(I2C_SDA==0)//读取SDA的值

dat&=~mask;//为0时,dat中对应位清零

else

dat|=mask;//为1时,dat中对应位置1

I2CDelay();

I2C_SCL=0;//再拉低SCL,以使从机发送出下一位

}

I2C_SDA=0;//8位数据发送完后,拉低SDA,发送应答信号

I2CDelay();

I2C_SCL=1;//拉高SCL

I2CDelay();

I2C_SCL=0;//再拉低SCL完成应答位,并保持住总线

returndat;

}



#include<reg52.h>

bitflag300ms=1;//300ms定时标志

unsignedcharT0RH=0;//T0重载值的高字节

unsignedcharT0RL=0;//T0重载值的低字节

unsignedcharGetADCValue(unsignedcharchn);

voidValueToString(unsignedchar*str,unsignedcharval);

voidConfigTimer0(unsignedintms);

externvoidLcdInit();

externvoidLcdShowStr(unsignedcharx,unsignedchary,constunsignedchar*str);

externvoidI2CStart();

externvoidI2CStop();

externunsignedcharI2CReadACK();

externunsignedcharI2CReadNAK();

externbitI2CWrite(unsignedchardat);

voidmain()

{

unsignedcharval;

unsignedcharstr[10];

EA=1;//开总中断

ConfigTimer0(10);//配置T0定时10ms

LcdInit();//初始化液晶

LcdShowStr(0,0,"AIN0AIN1AIN3");//显示通道指示

while(1)

{

if(flag300ms)

{

flag300ms=0;

//显示通道0的电压

val=GetADCValue(0);//获取ADC通道0的转换值

ValueToString(str,val);//转为字符串格式的电压值

LcdShowStr(0,1,str);//显示到液晶上

//显示通道1的电压

val=GetADCValue(1);

ValueToString(str,val);

LcdShowStr(6,1,str);

//显示通道3的电压

val=GetADCValue(3);

ValueToString(str,val);

LcdShowStr(12,1,str);

}

}

}

unsignedcharGetADCValue(unsignedcharchn)//读取当前的ADC转换值,chn为ADC通道号0-3

{

unsignedcharval;

I2CStart();

if(!I2CWrite(0x48<<1))//寻址PCF8591,如未应答,则停止操作并返回0

{

I2CStop();

return0;

}

I2CWrite(0x40|chn);//写入控制字节,选择转换通道

I2CStart();

I2CWrite((0x48<<1)|0x01);//寻址PCF8591,指定后续为读操作

I2CReadACK();//先空读一个字节,提供采样转换时间

val=I2CReadNAK();//读取刚刚转换完的值

I2CStop();

returnval;

}

voidValueToString(unsignedchar*str,unsignedcharval)//ADC转换值转为实际电压值的字符串形式

{

val=(val*25)/255;//电压值=转换结果*2.5V/255,式中的25隐含了一位十进制小数

str[0]=(val/10)+'0';//整数位字符

str[1]='.';//小数点

str[2]=(val%10)+'0';//小数位字符

str[3]='V';//电压单位

str[4]='\0';//结束符

}

voidConfigTimer0(unsignedintms)//T0配置函数

{

unsignedlongtmp;

tmp=11059200/12;//定时器计数频率

tmp=(tmp*ms)/1000;//计算所需的计数值

tmp=65536-tmp;//计算定时器重载值

tmp=tmp+12;//修正中断响应延时造成的误差

T0RH=(unsignedchar)(tmp>>8);//定时器重载值拆分为高低字节

T0RL=(unsignedchar)tmp;

TMOD&=0xF0;//清零T0的控制位

TMOD|=0x01;//配置T0为模式1

TH0=T0RH;//加载T0重载值

TL0=T0RL;

ET0=1;//使能T0中断

TR0=1;//启动T0

}

voidInterruptTimer0()interrupt1//T0中断服务函数

{

staticunsignedchartmr300ms=0;

TH0=T0RH;//定时器重新加载重载值

TL0=T0RL;

tmr300ms++;

if(tmr300ms>=30)//定时300ms

{

tmr300ms=0;

flag300ms=1;

}

}

细心阅读程序的同学会发现,我们程序在进行A/D读取数据的时候,共使用了两条程序去读了2个字节。I2CReadACK();val=I2CReadNAK();PCF8591的转换时钟是I2C的SCL,而A/D的特点是每次读到的都是上一次的转换结果,因此我们这里第一条语句的作用是产生一个整体的SCL时钟提供给PCF8591进行A/D转换,第二次是读取当前的转换结果。如果我们只使用第二条语句的话,每次读到的都是上一次的转换结果。

水电之家为您提供最全面的管材,管件,水电,电线,电工,管材水电品牌的装修知识点和各种管材水电的导购与在线购买服务,拥有最便宜的管材水电价格和最优质的售后服务,每天都有秒杀的抢购活动哦!敬请登陆水电之家:http://shuidian.jc68.com/
更多»您可能感兴趣的文章:
更多»有关 电工 的产品:
  • 水电电工资讯
  • 发表评论 | 0评
  • 评论登陆
  • 移动社区 天花之家 木门之家 灯具之家 铁艺之家 幕墙之家 五金头条 楼梯头条 墙纸头条 壁纸头条 玻璃头条 老姚之家 灯饰之家 电气之家 全景头条 照明之家 防水之家 防盗之家 区快洞察 建材 枣庄建材 临沂建材 南昌建材 上饶建材 抚州建材 宜春建材 吉安建材 赣州建材 鹰潭建材 新余建材 九江建材 萍乡建材 景德镇陶瓷 石家庄建材 衡水建材 廊坊建材 沧州建材 承德建材 建材之家 企业之家 720全景
    (c)2015-2017 Bybc.cn SYSTEM All Rights Reserved