概述
在單片機應(yīng)用系統(tǒng)中,串行通信總線技術(shù)是非常重要的通信手段。常用的串行總線通信方式包括異步串行通信 UART、I2C(Inter IC BUS)、單總線(One WIRE BUS)以及 SPI 總線(Serial Peripheral Interface BUS)等。單片機的串口通信為 UART 的一種,DS18B20 的通信方式為單總線。采用 I2C 總線通信方式的常用器件包括 E2PROM 存儲器件 AT24C01 以及 AD/DA 器件 PCF8951,這章內(nèi)容主要講解 I2C 總線通信工作原理并結(jié)合 AT24C01 進行應(yīng)用試驗。
13.1 I2C 總線通信原理
I2C(Inter-Integrated Circuit)總線是由 PHILIP S 公司開發(fā)的兩線式串行通信總線,由于連接主機以及外圍設(shè)備。兩根數(shù)據(jù)線一個為時鐘線 SCL,另一根為數(shù)據(jù)線 SDA,可實現(xiàn)數(shù)據(jù)的發(fā)送或接收。通常將 I2C 通信速率分為:低速模式 100Kbit/s、快速模式 400Kbit/s 以及高速模式 3.4Mbit/s,I2C 器件為向下兼容模式,一般所用 I2C 器件均支持低速模式。I2C 通信器件典型電路如下圖所示:
如上圖所示,在 I2C 總線上掛載多個外圍器件,總線與電源之間配置了上拉電阻,使所有器件之間形成了“線與”的邏輯關(guān)系,任何一個器件將總線拉低,總線將保持低電平,因此任意一個器件都可以當(dāng)成主設(shè)備或者從設(shè)備。
I2C 通信最底層的時序操作包含四種類型的信號,所用基于 I2C 總線的外圍器件都是在這五種底層信號的基礎(chǔ)上進行數(shù)據(jù)的讀寫,這五種信號分別是:
1) 起始信號;
2. 停止信號;
3. 寫字節(jié)信號;
4. 讀字節(jié)并發(fā)送應(yīng)答信號;
5. 讀字節(jié)并發(fā)送非應(yīng)答信號。
13.1.1 I2C 通信起始、停止信號
起始信號,功能為通知 I2C 器件可以開始進行數(shù)據(jù)操作,操作時序為:當(dāng) SCL 為高電平時,SDA 由高電平向低電平跳變。停止信號,功能為通知 I2C 器件數(shù)據(jù)操作已結(jié)束,操作時序為:當(dāng) SCL 為高電平時,SDA 由低電平向高電平跳變。時序如下圖所示:
I2C 起始信號、停止信號時序 C 語言函數(shù)如下所示:
void Delay_I2C(void)
{//延時函數(shù),設(shè)置傳輸速率
_nop_();
_nop_();
_nop_();
_nop_();
}
//總線起始信號
void Start_I2C(void)
{
//SCL高電平期間,拉低SDA
SCL_I2C = 0;
SDA_I2C = 1;//在SCL低電平期間先將SDA拉高,為起始信號做準備
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SDA_I2C = 0;//拉低SDA,發(fā)送起始信號
Delay_I2C();
SCL_I2C = 0;
}
//總線停止信號
void Stop_I2C(void)
{
//SCL高電平期間,拉高SDA
SCL_I2C = 0;
SDA_I2C = 0;//在SCL低電平期間先將SDA拉低,為停止信號做準備
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SDA_I2C = 1;//拉高SDA,發(fā)送停止信號
Delay_I2C();
SCL_I2C = 0;
}
13.1.2 I2C 寫字節(jié)信號
I2C 寫字節(jié)信號,功能為向總線寫入 1 字節(jié)的數(shù)據(jù),操作時序下圖所示:
在寫入數(shù)據(jù)的過程中,數(shù)據(jù)順序為從高位到低位,最先寫入的數(shù)據(jù)為 bit7,依次到 bit0 共 8 位數(shù)據(jù)。如果接收器件收到了上述 1 字節(jié)的數(shù)據(jù),會在 SCL 的第 9 個周期的高電平期間將 SDA 拉低為“0”,這個第 9 位數(shù)據(jù)稱為應(yīng)答位 ACK,作用為通知主機已經(jīng)收到了 1 字節(jié)的數(shù)據(jù)。因此,在主機程序中通過 ACK 位判斷 1 字節(jié)數(shù)據(jù)是否寫入成功。在寫數(shù)據(jù)的過程中要求,數(shù)據(jù)在 SCL 高電平期間要保持 SDA 數(shù)據(jù)穩(wěn)定,在 SCL 低電平期間,SDA 可由高電平變?yōu)榈碗娖交蛘叩碗娖阶優(yōu)楦唠娖?,如下圖所示。
I2C 寫字節(jié)信號 C 語言函數(shù)代碼如下圖所示:
//I2C寫入字節(jié)dat,返回應(yīng)答信號
bit Wr_I2C(unsigned char dat)
{
bit ack; //存儲應(yīng)答位
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
if((mask & dat)==0) SDA_I2C=0;
else SDA_I2C=1;
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0; //完成一位的傳送
}
SDA_I2C=1; //主機釋放總線
Delay_I2C();
SCL_I2C = 1;
ack = SDA_I2C;//獲取應(yīng)答位
Delay_I2C();
SCL_I2C = 0;
return ack; //返回0寫入成功,返回1寫入失敗
}
13.1.3 I2C 讀字節(jié)并發(fā)送應(yīng)答信號
I2C 讀字節(jié)并發(fā)送應(yīng)答信號時序與圖 13-4 基本相同,只不過 bit7-bit0 由 I2C 從器件給出,在 SCL 高電平期間主機將數(shù)據(jù)讀取,第 9 位應(yīng)答信號 ACK 由主機給出,ACK 為“0”表示主機后續(xù)還要繼續(xù)讀取數(shù)據(jù),為“1”時主機不再讀取后續(xù)數(shù)據(jù),可以結(jié)束通信。C 語言函數(shù)如下圖所示:
//I2C讀操作,并發(fā)送應(yīng)答信號
unsigned char RdACK_I2C(void)
{
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
unsigned char dat;
SDA_I2C=1;//確保主機釋放SDA
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
Delay_I2C();
SCL_I2C = 1;
if(SDA_I2C==0) dat &= ~mask;//為0時,dat對應(yīng)位清零
else dat |= mask;//否則置1
Delay_I2C();
SCL_I2C = 0;
}
SDA_I2C=0; //8位數(shù)據(jù)傳送完后,拉低SDA發(fā)送應(yīng)答信號
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0;
return dat;
}
13.1.4 I2C 讀字節(jié)并發(fā)送非應(yīng)答信號
與讀字節(jié)并發(fā)送應(yīng)答信號相同,唯一的區(qū)別為主機發(fā)出非應(yīng)答信號,即 ACK=1,主機不再讀取后續(xù)數(shù)據(jù),可以結(jié)束通信。C 語言函數(shù)如下圖所示:
//I2C讀操作,并發(fā)送非應(yīng)答信號
unsigned char RdNAK_I2C(void)
{
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
unsigned char dat;
SDA_I2C=1;//確保主機釋放SDA
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
Delay_I2C();
SCL_I2C = 1;
if(SDA_I2C==0) dat &= ~mask;//為0時,dat對應(yīng)位清零
else dat |= mask;//否則置1
Delay_I2C();
SCL_I2C = 0;
}
SDA_I2C=1; //8位數(shù)據(jù)傳送完后,拉高SDA發(fā)送非應(yīng)答信號
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0;
return dat;
}
將上述 5 個底層 I2C 總線操作函數(shù)放到文件“Drive_I2C.c”以及“Drive_I2C.h”。
“Drive_I2C.h”完整代碼如下:
#ifndef __I2C_H__
#define __I2C_H__
extern void Start_I2C(void); //起始信號
extern void Stop_I2C(void); //停止信號
extern unsigned char RdACK_I2C(void); //讀字節(jié)并發(fā)送應(yīng)答信號
extern unsigned char RdNAK_I2C(void); //讀字節(jié)并發(fā)送非應(yīng)答信號
extern bit Wr_I2C(unsigned char dat); //讀字節(jié)信號
#endif
“Drive_I2C.c”完整代碼如下:
#include< reg52.h >
#include< intrins.h >
sbit SCL_I2C = P2^0;//總線管腳定義
sbit SDA_I2C = P2^1;
void Delay_I2C(void)
{//延時函數(shù),設(shè)置傳輸速率
_nop_();
_nop_();
_nop_();
_nop_();
}
//總線起始信號
void Start_I2C(void)
{
//SCL高電平期間,拉低SDA
SCL_I2C = 0;
SDA_I2C = 1;//在SCL低電平期間先將SDA拉高,為起始信號做準備
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SDA_I2C = 0;//拉低SDA,發(fā)送起始信號
Delay_I2C();
SCL_I2C = 0;
}
//總線停止信號
void Stop_I2C(void)
{
//SCL高電平期間,拉高SDA
SCL_I2C = 0;
SDA_I2C = 0;//在SCL低電平期間先將SDA拉低,為停止信號做準備
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SDA_I2C = 1;//拉高SDA,發(fā)送停止信號
Delay_I2C();
SCL_I2C = 0;
}
//I2C寫入字節(jié)dat,返回應(yīng)答信號
bit Wr_I2C(unsigned char dat)
{
bit ack; //存儲應(yīng)答位
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
if((mask & dat)==0) SDA_I2C=0;
else SDA_I2C=1;
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0; //完成一位的傳送
}
SDA_I2C=1; //主機釋放總線
Delay_I2C();
SCL_I2C = 1;
ack = SDA_I2C;//獲取應(yīng)答位
Delay_I2C();
SCL_I2C = 0;
return ack; //返回0寫入成功,返回1寫入失敗
}
//I2C讀操作,并發(fā)送非應(yīng)答信號
unsigned char RdNAK_I2C(void)
{
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
unsigned char dat;
SDA_I2C=1;//確保主機釋放SDA
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
Delay_I2C();
SCL_I2C = 1;
if(SDA_I2C==0) dat &= ~mask;//為0時,dat對應(yīng)位清零
else dat |= mask;//否則置1
Delay_I2C();
SCL_I2C = 0;
}
SDA_I2C=1; //8位數(shù)據(jù)傳送完后,拉高SDA發(fā)送非應(yīng)答信號
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0;
return dat;
}
//I2C讀操作,并發(fā)送應(yīng)答信號
unsigned char RdACK_I2C(void)
{
unsigned char mask; //探測字節(jié)內(nèi)某一位值的掩碼變量
unsigned char dat;
SDA_I2C=1;//確保主機釋放SDA
for(mask=0x80;mask!=0;mask >>=1)//從高位依次到低位
{
Delay_I2C();
SCL_I2C = 1;
if(SDA_I2C==0) dat &= ~mask;//為0時,dat對應(yīng)位清零
else dat |= mask;//否則置1
Delay_I2C();
SCL_I2C = 0;
}
SDA_I2C=0; //8位數(shù)據(jù)傳送完后,拉低SDA發(fā)送應(yīng)答信號
Delay_I2C();
SCL_I2C = 1;
Delay_I2C();
SCL_I2C = 0;
return dat;
}
13.1.5 I2C 一次通信時序
所有基于 I2C 總線通信設(shè)備都是以上面 5 條最底層操作為基礎(chǔ)的,完成一次完整的 I2C 通信時序如下圖所示:
如圖所示,一次完整的 I2C 總線通信至少包含起始信號 、一次字節(jié)讀或?qū)懀蛘叨啻巫x或?qū)懀约巴V剐盘?。在起始信號與停止信號之間讀或?qū)懙木唧w內(nèi)容與 I2C 器件本身的上層通信協(xié)議有關(guān)。接下來我們將講解基于 I2C 總線通信技術(shù)的 E2PROM 存儲器 AT24C01 的上層通信協(xié)議以及具體使用實例。
13.2 E2PROM 存儲器 AT24C01 應(yīng)用
AT24C01 是 Atmel 公司生產(chǎn)的一款 E2PROM 數(shù)據(jù)存儲器,容量為 128 字節(jié),具有掉電不丟失的功能。在單片機中也有存儲器,一種為數(shù)據(jù)存儲器 RAM,一種為程序存儲器,在掉電的情況下 RAM 內(nèi)的數(shù)據(jù)會丟失,而程序存儲器一般不支持在線編程。然而在很多應(yīng)用場合我們希望把運行過程中重要的數(shù)據(jù)存儲下來,而在掉電的情況下數(shù)據(jù)不丟失。AT24C01 能滿足這樣的要求,它與單片之間通過 I2C 總線通信實現(xiàn)信息的交換。
下面介紹一下芯片管腳,A0~A2 為地址輸入引腳,SDA、SCL 為 I2C 總線接口,VCC,GND 分別為電源和地,WP 為寫保護管腳,當(dāng) WP 接高電平時,禁止外部對它進行寫數(shù)據(jù),只能讀取它的數(shù)據(jù)。如上圖所示,將 WP 接地,單片機即可對它進行讀也可以寫。
13.2.1 AT24C01 單字節(jié)寫通信
AT24C01 寫字節(jié)如下圖所示:
如上圖所示,AT24C01 的寫字節(jié)時序步驟如下:
1)起始信號;
2. 寫器件地址;
3. 寫存儲地址;
4. 寫存儲數(shù)據(jù);
5. 停止信號。
當(dāng) I2C 總線上掛載多個從器件時,單片機通過器件地址來區(qū)別器件,那在我們開發(fā)板上的 AT24C01 的器件地址是多少呢?AT24C01 器件地址如下所示:
如上圖所示,地址的高 4 位為“1010”固定值,即 0x5,后四位分別由 A2-A0、R/W 決定,在 RY-51 開發(fā)板上將器件的 A2-A0 都接地,如原理圖所示,因此為“000”,最后一位為讀寫方向位,當(dāng) R/W=0 時,表示我們接下來寫數(shù)據(jù),當(dāng) R/W=1 時,表示我們接下來要讀數(shù)據(jù)。很顯然我們這里是要寫數(shù)據(jù),因此 R/W=0。合并起來,要寫的地址為 0x50。第 3 步為寫存儲器地址,AT24C01 總共有 128 字節(jié)的存儲器,它的地址分別為 0x00~0x80,因此可以選擇任一地址存入數(shù)據(jù)。第 4 步為寫存儲數(shù)據(jù),即為你寫存儲的 8 位數(shù)據(jù)??偨Y(jié)上述,往 AT24C01 寫入一個字節(jié)數(shù)據(jù)函數(shù)如下所示:
//往AT24C01地址addr寫入單字節(jié)數(shù)據(jù)dat
void WrByte_AT24C01(unsigned char addr,unsigned char dat)
{
Start_I2C();
Wr_I2C(0x50< 1);//通知地址50的器件,接下來寫操作
Wr_I2C(addr); //寫入要操作的地址addr
Wr_I2C(dat); //向addr寫入數(shù)據(jù)dat
Stop_I2C();
}
13.2.2 AT24C01 單字節(jié)讀通信
隨機讀取 AT24C01 單字節(jié)通信如下圖所示:
隨機讀取單字節(jié)數(shù)據(jù)通信協(xié)議如上圖所示,步驟如下:
1) 起始信號
2. 寫器件地址,方向為寫;
3. 寫存儲器地址 addr;
4. 起始信號;
5. 寫器件地址,方向為讀;
6. 讀單字節(jié)數(shù)據(jù),并發(fā)送非應(yīng)答信號
7. 停止信號。
第 1 步至第 3 步為告訴 AT24C01 我將從地址 addr 處讀取數(shù)據(jù),第 4 步到第 6 步為讀取存儲器地址 addr 處的數(shù)據(jù),并告訴 AT24C01 后面不再繼續(xù)讀數(shù)據(jù)了,第 7 步結(jié)束本次通信。具體函數(shù)代碼如下圖所示:
//讀取AT24C01存儲地址addr處的數(shù)據(jù)
unsigned char RdByte_AT24C01(unsigned char addr)
{
unsigned char dat;
Start_I2C();
Wr_I2C(0x50< 1);//通知地址50的器件,接下來寫操作
Wr_I2C(addr); //寫入要操作的地址addr
Start_I2C();
Wr_I2C((0x50< 1)|0x01);//通知地址50的器件,接下來讀操作
dat = RdNAK_I2C();//從地址addr讀出數(shù)據(jù),讀出數(shù)據(jù)后不應(yīng)答E2Prom
Stop_I2C();
return dat;
}
到這里我們便完成了對 AT24C01 單字節(jié)的讀、寫通信函數(shù),按照慣例我們將函數(shù)封裝到“Drive_AT 24C01.c”、“Drive_AT 24C01.h”,如下。
Drive_AT 24C01.h 代碼:
#ifndef __AT24C01_H__
#define __AT24C02_H__
extern void WrByte_AT24C01(unsigned char addr,unsigned char dat);//寫單字節(jié)
extern unsigned char RdByte_AT24C01(unsigned char addr); //讀單字節(jié)
extern void WrStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len);//寫多字節(jié)
extern void RdStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len);//讀多字節(jié)
#endif
Drive_AT 24C01.c 代碼:
#include< reg52.h >
#include"Drive_I2C.h"
//往AT24C01地址addr寫入單字節(jié)數(shù)據(jù)dat
void WrByte_AT24C01(unsigned char addr,unsigned char dat)
{
Start_I2C();
Wr_I2C(0x50< 1);//通知地址50的器件,接下來寫操作
Wr_I2C(addr); //寫入要操作的地址addr
Wr_I2C(dat); //向addr寫入數(shù)據(jù)dat
Stop_I2C();
}
//讀取AT24C01存儲地址addr處的數(shù)據(jù)
unsigned char RdByte_AT24C01(unsigned char addr)
{
unsigned char dat;
Start_I2C();
Wr_I2C(0x50< 1);//通知地址50的器件,接下來寫操作
Wr_I2C(addr); //寫入要操作的地址addr
Start_I2C();
Wr_I2C((0x50< 1)|0x01);//通知地址50的器件,接下來讀操作
dat = RdNAK_I2C();//從地址addr讀出數(shù)據(jù),讀出數(shù)據(jù)后不應(yīng)答E2Prom
Stop_I2C();
return dat;
}
//多字節(jié)寫
void WrStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len)
{
while(len > 0)//檢測上一次是否完成所以數(shù)據(jù)寫操作
{
while(1)
{//循環(huán)檢測器件應(yīng)答信號
Start_I2C();
if(0 == Wr_I2C(0x50< 1)) break;//收到應(yīng)答,跳出循環(huán)
Stop_I2C();//沒收到應(yīng)答,發(fā)送停止信號,繼續(xù)循環(huán)檢測
}
Wr_I2C(addr); //寫入要操作的初始地址addr
while(len > 0)
{
Wr_I2C(*str++);//寫入一個字節(jié),并將字符串指針指向下一個字符
len--;//字符數(shù)減1
addr++;//存儲地址加1
if(0 == (addr & 0x07))//檢測是否到達了下一頁的起始地址,
break; //即上一個字節(jié)已經(jīng)寫到頁的最后邊界了
//跳出停止繼續(xù)寫,每頁的起始地址后3位為0
//因此判斷addr后3為是否為0即可
}
Stop_I2C();
}
}
//多字節(jié)讀
void RdStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len)
{
while(1)
{//循環(huán)檢測器件應(yīng)答信號
Start_I2C();
if(0 == Wr_I2C(0x50< 1)) break;//收到應(yīng)答,跳出循環(huán)
Stop_I2C();//沒收到應(yīng)答,發(fā)送停止信號,繼續(xù)循環(huán)檢測
}
Wr_I2C(addr); //寫入要操作的初始地址addr
Start_I2C();//再次發(fā)送起始信號
Wr_I2C((0x50< 1)|0x01);//通知地址50的器件,接下來讀操作
while(len > 1)
{
*str++ = RdACK_I2C();//讀字節(jié)并應(yīng)答
len--;
}
*str = RdNAK_I2C();//最后一個字節(jié),讀字節(jié)并非應(yīng)答
Stop_I2C();
}
//尋址AT24C01
bit Addressing_AT24C01(unsigned char addr)
{
bit ack;
Start_I2C();
ack = Wr_I2C(addr< 1);
Stop_I2C();
return ack;
}
如上所示,所有的代碼都是以 I2C 通信的 5 個底層函數(shù)為基礎(chǔ)的,因此我們需要將“Drive_I2C.h”文件包含到代碼中。
13.2.3 AT24C01 單字節(jié)讀寫應(yīng)用
下面我們建立一個工程,寫一個實例來展示單片機對 AT24C01 的讀寫應(yīng)用。應(yīng)用的功能為首先往 AT24C01 存儲器地址 0x08 處寫入數(shù)據(jù) 110,然后從該處把數(shù)據(jù)讀出來顯示在 1602 液晶上,以此來驗證讀寫操作的正確性,主文件“MainAT24C01.c”代碼如下所示:
#include < reg52.h >
#include"Drive_AT24C01.h" //包含AT24C01頭文件
#include"Drive_1602.h"
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^7;//數(shù)碼管段選、位選引腳定義
sbit WE = P2^6;
uchar str[10]=0;
void main()
{
uchar dat=0;
Init_1602();
P0 = 0xff;//關(guān)閉所有數(shù)碼管
WE = 1;
WE = 0;
//往AT24C01存儲器地址0x08處寫入數(shù)字110
WrByte_AT24C01(0x08,110);
Disp_1602_str(1,2,"AT24C02 test!");
//讀取AT24C01存儲器地址0x08處的數(shù)據(jù)
dat = RdByte_AT24C01(0x08);
str[0]=dat/100+'0';
str[1]=dat%100/10+'0';
str[2]=dat%10+'0';
//將數(shù)據(jù)顯示在1602的第2行第6列處
Disp_1602_str(2,6,str);
while(1);
}
將工程編譯后,下載到開發(fā)板驗證功能的正確性。
AT24C01 對寫時序有一個特殊的要求,當(dāng)完成一次數(shù)據(jù)通信后,需要延遲 tWR 才能開始下一次起始信號如下圖所示,tWR 為 AT24C01 內(nèi)部處理數(shù)據(jù)時間,查詢 AT24C01 數(shù)據(jù)手冊可知為 10ms。因此,我們在主程序代碼第 19 行與第 22 行之間插入了第 20 行代碼達到延時的目的,我們可以將第 20 行代碼挪到后面測試一下效果。
13.2.4 AT24C01 多字節(jié)寫通信
根據(jù)上面的介紹,大家很容易發(fā)現(xiàn)每隔 10ms 才能進行一次正常的寫數(shù)據(jù)操作是非常浪費時間的,尤其是在進行多個字節(jié)寫操作的時候。AT24C01 提供了另外一種多字節(jié)的寫模式,為頁操作模式。首先介紹一下 AT24C01 的內(nèi)部存儲器的分頁結(jié)構(gòu)。AT24C01 總共有 128 個字節(jié)的存儲空間,總共分為 16 頁,每一頁總有 8 各字節(jié)。第一頁的地址范圍為 0x00~0x07,依次往下均分為 16 頁。頁操作模式通信時序如下圖所示:
如上如所示,首先發(fā)送起始信號、寫器件地址、寫起始地址,緊接著寫入多個字節(jié),寫完一個字節(jié),器件內(nèi)部會將地址自動加 1,最后結(jié)束信號。這里需要注意的是,連續(xù)寫的多個字節(jié)必須在同一頁內(nèi),不能進行跨頁連續(xù)讀寫,因此一次通信周期內(nèi)最多可以寫入 8 個字節(jié)的數(shù)據(jù)。如果我們需要寫的數(shù)據(jù)很多,而一頁寫不下怎么辦?首先,啟動頁寫通信,在寫的過程中判斷是否要寫到頁的邊界了,當(dāng)?shù)竭_頁邊界后停止該次頁寫通信,再重新發(fā)起頁寫通信將剩余的數(shù)據(jù)寫入,這樣便實現(xiàn)了任意個字節(jié)的寫入,具體函數(shù)如下圖所示。其中,str 為需要寫入的字符串,addr 為寫入 AT24C01 的起始地址,len 為寫入字符個數(shù)。多字節(jié)寫數(shù)據(jù)代碼如下:
//多字節(jié)寫
void WrStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len)
{
while(len > 0)//檢測上一次是否完成所以數(shù)據(jù)寫操作
{
while(1)
{//循環(huán)檢測器件應(yīng)答信號
Start_I2C();
if(0 == Wr_I2C(0x50< 1)) break;//收到應(yīng)答,跳出循環(huán)
Stop_I2C();//沒收到應(yīng)答,發(fā)送停止信號,繼續(xù)循環(huán)檢測
}
Wr_I2C(addr); //寫入要操作的初始地址addr
while(len > 0)
{
Wr_I2C(*str++);//寫入一個字節(jié),并將字符串指針指向下一個字符
len--;//字符數(shù)減1
addr++;//存儲地址加1
if(0 == (addr & 0x07))//檢測是否到達了下一頁的起始地址,
break; //即上一個字節(jié)已經(jīng)寫到頁的最后邊界了
//跳出停止繼續(xù)寫,每頁的起始地址后3位為0
//因此判斷addr后3為是否為0即可
}
Stop_I2C();
}
}
13.2.5 AT24C01 多字節(jié)讀通信
多字節(jié)讀時序如下圖所示,與單字節(jié)類似,只是在第一個數(shù)據(jù)緊接著著讀取多個數(shù)據(jù),這里要注意的是只有最后一個字節(jié)的數(shù)據(jù)發(fā)送非應(yīng)答信號,前面的數(shù)據(jù)均發(fā)送應(yīng)答信號,這個也很好理解,應(yīng)為我們都到最后一個字節(jié)時要告訴 AT24C01 我們不再繼續(xù)讀數(shù)據(jù)了,因此發(fā)送非應(yīng)答信號。
多字節(jié)讀取函數(shù)代碼如下圖所示:
//多字節(jié)讀
void RdStr_AT24C01(unsigned char *str,unsigned char addr,unsigned char len)
{
while(1)
{//循環(huán)檢測器件應(yīng)答信號
Start_I2C();
if(0 == Wr_I2C(0x50< 1)) break;//收到應(yīng)答,跳出循環(huán)
Stop_I2C();//沒收到應(yīng)答,發(fā)送停止信號,繼續(xù)循環(huán)檢測
}
Wr_I2C(addr); //寫入要操作的初始地址addr
Start_I2C();//再次發(fā)送起始信號
Wr_I2C((0x50< 1)|0x01);//通知地址50的器件,接下來讀操作
while(len > 1)
{
*str++ = RdACK_I2C();//讀字節(jié)并應(yīng)答
len--;
}
*str = RdNAK_I2C();//最后一個字節(jié),讀字節(jié)并非應(yīng)答
Stop_I2C();
}
將上述的多字節(jié)寫、多字節(jié)讀取函數(shù)添加到驅(qū)動文件“Drive_AT 24C01.c”、“Drive_AT 24C01.h”中,后續(xù)應(yīng)用中只需要將文件添加到項目中,調(diào)用相關(guān)的函數(shù)就可以了。
13.2.6 AT24C01 多字節(jié)讀寫應(yīng)用
本小節(jié)實現(xiàn) AT24C01 多字節(jié)的讀寫應(yīng)用,先向 AT24C01 連續(xù)寫入多字節(jié)數(shù)據(jù),然后將數(shù)據(jù)讀出顯示在 1602 液晶顯示模塊上,驗證讀寫的正確性。主程序代碼如下所示:
#include < reg52.h >
#include"Drive_AT24C01.h" //包含AT24C01頭文件
#include"Drive_1602.h"
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^7;//數(shù)碼管段選、位選引腳定義
sbit WE = P2^6;
uchar str1[]="AT24c01 Wr Str!";
uchar str2[20];
void main()
{
uchar dat=0;
P0 = 0;//關(guān)閉所有數(shù)碼管
WE = 1;
WE = 0;
Init_1602();//1602初始化
WrStr_AT24C01(str1,0x05,16);//寫入16個字節(jié)
RdStr_AT24C01(str2,0x05,16);//讀取16個字節(jié)
Disp_1602_str(1,1,str2); //將數(shù)據(jù)從第一行第一列開始顯示
while(1);
}
如代碼所示,從地址 0x05 開始寫入 16 個字節(jié)的數(shù)據(jù),由于 AT24C01 為 8 個字節(jié)為一頁,因此本次為橫跨了 3 頁的寫操作,有效的驗證的多字節(jié)的寫操作。
13.3 本章小結(jié)
本章詳細介紹了I2C總線的通信原理,以及驅(qū)動函數(shù)的編寫,AT24C01芯片的使用。
-
單片機
+關(guān)注
關(guān)注
6030文章
44489瀏覽量
631975 -
uart
+關(guān)注
關(guān)注
22文章
1219瀏覽量
101118 -
I2C總線
+關(guān)注
關(guān)注
8文章
386瀏覽量
60791 -
總線通信
+關(guān)注
關(guān)注
0文章
48瀏覽量
11820 -
AT24C01
+關(guān)注
關(guān)注
0文章
3瀏覽量
5242
發(fā)布評論請先 登錄
相關(guān)推薦
評論