Eeprom Page Write (Writing long strings to 24LC256)

Eeprom Page Write (Writing long strings to 24LC256)

The Microchip 24LC256 eeprom and variants thereof are used in many projects for the storage of data when the microcontroller in use either doesn't have any eeprom memory or not enough. These useful eeproms use a simple I2C connection and are easy to setup and use. Writing and reading single bytes of data to any of the 32k memory locations is straightforward, but what if you want to store a string of characters? You might think this is actually easy to do as well. And indeed you can send it a string of characters, up to 64 in fact before the eeprom needs to store them using a feature called Page Write. But there is a big problem with this as we shall see.

So, what is page write?

First, lets look back at how we store a single byte of data. The examples shown are Arduino (version 1.0 and above), but the same basics apply to any other programming language.

Here is the routine for writing a byte of data

void writeEEPROM(int deviceaddress, unsigned int eeaddress, byte data ) 
{
  Wire.beginTransmission(deviceaddress);
  Wire.write((int)(eeaddress >> 8));   // MSB
  Wire.write((int)(eeaddress & 0xFF)); // LSB
  Wire.write(data);
  Wire.endTransmission();
 
  delay(5);
}

We start transmission to our device (I2C) address. Next we send 2 bytes which define the memory location we are writing to. Then finally the one byte of data we are storing.

A small delay is required to give time for the eeprom to save the data.

So far so good. Now, the 24LC256 eeprom has a feature called page write. This allows you to send up to 64 bytes of data before it needs to be saved. We can extend our writeEEPROM routine to perform this function. The routine can be re-written to that shown below.

void writeEEPROM(int deviceaddress, unsigned int eeaddress, char* data) 
{
  // Write a string of chars to eeprom
  // DO NOT USE THIS CODE - IT DOES NOT WORK 
  unsigned char i=0;
  
  Wire.beginTransmission(deviceaddress);
  Wire.write((int)((address) >> 8));   // MSB
  Wire.write((int)((address) & 0xFF)); // LSB

  do{ 
     Wire.write((byte) data[i]);
     i++;
  } while(data[i]);  
  Wire.endTransmission();
     
  delay(6);  // needs 5ms for page write
}

NOTE: The Arduino Wire library only has a 32 character buffer, so that is the maximun we can send using Arduino. This buffer includes the two address bytes which limits our data payload to 30 bytes.

The above code works exactly as it should. It sends the two starting address bytes, followed by each individual byte in the string. The problem with this method is that the 24LC256 eeprom doesn't work in the way we assume it does.

Let me explain. The eeprom's memory is split up into a number of pages that are 64 bytes wide.

So, starting at memory location 0 the pages are

  1. 0 - 63
  2. 64 - 127
  3. 128 - 191
  4. 192 - 255
  5. etc ...

The problem is "A page wite can only write to one page."

As per the 24LC256 datasheet..

"Page write operations are limited to writing bytes within a single physical page, regardless of the number of bytes actually being written. Physical page boundaries start at addresses that are integer multiples of the page buffer size (or ‘page size’) and end at addresses that are integer multiples of [page size - 1]. If a Page Write command attempts to write across a physical page boundary, the result is that the data wraps around to the beginning of the current page (overwriting data previously stored there), instead of being written to the next page, as might be expected. It is, therefore, necessary for the application software to prevent page write operations that would attempt to cross a page boundary."

What this means can be shown in the following diagram

24LC256 Eeprom Page Write problem

