PCF8591的軟件編程
PCF8591的通信接口是I2C,那么編程肯定是符合這個協(xié)議的。單片機對PCF8591進行初始化,一共發(fā)送三個字節(jié)即可。第一個字節(jié),和EEPROM類似,第一個字節(jié)是地址字節(jié),其中7位代表地址,1位代表讀寫方向。地址高4位固定是1001,低三位是A2,A1,A0,這三位我們電路上都接了GND,因此也就是000,如圖1所示。
圖1 PCF8591地址字節(jié)
發(fā)送到PCF8591的第二個字節(jié)將被存儲在控制寄存器,用于控制PCF8591的功能。其中第3位和第7位是固定的0,另外6位各自有各自的作用,如圖2所示,我逐一介紹。
圖2 PCF8591控制字節(jié)
控制字節(jié)的第6位是DA使能位,這一位置1表示DA輸出引腳使能,會產(chǎn)生模擬電壓輸出功能。第4位和第5位可以實現(xiàn)把PCF8591的4路模擬輸入配置成單端模式和差分模式,單端模式和差分模式的區(qū)別,我們17.4章節(jié)有介紹,這里大家只需要知道這兩位是配置AD輸入方式的控制位即可,如圖3所示。
圖3 PCF8591模擬輸入配置方式
控制字節(jié)的第2位是自動增量控制位,自動增量的意思就是,比如我們一共有4個通道,當(dāng)我們?nèi)渴褂玫臅r候,讀完了通道0,下一次再讀,會自動進入通道1進行讀取,不需要我們指定下一個通道,由于A/D每次讀到的數(shù)據(jù),都是上一次的轉(zhuǎn)換結(jié)果,所以同學(xué)們在使用自動增量功能的時候,要特別注意,當(dāng)前讀到的是上一個通道的值。為了保持程序的通用性,我們的代碼沒有使用這個功能,直接做了一個通用的程序。
控制字節(jié)的第0位和第1位就是通道選擇位了,00、01、10、11代表了從0到3的一共4個通道選擇。
發(fā)送給PCF8591的第三個字節(jié)D/A數(shù)據(jù)寄存器,表示D/A模擬輸出的電壓值。D/A模擬我們一會介紹,大家知道這個字節(jié)的作用即可。我們?nèi)绻麅H僅使用A/D功能的話,就可以不發(fā)送第三個字節(jié)。
下面我們用一個程序,把AIN0、AIN1、AIN3測到的電壓值顯示在液晶上,同時大家可以轉(zhuǎn)動電位器,會發(fā)現(xiàn)AIN0的值發(fā)生變化。
/***********************lcd1602.c文件程序源代碼*************************/
#include <reg52.h>
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
void LcdWaitReady() //等待液晶準備好
{
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do
{
LCD1602_E = 1;
sta = LCD1602_DB; //讀取狀態(tài)字
LCD1602_E = 0;
} while (sta & 0x80); //bit7等于1表示液晶正忙,重復(fù)檢測直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd) //寫入命令函數(shù)
{
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
void LcdWriteDat(unsigned char dat) //寫入數(shù)據(jù)函數(shù)
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str) //顯示字符串,屏幕起始坐標(x,y),字符串指針str
{
unsigned char addr;
//由輸入的顯示坐標計算顯示RAM的地址
if (y == 0)
addr = 0x00 + x; //第一行字符地址從0x00起始
else
addr = 0x40 + x; //第二行字符地址從0x40起始
//由起始顯示RAM地址連續(xù)寫入字符串
LcdWriteCmd(addr | 0x80); //寫入起始地址
while (*str != '\0') //連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
{
LcdWriteDat(*str);
str++;
}
}
void LcdInit() //液晶初始化函數(shù)
{
LcdWriteCmd(0x38); //16*2顯示,5*7點陣,8位數(shù)據(jù)接口
LcdWriteCmd(0x0C); //顯示器開,光標關(guān)閉
LcdWriteCmd(0x06); //文字不動,地址自動+1
LcdWriteCmd(0x01); //清屏
}
/***********************I2C.c文件程序源代碼*************************/
#include <reg52.h>
#include <intrins.h>
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
void I2CStart() //產(chǎn)生總線起始信號
{
I2C_SDA = 1; //首先確保SDA、SCL都是高電平
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0; //先拉低SDA
I2CDelay();
I2C_SCL = 0; //再拉低SCL
}
void I2CStop() //產(chǎn)生總線停止信號
{
I2C_SCL = 0; //首先確保SDA、SCL都是低電平
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1; //先拉高SCL
I2CDelay();
I2C_SDA = 1; //再拉高SDA
I2CDelay();
}
bit I2CWrite(unsigned char dat) //I2C總線寫操作,待寫入字節(jié)dat,返回值為應(yīng)答狀態(tài)
{
bit ack; //用于暫存應(yīng)答位的值
unsigned char mask; //用于探測字節(jié)內(nèi)某一位值的掩碼變量
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位數(shù)據(jù)發(fā)送完后,主機釋放SDA,以檢測從機應(yīng)答
I2CDelay();
I2C_SCL = 1; //拉高SCL
ack = I2C_SDA; //讀取此時的SDA值,即為從機的應(yīng)答值
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成應(yīng)答位,并保持住總線
return (~ack); //應(yīng)答值取反以符合通常的邏輯:0=不存在或忙或?qū)懭胧。?=存在且空閑或?qū)懭氤晒?/P>
}
unsigned char I2CReadNAK() //I2C總線讀操作,并發(fā)送非應(yīng)答信號,返回值為讀到的字節(jié)
{
unsigned char mask;
unsigned char dat;
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中對應(yīng)位清零
else
dat |= mask; //為1時,dat中對應(yīng)位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使從機發(fā)送出下一位
}
I2C_SDA = 1; //8位數(shù)據(jù)發(fā)送完后,拉高SDA,發(fā)送非應(yīng)答信號
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成非應(yīng)答位,并保持住總線
return dat;
}
unsigned char I2CReadACK() //I2C總線讀操作,并發(fā)送應(yīng)答信號,返回值為讀到的字節(jié)
{
unsigned char mask;
unsigned char dat;
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中對應(yīng)位清零
else
dat |= mask; //為1時,dat中對應(yīng)位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使從機發(fā)送出下一位
}
I2C_SDA = 0; //8位數(shù)據(jù)發(fā)送完后,拉低SDA,發(fā)送應(yīng)答信號
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成應(yīng)答位,并保持住總線
return dat;
}
/***********************main.c文件程序源代碼*************************/
#include <reg52.h>
bit flag300ms = 1; //300ms定時標志
unsigned char T0RH = 0; //T0重載值的高字節(jié)
unsigned char T0RL = 0; //T0重載值的低字節(jié)
unsigned char GetADCValue(unsigned char chn);
void ValueToString(unsigned char *str, unsigned char val);
void ConfigTimer0(unsigned int ms);
extern void LcdInit();
extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str);
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
void main ()
{
unsigned char val;
unsigned char str[10];
EA = 1; //開總中斷
ConfigTimer0(10); //配置T0定時10ms
LcdInit(); //初始化液晶
LcdShowStr(0, 0, "AIN0 AIN1 AIN3"); //顯示通道指示
while(1)
{
if (flag300ms)
{
flag300ms = 0;
//顯示通道0的電壓
val = GetADCValue(0); //獲取ADC通道0的轉(zhuǎn)換值
ValueToString(str, val); //轉(zhuǎn)為字符串格式的電壓值
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);
}
}
}
unsigned char GetADCValue(unsigned char chn) //讀取當(dāng)前的ADC轉(zhuǎn)換值,chn為ADC通道號0-3
{
unsigned char val;
I2CStart();
if (!I2CWrite(0x48<<1)) //尋址PCF8591,如未應(yīng)答,則停止操作并返回0
{
I2CStop();
return 0;
}
I2CWrite(0x40|chn); //寫入控制字節(jié),選擇轉(zhuǎn)換通道
I2CStart();
I2CWrite((0x48<<1)|0x01); //尋址PCF8591,指定后續(xù)為讀操作
I2CReadACK(); //先空讀一個字節(jié),提供采樣轉(zhuǎn)換時間
val = I2CReadNAK(); //讀取剛剛轉(zhuǎn)換完的值
I2CStop();
return val;
}
void ValueToString(unsigned char *str, unsigned char val) //ADC轉(zhuǎn)換值轉(zhuǎn)為實際電壓值的字符串形式
{
val = (val*25) / 255; //電壓值=轉(zhuǎn)換結(jié)果*2.5V/255,式中的25隱含了一位十進制小數(shù)
str[0] = (val/10) + '0'; //整數(shù)位字符
str[1] = '.'; //小數(shù)點
str[2] = (val%10) + '0'; //小數(shù)位字符
str[3] = 'V'; //電壓單位
str[4] = '\0'; //結(jié)束符
}
void ConfigTimer0(unsigned int ms) //T0配置函數(shù)
{
unsigned long tmp;
tmp = 11059200 / 12; //定時器計數(shù)頻率
tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
tmp = 65536 - tmp; //計算定時器重載值
tmp = tmp + 12; //修正中斷響應(yīng)延時造成的誤差
T0RH = (unsigned char)(tmp >> 8); //定時器重載值拆分為高低字節(jié)
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0為模式1
TH0 = T0RH; //加載T0重載值
TL0 = T0RL;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動T0
}
void InterruptTimer0() interrupt 1 //T0中斷服務(wù)函數(shù)
{
static unsigned char tmr300ms = 0;
TH0 = T0RH; //定時器重新加載重載值
TL0 = T0RL;
tmr300ms++;
if (tmr300ms >= 30) //定時300ms
{
tmr300ms = 0;
flag300ms = 1;
}
}
細心閱讀程序的同學(xué)會發(fā)現(xiàn),我們程序在進行A/D讀取數(shù)據(jù)的時候,共使用了兩條程序去讀了2個字節(jié)。I2CReadACK(); val = I2CReadNAK();PCF8591的轉(zhuǎn)換時鐘是I2C的SCL,而A/D的特點是每次讀到的都是上一次的轉(zhuǎn)換結(jié)果,因此我們這里第一條語句的作用是產(chǎn)生一個整體的SCL時鐘提供給PCF8591進行A/D轉(zhuǎn)換,第二次是讀取當(dāng)前的轉(zhuǎn)換結(jié)果。如果我們只使用第二條語句的話,每次讀到的都是上一次的轉(zhuǎn)換結(jié)果。
編輯:admin 最后修改時間:2018-05-08