Go Down

Topic: Write & read a struct to an i2c eeprom (Read 5146 times) previous topic - next topic

Suriken

I am using this library: https://github.com/jlesech/Eeprom24C01_02
It allows the user to write & read a single byte or an array.

In my project I need to use a struct to store all my data and searching on the forum I found a topic in which a user points here: http://www.arduino.cc/playground/Code/EEPROMWriteAnything

So I created an example code in which I try to use this 2 functions with the previous library but I did something wrong or I need to change something else because it is failing. Values I read from eeprom are not the ones I wrote before.

Tjis is my test sketch:

Code: [Select]

/**************************************************************************//**
 * \brief EEPROM 24C01 / 24C02 library for Arduino - Demonstration program
 * \author Copyright (C) 2012  Julien Le Sech - www.idreammicro.com
 * \version 1.0
 * \date 20120217
 *
 * This file is part of the EEPROM 24C01 / 24C02 library for Arduino.
 *
 * This library is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 ******************************************************************************/

/**************************************************************************//**
 * \file WriteReadByte.ino
 ******************************************************************************/
 
/******************************************************************************
 * Header file inclusions.
 ******************************************************************************/

#include <Wire.h>

#include <Eeprom24C01_02.h>

/******************************************************************************
 * Private macro definitions.
 ******************************************************************************/

/**************************************************************************//**
 * \def EEPROM_ADDRESS
 * \brief Address of EEPROM memory on TWI bus.
 ******************************************************************************/
#define EEPROM_ADDRESS  0x50

/******************************************************************************
 * Private variable definitions.
 ******************************************************************************/

static Eeprom24C01_02 eeprom(EEPROM_ADDRESS);

/******************************************************************************
 * Public function definitions.
 ******************************************************************************/

/**************************************************************************//**
 * \fn void setup()
 *
 * \brief
 ******************************************************************************/
typedef struct
{
int a;
int b;
int c[2];
} test;

test t;
test tt;

void setup()
{
  t.a    = 1;
  t.b    = 2;
  t.c[0] = 3;
  t.c[1] = 4;

    Serial.println("ORIG bytes:");
    Serial.println(tt.a);
    Serial.println(tt.b);
    Serial.println(tt.c[0]);
    Serial.println(tt.c[1]);
    // Initialize serial communication.
    Serial.begin(9600);
       
    // Initialize EEPROM library.
    eeprom.initialize();

    const byte address = 0;
   
    // Write a byte at address 0 in EEPROM memory.
    Serial.println("Write byte to EEPROM memory...");
    EEPROM_writeAnything(0, t);
   
    // Write cycle time (tWR). See EEPROM memory datasheet for more details.
    delay(10);
   
    // Read a byte at address 0 in EEPROM memory.
    Serial.println("Read byte from EEPROM memory...");
   
    EEPROM_readAnything(0, tt);
    Serial.println("Read bytes:");
    Serial.println(tt.a);
    Serial.println(tt.b);
    Serial.println(tt.c[0]);
    Serial.println(tt.c[1]);
}

/**************************************************************************//**
 * \fn void loop()
 *
 * \brief
 ******************************************************************************/
void loop()
{

}

template <class T> int EEPROM_writeAnything(int ee, const T& value)
{
    const byte* p = (const byte*)(const void*)&value;
    unsigned int i;
    for (i = 0; i < sizeof(value); i++)
          eeprom.writeByte(ee++, *p++);
    return i;
}

template <class T> int EEPROM_readAnything(int ee, T& value)
{
    byte* p = (byte*)(void*)&value;
    unsigned int i;
    for (i = 0; i < sizeof(value); i++)
          *p++ = eeprom.readByte(ee++);
    return i;
}


And this is my output:
Code: [Select]

Write byte to EEPROM memory...
Read byte from EEPROM memory...
Read bytes:
4865
-1
-1
-1


Any idea about what is happening? It could be related with the size of data I want to write? ints taking 2 bytes....

chucktodd

I am using this library: https://github.com/jlesech/Eeprom24C01_02
It allows the user to write & read a single byte or an array.

In my project I need to use a struct to store all my data and searching on the forum I found a topic in which a user points here: http://www.arduino.cc/playground/Code/EEPROMWriteAnything

So I created an example code in which I try to use this 2 functions with the previous library but I did something wrong or I need to change something else because it is failing. Values I read from eeprom are not the ones I wrote before.

You might be running into a problem with your Device's WritePage size.

Depending on exactly which EEPROM you are using(brand and Model), For instance a MicroChip 24LC01B 128byte EEPROM.  Which has a 8byte WritePage.

Any Single Write must fit within one of the 8 byte write pages:

Address: 0x00..0x07
Address: 0x08..0x0F
Address: 0x10..0x17
Address: 0x18..0x1F
Address: 0x20..0x27
Address: 0x28..0x2F
Address: 0x30..0x37
Address: 0x38..0x3F
Address: 0x40..0x47
Address: 0x48..0x4F
Address: 0x50..0x57
Address: 0x58..0x5F
Address: 0x60..0x67
Address: 0x68..0x6F
Address: 0x70..0x77
Address: 0x78..0x7F

