I2C LCD介面應用於STC89C516RD+之溫度與濕度計
一, 前言
市面上有應用於Arduino與樹莓派專用於I2C_LCD介面,使用的是PCF8574AT I2C擴充I/O,在網路上到處都能搜尋到相關前輩的設計範例,但是應用89C51環境的介紹,卻非常的稀少,本來在使用89C51時,使用直接由PORT去推動,可說是簡單而且資料充足,但是當在設計上需要更多的I/O PORT時,就因為直接推動佔去了 8+3=11 PINS,而大傷腦筋,因此才動起如何將此I2C_LCD介面轉換至89C51環境,所以花數天時間,將I2C PCF8574AT的驅動程式改寫,並且調整時脈,總算整體I2C程式撰寫完畢,在此分享給各位同好,如此下來將原先需佔用11 PINS I/O PORT節省為2PIN即可,可說是讓89C51的設計保留了更多的I/O腳,如此下來將來的24C02 FLASH RAM或是PCF8574的KEY INPUT/ LED OUT等的I2C元件也可以一起併入在同一個SMB BUS中,可以說是一個突破,此次使用的CPU是STC89C516RD+ PLCC44包裝,而相關的IC DATASHEET,I2C BUS,LCD INTERFACE與DHT-11溫濕度運等資料在此就不再多加撰述,各位同好可在網路中搜尋,就可取得。
二, 程式主要參數說明
PCF8574AT I2C Addr’s Port = 0x70h
(如果I2C_LCD介面板是使用的PCF8574時,修改I2C Addr’s Port = 0x40h)
此位址在圖片I2C_LCD#3.JPG中的A0/A1/A2選擇上,需加焊三只PULL-LOW 0歐姆電阻,各家的I2C_LCD介面板可能略有不同,請取得您所購得版本的資料去修改之。
[LCD] PCF8574AT
1 GND- 8 GND
2 +5V- 16 VCC
3 VLC- LCD contrast control voltage 20K歐姆可調電阻到地
4 RS - 4 PCF8574AT-P0 //RW低電平時,當RS=1 RW=0寫資料;RS=0 RW=0寫指令
5 RW - 5 PCF8574AT-P1 //RW引腳設置低電平
6 EN - 6 PCF8574AT-P2 //EN可讀寫使能端,高電平有效,下降沿鎖定
LED- 7 PCF8574AT-P3 //背光 LED
11 D4 - 9 PCF8574AT-P4 //4位元資料匯流排,要用PORT的高4位
12 D5 - 10 PCF8574AT-P5
13 D6 - 11 PCF8574AT-P6
14 D7 - 12 PCF8574AT-P7
DHT-11溫濕度檢知器接腳定義
P#1 +5Volt Power
P#2 DHT-11 DATA IN/OUT=>連接至P4^3腳
P#3 N.C.
P#4 GND
目前DHT-11溫濕度檢知器在資料讀取時,共有5個位元組,請詳看DATASHEET,唯一的是應用上,所檢知到的溫度或濕度在精確度上只到個位數,所以誤差會很大,這是比較遺憾之處,這可能未來需取用更精確至小數點一位數的下一代檢知器,才能比較符合使用的條件。
而時鐘部份的程式是利用STC89C516RD+內的TIMER 2產生20ms計時中斷,之後在中斷服務程式內,計次50次,形成每達一秒鐘就設定旗號,讓主程式在每次旗號被設置時,就將時鐘的"秒,分,時"等進行計算與進位,同時也讀取DHT-11溫濕度檢知器在資料一次,並顯示在LCD螢幕上,因為是使用STC89C516RD+內的TIMER 2計時中斷之原故,難免時間長了以後,會有一些誤差秒數出現,如果需要更精準的時鐘,那就可能需用外加DS1302時鐘IC來設計,在本案例中,暫時未採用此元件。
程式範例:
/*
file: LT-37.c I2C LCD TEST PROGRAM STC89C516RD+ (@12MHz)
/**************************************************************************
LCD1602液晶通過PCF8574AT轉換成I2C介面驅動函數
LCD1602液晶與PCF8574AT之間的連接方式:
[LCD] PCF8574AT
1 GND- 8 GND
2 +5V- 16 VCC
3 VLC- LCD contrast control voltage 20K歐姆可調電阻到地
4 RS - 4 PCF8574AT-P0 //RW低電平時,當RS=1 RW=0寫資料;RS=0 RW=0寫指令
5 RW - 5 PCF8574AT-P1 //RW引腳設置低電平
6 EN - 6 PCF8574AT-P2 //EN可讀寫使能端,高電平有效,下降沿鎖定
LED- 7 PCF8574AT-P3 //背光 LED
11 D4 - 9 PCF8574AT-P4 //4位元資料匯流排,要用PORT的高4位
12 D5 - 10 PCF8574AT-P5
13 D6 - 11 PCF8574AT-P6
14 D7 - 12 PCF8574AT-P7
說明:LCD驅動採用4線驅動方法,LCD1602的RW端接低電平,這樣無法讀數據,
操作需插入合適的延時,由於LCD1602是慢速器件,因此時序要注意,參考其手冊
LCD第一行顯示寄存器地址:0x80-0x8F
LCD第二行顯示寄存器位址:0xC0-0xCF
PCF8754AT設置地址:0x70H (此需視各家所設計的I2C轉換LCD介面板的設計位址而定)
如為使用PCF8574時,則選用的位址為0x40H
I2C介面線序定義:1=Vdd 2=Vss 3=SDA 4=SCL
***************************************************************************/
#include
#include
#include
#include
#include "delay.c"
#define XIO_ID 0x70 // Device identifier of the I/O-Expander (PCF8574AT)
#define _Nop() _nop_() //定義空指令
sbit I2C_SDA= P1^2; //PCF8674AT SDA
sbit I2C_SCL= P1^3; //PCF8574AT SCL
sbit second = P4^1; //LED每秒閃動一次
sbit DHT11 = P4^3; //DHT-11溫濕檢知
//--------------函數聲明----------------------
unsigned char temp[20];
unsigned xdata AC_ms,AC_sec,AC_min,AC_hrs,LCDtemp=0;
unsigned xdata I_RH,D_RH,I_Temp,D_Temp,CheckSum;
bit SetFlag,Turn_flag=0,LCD_flag=0;
void Init_Timer2(void);
void LCD1602_Write_Command(unsigned char command); //向LCD1602寫入指令
void LCD1602_Write_Data(unsigned char Data); //向LCD1602寫入資料
void LCD1602_Device_Init(void); //LCD1602初始化函數
void LCD1602_Set_Position(unsigned char x, unsigned char y); //向LCD1602設置下一個準備寫入的字元位置
void LCD1602_Write_String(unsigned char X, unsigned char Y, char *String); //向指定的位置開始寫字串
void XIO_write(unsigned char content);
void Start_I2c();
void Stop_I2c();
unsigned char i2c_write(unsigned char value);
unsigned char i2c_check(void);
void Request();
void Response();
unsigned int Receive_data();
void DHT_11();
/*******************************************************************/
int main()
{ unsigned int i;
P1 = 0xFF;LCD_flag=0;
Init_Timer2(); //時鐘計數器,20mS/次
for(i=0;i<50;i++) led="" p="">
{ DelayMs(50);second=~second; }
LCD1602_Device_Init(); //啟動I2C LCD介面板與1602液晶螢幕
DelayMs(5); //需延遲待待LCD螢幕內部運作
LCD1602_Write_Command(0x01); //清除螢幕內容
DelayMs(5);
ET2 = 1; //打開時鐘計數器2中斷予能
TR2 = 1; //時鐘計數器2啟動率數
EA = 1; //打開總中斷
Turn_flag = 0;
LCD1602_Write_String(20,1, "LT-37 I2C_LCD TEST"); //顯示軟體版本與名稱
while(1) //主程式
{
if( Turn_flag==1) //時鐘計數器2旗號已動作
{ AC_sec++;
if(AC_sec>=60)
{AC_sec=0;AC_min++;}
if(AC_min>=60)
{AC_min=0;AC_hrs++;}
if(AC_hrs>=24)
{AC_hrs=0;}
DHT_11(); //偵測DHT-11 溫度與濕度之讀取資料.並顯所取得之數據
second=~second; //LED每秒閃爍一次
Turn_flag=0; //清除時鐘計數器2旗號置0
}
sprintf(temp,"%02d:%02d:%02d ",(int)AC_hrs,(int)AC_min,(int)AC_sec); //顯示時鐘 時:分:秒
LCD1602_Write_String(0, 0, temp);
//************************************************
}
return 0;
}
//************************************************************
void DHT_11() //溫度與濕度讀取檢知數據
{
EA=0; // 禁能中斷,以避免影響讀取數據的時脈
Request(); // 送出啟動DHT-11的START BIT
Response(); // 待待回應
I_RH=Receive_data(); // 讀取濕度低位元至 I_RH
D_RH=Receive_data(); // 讀取濕度高位元至 D_RH
I_Temp=Receive_data(); // 讀取溫度低位元至 I_Temp
D_Temp=Receive_data(); // 讀取溫度高位元至 D_Temp
CheckSum=Receive_data();// 讀取檢查和位元至 CheckSum
EA=1; // 重新予能中斷
if ((I_RH + D_RH + I_Temp + D_Temp) != CheckSum)
{ LCD1602_Write_String(20, 0, "Error"); //如果比對CHECKSUM與讀取之數據有差時,顯示ERROR字樣
}
else
{
sprintf(temp,"Hum = %d Tem = %d.C",(int)I_RH,(int)I_Temp); //CHECKSUM吻合時顯示溫度與濕度
LCD1602_Write_String(20, 0, temp);
}
}
void Request() // DHT-11啟動START BIT
{
DHT11 = 0; // 設定資料位元至"LOW"
DelayMs(16); //*** 等待16ms,此時脈非常重要,會影響讀取數據的正確性,
DHT11 = 1; // 設定資料位元至"HIGH"
}
void Response() //等待 DHT11回應
{
while(DHT11==1) _nop_();
while(DHT11==0) _nop_();
while(DHT11==1) DelayUs2x(2);;
}
unsigned int Receive_data() /* DHT-11讀取數據 */
{ unsigned int q,c=0;
for (q=0; q<8; q="" p="">
{
while(DHT11==0); // 等待DHT-11開始送出數據
DelayUs2x(20); //*** 延遲時脈
if(DHT11 == 1) // 判別所讀到的位元是"0"或"1"
{c = (c<<1)|(0x01);} high="" p="">
else // 如果不符合"HIGH"時
{c = (c<<1);} low="" p="">
while(DHT11==1);
}
return c; //結束一個位元組之讀取,並送回所讀取之資料
}
/*------------向LCD1602寫入指令----------------------------
函數名稱:void LCD1602_Write_Command(unsigned char command)
向LCD1602寫入指令
函數參數:unsigned char command //準備寫入的指令
函數說明:向LCD1602的指令寄存器寫入一個指令
---------------------------------------------------------------*/
void LCD1602_Write_Command(unsigned char command) //向LCD1602寫入指令
{
DelayMs(2); //此處插入一個等待很重要,小於這個時間會導致連續寫時出現錯誤
//先寫入高4位
LCDtemp = command & 0xf0 | 0x08; //先處理高4位,EN=0,RW=0,RS=0
XIO_write(LCDtemp);
LCDtemp |= 0x0c; //拉高EN
XIO_write(LCDtemp);
LCDtemp &= 0xfb; //EN置低,下降沿寫入液晶
XIO_write(LCDtemp);
//接下來寫入低4位
if(LCD_flag==0) return; //判斷是否是冷開機
LCDtemp = command<<4; p="">
XIO_write(LCDtemp);
LCDtemp |= 0x0c; //拉高EN
XIO_write(LCDtemp);
LCDtemp &= 0xfb; //EN置低,下降沿寫入液晶
XIO_write(LCDtemp);
}
/*------------向LCD1602寫入資料----------------------------
函數名稱:void LCD1602_Write_Data(unsigned char data)
向LCD1602寫入資料
函數參數:unsigned char data //準備寫入的資料
函數說明:向LCD1602的指令寄存器寫入一個資料
---------------------------------------------------------------*/
void LCD1602_Write_Data(unsigned char Data) //向LCD1602寫入資料
{
DelayMs(2); //此處插入一個等待很重要,小於這個時間會導致連續寫時出現錯誤
//先寫入高4位
LCDtemp = (Data & 0xf0) | 0x09; //先處理高4位,EN=0,RW=0,RS=1
XIO_write(LCDtemp);
LCDtemp |= 0x0c; //拉高EN
XIO_write(LCDtemp);
LCDtemp &= 0xfb; //EN置低,下降沿寫入液晶
XIO_write(LCDtemp);
//接下來寫入低4位
LCDtemp = (Data<<4) 0x09="" p="">
XIO_write(LCDtemp);
LCDtemp |= 0x0c; //拉高EN
XIO_write(LCDtemp);
LCDtemp &= 0xfb; //EN置低,下降沿寫入液晶
XIO_write(LCDtemp);
}
/*------------------------------------------------------------------
函數名稱:void LCD1602_Init(void)//LCD1602初始化函數
函數參數:無
函數說明:參考LCD1602液晶說明手冊,請特別留意LCD_flag
------------------------------------------------------------------*/
void LCD1602_Device_Init(void) //LCD1602初始化函數
{
DelayMs(50); //冷開機
LCD1602_Write_Command(0x38);
DelayMs(50);
LCD1602_Write_Command(0x28);
DelayMs(5);
LCD1602_Write_Command(0x28);
DelayMs(5);
LCD1602_Write_Command(0x28);
DelayMs(50); //先等待50毫秒VDD上電穩定
LCD_flag=1;
LCD1602_Write_Command(0x28); //先進行功能設置,四位元資料介面,兩行顯示,5′7點陣字元。
DelayMs(25); //等待上條指令完成
LCD1602_Write_Command(0x0f); //Display On/Off Control,顯示開關:D=1打開,游標開關:C=1打開,閃爍開關:B=1打開
DelayMs(5);
LCD1602_Write_Command(0x01); //清屏
DelayUs2x(5);
LCD1602_Write_Command(0x06); //AC位址遞增模式
DelayUs2x(5);
LCD1602_Write_Command(0x01); //清屏
DelayUs2x(5);
LCD1602_Write_Command(0x02); //HOME
DelayMs(5);
LCD1602_Write_Command(0x0f); //Display On/Off Control,顯示開關:D=1打開,游標開關:C=1打開,閃爍開關:B=1打開
DelayMs(5);
}
/*--------設置AC地址--------------------------------------------------
函數名稱:void LCD1602_Set_Position(unsigned char x, unsigned char y)
向LCD1602設置下一個準備寫入的字元位置
函數參數:unsigned char x 行地址 0~F
unsigned char y 列地址 0~1
函數說明:設置AC地址,
---------------------------------------------------------------------*/
void LCD1602_Set_Position(unsigned char x, unsigned char y)
{ //向LCD1602設置下一個準備寫入的字元位置
unsigned char address = (y * 0x40) + (0x80 + x) ;
LCD1602_Write_Command(address);
DelayUs2x(25);
}
/*--------向指定的位置開始寫字串 ---------------------------------------
函數名稱:LCD1602_Write_String(unsigned char X, unsigned char Y, char *s)
向指定的位置開始寫字串
函數參數:unsigned char X //列位置
unsigned char Y //行位置
char *String //要顯示的字串
函數說明:向指定的位置開始寫字串
------------------------------------------------------------------------*/
void LCD1602_Write_String(unsigned char X, unsigned char Y, char *String)
{ //向指定的位置開始寫字串
LCD1602_Set_Position(X, Y);
while(*String)
{
LCD1602_Write_Data(*String++);
DelayUs2x(25);
}
}
void XIO_write(unsigned char content)
{
Start_I2c(); // 產生i2c啟動位元
if ( i2c_write(XIO_ID)==1 ) //設定I2C位址 0x70h
{
return;
}
// 送出I2C DATA
if ( i2c_write(content)==1 )
{
return;
}
Stop_I2c(); // 產生終止位元
return;
}
void Start_I2c()
{ DelayUs2x(1);
I2C_SDA=1; //發送起始條件的資料信號
DelayUs2x(1);
I2C_SCL=1;
DelayUs2x(2);
I2C_SDA=0; //發送起始信號
DelayUs2x(2);
I2C_SCL=0; //鉗住I2C匯流排,準備發送或接收資料
DelayUs2x(1);
}
/*------------------------------------------------
結束匯流排
------------------------------------------------*/
void Stop_I2c()
{
I2C_SCL=1; //發送結束條件的資料信號
DelayUs2x(2);
I2C_SDA=1; //結束條件建立時間大於4μ
DelayUs2x(2);
}
unsigned char i2c_write(unsigned char value)
{
idata unsigned char counter =0; // 8位元計次
for ( counter =0; counter < 8; counter++ )
{
I2C_SDA =(bit)((value & 0x80) >> 7); // 檢測所要送出字元是否為"HIGH",並移位
I2C_SCL =1; // 送出 SCL
while(I2C_SCL==0); // 同步SCL信號
EA=0; // 禁能中斷,以避免影響時脈
DelayUs2x(2);
EA=1; // 予能中斷
I2C_SCL =0; // 完成一個位元的週期
value <<=1; p="">
};
return(i2c_check());
}
unsigned char i2c_check(void)
{
EA =0; // 禁能中斷,以避免影響時脈
I2C_SDA =1;
I2C_SCL =1;
while(I2C_SCL==0); // 同步時脈週期
DelayUs2x(2);
if (I2C_SDA==1)
{
I2C_SCL =0; // 開始一個週期時脈
EA =1; // 予能中斷
return(1); // 未取得Acknowledge
}
I2C_SCL =0; // Force a clock cycle
EA =1; // 予能中斷
return(0); // 取得Acknowledge
}
/*------------------------------------------------
計時器 2 的初始化
------------------------------------------------*/
void Init_Timer2(void) // 計時器 2 的初始化
{
RCAP2H=(65536-20000)/256; //
RCAP2L=(65536-20000)%256; //
ET2=1; //禁能中斷
TR2=1; // timer2 run
C_T2=0; // interrupt mode
CP_RL2=0; // reload mode
}
//***********************************
void TIMER2(void) interrupt 5 using 1
{
RCAP2H=(65536-20000)/256; //
RCAP2L=(65536-20000)%256; //
AC_ms++;
if(AC_ms>=50) {AC_ms=0;Turn_flag=1; } //每秒滿足時,設定旗號
TF2=0; //清除timer2中斷旗號
}
限會員,要發表迴響,請先登入


