用Arduino玩ESP32(09):外接EEPROM(AT24CXX)


各型號基本說明

型號               容量               器件尋址字節(8位)       一次裝載字節數 (頁長度)
AT24C01           128×8               1010A2A1A0 R/W             8
AT24C02           256×8               1010A2A1A0 R/W             8 
AT24C04           512×8               1010A2A1P0 R/W            16 
AT24C08           1024×8              1010A2P1P0 R/W            16 
AT24C16           2048×8              1010P2P1P0 R/W            16 
AT24C32           4096*8              1010A2A1A0 R/W            32
AT24C64           8192*8              1010A2A1A0 R/W            32
AT24C128          16384*8             1010A2A1A0 R/W            64
AT24C256          16384*8             1010A2A1A0 R/W            64

1、AT24C01~AT24C16:容量分別是128,256,512,1024,2048個字節,頁長度分別是8,8,16,16,16,讀寫都是先發設備地址,然后發一個字節的字節地址。

 
AT24C01~AT24C16

 

  • 24c01、02 1K/2K EEPROM 在一條IIC總線上可以掛8個,地址由A2,A1,A0確定;
  • 24C04 4k EEPROM 只有A2,A1的做地址位,這樣一條IIC總線上能掛4個設備,A0是用來確定內部頁地址的,A0在芯片上沒有線連接的(NA);
  • 24C08 8k EEPROM 使用A2來確定地址線,A1,A0位是在確定內部頁地址的,一條IIC總線能擴展2片;
  • 24C16 16k EEPROM,A2A1A0都是確定內部頁地址的,一條總線上只能掛1個一個這樣的設備。

2、AT24C32~AT24C64:,容量分別是4096,8192個字節,頁長度分別是32,32字節,讀寫都是先發設備地址,然后發一個字節的高地址,再發一個字節的低地址,設備地址如下所示

 
AT24C32~AT24C64

 

3、 AT24C128~AT24C512:容量分別是16384,32768個字節,頁長度分別是64,64,128字節,讀寫都是先發設備地址,然后發一個字節的高地址,再發一個字節的低地址,
A1、A0:輸入腳用於多個器件級聯時設置器件地址,當這些腳懸空時默認值為0,最多可以級聯4個設備,只有一個設備的話,A0,A1懸空或者為0
它們的設備地址如下所示:

 
AT24C128~AT24C512

 

有關E2PROM的數據管理(來自網絡)

  1. 操作注意事項:分頁操作需要有5ms延遲等待時間才可以(以類AT24C04的產品為例),也就是模塊化程序設計中,在寫數據之前、寫數據完畢后、度數據之前、讀數據之后都需要考慮加5ms的延時時間。本來IIC的讀寫速率就不是很高,外加這些延遲一定會勢必影響系統設計的實時性,但也不得不從讀寫性能的角度出發。
  1. 上拉電阻的選擇:出於穩定性考慮,WP、SDA、SCL引腳都會設置上拉電阻,常用的電阻值為 4.7K 、10K電阻,個人比較推薦4.7K。

  2. 硬件IIC與軟件模擬IIC的比較:對MCU資源不是很敏感的應用,都會考慮軟件模擬的方式,畢竟這個移植起來真的很方便,只有第一做軟件部分的時序、保護性設計作為足夠好,后面拿過來修改時鍾就可以直接,確實方便。

  3. 默認參數的寫入:設置新E2PROM的時間戳標志,每次系統啟動時檢查這個時間戳和MCU自身存儲的時間戳是否一致,不一致則初始化整個E2PROM為默認參數;當然軟件程序的升級,這個時間戳表示也有必要做更改。

  4. 數據容錯和管理:
    把數據以有意義的數據塊作分類管理,在數據的塊的頭、位加固定標識和CS/crc校驗 模式,格式如

數據塊開始字   數據長度         數據            校驗        結束
  0xA5         Length     Byte0---ByteN      CS/CRC      0x5A

  實際用於產品中,可以挑選這個格式內容里面的部分內容使用,比如去掉結束符等。
  個人之所以建立寫入開始字、結束字,原因是方便最好讀出來的數據做數據格式檢查,確認寫入、讀出的數據可靠性最高。
  為增強實際的可靠性,在需要寫入的時候,可以在寫入后,再讀出來進行數據的比對,確認寫入是否正確;或者在需要讀出的時候,讀兩次、或者多次,檢查每次的數據是否一致。