If your structure is 10 bytes long and you use the WriteAnything() Template to save it at address 0x14.

char buffer[10] = "ABCDEFGHI"; // with appended null

What you expect the EEPROM to store:

EEPROM memory Map( as HexiDecimal Characters):

0x10: xx xx xx xx 41 42 43 44
0x18: 45 45 45 48 49 00 xx xx

What is actually Stored:

0x10: 45 46 47 48 49 00 43 44


The way an EEPROM works, is that it has to erase the memory cells before it can change them.

When you issue a Write command, you do send the address followed by the data.

Code: [Select]

Wire.beginTransaction(I2CChipAddress);
Wire.write((byte)address); // starting point were to store data
Wire.Write((byte)firstDataByte);
Wire.Write((byte)secondDataByte);
Wire.Write((byte)thirdDataByte);
//...
Wire.Write((byte)lastDataByte); // all of these Wire.write() commands actually just buffer the data
Wire.endTransaction(); // this command actually does all of the transmitting to the EEPROM.



The EEPROM takes the first byte of the write sequence, stores it as the 'address pointer',
Calculates which WritePage is going to be changed: Page=addressPoint / WritePageSize;
then copies this page from the EEPROM array into the WritePageBuffer.

then calculates the offset into the WritePageBuffer:  offset= addressPointer | WritePageSize;

then it starts overwriting it's WritePageBuffer one byte at a time with the data it is receiving.  If the offset pointer is incremented past the end of the WritePageBuffer, it restarts at the beginning of the buffer: offset=0;

After the last byte is received the Chip then Erases the Complete Page in the EEPROM array, then it writes the 'new' values from the WritePageBuffer.

So, to store data into an EEPROM you must break your data into pieces that fit on it's pages.

For my example:
Ten bytes of data, stored in addresses 0x14 through 0x1D.  Two Write operations are required.  because this address range occupies two of the 8byte Write pages.

Code: [Select]

char buffer[10] ="ABCDEFGHI"; // remember the terminating Null!

// now we must wait for the EEPROM chip to actually to complete any prior Write cycle

bool ready=false;
while(!ready){
  Wire.beginTransmission(I2CEEPROMAddress);
  ready = Wire.endTransmission()==0; // Chip answered!, prior Write Cycle has completed!
  }

Wire.beginTransmission(I2CEPPROMAddress);

Wire.write((byte)0x14); // starting address in EEPROM
for(byte i = 0; i<4; i++){
  Wire.write((byte)buffer[i]);
  }
Wire.endTransmission(); // send the first partial page of data

// now we must wait for the EEPROM chip to actually complete the Write cycle

bool ready=false;
while(!ready){
  Wire.beginTransmission(I2CEEPROMAddress);
  ready = Wire.endTransmission()==0; // Chip answered!, prior Write Cycle has completed!
  }

Wire.beginTransmission(I2CEPPROMAddress);

Wire.write((byte)0x18); // starting address of next page EEPROM
for(byte i = 4; i<10; i++){
  Wire.write((byte)buffer[i]);
  }
Wire.endTransmission(); // send the first partial page of data


As a note, Reading from the Chip is not limited to WritePageSize chunks, once you specify the starting address, the chip directly accesses the EEPROM array.

Chuck.
Currently built mega http server, Now converting it to ESP32.

Suriken

#2
Feb 26, 2017, 10:25 am Last Edit: Feb 26, 2017, 11:01 am by Suriken
Thank you Chucktodd. I think I understand it better now.

I bought the eeprom on ebay, it is labeled as 24C01WP 8Kb.
 
In my real project I have an struct about 240bytes. I was thinking on a function in which I pass the whole struct and save/read it completelly. I thought it would be easier. I think I will have to read a bit about what you just explained and try to implement myself for my struct. Draw a memory map, give positions to every variable....

One more question. If I try to save a 2 bytes value (like int) or 4 bytes (like float) I have to split them into bytes and then, save each one of those bytes in each position.
Then, when I want to read, I will read all bytes and join them to get my value, is this correct? I am going to start writing a map of my memory and put all my values in their positions before coding it.

chucktodd

#3
Feb 28, 2017, 03:25 am Last Edit: Feb 28, 2017, 03:37 am by chucktodd Reason: additional Info
Thank you Chucktodd. I think I understand it better now.

I bought the eeprom on ebay, it is labeled as 24C01WP 8Kb.
 
In my real project I have an struct about 240bytes. I was thinking on a function in which I pass the whole struct and save/read it completelly. I thought it would be easier. I think I will have to read a bit about what you just explained and try to implement myself for my struct. Draw a memory map, give positions to every variable....

One more question. If I try to save a 2 bytes value (like int) or 4 bytes (like float) I have to split them into bytes and then, save each one of those bytes in each position.
Then, when I want to read, I will read all bytes and join them to get my value, is this correct? I am going to start writing a map of my memory and put all my values in their positions before coding it.
You will have to break all data you WRITE to the chip into blocks that fit on One WritePage.  

Your 24C01 is NOT 8kilobit, it is 1kilobit or 128Bytes.  A 24C08 is 8kilobits or 1,024bytes.

