1、24C02簡介
24C02是一個2Kbit的串行EEPROM存儲芯片,可存儲256個字節數據。工作電壓范圍為1.8V到6.0V,具有低功耗CMOS技術,自定時擦寫周期,1000000次編程/擦除周期,可保存數據100年。24C02有一個16字節的頁寫緩沖器和一個寫保護功能。通過I2C總線通訊讀寫芯片數據,通訊時鍾頻率可達400KHz。
可以通過存儲IC的型號來計算芯片的存儲容量是多大,比如24C02后面的02表示的是可存儲2Kbit的數據,轉換為字節的存儲量為2*1024/8 = 256byte;有比如24C04后面的04表示的是可存儲4Kbit的數據,轉換為字節的儲存量為2*1024/8 = 512byte;以此來類推其它型號的存儲空間。
24C02的管腳圖如下:
VCC和VSS是芯片的電源和地,電壓的工作范圍為:+1.8V~+6.0V。
A0、A1、A2是IC的地址選擇腳。
WP是寫保護使能腳。
SCL是I2C通訊時鍾引腳。
SDA是I2C通訊數據引腳。
2、24C02的設備地址和寫保護功能
I2C主機在與24C02通訊時,需要發送一個設備地址進行尋址,在I2C總線上,每一個從機設備的地址都是唯一的。
24C02的設備地址包含兩部分,第一部分是bit7~bit4是固定的“1010”,第二部分bit3~bit1位由A2、A1、A0組成。主機在與24C02進行通訊時,除了發送設備地址還需要發送數據的讀寫方向位R/W,24C02的是設備地址與R/W位組成了一個字節的數據。如下圖:
上圖列出了幾個存儲IC的設備地址與R/W位組成的字節。由圖中可以看到,存儲IC地址的bit7~bit4位固定為“1010”;bit3~bit1位由A2、A1、A0引腳的電平狀態決定,如果Ax接的是電源(高電平),那么Ax=1,如果Ax接的是地,那么Ax=0,即由A2、A1、A0可以組合成8種設備地址,也就是說在同一個I2C總線上可以同時掛載8個24C02芯片。一般如果I2C總線上只有一片24C02芯片的話,A2、A1、A0引腳都接到地。
由於24C02只有256個字節的存儲空間,所以只需要1個字節就可以尋址完24C02的存儲空間,但是無法尋址完更大容量的存儲IC,比如24C04的存儲容量是512字節,需要9個bit的地址位才能尋址完。由上圖可以看到,24C04的設備地址內是沒有A0參數的,被a8代替了,這個a8就是24C04的第9個bit的地址位,也就是說24C04的A0引腳是不起作用的,這樣也就造成了在I2C總線上只能同時掛載4個24C04芯片。其它存儲器如24C08、24C16也可以這么類推。
24C02的WP引腳是寫保護引腳,當WP引腳接高電平的時,24C02只能進行讀取操作,不能進行寫操作。只有當WP引腳懸空或接低電平時,24C02才能進行寫操作。
3、24C02數據讀取操作
在這里只是對24C02的讀寫進行一些說明和一些注意的實現,不會涉及具體的程序代碼,只是進行代碼概述,工程代碼已經上傳到個人GitHub中,感興趣的可以去GitHub中下載查看,GitHub代碼地址如下:
https://github.com/h1019384803/STM32F103ZET6_I2C.git。這是一個使用STM32F103ZET6的IO模擬I2C操作24C02的工程。
MCU通過使用I2C讀取24C02任意存儲空間地址內的數據,代碼如下:
1 uint8_t AT24CXX_READ_ONE_BYTE(uint16_t address) 2 { 3 uint8_t dat; 4
5 I2C_START(); 6 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 7 if(AT24CXX_ERR != 0)//沒有響應直接退出
8 { 9 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF); 10 if(AT24CXX_ERR != 0) 11 { 12 I2C_START(); 13
14 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_READ_CMD); 15 if(AT24CXX_ERR != 0) 16 { 17 dat = I2C_READ_BYTE(0); 18 I2C_STOP(); 19 } 20 } 21 } 22
23 return dat; 24 }
第5行主機產生一個I2C起始信號,第6行發送設備地址和寫數據位給24C02,第9行是發送需要讀取的地址給24C02,第12行主機產生一個重復起始信號,第14行設備地址和讀數據位給24C02,第17行是讀取24C02相應地址存儲的數據。第18行是主機產生I2C結束信號。
在上面的程序代碼中,AT24CXX_ERR是用來獲取24C02的應答信號,如果主機與24C02的通訊正常,主機每發送一個字節給24C02,24C02都會反饋一個應答信號給主機,如果24C02沒有反饋應答信號,那么說明24C02正在進行其它操作或者通訊異常導致無法通訊,主機會產生一個結束信號來結束操作。在I2C_WRITE_BYTE()函數內部有一個等待應答信號的操作,如果沒有收到應答信號,在I2C_WRITE_BYTE()函數內會產生一個停止信號來結束當前操作。AT24CXX_ERR用來判斷接下來的操作是否執行,如果AT24CXX_ERR=0說明沒有收到應答信號,直接退本次讀取操作;如果AT24CXX_ERR!=0說明有收到應答信號,繼續讀取操作。
24C02內部有一個地址計數器,主機發送要讀寫的存儲空間地址給24C02,就相當於改變24C02的內部地址計數器的值,主機每讀寫一個字節24C02之后,它內部地址計數器的值就會自動加1。也就是說如果當前地址是N,那么主機讀取完一個字節的數據之后,再次讀的話就變為了讀取N+1地址的數據。
這里需要注意的一點是,24C02的內部地址計數器的地址只能從0~255之間遞增,這是因為24C02的存儲控制只有256個字節,地址計數器只能在0~255(共256個地址)內變化。如果連續讀取使得地址計數器超過255,那么地址計數器就會從0地址開始循環。比如說當前內部計數器地址為255,主機在讀取一個字節數據之后會導致內部計數器地址變為0,那么主機再次讀取數據的時候讀取得到的是24C02地址0的數據。
MCU使用I2C連續讀取24C02內多個存儲空間地址數數的代碼如下:
1 void AT24CXX_READ_BUFF(uint16_t address,uint8_t *buffer,uint16_t Len) 2 { 3 uint16_t i; 4
5 I2C_START(); 6 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 7 if(AT24CXX_ERR != 0)//沒有響應直接退出
8 { 9 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF); 10 if(AT24CXX_ERR != 0) 11 { 12 I2C_START(); 13 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_READ_CMD); 14 if(AT24CXX_ERR != 0) 15 { 16 for(i=0;i<Len;i++) 17 { 18 buffer[i] = I2C_READ_BYTE(0); 19 } 20 } 21
22 if(AT24CXX_ERR != 0) 23 { 24 I2C_STOP(); 25 } 26 } 27 } 28 }
上面的代碼,大部分跟讀取一個字節的程序代碼是一樣的,不一樣的是第16~19行,這里用一個for循環來連續讀取24C02內的數據,這里並沒有對超范圍讀取數據進行限制,所以在使用的時候需要注意不要連續讀取超過24C02的存儲空間,就算超過也不會有問題,只是會重新開始從0地址讀取。
4、24C02數據寫入操作
MCU使用I2C寫入一個字節數據到24C02任意存儲空間地址內的代碼如下:
1 void AT24CXX_WRITE_ONE_BYTE(uint16_t address,uint8_t dat) 2 { 3 I2C_START(); 4 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 5 if(AT24CXX_ERR != 0)//沒有響應直接退出
6 { 7 AT24CXX_ERR = I2C_WRITE_BYTE(address & 0xFF); 8 if(AT24CXX_ERR != 0) 9 { 10 AT24CXX_ERR = I2C_WRITE_BYTE(dat); 11 if(AT24CXX_ERR != 0) 12 { 13 I2C_STOP(); 14 } 15 } 16 } 17 }
第3行主機產生一個I2C起始信號,第4行發送設備地址和寫數據位給24C02,第7行是發送需要寫入數據的地址給24C02,第10行是將要寫入的數據發送給24C02。第18行是主機產生I2C結束信號。上面大部分操作跟讀取是一樣的,不一樣的只是最后將讀取操作改為了寫入操作。
如果需要連續寫入數據,可以如下:
1 for(i = 0;i < 256;i ++) 2 { 3 AT24C02_BUFF[i] = i; 4 AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]); 5 }
但是在實際使用的過程中,發現只有一部分AT24C02_BUFF[]數組里面的數據被寫入到了24C02當中,有一些數據沒有寫進24C02。這是因為24C02擦寫數據沒有那么快,需要一定的時間,在24C02正在擦寫數據的過程中,是不會應答主機的通訊的,所以如果主機在寫入一個數據之后又立馬寫入另一個數據,就會導致24C02跟不上主機的通訊速度從而導致無法寫入數據。
需要注意的是24C02並不是在主機發送數據給24C02之后就立馬擦寫數據的,24C02是在主機產停止信號之后才開始擦寫數據的,並且在擦寫數據完成之前不會響應主機的其它操作。
可以通過一定的延時函數來等待24C02擦寫完成,代碼如下:
for(i = 0;i < 256;i ++) { AT24C02_BUFF[i] = i; AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]); HAL_Delay(1); }
通過調用HAL_Delay()函數進行延時,具體的延時時間可以通過調試來決定,這里使用1ms的延時時間,具體24C02擦寫數據需要多久並不清楚。
除了通過延時函數進行等待24C02擦寫完成,也可以通過發送設備地址給24C02,然后查詢是否有應答信號返回來判斷24C02是否擦寫完成。24C02在擦寫數據時是不會反饋應答信號給主機的,這樣就可以通過不斷的發送數據給24C02,然后查詢應答信號來判斷24C02是否擦寫完成,一旦擦寫完成就可以進行下一個數據的寫入。代碼如下:
1 void Wait_AT24CXX_WRITE_OK(void) 2 { 3 uint8_t Wait_Cnt; 4
5 Wait_Cnt = 50; 6 do
7 { 8 I2C_START(); 9 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 10 if(AT24CXX_ERR != 0) 11 { 12 I2C_STOP();//接收到響應信號退出
13 break; 14 } 15
16 }while(Wait_Cnt--); 17
18 }
Wait_Cnt是一個次數限制變量,不能無限的在里面等待,不然遇到異常就有可能造成程序卡死。
程序通過發送起始信號、發送設備地址和寫數據方向給24C02,如果24C02反饋了一個應答信號給主機,主機就產生一個停止信號,然后退出當前循環。應用代碼如下:
1 for(i = 0;i < 256;i ++) 2 { 3 AT24C02_BUFF[i] = i; 4 AT24CXX_WRITE_ONE_BYTE(i,AT24C02_BUFF[i]); 5 Wait_AT24CXX_WRITE_OK();//可以通過發送設備地址給從機,通過從機反饋的響應信號來判斷從機是否可以正常通訊
6 }
5、24C02頁寫入
24C02有一個頁寫入功能,可以連續寫入16個字節的數據。
24C02可以以頁來划分存儲空間,每16個字節組成一個頁,24C02的存儲空間大小為256個字節,所以24C02總共有16個頁。如:
頁0:地址從0x00~0x0F
頁1:地址從0x10~0x1F
......
頁15:地址從0xF0~0xFF
24C02可以在一個頁內連續的寫入數據,但是需要注意的是如果寫入的數據超過頁大小,那么就會覆蓋頁初始地址的值,比如說連續寫入3個數據,第1個數據寫入到地址0x0F當中,第2個數據由於溢出頁的限制,會被寫入到地址0x00當中,第3個數據會被寫入到地址0x01當中。
以個人的理解,24C02內部有一個16byte的數據緩存器,在上面的介紹中知道,主機在發送數據給24C02的時候,24C02是不會擦寫數據的,只有當主機發送停止信號之后24C02才會擦寫數據。那么當主機發送數據給24C02時,只是將數據寫入到了24C02內部的緩存器中,只有當主機發送結束信號之后,24C02才將緩存器內的數據寫入到內部存儲空間。
由24C02的數據緩存器只有16個byte(每個型號的存儲IC的頁大小是不一樣的也就是緩存器大小是不不一樣的)。所以如果寫入的數據超過緩存器的大小就會覆蓋之前寫入的數據。
使用頁寫入連續將數據寫入24C02的代碼如下:
1 void AT24CXX_WRITE_BUFF(uint16_t address,uint8_t *Buffer,uint16_t Len) 2 { 3 uint8_t i; 4 uint16_t re_main; 5
6 if(address >= 256)//對輸入的地址進行限制,24C02只有256個字節的存儲空間,其它型號的存儲器IC可以通過查資料
7 { 8 return; 9 } 10
11 re_main = 256 - address;//計算出還有多少存儲空間
12
13 if(Len > re_main)//如果要寫入的數據量超過剩余存儲空間,則只寫入剩余存儲空間數量的數據
14 { 15 Len = re_main; 16 } 17
18 re_main = 16 - address%16;//計算當前頁還可以寫入多少個數據
19
20 if(Len <= re_main)//如果要寫入的數據小於等於當前頁剩余的存儲空間,則只寫入Len個字節數據就好,不需要跨頁操作
21 { 22 re_main = Len; 23 } 24
25 do
26 { 27 I2C_START(); 28 AT24CXX_ERR = I2C_WRITE_BYTE(AT24CXX_WIRTE_CMD); 29 if(AT24CXX_ERR == 0)//沒有響應直接退出
30 { 31 break; 32 } 33
34 I2C_WRITE_BYTE(address & 0xFF); 35 for(i = 0;i < re_main;i ++)//最多連續寫入一個頁數據的大小
36 { 37 AT24CXX_ERR = I2C_WRITE_BYTE(Buffer[i]); 38 } 39
40 I2C_STOP(); 41 Wait_AT24CXX_WRITE_OK();//等待24C02完成擦寫數據動作
42
43 if(re_main != Len) 44 { 45 address += re_main;//已經寫入re_main個數據,
46 Buffer += re_main; 47 Len -= re_main; 48
49 re_main = 16;//寫一個頁的的大小也是16個字節
50
51 if(Len <= re_main) 52 { 53 re_main = Len; 54 } 55 } 56 else
57 { 58 break;//數據寫入完成退出
59 } 60 } 61 while(1); 62 }
AT24CXX_WRITE_BUFF()函數的思路如下:
首先判斷輸入的地址是否超過存儲IC的存儲空間,如果超過則退出,如第6~9行。
計算出輸入的地址到存儲器存儲的結束地址剩余多少空間,如果要寫入的數據比剩余空間還多,那么剩余多少空間就寫入多少空間,需要寫入的多余部分就去掉,如第11~16行。
計算當前頁還可以寫入多少數據,如果當前頁剩余的空間比Len要寫入的數據長度還大,那么只要寫入當前頁就足夠了,不需要再跨頁寫入數據。如第18~23行。
通過一個while(1)循環連續寫入數據到24C02中。
第27~41行是將re_main個數據寫入到24C02當中,由於是在一個頁內操作,所以可以連續寫入,寫完之后在產生一個結束信號然后等待24C02擦寫完成。
如果不需要跨頁寫入就已經將數據全部寫完,那么就可以直接break退出while循環,如第58行。
如果需要跨頁寫入數據,還需要將地址、buffer、Len減去已經寫入的數據量,然后下一個頁可以寫入整個頁的數據量,也就是16個字節,通過比較判斷一個頁的剩余空間是否能夠寫完剩余的Len數據,如果不行就重復循環操作,如果可以寫完,在寫完之后就退出while循環。