The blue dashed line indicates the data we are intending to write (ignoring Arduino's 32 byte limit)

The green dashed line shows what the 24LC256 eeprom does with the data.

When storing the data, if the eeprom address reaches a page boundary, the address is wrapped back to the beginning of the page and the data is written there. This causes over writing of data.

The eeprom read functionality has no such page boundary issues and will happily read past a page boundary. So when we read the data back it looks like it is corrupted. It isn't, it was just not saved in the way we had thought it would be.

The Solution

The solution to this is not straightforward. If storing strings that are shorter than 64 bytes and we have lots of eeprom memory available, you could opt to always store the data starting at a page boundary (i.e. 0, 64, 128 etc) and you won't have a problem. If we are logging data and want it stored consecutively and dont want to waste memory, we need to come up with a solution.

We can store the data one byte at a time. This would indeed work and would not be effected by the page boundary problem. The problem with this solution is that each byte written needs a small delay to allow the eeprom to store the data. This is in the region of 3.5ms ( we have allowed 5ms in the code). If you write a 100 character string, this would add up to 350ms, over a third of a second. We would be restricted to only being able to write 3 such lines per second.

A better solution provided here calculates how much remaining space there is in the initial page that is being written to, and then breaks up the write into separate page write chunks to make the writing fit into the page boundaries.

E.g. We want to write 78 bytes starting at memory location 55 (ending at memory location 132). This overlaps into 3 pages.

 0                               64                               128 
 |------------page----------------|--------------page--------------|----- 
                             |------------------data-------------------|
                             55                                       132 

The program below will split the write up as follows

  • write 9 bytes  ( 55 to  63) - up to page boundary
  • write 16 bytes ( 64 to  79)
  • write 16 bytes ( 80 to  95)
  • write 16 bytes ( 96 to 111)
  • write 16 bytes (112 to 127) - up to page boundary
  • write 5 bytes  (128 to 132)


Why 16 bytes and not 32 or 64? Remember the Arduino uses a 32 byte buffer for sending and receiving data so this is the maximum you can send. But this buffer needs to include the memory location bytes which reduces the available space to 30 bytes. So 16 bytes is used as an easy divisor of 64. Other programming languages can adjust this code to accomodate 32 or 64 byte writes.

The overhead for these 6 writes  = 5ms x 6  = 30ms, whilst the overhead for writing 78 individual bytes = 3.5ms x 78 = 273ms. A considerable improvement.

So here is the complete code. Any suggestions for improvement are always welcome

/*
 * EEPROM_PAGE_WRITE
 *
 * Example program showing a method for writing large amounts of
 * data to an eeprom (24LC256/512 etc), whilst avoiding page write
 * boundaries
 *
 * copyright www.hobbytronics.co.uk 2012
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version. see <http://www.gnu.org/licenses/>
 *
 * PROBLEM: There are two methods for writing data to an eeprom
 * 1. Write a single byte. Apart from the overhead of sending the
 *    eeprom memory address, the eeprom needs 3ms for each write to
 *    complete. Thus if you send 100 bytes it needs 300ms
 * 2. Write an array of bytes (called Page write). Up to 64 bytes 
 *    can be sent before the eeprom needs to save the data. 
 *    The save only needs 5ms thus saving considerable time. 
 * There is however a problem with method 2. The eeprom's memory is
 * split into 64byte chunks (or pages) and the page write can only write
 * within one page. Attempting to write past a page boundary will lead
 * to the the memory pointer wrapping back to the start of the page
 * and data at the beginning will be overwritten.
 *
 * The solution provided here calculates the remaining space in the
 * initial page that is being written to and breaks up the write into
 * separate writes to make the data fit the page boudaries.
 *
 * E.g. We want to write 78 bytes starting at memory location 55 (ending
 * at memory location 132). This overlaps into 3 pages.
 * 0                               64                               128 
 * |------------page----------------|--------------page--------------| 
 *                            |------------------data-------------------|
 *                            55                                       132 
 * The program below will split the write up as follows
 * - write 9 bytes  ( 55 to  63) - up to page boundary
 * - write 16 bytes ( 64 to  79)
 * - write 16 bytes ( 80 to  95)
 * - write 16 bytes ( 96 to 111)
 * - write 16 bytes (112 to 127) - up to page boundary
 * - write 5 bytes  (128 to 132)
 *
 * Why 16 bytes and not 32 or 64? Arduino uses a 32 byte buffer for sending
 * and receiving data so this is the maximum you can send. But this buffer
 * needs to include the memory location bytes which reduces the available 
 * space to 30 bytes. So 16 bytes is used as an easy divisor of 64.
 *
 * The overhead for these 6 writes              = 5ms x 6    = 30ms
 * The overhead for writing 78 individual bytes = 3.5ms x 78 = 273ms
*/

#include <Wire.h>
#define eeprom1 0x50    //Address of 24LC256 eeprom chip
#define WRITE_CNT 5

unsigned char rdata[32];

void setup(void)
{
  Serial.begin(9600);
  Wire.begin();  
 
  unsigned int i;
  // define large string of data to be written
  char str_data[]={"Hello-1234567890-and-abcdefghijklmnopqrstuvwxyz-Goodbye\n"};

  // Work out length of data
  char str_len=0;  
  do{ str_len++; } while(str_data[str_len]);  
  
  // Write out data several times consecutively starting at address 0
  for(i=0;i<WRITE_CNT;i++) writeEEPROM(eeprom1,i*str_len,str_data);

  // read back the data 28 bytes at a time
  // reading data doesn't suffer from the page boundary rules
  Serial.println("DATA READ");
  for(i=0;i<10;i++) {
    readEEPROM(eeprom1, (i*28), rdata, 28);
    Serial.write(rdata,28);
  }  

}
 
void loop(){
}
 
void writeEEPROM(int deviceaddress, unsigned int eeaddress, char* data) 
{
  // Uses Page Write for 24LC256
  // Allows for 64 byte page boundary
  // Splits string into max 16 byte writes
  unsigned char i=0, counter=0;
  unsigned int  address;
  unsigned int  page_space;
  unsigned int  page=0;
  unsigned int  num_writes;
  unsigned int  data_len=0;
  unsigned char first_write_size;
  unsigned char last_write_size;  
  unsigned char write_size;  
  
  // Calculate length of data
  do{ data_len++; } while(data[data_len]);   
   
  // Calculate space available in first page
  page_space = int(((eeaddress/64) + 1)*64)-eeaddress;

  // Calculate first write size
  if (page_space>16){
     first_write_size=page_space-((page_space/16)*16);
     if (first_write_size==0) first_write_size=16;
  }   
  else 
     first_write_size=page_space; 
    
  // calculate size of last write  
  if (data_len>first_write_size) 
     last_write_size = (data_len-first_write_size)%16;   
  
  // Calculate how many writes we need
  if (data_len>first_write_size)
     num_writes = ((data_len-first_write_size)/16)+2;
  else
     num_writes = 1;  
     
  i=0;   
  address=eeaddress;
  for(page=0;page<num_writes;page++) 
  {
     if(page==0) write_size=first_write_size;
     else if(page==(num_writes-1)) write_size=last_write_size;
     else write_size=16;
  
     Wire.beginTransmission(deviceaddress);
     Wire.write((int)((address) >> 8));   // MSB
     Wire.write((int)((address) & 0xFF)); // LSB
     counter=0;
     do{ 
        Wire.write((byte) data[i]);
        i++;
        counter++;
     } while((data[i]) && (counter<write_size));  
     Wire.endTransmission();
     address+=write_size;   // Increment address for next write
     
     delay(6);  // needs 5ms for page write
  }
}
 
void readEEPROM(int deviceaddress, unsigned int eeaddress,  
                 unsigned char* data, unsigned int num_chars) 
{
  unsigned char i=0;
  Wire.beginTransmission(deviceaddress);
  Wire.write((int)(eeaddress >> 8));   // MSB
  Wire.write((int)(eeaddress & 0xFF)); // LSB
  Wire.endTransmission();
 
  Wire.requestFrom(deviceaddress,num_chars);
 
  while(Wire.available()) data[i++] = Wire.read();

}

Shopping Cart
0 items
 
DELIVERY/PAYMENT

Free Delivery Orders over £50

UK - £2.40 orders under £50
Airmail from £2.95 (+VAT)
More Details...

 
Brands
 
 Check out our videos
Follow us on:
acebook