從今天早上開始看AT24C02的手冊,憑着有些撮的英語水平,24頁的資料,愣是啃了半天,上午的時光就過去了。
AT24C02是一款EEPROM芯片,IIC接口,就是兩條線:SDA與SCL;不過對於單單操縱這款芯片而言,沒有設計到IIC總線協議之中所謂的仲裁。因而,大體看下了芯片手冊,心中就知道大概怎么操作了。
另外,24C02的2K是指2Kbit。一般的話,存儲器都用字節來衡量,所以其實24c02只有256byte。是不是比較小?這256byte又分成了32pages,每頁有8byte。
現在來看看該如何編寫代碼,首先得知道起始與停止條件
所謂起始條件:在SCL高電平期間,SDA的一個下降沿;
所謂停止條件:在SCL高電平期間,SDA的一個上升沿;
當然起始和停止條件與普通的位傳送是不同的,位傳送的時序如圖所示
可以看到,在SCL的高電平期間,SDA是不允許變化的;而只有在SCL的電平期間,SDA才能夠出現變化;
當傳輸完1字節之后,可能需要有一個應答信號。說到這兒,就需要明白幾個概念:發送器、接受器、主器件、從器件。
發送器:在總線上發送數據的器件。
接受器:在總線上接受數據的器件。
主器件:控制信息交互的器件,產生SCL時鍾信息,產生起始與停止條件。
從器件:受從器件控制的器件。
那這兒的應答信號,就是總線上的接受器每接受到一個字節后產生的應答。如圖所示:
需要注意的是,在等待應答信號之前,需要將SDA置高,即釋放掉總線。
24c02寫的方式有兩種:字節寫、頁寫(就是連續寫多個字節)。
掌握了字節寫,對於頁寫就容易的多了。
字節寫的時序如圖所示:
字節寫的時序:首先是起始條件(由MCU產生),接着單片機向總線上傳送器件地址,總線上地址相同的器件會有一個ACK(即應答信息),然后向器件寫入字地址(告訴24c02想把信息寫在那個地址,24c02剛好有256個字節,8bit的字地址信息剛好表示),同樣會有一個應答信息,緊接着需要寫入需要傳送的數據(8位),同理會有一個應答信息。最后,需要主器件產生一個停止條件。Ok,寫字節就結束了,是不是很簡單,頁寫只不過是在這一步沒有發送停止條件,而是接着發數據,這個時候24c02會知道你要寫下一個字節,它會自動的加地址。
再來看看如何讀取,讀取有三種方式:當前地址讀、隨機讀(我得承認這個名字不是太好)、順序讀。
當前地址讀取是指你可以讀取到芯片內部地址計數器(最近一次操作留下的值)加上1的地址上的值。看清楚是加上1地址上的值,不過不太明白有何用處。
接下來的隨機讀取跟是讓我迷惑(剛看手冊時候),隨機的意思不就是產生一個無腦的地址,然后去讀取,這樣的話,讀取有什么意義?不過我想錯了,你可以看看時序圖:
看到了,先發寫命令了,也傳送了字地址,然后沒有發寫的內容。轉而,重新開始發起始條件、器件地址(這次最后一位是讀),然后再接受總線傳送來的數據,此后主控器作為接受方,不用發應答信號,直接發一個停止條件,就ok了。
所以要想指定一個地址,就可以用隨機讀這個方法。不過隨機讀說法不太形象,改為指定地址讀跟容易理解。
順序讀跟頁寫是對應的,只是在隨機讀或當前地址讀后面接着接受數據。
寫了三頁,貌似不是很長。那就貼上代碼,來充長。
個人喜歡寫一個頭文件,然后,再寫一個c文件。
頭文件部分:
#ifndef __hal_24c02_h__
#define __hal_24c02_h__
#include"reg52.h"
#include"hal.h"
#include"datatype.h"
#include"delay.h"
sbit scl=P3^7;//IIC時鍾線
sbit sda=P3^6;//IIC數據線
#define SCL scl
#define SDA sda
//起始條件,SCL高電平時候,SDA的一個下降沿
#define HAL_24C02_START() {SDA=1;SCL=1;SDA=0;}
//停止條件,SCL高電平時候,SDA的一個上升沿
#define HAL_24C02_STOP() {SDA=0;SCL=1;SDA=1;}
#define HAL_24C02_BYTE_ADDR(addr) hal_24c02_send_char(addr)
#define HAL_24C02_ACK() hal_24c02_acknowledge(0)
void hal_24c02_init();
void hal_24c02_send_char(uchar val);
uchar hal_24c02_receive_char();
void hal_24c02_search_device(bit a2,bit a1,bit a0,bit rw);
void hal_24c02_acknowledge(bit mcu);
//學會使用以下兩個函數即可
//需要注意寫完之后,等待5ms之后,再去讀取
void hal_24c02_byte_write(bit a2,bit a1,bit a0,uchar byte_addr,uchar byte_data);
uchar hal_24c02_byte_read(bit a2,bit a1,bit a0,uchar byte_addr);
#endif
C文件部分:
#include"hal_24c02.h"
//初始化,需將時鍾線和數據線拉高
void hal_24c02_init()
{
SCL=1;
SDA=1;
}
//IIC協議要求從高位開始傳送,傳送1字節
void hal_24c02_send_char(uchar val)
{
uchar i;
SCL=0;//此時,SCL被拉低,才允許SDA變化
for(i=0;i<8;i++)
{
if(val&(0x80>>i))
SDA=1;
else
SDA=0;
SCL=1;//拉高SCL,不允許SDA變化
delay_ms(1);
SCL=0;//拉低SCL,開始下1bit傳送
}
//不包括應答脈沖,此時,SCL被拉低,SDA未被釋放
SDA=1;//釋放數據線
//SCL處於低電平;SDA處於高電平
}
uchar hal_24c02_receive_char()
{
//SCL低電平 SDA高電平
uchar tmp=0,i;
for(i=0;i<8;i++)
{
SCL=1;
if(SDA)
tmp=tmp|(0x80>>i);
SCL=0;
}
return tmp;
//SCL低電平 SDA高電平
}
//a2、a1、a0:表示器件地址
//rw:1 代表讀操作;0 代表寫操作
void hal_24c02_search_device(bit a2,bit a1,bit a0,bit rw)
{
uchar tmp;
tmp=0xa0|(a2?0x08:0x00)|(a1?0x04:0x00)|(a0?0x02:0x00)|rw;
hal_24c02_send_char(tmp);
}
//參數mcu=1,表示由主機主動產生應答信號,此時還需要將SDA拉低
//參數mcu=0,表示非主機參數應答信號,此時主機只需要產生SCL脈沖
//返回1:表示有應答;0:無應答
void hal_24c02_acknowledge(bit mcu)
{
//SCL處於低電平;SDA處於高電平
if(mcu) SDA=0;//產生1應答信號
SCL=1;
delay_ms(1);
while(SDA);//等待應答
SCL=0;
if(mcu) SDA=1;//釋放總線
//SCL處於低電平;SDA處於高電平
}
//參數a2,a1,a0:器件地址
//參數byte_addr:字節地址
//參數byte_data:需要寫的數據
void hal_24c02_byte_write(bit a2,bit a1,bit a0,uchar byte_addr,uchar byte_data)
{
HAL_24C02_START()
hal_24c02_search_device(a2,a1,a0,0);
HAL_24C02_ACK();
HAL_24C02_BYTE_ADDR(byte_addr);
HAL_24C02_ACK();
hal_24c02_send_char(byte_data);
HAL_24C02_ACK();
HAL_24C02_STOP()
}
//參數a2 a1 a0為器件地址;
//參數byte_char為需要讀取的字節地址
//返回一個uchar型
uchar hal_24c02_byte_read(bit a2,bit a1,bit a0,uchar byte_addr)
{
uchar val=0;
HAL_24C02_START()
hal_24c02_search_device(a2,a1,a0,0);//偽寫
HAL_24C02_ACK();
HAL_24C02_BYTE_ADDR(byte_addr);//偽寫的目的,就是為了傳送地址
HAL_24C02_ACK();
HAL_24C02_START();
hal_24c02_search_device(a2,a1,a0,1);//真讀
HAL_24C02_ACK();
val=hal_24c02_receive_char();
//NO ACK
HAL_24C02_STOP()
return val;
}