I would recommend you write a function() that does write page handling by itself.

Code: [Select]

bool writeI2CBlock(uint8_t i2cChipAddress, uint16_t dataAddress, char* data, uint16_t length, uint8_t WritePageSize, bool sixteenBit=false){

/*
I2cChipAddres is the 7bit chip address, usually 0x50 .. 0x57
dataAddress is the location to store at
data is a pointer to to info to be written to the chip
length is howmany bytes to send to the chip
WritePageSize is the WritePageSize of the chip (in your case: 8)
sixteenBit controls whether to send one byte of address or two bytes, your chip only wants One,
  a 24LC32 to 24LC512 (4k to 64kByte) expect two bytes of address
*/
}


Here is the Code I use to write to 24LC32 to 24LC512 I2C EEPROMS.
Code: [Select]


bool writeI2CBin(const uint8_t id,uint16_t adr, char data[],const uint16_t len,const uint8_t pageSize){
// Have to handle write page wrapping,
// 24lc512 has 128 byte
// 24lc64 has 32 byte
/*
Serial.print("writeBin(0x");
Serial.print(id,HEX);
Serial.print(",0x");
Serial.print(adr,HEX);
Serial.print(",len=");
Serial.println(len,DEC);
*/
uint16_t bk = len;
bool abort=false;
uint8_t i;
uint16_t j=0;
uint32_t timeout;
uint16_t mask = pageSize-1;
while((bk>0)&&!abort){
 i =30;
 if(i>bk) i=bk;
 if(((adr)&~mask)!=((((adr)+i)-1)&~mask)){ // over page!
  i = (((adr)|mask)-(adr))+1;
// Serial.print("adjust i =");
// Serial.println(i,DEC);
 }
 timeout = millis();
 bool ready=false;
 while(!ready&&(millis()-timeout<10)){
 Wire.beginTransmission((uint8_t)id);
 ready=(Wire.endTransmission(true)==0); // wait for device to become ready!
 }
 if(!ready){
 abort=true;
 Serial.print(id,HEX);
 Serial.println(F(" chip timeout"));
 break;
 }
// Serial.print("adr=");Serial.print(*adr,HEX);
// Serial.print(":");
 Wire.beginTransmission((uint8_t)id);
 Wire.write((uint8_t)highByte(adr));
 Wire.write((uint8_t)lowByte(adr));

 bk = bk -i;
 adr = (adr) + i;

 while(i>0){
// Serial.print(" ");
// Serial.print((uint8_t)data[j],HEX);
 Wire.write((uint8_t)data[j++]);
 i--;
 }
 
 uint8_t err=Wire.endTransmission();
 if(err!=0){
 Serial.print(F("write Failure="));
 Serial.println(err,DEC);
 abort = true;
 break;
 }
// else Serial.println();
 }
return !abort;
}



My code not handle a write request that overflows the memory size of the EEPROM.

If you use a 8kB (24LC32) chip, valid addresses range 0x0000 ..  0x0FFF
and tell this routine to write 100 bytes starting at address 0x00fff, it will store the first byte correctly, but the other 99 will be from 0x0000 to 0x0062.

[edit, add]
One other note: the 24C01 thru 24C16 all use one 8bit byte of address, since a byte can only store 0..255, and a 24C16 is 16kilobits (2,048bytes) the other 3 bits of address is sent by modifying the I2CChipAddress:

to access byte at data address 2000(dec) or 0x7D0(hex) the I2CChip Address becomes 0x57, and 0xD0 is sent as the byte address.  This means that only One 24C16 can exist on the I2C bus at a time.

With 24C32, which use 16bit address you can have 8 of them on the same I2C buss 0x50 .. 0x57.

I use a memory board with 8 24LC512 to store 512kbytes of data, 8 chips each with 64kbytes.


Chuck.
Currently built mega http server, Now converting it to ESP32.

cattledog

Quote
// 24lc512 has 128 byte
Do you have to modify the buffer size in the Wire library for writing pages sizes > 32?

chucktodd

Do you have to modify the buffer size in the Wire library for writing pages sizes > 32?
If you wanted to send 128 bytes at a time, yes.

I wrote my writeI2CBin() function to use the 32 byte Wire.h limits.

It only sends a maximum of 30 data bytes at a time, +2 address bytes for 32byte Wire.h Limit.

The WritePage size is used to decide when to break a write into multiple pieces.

If I send a 200 byte block to the writeI2CBin() function, and WritePage is 128.  at a minimum, The routine will executed 7, 30byte data blocks.  Depending on how the address aligns with the WritePage, an extra 1 or 2 block may be required.

  • The routine starts by trying to send 30 bytes.
      If 30 bytes aren't available, set size to available bytes.
  • Then it check so see if a WritePage boundary would be crossed by this block.
      If so, it shortens the block to stop at the boundary.
  • Then it actually writes the adjusted block.
  • Then it updates the next block address, checks to see if it is done.
  • Else jumps back to 1. and sends the next block


Chuck.
Currently built mega http server, Now converting it to ESP32.

Go Up