對於出現異常的數據,最好有容錯機制,可以回到默認狀態值,不至於系統此時因為某個參數改變的崩潰。

  1. 實際底層操作是否需要關閉主程序的中斷:一般按照上述(5)操作,有多次冗余操作設計,可以不關閉主程序中斷。而且,IIC為等待型操作,一般不會因為系統延遲導致時鍾脈寬拉長,影響字節寫入、讀出。

  2. E2PROM擦寫次數的延長: 如果現在手上的E2PROM的擦寫次數是10萬次,項目要求為100萬次,且E2PROM內有很多空閑字節的沒有使用。

  可以這樣操作,將數據整理好,以數據塊的方式存儲,一組數據分10個塊地址存儲,每次寫完后轉移到下一塊寫,即10次寫操作中每個物理的數據存儲地址只操作了1次。
   注意此時的寫塊數據的指針不能單獨存、操作,不然這個字節的操作頻率高,也就受到10萬次的限制,這個關鍵的鏈子在10萬次的時候掉了,其他字節也就掛了。這個表征操作哪個塊的指針或者說標示符,當然也需要是移動的,至於具體怎么實現,就是見仁見智的事了。

I²C線路需要上拉電阻才能正常通信。這些電阻的值取決於線路的電容和您想要通信的頻率,一般使用4.7kΩ上拉電阻。

 
 

 

Arduino簡單程序示例

//原程序地址:https://forum.arduino.cc/index.php?topic=62822.msg751697#msg751697
/* 
 *  Use the I2C bus with small EEPROMs
 *  24C01, 20C02, 24C04, 24C08, 24C16
 *
 * For a single device, connect as follows:
 * EEPROM 4 (GND) to GND
 * EEPROM 8 (Vcc) to Vcc (5 Volts) 24C04實際3.3V的電壓也能使用,最低1.8V,最高5.5V
 * EEPROM 5 (SDA) to ESP32-S Pin 21
 * EEPROM 6 (SCL) to ESP32-S Pin 22
 * EEPROM 7 (WP)  to GND  低電平,可讀寫
 * EEPROM 1 (A0)  to GND
 * EEPROM 2 (A1)  to GND
 * EEPROM 3 (A2)  to GND
 */

#include <Wire.h>


// The seven-bit device address for EEPROMs 7 bit 地址 1010000(0x50) 開始 期中1010為廠商地址
// I'll define it here rather than hard-code it inside all of the functions.
const byte DEVADDR = 0x50;

void setup()
{
   byte msg1[] = "Message 1.";   // data to write
   byte msg2[] = "Zaphod says yo";
   byte msg3[] = "Tttthat's all, folks!";
   byte msgf[16] = {
       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
   };
   
   
   Wire.begin();
   Serial.begin(9600);

   //
   // Change #if 0 to #if 1 and it will erase the
   // EEPROM pages that we are going to write to:
   //
   #if 0
     eeprom_write_page(DEVADDR, 0x000, msgf, 16);
     eeprom_write_page(DEVADDR, 0x010, msgf, 16);
     eeprom_write_page(DEVADDR, 0x020, msgf, 16);
     eeprom_write_page(DEVADDR, 0x100, msgf, 16);
     eeprom_write_page(DEVADDR, 0x1f0, msgf, 16);
     Serial.println("After erasing pages starting at 0x000, 0x100, and 0x1f0:");
     eeprom_dump(DEVADDR, 0, 512);
   #endif

   //
   // Change #if 1 to #if 0 so that it won't write over the stuff next time
   //
   #if 1
   // Write some stuff to EEPROM 
   eeprom_write_page(DEVADDR, 0x000, msg1, sizeof(msg1));
   eeprom_write_page(DEVADDR, 0x100, msg2, sizeof(msg2));
   eeprom_write_page(DEVADDR, 0x1f0, msg3, 16);
   #endif

   Serial.println("Memory written");
}

void loop()
{
   //
   // Read the first page in EEPROM memory, a byte at a time
   //
   Serial.println("eeprom_read_byte, starting at 0");
   for (int i = 0; i < 16; i++) {
       byte b = eeprom_read_byte(DEVADDR, i);
       Serial.print(b, HEX);
       Serial.print(' ');
   }
   Serial.println();
   
   //
   // Read the first page using the read_buffer function
   //
   Serial.println("eeprom_read_buffer, starting at 0");
   byte buffer[16];
   eeprom_read_buffer(DEVADDR, 0, buffer, sizeof(buffer));
   
   //
   //First print the hex bytes on this row
   //
   for (int i = 0; i < sizeof(buffer); i++) {
       char outbuf[6];
       sprintf(outbuf, "%02X ",buffer[i]);
       Serial.print(outbuf);
   }
   Serial.println();

   //
   // Now print the char if printable ASCII
   // otherwise print '.'
   //
   for (int i = 0; i < sizeof(buffer); i++) {
       if (isprint(buffer[i])) {
           Serial.print(buffer[i]);
       }
       else {
           Serial.print('.');
       }
   }
   Serial.println();
   
   // Now dump 512 bytes
   Serial.println("eeprom_dump(DEVADDR, 0, 512)");
   eeprom_dump(DEVADDR, 0, 512);
   Serial.println();

   delay(20000);

}

