Reading from eeprom

Hello.

I'm not using Arduino directly for what I'm trying to do. In this case I'm using Arudino just as a programmer to the ESP01 module I'm using.

So, my goal is to be able to write a string to an eeprom chip (Microchip 24LC16B) and then, be able to read that same string an print it out to the serial monitor.

I'm using the Wire library from Arduino an my code is the following:

#include <Wire.h>

#define GOT_HERE()  do {Serial.print('['); Serial.print(__LINE__); Serial.println(']'); Serial.flush();} while(0)
#define MAX_BUFFER_ADDR_PK 102 
#define SDA_PIN 4
#define SCL_PIN 5

const int I2C_SLAVE_ADDR0 = 0x50;
const int I2C_SLAVE_ADDR1 = 0x51;
const int I2C_SLAVE_ADDR2 = 0x52;
const int I2C_SLAVE_ADDR3 = 0x53;
const int I2C_SLAVE_ADDR4 = 0x54;
const int I2C_SLAVE_ADDR5 = 0x55;
const int I2C_SLAVE_ADDR6 = 0x56;
const int I2C_SLAVE_ADDR7 = 0x57;

char addrpk[MAX_BUFFER_ADDR_PK] = {};
bool ok = true;
int writt = 0;
int status = 0;

void setup(){
    Serial.begin(115200);
    Wire.begin(/*SDA_PIN, SCL_PIN*/);
    // Wire.setClock(100);
}

void loop() {
    strcpy(addrpk, "{\"bc1qjaz2248t09y98vaqguq2560t7kclhesapce5sf\":\"KxdK7M6b148NsEyH6EvR91xSWN27uzCxYfhgdRznZEZfX7Kkr9Rh\"}"); 
    using periodic = esp8266::polledTimeout::periodicMs;
    static periodic nextPing(1000);

    if(nextPing && ok){
        // GOT_HERE();
        Wire.beginTransmission(I2C_SLAVE_ADDR7);
        // GOT_HERE();
        writt = Wire.write(addrpk);
        Serial.print("Written ");
        Serial.print(writt);
        Serial.println(" bytes!");
        // GOT_HERE();
        Serial.print("Wire endTrans: ");
        Serial.println(Wire.endTransmission());
        // GOT_HERE();
        status = Wire.requestFrom(I2C_SLAVE_ADDR7, strlen(addrpk));
        Serial.print("Wire reqFrom: ");
        Serial.println(status);
        // GOT_HERE();
        while( Wire.available() ){
            char c = Wire.read();
            Serial.print(c);
            // GOT_HERE();
        }
        ok = false;
    } 
}

So, I am printing out the return of Wire.endTransmission() which returns 0. This means the write procedure was successeful. Then I also print out the return of Wire.Write() which returns 101 as expected.

When I try to read from the eeprom, the return of Wire.reqFrom() is 0. I can't figure out why so far.
This is what I see in the Serial Monitor:

