用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("|");
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容