An I2C EEPROM Class for Arduino.

Last Modified: December 15, 2013, at 11:36 AM
By: robtillaart
Platforms: UNO (others not tested)

remarks & comments

all versions see github

Intro

One of the main applications for the Arduino board is reading and logging of sensor data. The data can be written to several devices. One of the devices I experimented with was an I2C EEPROM 24LC256 which was able to hold 32KB memory. That may not sound much but with a logging need of less than a KB per day it is enough for one month. 1KB /day is still 40 bytes / hour, e.g 40 single byte temp readings.

Besides for logging the I2C EEPROM can be used for configuration, serial number, license info, and (const) messages to be sent to a screen or so. In a way an EEPROM can be used as a sort of "slow RAM.". I found - http://playground.arduino.cc/Code/I2CEEPROM - very usefull but major drawback was that the Wire library to read/write to the I2C EEPROM only read and writes in relative small chunks of max. 32 bytes.

2011-05-18 lib works with 24LC512 too, thanks Ross for testing.

2013-06-09 ported the lib to Arduino 1.0.x by craigcurtin & robtillaart

2013-11-03 improved performance of the library (1.0.04 version, skipped a few debug versions), + increased efficiency of internal buffers, + replaced 5 millisecond write latency delay with a polling mechanism. code also on github

2013-11-06 version 1.0.05 + improved waitEEReady() -> on average far lower write latency than 1.0.00 reference. + added determineSize -> behind #ifdef to keep base lib small + improved comments + corrected return type readBlock() + updated test sketch + readbyte is on par again with 1.0.00 reference version

2013-12-15 version 1.1.00 + breaking interface - added begin() function + some performance optimizations + only available on github for now, see link above...

Connection

http://pdf.datasheetcatalog.com/datasheet2/7/0yuw0yc3x0278cpz78zge08qi13y.pdf

Arduino Analog pin 4 - SDA - EEPROM pin 5
Arduino Analog pin 5 - SCL - EEPROM pin 6
Arduino 5V           - VCC - EEPROM pin 8
Arduino GND          - VSS - EEPROM pin 4 

Pin 1,2,3 of the eeprom must be connect to GND too unless other address is used, see datasheet. Pin 7 (write protect) should also be connected to GND

I2C EEPROM library

The library proposed here is inspired by the website mentioned earlier and is in fact a simple wrapper around the Wire library. It does writing to and reading from a I2C EEPROM, nothing more or less. What makes it special is that it's interface allows blocks of "any" size to be written or read. Internal the object to be written is chunked to smaller pieces, but the programmer only need to remember the address where the object is located.

The interface of the class supports functions for reading and writing a single byte and functions that write/read a block. Furthermore there is a constructor that needs the I2C address to identify the right EEPROM.