�l`��o�lWritten 101 bytes!

Wire endTrans: 0

Wire reqFrom: 0

Any help is appreciated.
Thanks Psy

This may not solve the problem, but strncpy() will prevent you from writing past the end of the allocated output array.

Keep in mind that the Wire read/write buffer is only 32 bytes long. I don't know what happens in your case.

In my case, I'm using ESP01, which has a 128 bytes buffer!

Could you elaborate a bit further about strncpy(), please?

https://cplusplus.com/reference/cstring/strncpy/

What I meant was why would it be better than strcpy() itself!

Writing past the bounds of an array can cause extreme and completely unpredictable difficulties with program operation.

Doing so is one of the more common reasons beginners post on this forum, with a title like "my program doesn't work".

Well, I tried to use strncpy() but it got worse than what it was before and therefore I decided to simplify things and simply initialize the string directly when I declared it.

However, I'm still not being able to read from the eeprom. Only write into it.
The code is this now:

#include <Wire.h>

#define GOT_HERE()  do {Serial.print('['); Serial.print(__LINE__); Serial.println(']'); Serial.flush();} while(0)
#define MAX_BUFFER_ADDR_PK 102 
#define SDA_PIN 4
#define SCL_PIN 5

const int I2C_SLAVE_ADDR0 = 0x50;
const int I2C_SLAVE_ADDR1 = 0x51;
const int I2C_SLAVE_ADDR2 = 0x52;
const int I2C_SLAVE_ADDR3 = 0x53;
const int I2C_SLAVE_ADDR4 = 0x54;
const int I2C_SLAVE_ADDR5 = 0x55;
const int I2C_SLAVE_ADDR6 = 0x56;
const int I2C_SLAVE_ADDR7 = 0x57;

char addrpk[] = {"\{\"bc1qjaz2248t09y98vaqguq2560t7kclhesapce5sf\":\"KxdK7M6b148NsEyH6EvR91xSWN27uzCxYfhgdRznZEZfX7Kkr9Rh\"\}"};
bool ok = true;
int writt = 0;
int status = 0;

void setup(){
    Serial.begin(115200);
    Wire.begin(SDA_PIN, SCL_PIN);
    // Wire.setClock(100);
}

void loop() {
    using periodic = esp8266::polledTimeout::periodicMs;
    static periodic nextPing(1000);

    if(nextPing && ok){
        // GOT_HERE();
        Wire.beginTransmission(I2C_SLAVE_ADDR7);
        // GOT_HERE();
        writt = Wire.write(addrpk);
        Serial.print("Written ");
        Serial.print(writt);
        Serial.println(" bytes!");
        // GOT_HERE();
        Serial.print("Wire endTrans: ");
        Serial.println(Wire.endTransmission());
        // GOT_HERE();
        status = Wire.requestFrom(I2C_SLAVE_ADDR7, strlen(addrpk));
        Serial.print("Wire reqFrom: ");
        Serial.println(status);
        // GOT_HERE();
        while( Wire.available() ){
            char c = Wire.read();
            Serial.print(c);
            // GOT_HERE();
        }
        ok = false;
    } 
}

I get 101 bytes written but always get 0 bytes read!
Any suggestions on what can I be doing wrong?

First, I think you are not managing the pointers to specific write/read addresses correctly. I think this is because the esp8266 wire.h master writer example is based on a slave using .onReceive() and .receiveEvent(). The eeprom requires more specific memory address management.

Second, I believe that you are running into page boundary issues with your bulk writes.

I would try your code with writing and reading the individual bytes(chars) of your long message string with a specific eeprom memory address specified and incremented. If you get the data stored and retrieved correctly, then you can work to speed up and optimize the routines.

void writeEEPROM(byte address, byte val, int i2c_Address)
{
  Wire.beginTransmission(i2c_Address);
  Wire.write(address); //one byte memory address for small eeprom
  Wire.write(val);
  Wire.endTransmission();
  delay(5);
}

// Function to read from EEPROM
byte readEEPROM(byte address, int i2c_Address)
{
  byte rcvData = 0xFF;

  Wire.beginTransmission(i2c_Address);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(i2c_Address, 1);
  rcvData =  Wire.read();
  return rcvData;
}

Ok, I can try that. However, if I understand, the I2C address of the device, in this case, as this is an eeprom with multiple memory blocks, each block is referred to as it's own I2C address and that is the memory address I should write/read to/from.

So, from your code, what would be the difference between address and i2c_address?
As an example, this eeprom has 8 memory blocks, starting at 0x50 and ening at 0x57. So, if I run an I2C scan code, I will find 7 devices on the I2C bus that are in fact, the 8 memory blocks of this single device. If I want to write/read to/from the 3rd block, the address I should use is 0x52. So, I'm not sure what would I need to use in your code in address and i2c_address parameters!

Each of the 8 memory blocks has individual byte storage address running from 0 to 255.

The i2c address is one of the block addresses. The address is one of the specific memory locations within that block.

It's like there are a bunch of storage lockers in a station. The station is at the i2c address, and each of the storage lockers has an individual address so you know where to save and retrieve stuff.

The difference with your eeprom and most of the external eeproms discussed in tutorials is that there are only 255 lockers in each station and you don't use the typical two byte locker address as you would in an eeprom with say 24K addresses.

Hum, ok. I understand better now.
Just one more detail. In the datasheet, Section 6.3 Write Protection, they say that this setting allows the user to write (or not) the entire array from 0x000 to 0x7ff. But this doesn't match what you people say, does it? 255 is 0xff. I just need to clear this question, I guess! :slight_smile:

I think there is some confusion between bits and bytes.

The eeprom is 16K bits, but since you read and write by bytes its easier to think of the eeprom as 2K bytes, organized into 8 x 255 byte blocks.

It looks like the write protect is global and not block by block.

The EEPROM type is: 24LC16. It's features are:
Bit Capacity: 16x1024 bits
Byte Capacity: 2046 (16x1024/8)
Number of Blocks = 8 (2046/256)
Page Size : 16 bytes (number of locations that can be written in ONE GO)
No of Slave in each EEPROM = Number of Blocks = 8
Slave/Device Address of Block-0 : 1010000 (always 7-bit) (0x50 in Hex)
Slave/Device Address of Block-1 : 1010001 (0x51)
.......................................................................................
Slav/Device Address of Block-7: 1010111 (0x57)
Internal Address Range for Each Block of EEPROM: 0x00 - 0xFF
Page Address range of each Block: 0x00 - 0x0F, 0x10 - 0x1F, ...., 0xF0 - 0xFF

Sketch to write 0x01, 0x02, ..., 0x10 into locations 0x00 - 0x0F of Block-0.

#include<Wire.h>
byte myData[] = 
{
    0x01, 0x02, 0x03,, 0x04, 0x05, 0x06, 0x07, 0x08, 
    0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};

byte rdData[16];

void setup()
{
    Serial.begin(9600);
    Wire.beginTransmission(0x50);
    Wire.write(0x00);  //pointing at first EEPROM location of lock-0
    Wire.write(myData, sizeof myData);
    Wire.endTransmission();
    delay(2);  //write delay
    //------reading back and display---------
    Wire.beginTransmission(0x50);
    Wire.write(0x00);  //pointing at first EEPROM location of lock
    Wire.endTransmission();

    Wire.requesFrom(0x50, 16);  //requesting for 16-byte data
    for(int i = 0; i<16; i++)
    {
       Serial.print(Wire.read(), HEX);
       Serial.print(' ');   //inserting space between bytes display
    }
}

void loop(){}

Thanks @GolamMostafa

I don't want to use your code as is because I won't learn much. I need to make some changes and try to understand, if it doesn't work, why it isn't working. If I simply copy paste your code, it will probably run flawlessly and I won't learn much. I need to go through the pain of code not working and fix it step by step until it works and understand what was wrong in the proccess!

So, I am trying this code:

#include <Wire.h>

#define GOT_HERE()  do {Serial.print('['); Serial.print(__LINE__); Serial.println(']'); Serial.flush();} while(0)
#define MAX_BUFFER_ADDR_PK 102 
#define SDA_PIN 4
#define SCL_PIN 5

const int I2C_SLAVE_ADDR7 = 0x57;

char addrpk[] = {"\{\"bc1qjaz2248t09y98vaqguq2560t7kclhesapce5sf\":\"KxdK7M6b148NsEyH6EvR91xSWN27uzCxYfhgdRznZEZfX7Kkr9Rh\"\}"};
bool ok = true;

void writeEEPROM(byte address, byte val, int i2c_address){
    Wire.beginTransmission(i2c_address);
    Wire.write(address);
    Wire.write(val);
    delay(5);
}

byte readEEPROM(byte address, int i2c_address){
    byte rcvData = 0xff;

    Wire.beginTransmission(i2c_address);
    Wire.write(address);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address, 1);
    rcvData = Wire.read();
    return rcvData;
}

void setup(){
    Serial.begin(115200);
    Wire.begin(SDA_PIN, SCL_PIN);
    // Wire.setClock(100);
}

void loop() {
    // using periodic = esp8266::polledTimeout::periodicMs;
    // static periodic nextPing(1000);

    if(/*nextPing &&*/ ok){
        Serial.println("Wrtting onto eeprom...");
        for(uint8_t i = 0; i < 10; i++){
            Serial.print("Byte to write is:");
            Serial.println(0x61 + i, HEX);
            writeEEPROM(0x00 + i, 0x61 + i, I2C_SLAVE_ADDR7);
        }
        Serial.print("Write operation return: ");
        Serial.println(Wire.endTransmission());
        Serial.println("Reading from eeprom now...");
        for(uint8_t i = 0; i < 10; i++){
            Serial.print("Char ");
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.println(readEEPROM(0x00 + i, I2C_SLAVE_ADDR7), HEX);
            delay(5);
        }
        ok = false;
    } 
}

but te result is this:

d`��g�dWrtting onto eeprom...

