摘要:這是一個記錄軟硬件結(jié)合的物聯(lián)網(wǎng)項(xiàng)目,目的是打造微信實(shí)時監(jiān)控室內(nèi)環(huán)境蚁廓,由于傳感器簡陋陌选,這篇文章只介紹溫度和濕度的監(jiān)控,使用的是DHT11溫濕度傳感器肴甸,主機(jī)為Arduino UNO+W5100 Ethernet/SD擴(kuò)展版寂殉,路由器為HG255D Pandorabox by lintel,物聯(lián)網(wǎng)服務(wù)由樂聯(lián)網(wǎng)提供,本篇文章主要內(nèi)容有(1)DHT11硬件基礎(chǔ)(2)Arduino讀取DHT11的溫濕度數(shù)據(jù)(3)LCD1602_I2C硬件基礎(chǔ)及在LCD模塊上顯示DHT11獲取的溫濕度數(shù)據(jù)(4)將數(shù)據(jù)上傳到樂聯(lián)網(wǎng)(5)微信查詢傳感器數(shù)據(jù)
DHT11硬件基礎(chǔ)
DHT11是一款性價比極高的含有已校準(zhǔn)數(shù)字信號輸出的溫濕度復(fù)合傳感器雷滋,傳感器包括一個電阻式感濕元件和一個NTC測溫元件并與一個高性能8位單片機(jī)相連接不撑。
引腳說明
DHT11有四個引腳,一個引腳為空腳不連接元器件(所以一些采用DHT11傳感器的封裝好的溫濕度傳感器只能看到3個引腳)晤斩,其中正極接VCC(工作電壓為3-5.5VDC)焕檬,負(fù)極接GND,由于DHT11是以串行接口以高低電平傳遞數(shù)據(jù)澳泵,所以數(shù)據(jù)總線(SDATA)引腳連接單片機(jī)或Arduino的數(shù)字信號(Digital)引腳实愚,通常在元器件上正極標(biāo)有(+),負(fù)極標(biāo)有(—),數(shù)據(jù)引腳標(biāo)有(S)腊敲,我用的是KEYES的元器件击喂,左邊標(biāo)有(S)為數(shù)據(jù)引腳,右邊標(biāo)有(-)為負(fù)極引腳碰辅,中間則為正極引腳懂昂,不同廠家的元器件引腳順序不同,連接時要找準(zhǔn)引腳没宾,否者單片機(jī)則讀取不到數(shù)據(jù)凌彬。
數(shù)據(jù)傳輸原理
MCU發(fā)送開始信號(+-+)->觸發(fā)DHT響應(yīng)(-+-)數(shù)據(jù)采集->{[準(zhǔn)備發(fā)送(-)+發(fā)送數(shù)據(jù)(+)]*5}->機(jī)械拉低(-)->MCU拉高(+)
DHT11采用的是串行接口(單線雙向)的通訊方式,DATA用于微處理器與DHT11之間的通訊和同步,采用單總線數(shù)據(jù)格式,一次通訊時間4ms左右,數(shù)據(jù)分小數(shù)部分和整數(shù)部分,具體格式在下面說明,當(dāng)前小數(shù)部分用于以后擴(kuò)展,現(xiàn)讀出為零.操作流程如下:一次完整的數(shù)據(jù)傳輸為40bit,高位先出循衰。
數(shù)據(jù)格式:
40bit=8bit濕度整數(shù)數(shù)據(jù)+8bit濕度小數(shù)數(shù)據(jù)+8bi溫度整數(shù)數(shù)據(jù)+8bit溫度小數(shù)數(shù)據(jù)+8bit校驗(yàn)和數(shù)據(jù)
傳送正確時校驗(yàn)和數(shù)據(jù)等于“8bit濕度整數(shù)數(shù)據(jù)+8bit濕度小數(shù)數(shù)據(jù)+8bi溫度整數(shù)數(shù)據(jù)+8bit溫度小數(shù)數(shù)據(jù)”所得結(jié)果的末8位铲敛。
數(shù)據(jù)傳輸過程
用戶MCU發(fā)送一次開始信號后,DHT11從低功耗模式轉(zhuǎn)換到高速模式,等待主機(jī)開始信號結(jié)束后,DHT11發(fā)送響應(yīng)信號,送出40bit的數(shù)據(jù),并觸發(fā)一次信號采集,用戶可選擇讀取部分?jǐn)?shù)據(jù).從模式下,DHT11接收到開始信號觸發(fā)一次溫濕度采集,如果沒有接收到主機(jī)發(fā)送開始信號,DHT11不會主動進(jìn)行溫濕度采集.采集數(shù)據(jù)后轉(zhuǎn)換到低速模式。
通訊過程如圖所示
總線空閑狀態(tài)為高電平,主機(jī)把總線拉低等待DHT11響應(yīng),主機(jī)把總線拉低必須大于18毫秒,保證DHT11能檢測到起始信號会钝。DHT11接收到主機(jī)的開始信號后,等待主機(jī)開始信號結(jié)束,然后發(fā)送80us低電平響應(yīng)信號.主機(jī)發(fā)送開始信號結(jié)束后,延時等待20-40us后, 讀取DHT11的響應(yīng)信號,主機(jī)發(fā)送開始信號后,可以切換到輸入模式,或者輸出高電平均可, 總線由上拉電阻拉高伐蒋。
總線為低電平,說明DHT11發(fā)送響應(yīng)信號,DHT11發(fā)送響應(yīng)信號后,再把總線拉高80us,準(zhǔn)備發(fā)送數(shù)據(jù),每一bit數(shù)據(jù)都以50us低電平時隙開始,高電平的長短定了數(shù)據(jù)位是0還是1.格式見下面圖示.如果讀取響應(yīng)信號為高電平,則DHT11沒有響應(yīng),請檢查線路是否連接正常.當(dāng)最后一bit數(shù)據(jù)傳送完畢后,DHT11拉低總線50us,隨后總線由上拉電阻拉高進(jìn)入空閑狀態(tài)迁酸。
數(shù)字0信號表示方法如圖所示
數(shù)字1信號表示方法如圖所示
Arduino讀取DHT11的溫濕度數(shù)據(jù)
int DHpin=8; //定義數(shù)字引腳8為DHT11數(shù)據(jù)接收/發(fā)送引腳
byte dat[5]; //聲明數(shù)組先鱼,用于儲存5組8bit的數(shù)據(jù)
void setup() //只執(zhí)行一次
{
Serial.begin(9600); //串口通信波特率(和電腦)
pinMode(DHpin,OUTPUT); //輸出模式
digitalWrite(DHpin,HIGH); //靜息電平,空閑狀態(tài)
}
void start_test() //MCU發(fā)送開始信號
{
digitalWrite(DHpin,LOW); //拉低電平發(fā)送信號
delay(30);
digitalWrite(DHpin,HIGH); //拉高電平胁出,發(fā)送信號結(jié)束
delayMicroseconds(40);
pinMode(DHpin,INPUT); //輸入模式型型,開始檢測DHT發(fā)來的信號
while(digitalRead(DHpin)==LOW); //DHT發(fā)來響應(yīng)信號,低電平80us
delayMicroseconds(80);
while(digitalRead(DHpin)==HIGH); //高電平80us全蝶,響應(yīng)信號結(jié)束
delayMicroseconds(80);
for(int i=0;i<4;i++) //開始讀取數(shù)據(jù)
dat[i]=read_data();
pinMode(DHpin,OUTPUT); //讀取數(shù)據(jù)結(jié)束
digitalWrite(DHpin,HIGH); //拉高電平闹蒜,空閑狀態(tài)
}
byte read_data() //讀取數(shù)據(jù)的方法
{
byte data;
for(int i=0;i<8;i++) //1bit的讀取數(shù)據(jù)
{
if(digitalRead(DHpin)==HIGH)
{
while(digitalRead(DHpin)==HIGH); //檢測到高電平開始,延遲30us
delayMicroseconds(30);
if(digitalRead(DHpin)==HIGH) //如果30us后,電平拉高,則為1抑淫,否者則為0
data|=(1<<(7-i));
}
}
return data;
}
void loop() //串口輸出數(shù)據(jù)
{
start_test();
Serial.print("t1:");
Serial.print(dat[0],DEC); //顯示濕度的整數(shù)位绷落;
Serial.print('.');
Serial.print(dat[1],DEC); //顯示濕度的小數(shù)位;
Serial.print('%');
Serial.print("t2:");
Serial.print(dat[2],DEC); //顯示溫度的整數(shù)位始苇;
Serial.print('.');
Serial.print(dat[3],DEC); //顯示溫度的小數(shù)位;
Serial.print('C');
delay(5000); //延遲5s
}
LCD1602_I2C硬件基礎(chǔ)
LCD1602_I2C由兩部分組成催式,LCD1602和一個I2C并行串口電路構(gòu)成函喉,LCD提供液晶屏顯示哺窄,I2C并行串口電路為單片機(jī)提供并行串口通訊捐下,減少LCD1602所需的引腳账锹。
LCD1602硬件基礎(chǔ)
LCD:英文全稱為Liquid Crystal Display,即為液態(tài)晶體顯示坷襟,也就是我們常說的液晶顯示了奸柬。1602則是表示這個液晶一共能顯示2行數(shù)據(jù),每一行顯示16個字符婴程。這個就是LCD1602的全部來由廓奕。
我們首先來看1602的引腳定義,1602的引腳是很整齊的SIP單列直插封裝,所以器件手冊只給出了引腳的功能數(shù)據(jù)表:
我們只需要關(guān)注以下幾個管腳:
3腳:VL档叔,液晶顯示偏壓信號懂从,用于調(diào)整LCD1602的顯示對比度,一般會外接電位器用以調(diào)整偏壓信號蹲蒲,注意此腳電壓為0時可以得到最強(qiáng)的對比度。
4腳:RS侵贵,數(shù)據(jù)/命令選擇端届搁,當(dāng)此腳為高電平時,可以對1602進(jìn)行數(shù)據(jù)字節(jié)的傳輸操作窍育,而為低電平時卡睦,則是進(jìn)行命令字節(jié)的傳輸操作。命令字節(jié)漱抓,即是用來對LCD1602的一些工作方式作設(shè)置的字節(jié)表锻;數(shù)據(jù)字節(jié),即使用以在1602上顯示的字節(jié)乞娄。這是一個選擇數(shù)據(jù)寄存器還是選擇指令寄存器的過程瞬逊,值得一提的是,LCD1602的數(shù)據(jù)是8位的仪或。
5腳:R/W确镊,讀寫選擇端。當(dāng)此腳為高電平可對LCD1602進(jìn)行讀數(shù)據(jù)操作范删,反之進(jìn)行寫數(shù)據(jù)操作蕾域。筆者認(rèn)為,此腳其實(shí)用處不大到旦,直接接地永久置為低電平也不會影響其正常工作旨巷。但是尚未經(jīng)過復(fù)雜系統(tǒng)驗(yàn)證,保留此意見添忘。
6腳:E采呐,使能信號,其實(shí)是LCD1602的數(shù)據(jù)控制時鐘信號昔汉,利用該信號的上升沿實(shí)現(xiàn)對LCD1602的數(shù)據(jù)傳輸懈万。
7~14腳:8位并行數(shù)據(jù)口拴清,使得對LCD1602的數(shù)據(jù)讀寫大為方便。
LCD1602通訊過程
現(xiàn)在來看LCD1602的操作時序:
在此会通,我們可以先不讀出它的數(shù)據(jù)的狀態(tài)或者數(shù)據(jù)本身口予。所以只需要看兩個寫時序:
當(dāng)我們要寫入指令字節(jié),設(shè)置LCD1602的工作方式時:需要把RS置為低電平涕侈,RW置為低電平沪停,然后將數(shù)據(jù)送到數(shù)據(jù)口D0~D7,最后E引腳一個高脈沖將數(shù)據(jù)寫入裳涛。
當(dāng)我們要寫入數(shù)據(jù)字節(jié)木张,設(shè)置LCD1602的工作方式時:需要把RS置為高電平,RW置為低電平端三,然后將數(shù)據(jù)送到數(shù)據(jù)口D0~D7舷礼,最后E引腳一個高脈沖將數(shù)據(jù)寫入。
發(fā)現(xiàn)了么郊闯,寫指令和寫數(shù)據(jù)妻献,差別僅僅在于RS的電平不一樣而已。以下是LCD1602的讀時序圖:
當(dāng)要寫命令字節(jié)的時候团赁,時間由左往右育拨,RS變?yōu)榈碗娖剑琑/W變?yōu)榈碗娖交渡悖⒁饪词荝S的狀態(tài)先變化完成熬丧。然后這時,DB0~DB7上數(shù)據(jù)進(jìn)入有效階段怀挠,接著E引腳有一個整脈沖的跳變析蝴,接著要維持時間最小值為tpw=400ns的E脈沖寬度。然后E引腳負(fù)跳變唆香,RS電平變化嫌变,R/W電平變化。這樣便是一個完整的LCD1602寫命令的時序躬它。
LCD1602基本指令
Arduino與LCD1602的通訊
8針接法腾啥,數(shù)據(jù)輸出口:DB0-DB7。
// 8pinlcd1602.ino
int DI = 12; //定義數(shù)據(jù)/命令針腳
int RW = 11; //定義讀/寫針腳
int DB[] = {3, 4, 5, 6, 7, 8, 9, 10};//使用數(shù)組來定義總線需要的管腳
int Enable = 2; //定義使能管腳
//LCD寫命令的方法
void LcdCommandWrite(int value) {
// 定義所有引腳
int i = 0;
digitalWrite(DI, LOW); //DI為O冯吓,RW為0倘待,寫入命令
digitalWrite(RW, LOW);
for (i=DB[0]; i <= DI; i++) //總線賦值
{
digitalWrite(i,value & 01); //獲取每個字節(jié)的第一位
value >>= 1; //2進(jìn)制移位,將要獲取的位移至第一位
}
digitalWrite(Enable,LOW); //使能拉低電平组贺,準(zhǔn)備觸發(fā)高脈沖
delayMicroseconds(1);
digitalWrite(Enable,HIGH); //Arduino拉高電平凸舵,高脈沖將數(shù)據(jù)泵入指令寄存器
delayMicroseconds(1);
digitalWrite(Enable,LOW); //使能恢復(fù)低電平,靜息電平
delayMicroseconds(1);
}
void LcdDataWrite(int value) {
// 定義所有引腳
int i = 0;
digitalWrite(DI, HIGH); //DI為1失尖,RW為0啊奄,寫入數(shù)據(jù)
digitalWrite(RW, LOW);
for (i=DB[0]; i <= DB[7]; i++) {
digitalWrite(i,value & 01); //獲取每個字節(jié)的第一位渐苏,0xXX & 01 =0 | 1
value >>= 1;
}
digitalWrite(Enable,LOW);
delayMicroseconds(1);
digitalWrite(Enable,HIGH); //高脈沖泵入數(shù)據(jù),寫入數(shù)據(jù)寄存器
delayMicroseconds(1);
digitalWrite(Enable,LOW);
delayMicroseconds(1);
}
void setup (void) { //起始函數(shù)菇夸,只執(zhí)行一次
int i = 0;
for (i=Enable; i <= DI; i++) { //將所有管腳設(shè)置為輸出
pinMode(i,OUTPUT);
}
delay(100);
// 短暫的停頓后初始化LCD
// 用于LCD控制需要
LcdCommandWrite(0x38); // 設(shè)置為8-bit接口琼富,2行顯示,5x7文字大小
delay(64);
LcdCommandWrite(0x38); // 設(shè)置為8-bit接口庄新,2行顯示鞠眉,5x7文字大小
delay(50);
LcdCommandWrite(0x38); // 設(shè)置為8-bit接口,2行顯示择诈,5x7文字大小
delay(20);
LcdCommandWrite(0x06); // 輸入方式設(shè)定
// 自動增量械蹋,沒有顯示移位
delay(20);
LcdCommandWrite(0x0E); // 顯示設(shè)置
// 開啟顯示屏,光標(biāo)顯示羞芍,無閃爍
delay(20);
LcdCommandWrite(0x01); // 屏幕清空哗戈,光標(biāo)位置歸零
delay(100);
LcdCommandWrite(0x80); // 顯示設(shè)置
// 開啟顯示屏,光標(biāo)顯示荷科,無閃爍
delay(20);
}
void loop (void) {
LcdCommandWrite(0x01); // 屏幕清空谱醇,光標(biāo)位置歸零
delay(10);
LcdCommandWrite(0x80+3); //0x80+為第一行命令地址,0x80+3表示第1行第4列的字符
delay(10);
// 寫入歡迎信息
LcdDataWrite('W');
LcdDataWrite('e');
LcdDataWrite('l');
LcdDataWrite('c');
LcdDataWrite('o');
LcdDataWrite('m');
LcdDataWrite('e');
LcdDataWrite(' ');
LcdDataWrite('t');
LcdDataWrite('o');
delay(10);
LcdCommandWrite(0xc0+1); // 定義光標(biāo)位置為第二行第二個位置
delay(10);
LcdDataWrite('M');
LcdDataWrite('a');
LcdDataWrite('k');
LcdDataWrite('e');
LcdDataWrite('B');
LcdDataWrite('l');
LcdDataWrite('a');
LcdDataWrite('z');
LcdDataWrite('e');
delay(5000);
LcdCommandWrite(0x01); // 屏幕清空步做,光標(biāo)位置歸零
delay(10);
LcdDataWrite('I');
LcdDataWrite(' ');
LcdDataWrite('a');
LcdDataWrite('m');
LcdDataWrite(' ');
LcdDataWrite('R');
LcdDataWrite('i');
LcdDataWrite('c');
LcdDataWrite('e');
LcdDataWrite('L');
LcdDataWrite('y');
LcdDataWrite('n');
delay(3000);
LcdCommandWrite(0x02); //設(shè)置模式為新文字替換老文字,無新文字的地方顯示不變奈附。
delay(10);
LcdCommandWrite(0x80+5); //定義光標(biāo)位置為第一行第六個位置
delay(10);
LcdDataWrite('t');
LcdDataWrite('h');
LcdDataWrite('e');
LcdDataWrite(' ');
LcdDataWrite('a');
LcdDataWrite('d');
LcdDataWrite('m');
LcdDataWrite('i');
LcdDataWrite('n');
delay(5000);
}
4針接法全度,數(shù)據(jù)輸出口:DB4-DB7,傳輸一個字節(jié)分兩次傳輸斥滤,每次傳輸4bit将鸵,需要2倍的時間。
// 4pinlcd1602.ino
int LCD1602_RS=12;
int LCD1602_RW=11;
int LCD1602_EN=10;
int DB[] = { 6, 7, 8, 9}; //定義4位總線
char str1[]="Welcome to"; //自定義字符佑颇,數(shù)據(jù)格式為字符(一個數(shù)組的值等于一個字符)
char str2[]="MakeBlaze";
char str3[]="this is the";
char str4[]="4-bit interface";
void LCD_Command_Write(int command) //LCD寫命令函數(shù)
{
int i,temp;
digitalWrite( LCD1602_RS,LOW);
digitalWrite( LCD1602_RW,LOW);
digitalWrite( LCD1602_EN,LOW);
temp=command & 0xf0; //切割字節(jié)顶掉,獲取高4位,0xff=11110000
for (i=DB[0]; i <= 9; i++)
{
digitalWrite(i,temp & 0x80); //獲取最高位挑胸,0x80=10000000
temp <<= 1;
}
digitalWrite( LCD1602_EN,HIGH); //高脈沖泵入指令寄存器痒筒,位于棧底,D7位為0
delayMicroseconds(1);
digitalWrite( LCD1602_EN,LOW);
temp=(command & 0x0f)<<4; ////切割字節(jié)茬贵,獲取低4位簿透,0xff=11110000
for (i=DB[0]; i <= 9; i++)
{
digitalWrite(i,temp & 0x80); //獲取最高位,0x80=10000000
temp <<= 1;
}
digitalWrite( LCD1602_EN,HIGH); //高脈沖泵入指令寄存器解藻,位于棧頂
delayMicroseconds(1);
digitalWrite( LCD1602_EN,LOW);
}
void LCD_Data_Write(int dat) //LCD寫數(shù)據(jù)的函數(shù)
{
int i=0,temp;
digitalWrite( LCD1602_RS,HIGH);
digitalWrite( LCD1602_RW,LOW);
digitalWrite( LCD1602_EN,LOW);
temp=dat & 0xf0;
for (i=DB[0]; i <= 9; i++)
{
digitalWrite(i,temp & 0x80);
temp <<= 1;
}
digitalWrite( LCD1602_EN,HIGH);
delayMicroseconds(1);
digitalWrite( LCD1602_EN,LOW);
temp=(dat & 0x0f)<<4;
for (i=DB[0]; i <= 9; i++)
{
digitalWrite(i,temp & 0x80);
temp <<= 1;
}
digitalWrite( LCD1602_EN,HIGH);
delayMicroseconds(1);
digitalWrite( LCD1602_EN,LOW);
}
void LCD_SET_XY( int x, int y ) //設(shè)置字符位置
{
int address;
if (y ==0) address = 0x80 + x; //如果是第一行老充,則地址設(shè)置為0x80+
else address = 0xC0 + x; //如果是第二行,則地址設(shè)置為0xC0+
LCD_Command_Write(address); //寫入地址寄存器
}
void LCD_Write_Char( int x,int y,int dat) //設(shè)置LCD顯示數(shù)據(jù)
{
LCD_SET_XY( x, y ); //設(shè)置LCD位置
LCD_Data_Write(dat); //將字模對應(yīng)的地址編號寫入數(shù)據(jù)寄存器
}
void LCD_Write_String(int X,int Y,char *s)
{
LCD_SET_XY( X, Y ); //設(shè)置地址
while (*s) //寫字符串
{
LCD_Data_Write(*s);
s ++;
}
}
void setup (void)
{
int i = 0; //設(shè)置所有管腳為輸出
for (i=6; i <= 12; i++)
{
pinMode(i,OUTPUT);
}
delay(100);
LCD_Command_Write(0x28); //4線 2行 5x7螟左,8線我0x38
delay(50);
LCD_Command_Write(0x06); //寫一個字符后地址指針加1啡浊,光標(biāo)向后移一位
delay(50);
LCD_Command_Write(0x0c); //打開顯示觅够,不顯示光標(biāo)
delay(50);
LCD_Command_Write(0x80); // 顯示設(shè)置
// 開啟顯示屏,光標(biāo)顯示巷嚣,無閃爍
delay(50);
LCD_Command_Write(0x01); //顯示清屏
delay(50);
}
void loop (void)
{
LCD_Command_Write(0x01);
delay(50);
LCD_Write_String(3,0,str1);//第1行喘先,第4個地址起
delay(50);
LCD_Write_String(1,1,str2);//第2行,第2個地址起
delay(5000);
LCD_Command_Write(0x01);
delay(50);
LCD_Write_String(0,0,str3);
delay(50);
LCD_Write_String(0,1,str4);
delay(5000);
}
LCD1602內(nèi)部構(gòu)造
LCD1602模塊內(nèi)部由一塊LCD顯示屏(LCDpanel)涂籽,控制器(controller),列驅(qū)動器(segment driver)和偏壓產(chǎn)生電路構(gòu)成苹祟。
控制器接收來自MPU(這里為Arduino)的指令和數(shù)據(jù),控制著整個模塊的運(yùn)行评雌,控制器主要指令寄存器(IR),數(shù)據(jù)寄存器(DR)树枫,忙標(biāo)志(BF),地址計(jì)數(shù)器(AC)景东,顯示數(shù)據(jù)寄存器(DDRAM)砂轻,字符發(fā)生器(CGROM(內(nèi)置字符庫),CGRAM(自定義字符庫))構(gòu)成斤吐。
Arduino傳輸過來的指令儲存在指令寄存器之中搔涝,內(nèi)部存儲著DDRAM和CGRAM中數(shù)據(jù)顯示的指令代碼或地址信息,然后主控芯片(SoC)讀取數(shù)據(jù)和措,儲存DDRAM中根據(jù)指令代碼執(zhí)行具體的命令庄呈,如字符的位置,光標(biāo)的開關(guān)派阱,字符和光標(biāo)的移位等等诬留。
而Arduino傳輸過來的數(shù)據(jù)則儲存在數(shù)據(jù)寄存器之中,從CGROM中查找到想要顯示的字符的字符碼贫母,送入DDRAM之中文兑,在LCD顯示屏上與DDRAM存儲單元對應(yīng)的規(guī)定位置顯示出該字符。
主控芯片根據(jù)DDRAM中儲存的信息腺劣,從數(shù)字引腳中發(fā)出40SEG的掃描信號外加將信息傳輸?shù)絊egment Driver绿贞,由Segment Driver構(gòu)成的40SEG掃描信號,構(gòu)成LCD顯示屏的列橘原,行由公共電極(ROM)籍铁,原理類似掃描鍵盤,從而控制整個LCD顯示屏的輸出趾断。
I2C并口擴(kuò)展電路
我這里用的是一顆PCF8574T芯片寨辩,其他型號的芯片應(yīng)該工作原理相同,它通過兩條雙向總線(I2C)可以使大多數(shù)的MCU(這里是Arduino)實(shí)現(xiàn)遠(yuǎn)程I/O口擴(kuò)展歼冰,當(dāng)然要以犧牲部分性能為代價靡狞,不過這里我們傳遞的數(shù)據(jù)量小,所以性能的丟失可以忽略不計(jì)隔嫡,該器件包含一個8位準(zhǔn)雙向口(這里用于連接LCD1602甸怕,采用4位接法甘穿,加上RS,RW,E共占用7個I/O,當(dāng)然也可以使用I/O口更多的芯片實(shí)現(xiàn)8位接法)和一個I2C總線接口(這里連接Arduino梢杭,占用2個數(shù)字引腳)温兼。
I2C通訊過程
I2C總線由兩條線組成,一條串行數(shù)據(jù)中線(SDA)和一條串行時鐘總線(SCL)武契,當(dāng)總線空閑時募判,SDA和SCL均保持高電平,這時如果咒唆,SDA電平由高電平變化為低電平届垫,標(biāo)志著起始信號的開始,隨后SCL電平由高變低開始位傳輸全释,SDA開始傳遞數(shù)據(jù)(電平拉高或拉低)装处,SCL隨后由低變高,并保持一段時間浸船,若SDA線上的電平保持穩(wěn)定妄迁,則認(rèn)為SDA是在傳輸數(shù)據(jù)bit,此時SDA上高電平表示1李命,低電平表示0登淘,SLC由高變低,改變數(shù)據(jù)封字,準(zhǔn)備檢測下一個bit形帮,連續(xù)傳遞8個bit(7個數(shù)據(jù)位加一個R/W操作位1bit),等待PCF8574T應(yīng)答周叮,第9個clock,若從IC發(fā)ACK界斜,SDA會被拉低仿耽,若沒有ACK,SDA會被置高各薇,這會引起Master發(fā)生RESTART或STOP流程项贺,一個字節(jié)的寫入完成。電平由低至高變化定義為總線的停止信號峭判。
PCF8574T內(nèi)部結(jié)構(gòu)
由MCU傳遞過來的數(shù)據(jù)存儲在每個引腳的寄存器中开缎,然后寫入LCD1602。完成通過I2C控制LCD1602的過程林螃。
I2C驅(qū)動
********************************************************************/
#include <reg51.h>
#include <intrins.h>
#define uchar unsigned char /*宏定義*/
#define uint unsigned int
#define _Nop() _nop_() /*定義空指令*/
sbit SDA=P3^4; /*模擬I2C數(shù)據(jù)傳送位*/
sbit SCL=P3^5; /*模擬I2C時鐘控制位*/
bit ack; /*應(yīng)答標(biāo)志位*/
/*******************************************************************
起動總線函數(shù)
函數(shù)原型: void Start_I2c();
功能: 啟動I2C總線,即發(fā)送I2C起始條件.
********************************************************************/
void Start_I2c()
{
SDA=1; /*發(fā)送起始條件的數(shù)據(jù)信號*/
_Nop();
SCL=1;
_Nop(); /*起始條件建立時間大于4.7us,延時*/
_Nop();
_Nop();
_Nop();
_Nop(); SDA=0; /*發(fā)送起始信號*/
_Nop(); /* 起始條件鎖定時間大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; /*鉗住I2C總線奕删,準(zhǔn)備發(fā)送或接收數(shù)據(jù) */
_Nop();
_Nop();
}
/*******************************************************************
結(jié)束總線函數(shù)
函數(shù)原型: void Stop_I2c();
功能: 結(jié)束I2C總線,即發(fā)送I2C結(jié)束條件.
********************************************************************/
void Stop_I2c()
{
SDA=0; /*發(fā)送結(jié)束條件的數(shù)據(jù)信號*/
_Nop(); /*發(fā)送結(jié)束條件的時鐘信號*/
SCL=1; /*結(jié)束條件建立時間大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; /*發(fā)送I2C總線結(jié)束信號*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*******************************************************************
字節(jié)數(shù)據(jù)發(fā)送函數(shù)
函數(shù)原型: void SendByte(uchar c);
功能: 將數(shù)據(jù)c發(fā)送出去,可以是地址,也可以是數(shù)據(jù),發(fā)完后等待應(yīng)答,并
對
此狀態(tài)位進(jìn)行操作.(不應(yīng)答或非應(yīng)答都使ack=0)
發(fā)送數(shù)據(jù)正常,ack=1; ack=0表示被控器無應(yīng)答或損壞疗认。
********************************************************************/
void SendByte(uchar c) {
uchar BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) /*要傳送的數(shù)據(jù)長度為8位*/
{
if((c<<BitCnt)&0x80)SDA=1; /*判斷發(fā)送位*/
else SDA=0;
_Nop();
SCL=1; /*置時鐘線為高完残,通知被控器開始接收數(shù)據(jù)位*/
_Nop();
_Nop(); /*保證時鐘高電平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0;
}
_Nop();
_Nop();
SDA=1; /*8位發(fā)送完后釋放數(shù)據(jù)線伏钠,準(zhǔn)備接收應(yīng)答位*/
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)ack=0;
else ack=1; /*判斷是否接收到應(yīng)答信號*/
SCL=0;
_Nop();
_Nop();
}
/*******************************************************************
字節(jié)數(shù)據(jù)接收函數(shù)
函數(shù)原型: uchar RcvByte();
功能: 用來接收從器件傳來的數(shù)據(jù),并判斷總線錯誤(不發(fā)應(yīng)答信號),
發(fā)完后請用應(yīng)答函數(shù)應(yīng)答從機(jī)谨设。
********************************************************************/
uchar RcvByte()
{
uchar retc;
uchar BitCnt;
retc=0;
SDA=1; /*置數(shù)據(jù)線為輸入方式*/
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; /*置時鐘線為低熟掂,準(zhǔn)備接收數(shù)據(jù)位*/
_Nop();
_Nop(); /*時鐘低電平周期大于4.7μs*/
_Nop();
_Nop();
_Nop();
SCL=1; /*置時鐘線為高使數(shù)據(jù)線上數(shù)據(jù)有效*/
_Nop();
_Nop();
retc=retc<<1;
if(SDA==1)retc=retc+1; /*讀數(shù)據(jù)位,接收的數(shù)據(jù)位放入retc中 */
_Nop();
_Nop();
}
SCL=0;
_Nop();
_Nop();
return(retc);
}
/********************************************************************
應(yīng)答子函數(shù)
函數(shù)原型: void Ack_I2c(bit a);
功能: 主控器進(jìn)行應(yīng)答信號(可以是應(yīng)答或非應(yīng)答信號,由位參數(shù)a決定)
********************************************************************/
void Ack_I2c(bit a)
{
if(a==0)SDA=0; /*在此發(fā)出應(yīng)答或非應(yīng)答信號 */
else SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); /*時鐘低電平周期大于4μs*/
_Nop(); _Nop();
_Nop();
SCL=0; /*清時鐘線扎拣,鉗住I2C總線以便繼續(xù)接收*/
_Nop();
_Nop();
}
/*******************************************************************