determineSize() is a non destructive test (unless interrupted). It returns the size of the I2C EEPROM in KB, It is optional code (#ifdef) to keep library footprint small.

    I2C_eeprom(uint8_t deviceAddress);

    // all three return Wire.endtransmission status ==> succes == 0.
    int writeByte(uint16_t address, uint8_t value);
    int writeBlock(uint16_t address, uint8_t* buffer, uint16_t length);
    int setBlock(uint16_t address, uint8_t value, uint16_t length);

    // returns byte
    uint8_t readByte(uint16_t address);
    // readBlock returns count --> changed to uint16_t in v1.0.05
    uint16_t readBlock(uint16_t address, uint8_t* buffer, uint16_t length);

   // retuns size in KB, 0,1,2,4,8,16,32,64
    uint8_t determineSize();

Usage

A sketch shows how the class can be used, and it tests the 64 byte boundary bug and some performance figures.

//
//    FILE: I2C_eeprom_test.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.05
// PURPOSE: show/test I2C_EEPROM library
//

#include <Wire.h> //I2C library
#include <I2C_eeprom.h>

I2C_eeprom ee(0x50);

void setup() 
{
  Serial.begin(115200);
  Serial.print("Demo I2C eeprom library ");
  Serial.print(I2C_EEPROM_VERSION);
  Serial.println("\n");

  Serial.println("\nTEST: 64 byte page boundary writeBlock");
  ee.setBlock(0,0,128);
  dumpEEPROM(0, 128);
  char data[] = "11111111111111111111";
  ee.writeBlock(60, (uint8_t*) data, 10);
  dumpEEPROM(0, 128);

  Serial.println("\nTEST: 64 byte page boundary setBlock");
  ee.setBlock(0,0,128);
  dumpEEPROM(0, 128);
  ee.setBlock(60, '1', 10);
  dumpEEPROM(0, 128);

  Serial.println("\nTEST: 64 byte page boundary readBlock");
  ee.setBlock(0,0,128);
  ee.setBlock(60, '1', 6);
  dumpEEPROM(0, 128);
  char ar[100];
  memset(ar,0,100);
  ee.readBlock(60, (uint8_t*)ar, 10);
  Serial.println(ar);

  Serial.println("\nTEST: write large string readback in small steps");
  ee.setBlock(0,0,128);
  char data2[] = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999A"; 
  ee.writeBlock(10, (uint8_t *) &data2, 100);
  dumpEEPROM(0, 128);
  for (int i = 0; i<100; i++)
  {
    if (i%10 == 0 ) Serial.println();
    Serial.print(' ');
    Serial.print(ee.readByte(10+i));
  }
  Serial.println();

  Serial.println("\nTEST: check almost endofPage writeBlock");
  ee.setBlock(0,0,128);
  char data3[] = "6666"; 
  ee.writeBlock(60, (uint8_t *) &data3, 2);
  dumpEEPROM(0, 128);

  Serial.println();
  Serial.print("\nI2C speed:\t");
  Serial.println(16000/(16+2*TWBR));
  Serial.print("TWBR:\t");
  Serial.println(TWBR);
  Serial.println();

  Serial.print("\nTEST: timing writeByte()\t");
  uint32_t start = micros();
  ee.writeByte(10, 1);
  uint32_t diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  Serial.print("TEST: timing writeBlock(50)\t");
  start = micros();
  ee.writeBlock(10, (uint8_t *) &data2, 50);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  Serial.print("TEST: timing readByte()\t\t");
  start = micros();
  ee.readByte(10);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  Serial.print("TEST: timing readBlock(50)\t");
  start = micros();
  ee.readBlock(10, (uint8_t *) &data2, 50);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  // same tests but now with a 5 millisec delay in between.
  delay(5);

  Serial.print("\nTEST: timing writeByte()\t");
  start = micros();
  ee.writeByte(10, 1);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  delay(5);

  Serial.print("TEST: timing writeBlock(50)\t");
  start = micros();
  ee.writeBlock(10, (uint8_t *) &data2, 50);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  delay(5);

  Serial.print("TEST: timing readByte()\t\t");
  start = micros();
  ee.readByte(10);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  delay(5);

  Serial.print("TEST: timing readBlock(50)\t");
  start = micros();
  int xx = ee.readBlock(10, (uint8_t *) &data2, 50);
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);

  // does it go well?
  Serial.println(xx);

  Serial.println("\nTEST: determine size");
  start = micros();
  int size = ee.determineSize();
  diff = micros() - start;  
  Serial.print("TIME: ");
  Serial.println(diff);
  Serial.print("SIZE: ");
  Serial.print(size);
  Serial.println(" KB");

  Serial.println("\tDone...");
}

void loop() {}

void dumpEEPROM(uint16_t addr, uint16_t length)
{
  // block to 10
  addr = addr / 10 * 10;
  length = (length + 9)/10 * 10;

  byte b = ee.readByte(addr); 
  for (int i = 0; i < length; i++) 
  {
    if (addr % 10 == 0)
    {
      Serial.println();
      Serial.print(addr);
      Serial.print(":\t");
    }
    Serial.print(b);
    b = ee.readByte(++addr); 
    Serial.print("  ");
  }
  Serial.println();
}
// END OF FILE

Notes

To use the library, make a folder in your SKETCHBOOKPATH\libaries with the name I2C_EEPROM and put the .h and .cpp there. Optionally make a examples subdirectory to place the sample app.

The library does not validate anything (like address out of range), that is up to the programmer. Maybe in a next release ;)

24LC512 (64K) confirmed to work too. For larger than 64KB EEPROMS the address variables should be changes from uint16_t to long.

Bugs fixed (not complete)

2011-02-11 - Fixed bug with writing blocks over 64 byte boundaries. Added a more elaborate test program to show it does work now.

2013-06-09 - Support for Arduino 1.0.x

2013-11-01 - WriteBlock bug fixed (intermediate version)

2013-11-03 - ReadBlock bug fixed (intermediate version)

2013-11-06 - return value of readBlock fixed.

Todo

  • Test especially _ReadBlock() through & through - timing? - missing bytes?
  • create a new class with a sort of directory, e.g. to search strings/var's by name: { name, location} -> string. idea: { length (byte) state (byte) name (string8) value (length bytes) }. boolean readObject(name, *value); int writeObject(name, *value, size);
  • Validate params within size of EEPROM
  • Extend to writing/reading over EEPROM borders the 24LC256 has three address lines => up to 8 chips on the bus == 256KB "RAM"
  • default constructor for address 0x50 as this is most used?
  • create a multi EEPROM lib with auto-IC choice

Enjoy tinkering,

rob.tillaart@removethisgmail.com

I2C_eeprom.h

#ifndef I2C_EEPROM_H
#define I2C_EEPROM_H
//
//    FILE: I2C_eeprom.h
//  AUTHOR: Rob Tillaart
// PURPOSE: Simple I2C_eeprom library for Arduino with EEPROM 24LC256 et al.
// VERSION: 1.0.05
// HISTORY: See I2C_eeprom.cpp
//     URL: http://arduino.cc/playground/Main/LibraryForI2CEEPROM
//
// Released to the public domain
//

#include <Wire.h>

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#include "Wstring.h"
#include "Wiring.h"
#endif

#define I2C_EEPROM_VERSION "1.0.05"

// I2C_EEPROM_PAGESIZE must be multiple of 2 e.g. 16, 32 or 64
// 24LC256 -> 64 bytes
#define I2C_EEPROM_PAGESIZE 64

// TWI buffer needs max 2 bytes for address
#define I2C_TWIBUFFERSIZE  30

// to break blocking read/write
#define I2C_EEPROM_TIMEOUT  1000

// comment next line to keep lib small
#define I2C_EEPROM_EXTENDED

class I2C_eeprom
{
public:
    I2C_eeprom(uint8_t deviceAddress);

    int writeByte(uint16_t address, uint8_t value);
    int writeBlock(uint16_t address, uint8_t* buffer, uint16_t length);
    int setBlock(uint16_t address, uint8_t value, uint16_t length);

    uint8_t readByte(uint16_t address);
    uint16_t readBlock(uint16_t address, uint8_t* buffer, uint16_t length);

#ifdef I2C_EEPROM_EXTENDED
    uint8_t determineSize();
#endif

private:
    uint8_t _deviceAddress;
    uint32_t _lastWrite;  // for waitEEReady

    int _pageBlock(uint16_t address, uint8_t* buffer, uint16_t length, bool incrBuffer);
    int _WriteBlock(uint16_t address, uint8_t* buffer, uint8_t length);
    uint8_t _ReadBlock(uint16_t address, uint8_t* buffer, uint8_t length);

    void waitEEReady();
};

#endif
// END OF FILE

I2C_eeprom.cpp

//
//    FILE: I2C_eeprom.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 1.0.05
// PURPOSE: Simple I2C_eeprom library for Arduino with EEPROM 24LC256 et al.
//
// HISTORY:
// 0.1.00 - 2011-01-21 initial version
// 0.1.01 - 2011-02-07 added setBlock function
// 0.2.00 - 2011-02-11 fixed 64 bit boundary bug
// 0.2.01 - 2011-08-13 _readBlock made more robust + return value
// 1.0.00 - 2013-06-09 support for Arduino 1.0.x
// 1.0.01 - 2013-11-01 fixed writeBlock bug, refactor
// 1.0.02 - 2013-11-03 optimize internal buffers, refactor
// 1.0.03 - 2013-11-03 refactor 5 millis() write-latency
// 1.0.04 - 2013-11-03 fix bug in readBlock, moved waitEEReady() -> more efficient.
// 1.0.05 - 2013-11-06 improved waitEEReady(), added determineSize()
//
// Released to the public domain
//

#include <I2C_eeprom.h>

I2C_eeprom::I2C_eeprom(uint8_t device)
{
    _deviceAddress = device;
    Wire.begin();
    _lastWrite = 0;
    TWBR = 12;          // 12=400Khz  32=200  72=100 152=50    F_CPU/16+(2*TWBR)
}

int I2C_eeprom::writeByte(uint16_t address, uint8_t data)
{
    int rv = _WriteBlock(address, &data, 1);
    return rv;
}

int I2C_eeprom::setBlock(uint16_t address, uint8_t data, uint16_t length)
{
    uint8_t buffer[I2C_TWIBUFFERSIZE];
    for (uint8_t i =0; i< I2C_TWIBUFFERSIZE; i++) buffer[i] = data;

    int rv = _pageBlock(address, buffer, length, false); // todo check return value..
    return rv;
}

int I2C_eeprom::writeBlock(uint16_t address, uint8_t* buffer, uint16_t length)
{
    int rv = _pageBlock(address, buffer, length, true); // todo check return value..
    return rv;
}

uint8_t I2C_eeprom::readByte(uint16_t address)
{
    uint8_t rdata;
    _ReadBlock(address, &rdata, 1);
    return rdata;
}

uint16_t I2C_eeprom::readBlock(uint16_t address, uint8_t* buffer, uint16_t length)
{
    uint16_t rv = 0;
    while (length > 0)
    {
        uint8_t cnt = min(length, I2C_TWIBUFFERSIZE);
        rv += _ReadBlock(address, buffer, cnt);
        address += cnt;
        buffer += cnt;
        length -= cnt;
    }
    return rv;
}

