1.淺釋E2Write函數(shù)
宋老師的例程lesson14_3和lesson14_4里的“E2Write(unsigned char *buf, unsigned char addr, unsigned char len)”書寫內(nèi)容是不一樣的,lesson14_4的E2Write函數(shù)比lesson14_3的E2Write函數(shù)運行高效,lesson14_3的E2Write函數(shù)是每寫入一個字節(jié)就要經(jīng)歷起始信號,停止信號,寫入下一個字節(jié)又要把這些步驟經(jīng)歷一遍。而lesson14_4的E2Write函數(shù)支持EEPROM頁寫入(參考《手把手教你學(xué)51單片機》文檔14.3.3節(jié)),所以在建立起文件時我們選用lesson14_4的E2Write函數(shù)。
大家新建兩個文件“iic.c”和“iic.h”,我們把IIC的相關(guān)函數(shù)和EEPROM的相關(guān)函數(shù)都合成在“iic.c”這個單獨文件里。
2.iic.c的代碼
#include <reg52.h> #include <iic.h> #include <intrins.h> #define I2CDelay() {_nop_();_nop_();_nop_();_nop_();} /* 產(chǎn)生總線起始信號 */ void I2CStart() { I2C_SDA = 1; //首先確保SDA、SCL都是高電平 I2C_SCL = 1; I2CDelay(); I2C_SDA = 0; //先拉低SDA I2CDelay(); I2C_SCL = 0; //再拉低SCL } /* 產(chǎn)生總線停止信號 */ void I2CStop() { I2C_SCL = 0; //首先確保SDA、SCL都是低電平 I2C_SDA = 0; I2CDelay(); I2C_SCL = 1; //先拉高SCL I2CDelay(); I2C_SDA = 1; //再拉高SDA I2CDelay(); } /* I2C總線寫操作,dat-待寫入字節(jié),返回值-從機應(yīng)答位的值 */ unsigned char I2CWrite(unsigned char dat) { unsigned char 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ū)懭氤晒? } /* I2C總線讀操作,并發(fā)送應(yīng)答或者非應(yīng)答信號,返回值-讀到的字節(jié) */ unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack) { 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 = nak_or_ack; //8位數(shù)據(jù)發(fā)送完后,傳入的參數(shù)NAK_OR_ACK決定是否應(yīng)答,為1不應(yīng)答,為0應(yīng)答 I2CDelay(); I2C_SCL = 1; //拉高SCL I2CDelay(); I2C_SCL = 0; //再拉低SCL完成非應(yīng)答位,并保持住總線 return dat; } /* E2讀取函數(shù),buf-數(shù)據(jù)接收指針,addr-E2中的起始地址,len-讀取長度 */ void E2Read(unsigned char *buf, unsigned char addr, unsigned char len) { do { //用尋址操作查詢當前是否可進行讀寫操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進行下一次查詢 { break; } I2CStop(); } while(1); I2CWrite(addr); //寫入起始地址 I2CStart(); //發(fā)送重復(fù)啟動信號 I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作 while (len > 1) //連續(xù)讀取len-1個字節(jié) { *buf++ = I2CReadNAK_OR_ACK(0); //最后字節(jié)之前為讀取操作+應(yīng)答 len--; } *buf = I2CReadNAK_OR_ACK(1); //最后一個字節(jié)為讀取操作+非應(yīng)答 I2CStop(); } /* E2寫入函數(shù),buf-源數(shù)據(jù)指針,addr-E2中的起始地址,len-寫入長度 */ void E2Write(unsigned char *buf, unsigned char addr, unsigned char len) { while (len > 0) { //等待上次寫入操作完成 do { //用尋址操作查詢當前是否可進行讀寫操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進行下一次查詢 { break; } I2CStop(); } while(1); //按頁寫模式連續(xù)寫入字節(jié) I2CWrite(addr); //寫入起始地址 while (len > 0) { I2CWrite(*buf++); //寫入一個字節(jié)數(shù)據(jù) len--; //待寫入長度計數(shù)遞減 addr++; //E2地址遞增 if ((addr&0x07) == 0) //檢查地址是否到達頁邊界,24C02每頁8字節(jié), { //所以檢測低3位是否為零即可 break; //到達頁邊界時,跳出循環(huán),結(jié)束本次寫操作 } } I2CStop(); } }
3.iic.h的代碼
#ifndef __IIC_H__ #define __IIC_H__ sbit I2C_SCL = P3^7; sbit I2C_SDA = P3^6; void I2CStart();//產(chǎn)生總線起始信號 void I2CStop(); //產(chǎn)生總線停止信號 unsigned char I2CWrite(unsigned char dat);//I2C總線寫操作,dat-待寫入字節(jié),返回值-從機應(yīng)答位的值 unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack);//I2C總線讀操作,并發(fā)送應(yīng)答或者非應(yīng)答信號,返回值-讀到的字節(jié) void E2Read(unsigned char *buf, unsigned char addr, unsigned char len); //E2讀取函數(shù),buf-數(shù)據(jù)接收指針,addr-E2中的起始地址,len-讀取長度 void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);//E2寫入函數(shù),buf-源數(shù)據(jù)指針,addr-E2中的起始地址,len-寫入長度 #endif
4.部分代碼的修改
“unsigned char I2CReadACK()”和“unsigned char I2CReadNAK()”這里我們合成了一個函數(shù)為
“unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack)”,利用參數(shù)的傳遞決定是否產(chǎn)生應(yīng)答。
那么在main.c中,幾乎也是只需要出現(xiàn)EEPROM的寫函數(shù)“E2Write()”和讀函數(shù)“E2Read()”而已了。
記得把“iic”添加到工程文件中
5.main.c測試代碼
#include <reg52.h> #include <function.h>//詳見第六章第8講 #include <lcd.h> //詳見第十一章第3講 #include <iic.h> void main() { unsigned char buf[]={"We can learn SCM well!"};//我們可以學(xué)好單片機 unsigned char str[sizeof(buf)]; //數(shù)組長度與buf的一樣 InitLcd1602(); //初始化液晶 E2Write(buf,0x8E,sizeof(buf));//把buf數(shù)組里面的內(nèi)容在EEPROM中從地址0x8E開始寫,直到把數(shù)組里的內(nèi)容全部寫完進去,在EEPROM中保存起來 delay_ms(1000); //過1秒之后再讀出里面的內(nèi)容顯示在液晶屏上 E2Read(str,0x8E,sizeof(buf)); //用另一個數(shù)組存取從EEPROM中讀出的內(nèi)容 LcdShowStr_len(0, 0,str, 16); LcdShowStr(0, 1, str+16+1); while(1); }
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點擊了解:
一點編程也不會寫的:零基礎(chǔ)C語言學(xué)練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進階課程
從零到寫出一個爬蟲的Python編程課程
只會語法寫不出代碼?手把手帶你寫100個編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程
手把手講解近五年真題的藍橋杯輔導(dǎo)課程