void eeprom_write_byte(byte deviceaddress, int eeaddress, byte data)
{
   // Three lsb of Device address byte are bits 8-10 of eeaddress
   byte devaddr = deviceaddress | ((eeaddress >> 8) & 0x07);
   byte addr    = eeaddress;
   Wire.beginTransmission(devaddr);
   Wire.send(int(addr));
   Wire.send(int(data));
   Wire.endTransmission();
   delay(10);
}

 // Pages are blocks of 16 bytes, starting at 0x000.
 // That is, pages start at 0x000, 0x010, 0x020, ...
 // For a device "page write", the last byte must be
 // on the same page as the first byte.
 //
 // No checking is done in this routine.
 //
 // TODO: Do some checking, or, better yet (maybe)
 // make length an int and do repeated device
 // page writes if necessary. (Then maybe rename to
 // eeprom_write_pages or some such thing.)
 //
void eeprom_write_page(byte deviceaddress, unsigned eeaddr,
                      const byte * data, byte length)
{
   // Three lsb of Device address byte are bits 8-10 of eeaddress
   byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
   byte addr    = eeaddr;
   Wire.beginTransmission(devaddr);
   Wire.send(int(addr));
   for (int i = 0; i < length; i++) {
       Wire.send(data[i]);
   }
   Wire.endTransmission();
   delay(10);
}

// TODO: Change to integer data type and return -1 if can't
// read.
//
int eeprom_read_byte(byte deviceaddress, unsigned eeaddr)
{
   byte rdata = -1;

   // Three lsb of Device address byte are bits 8-10 of eeaddress
   byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
   byte addr    = eeaddr;

   Wire.beginTransmission(devaddr);
   Wire.send(int(addr));
   Wire.endTransmission();
   Wire.requestFrom(int(devaddr), 1);
   if (Wire.available()) {
       rdata = Wire.receive();
   }
   return rdata;
}

//
// Returns number of bytes read from device
//
// Due to buffer size in the Wire library, don't read more than 30 bytes
// at a time!  No checking is done in this function.
//
// TODO: Change length to int and make it so that it does repeated
// EEPROM reads for length greater than 30.

int eeprom_read_buffer(byte deviceaddr, unsigned eeaddr,
                       byte * buffer, byte length)
{
   // Three lsb of Device address byte are bits 8-10 of eeaddress
   byte devaddr = deviceaddr | ((eeaddr >> 8) & 0x07);
   byte addr    = eeaddr;
   
   Wire.beginTransmission(devaddr);
   Wire.send(int(addr));
   Wire.endTransmission();

   Wire.requestFrom(devaddr, length);
   int i;
   for (i = 0; i < length && Wire.available(); i++) {
       buffer[i] = Wire.receive();
   }
   return i;
}

//
// The display is like hexdump -C.  It will always
// begin and end on a 16-byte boundary.
//

void eeprom_dump(byte devaddr, unsigned addr, unsigned length)
{
   // Start with the beginning of 16-bit page that contains the first byte
   unsigned startaddr = addr & (~0x0f);

   // stopaddr is address of next page after the last byte
   unsigned stopaddr  = (addr + length + 0x0f) & (~0x0f);

   for (unsigned i = startaddr; i < stopaddr; i += 16) {
       byte buffer[16]; // Hold a page of EEPROM
       char outbuf[6];  //Room for three hex digits and ':' and ' ' and '\0'
       sprintf(outbuf, "%03x: ", i);
       Serial.print(outbuf);
       eeprom_read_buffer(devaddr, i, buffer, 16);
       for (int j = 0; j < 16; j++) {
           if (j == 8) {
               Serial.print(" ");
           }
           sprintf(outbuf, "%02x ", buffer[j]);
           Serial.print(outbuf);            
       }
       Serial.print(" |");
       for (int j = 0; j < 16; j++) {
           if (isprint(buffer[j])) {
               Serial.print(buffer[j]);
           }
           else {
               Serial.print('.');
           }
       }
       Serial.println("|");
   }
}

 



作者:幸運派
鏈接:https://www.jianshu.com/p/4e7a730e9a97
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM