Last Modified: | December 15, 2013, at 11:36 AM |
By: | robtillaart |
Platforms: | UNO (others not tested) |
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 - https://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...
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
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();
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
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.
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.
Enjoy tinkering,
rob.tillaart@removethisgmail.com
#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: https://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
// // 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 //
####################################### # 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) #######################################