#ifdef I2C_EEPROM_EXTENDED
// returns 64, 32, 16, 8, 4, 2, 1, 0
// 0 is smaller than 1K
uint8_t I2C_eeprom::determineSize()
{
    uint8_t rv = 0;  // unknown
    uint8_t orgValues[8];
    uint16_t addr;

    // remember old values, non destructive
    for (uint8_t i=0; i<8; i++)
    {
        addr = (512 << i) + 1;
        orgValues[i] = readByte(addr);
    }

    // scan page folding
    for (uint8_t i=0; i<8; i++)
    {
        rv = i;
        uint16_t addr1 = (512 << i) + 1;
        uint16_t addr2 = (512 << (i+1)) + 1;
        writeByte(addr1, 0xAA);
        writeByte(addr2, 0x55);
        if (readByte(addr1) == 0x55) // folded!
        {
            break;
        }
    }

    // restore original values
    for (uint8_t i=0; i<8; i++)
    {
        uint16_t addr = (512 << i) + 1;
        writeByte(addr, orgValues[i]);
    }
    return 0x01 << (rv-1);
}
#endif

////////////////////////////////////////////////////////////////////
//
// PRIVATE
//

// _pageBlock aligns buffer to page boundaries for writing.
// and to TWI buffer size
// returns 0 = OK otherwise error
int I2C_eeprom::_pageBlock(uint16_t address, uint8_t* buffer, uint16_t length, bool incrBuffer)
{
    int rv = 0;
    while (length > 0)
    {
        uint8_t bytesUntilPageBoundary = I2C_EEPROM_PAGESIZE - address%I2C_EEPROM_PAGESIZE;
        uint8_t cnt = min(length, bytesUntilPageBoundary);
        cnt = min(cnt, I2C_TWIBUFFERSIZE);

        int rv = _WriteBlock(address, buffer, cnt); // todo check return value..
        if (rv != 0) return rv;

        address += cnt;
        if (incrBuffer) buffer += cnt;
        length -= cnt;
    }
    return rv;
}

// pre: length <= I2C_EEPROM_PAGESIZE  && length <= I2C_TWIBUFFERSIZE;
// returns 0 = OK otherwise error
int I2C_eeprom::_WriteBlock(uint16_t address, uint8_t* buffer, uint8_t length)
{
    waitEEReady();

    Wire.beginTransmission(_deviceAddress);
#if defined(ARDUINO) && ARDUINO >= 100
    Wire.write((int)(address >> 8));
    Wire.write((int)(address & 0xFF));
    for (uint8_t cnt = 0; cnt < length; cnt++)
    Wire.write(buffer[cnt]);
#else
    Wire.send((int)(address >> 8));
    Wire.send((int)(address & 0xFF));
    for (uint8_t cnt = 0; cnt < length; cnt++)
    Wire.send(buffer[cnt]);
#endif
    int rv = Wire.endTransmission();
    _lastWrite = micros();
    return rv;
}

// pre: buffer is large enough to hold length bytes
// returns bytes written
uint8_t I2C_eeprom::_ReadBlock(uint16_t address, uint8_t* buffer, uint8_t length)
{
    waitEEReady();

    Wire.beginTransmission(_deviceAddress);
#if defined(ARDUINO) && ARDUINO >= 100
    Wire.write((int)(address >> 8));
    Wire.write((int)(address & 0xFF));
#else
    Wire.send((int)(address >> 8));
    Wire.send((int)(address & 0xFF));
#endif
    int rv = Wire.endTransmission();
    if (rv != 0) return 0;  // error

    Wire.requestFrom(_deviceAddress, length);
    uint8_t cnt = 0;
    uint32_t before = millis();
    while ((cnt < length) && ((millis() - before) < I2C_EEPROM_TIMEOUT))
    {
#if defined(ARDUINO) && ARDUINO >= 100
        if (Wire.available()) buffer[cnt++] = Wire.read();
#else
        if (Wire.available()) buffer[cnt++] = Wire.receive();
#endif
    }
    return cnt;
}

void I2C_eeprom::waitEEReady()
{
#define I2C_WRITEDELAY  5000

    // Wait until EEPROM gives ACK again.
    // this is a bit faster than the hardcoded 5 milli
    while ((micros() - _lastWrite) <= I2C_WRITEDELAY)
    {
        Wire.beginTransmission(_deviceAddress);
        int x = Wire.endTransmission();
        if (x == 0) break;
    }
}

//
// END OF FILE
//

keywords.txt

#######################################
# Syntax Coloring Map For I2C_EEPROM
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

I2C_eeprom      KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

readByte        KEYWORD2
writeByte       KEYWORD2
setBlock        KEYWORD2
readBlock       KEYWORD2
writeBlock      KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

Share