Byte to write is:61

Byte to write is:62

Byte to write is:63

Byte to write is:64

Byte to write is:65

Byte to write is:66

Byte to write is:67

Byte to write is:68

Byte to write is:69

Byte to write is:6A

Write operation return: 0

Reading from eeprom now...

Char 1: FF

Char 2: FF

Char 3: FF

Char 4: FF

Char 5: FF

Char 6: FF

Char 7: FF

Char 8: FF

Char 9: FF

Char 10: 6A

The only thing different I think I'm doing, is requesting a single byte 10 times in a for loop while you requested the 16 bytes all at once, right? So, why am I getting different results from the ones I wrote to the eeprom? I mean, lead me in the right direction, don't actually tell me the exact error!

I appreciate your intention of learning yourself in which my codes may work as reference in case your need to fix your codes.

It is always good to follow SSS Strategy to build a project of any complexity level. SSS stands for "Small Start Strategy".

While working with I2C Protocol, it is very important that we have clear meanings/understandings about Slave/Device Address and I2C (Bus) Address.

Slave/Device Address is always 7-bit. It is the address that we use in the following code lines:

#define slaveAddress 0b1010111  //I avoid 0x57 not to confuse with: 01010111
Wire.beginTransmission(slaveAddress);  
Wire.requestFrom(slaveAddress, n);    //n = number of bytes requested

I2C (bus) Address is always 8-bit which is automatically created from the 7-bit Slave Address by the I2C-Master.

