字體:小 中 大 | |
|
|
2017/06/17 08:37:15瀏覽1706|回應0|推薦0 | |
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中斷旗號 }
|
|
( 興趣嗜好|電腦3C ) |