During "Write Mode" (beginTransmission()) operation, the Master puts a zero (0) at the right-most position of the 7-bit Slave Address in order to make it 8-bit which satisfies the byte-oriented feature of I2C Protocol.

During "Read Mode" (requestFrom()) operation, the Master puts a one (1) at the right-most position of the 7-bit Slave Address in order to make it 8-bit which satisfies the byte-oriented feature of I2C Protocol.

1. Make correct connection between EEPROM and UNO having 2x4.7k pull-up resistors for the I2C Bus. Do not forget to connect the GND line between them.

2. Write 1-byte data (say: 0x23) at location 0x2F of Block-7 (slaveAddress: 0b1010111) of the 24LC16B EEPROM.

3. Read the data back from the same location and show on the Serial
Monitor.

Would appreciate to see your sketch to accomplish the above tasks.

You are jumping to multi byte writes before figuring out the single byte write. For the multi byte writes, the length of the written buffer needs to be considered.

For the single byte write the code should be like this

for (uint8_t i = 0; i < 10; i++)
{
  Serial.print("Byte to write is:");
  Serial.println(0x61 + i, HEX);
  writeEEPROM(0x00 + i, 0x61 + i, I2C_SLAVE_ADDR7);
}


void writeEEPROM(byte address, byte val, int i2c_address)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(address);
  Wire.write(val);
  Wire.endTransmission();
  delay(5);
}

Once you have this working correctly, we can move to a writeBytes() function.

Yes, I already understood that each block has its own memory address in the I²C bus (0x50 to 0x57) and each of these blocks can be written at memory address 0x00 up to 0xff.

I understand this very well. No problem here. It can't be more clear that what the datasheet says. My struggle is that I hate to have to deal with 3-bit stuff when it comes to hexadecimal format numbers. I mean, if I'm given 0x4ac, I have to mentally transform this to a 4-bit format number, therefore I'm "presented" with the following dilema: is it 0x04ac or is it 0x4ac0. I know it's the former, but I know it because while typing this message I already did the transformation mentally. But I had to do it! It doesn't come to me naturally.

I'm also aware of this. The datasheet is also crystal clear about this. The LSB of the Control Byte (or Slave Address) is the bit that defines a write / read operation.
image

I'm already using 10k pull-ups...

Will do so.

@cattledog allow me to follow @GolamMostafa suggestions or I will get lost easily if I try to do multiple things at once.
I'm going to take a step back to a single write / read an then will return to my code that originated this thread!

Edited;
One question about the reference code.
For the read operation, we begin transmission, write the memory address where we want to read and only then we provide the block I²C bus memory address in the requestFrom() function? This part is not intuitive to me. I mean, it would make more sense the following order:
1 - Begin I²C protocol -> Wire.beginTransmission(I2C_SLAVE_ADDR7);
2 - point to the memory address within the block memory address on the I²C bus -> Wire.write(0x2f);
3 - Fetch the data into internal buffer -> Wire.requestFrom(I2C_SLAVE_ADDR7, sizeof(0x23))
4 - read the data from the internal buffer and print it to serial port -> Serial.println(Wire.read())
5 - Release the I²C bus -> Wire.endTransmission()

@GolamMostafa ok, I think I made it work as you asked.
Here is the code I used:

#include <Wire.h>

#define SDA_PIN 4
#define SCL_PIN 5

const int I2C_SLAVE_ADDR7 = 0x57;
bool ok = true;
byte myData = 0x23;

void setup(){
    Serial.begin(115200);
    Wire.begin(SDA_PIN, SCL_PIN);
}

void loop(){
    if(ok){
        Wire.beginTransmission(I2C_SLAVE_ADDR7);
        Wire.write(0x2f);
        Wire.write(myData);
        Wire.endTransmission();
        delay(5);
        /* ---- Read from EEPROM ---- */
        Wire.beginTransmission(I2C_SLAVE_ADDR7);
        Wire.write(0x2f);
        Wire.endTransmission();
        Wire.requestFrom(I2C_SLAVE_ADDR7, sizeof(myData));
        Serial.print("Byte read: ");
        Serial.println(Wire.read(), HEX);
        ok = false;
    }
}

When the data sheet says about 2 ms for write delay, is there any good reason to use 5 ms?

The rationality of the use of ok flag is not clear to me.

To be honest, I don't even remember to read 2ms for write delay. I'll check the datasheet again to see if I can find that info, on my own.
The datasheet actually says 5ms per byte or page for Write Cycle Time. (datasheet, page 3, Table 1-2)

The ok flag is just to prevent the code to be writing the very same thing over and over again. I know this is paranoid, but the chip has a theoretical life span stated in read / write cycles (1 million), right?
(datasheet, also page 3, Table 1-2)