I2C / TWI - detect busy

Working with an 24FC256 EEPROM

Got it working, but after sending data to it, once the Stop condition is received, it enters an internal write cycle, which means it will not generate Acknowledge signals, according to datasheet.

My test program consists of writing 30 bytes, then reading them back. I got the read-function working by checking the return value from Wire.requestFrom(), trying again until it delivers the bytes I asked for, but I haven't found a way for multiple writes in series, perhaps other than reading a byte inbetween ...

Advice?

#include <Wire.h>

#define DEVICE_ADDR 0x50

#define WRITE_COUNT 30  
  // Wire.h writebuffer is 32, but we send a 2 byte address, so max 30 data bytes
  // (EEPROM page size = 64 bytes)

byte buf[WRITE_COUNT];

/*
 * At 400k writing a 30 bytes block takes 1 ms, while
 * reading the same takes 4 ms. I would like it to be
 * faster on read, but it works now!
 * 
 * The 24LC256 has 64 bytes page size. The Wire library 
 * has 32 bytes buffer, which given that the address 
 * is 2 bytes, means we can write 30 bytes.
 * 
 * After writing a number of bytes, the device enters a
 * write cycle, where it does not respond normally.
 * 
 * For the immediate read, we detect this, by checking
 * return value from requestFrom(), which should be the 
 * number of bytes we asked for.  
 * 
 */
void setup() {
  Serial.begin(9600);
  
  Wire.begin();
  Wire.setClock(400000);

  while (!Serial) ;

  for (int i=0; i<WRITE_COUNT; i++) buf[i]=i;

  long start=millis();
  writeData(0,buf);
  //writeData(128,buf); // <-- second write without delay fails both writes
  
  long end=millis();
  Serial.print("Writing done: ");
  Serial.println(end-start);
  
  start=millis();
  readBytes(0,buf);
  end=millis();
  
  Serial.print("Reading ");
  Serial.print(WRITE_COUNT);
  Serial.print(" bytes = ");
  Serial.println(end-start);
  for (int i=0; i<WRITE_COUNT; i++) Serial.println(buf[i]);
}

void writeData(unsigned int address, byte *buf) {

  for (;;) {
    Wire.beginTransmission(DEVICE_ADDR);
  
    Wire.write((address >> 8) & 0xFF); // MSB
    
    Wire.write((address & 0xFF)); // LSB
  
    for (int i=0; i<WRITE_COUNT; i++) {
      Wire.write(buf[i]);
    }
    Wire.endTransmission();  // <-- works for single call to writeData or with 5 ms delay inbetween
    
    //int count = Wire.endTransmission();
    
    //if (count != WRITE_COUNT + 2) continue;
      // +2 for address
    break;
  }
 
}

/*
 * Seems that if we do not wait long enough after writing data, we
 * have no way of detecting that when trying to read, ending in a
 * situation where 
 */
int readBytes (unsigned int address, byte *buf) {
  for (;;) {
    Wire.beginTransmission(DEVICE_ADDR);
    Wire.write((int)(address >> 8)); // MSB
    Wire.write((int)(address & 0xFF)); // LSB
  
    Wire.endTransmission();
    /*Serial.print("endTransmission returns ");
    Serial.println(count);
    if (count != 2) {
      // device busy?
      Serial.println("Busy");
      continue;
    }*/

    int count = Wire.requestFrom(DEVICE_ADDR, WRITE_COUNT);
    //Serial.print("requestFrom: ");
    //Serial.println(code);
    if (count != WRITE_COUNT) continue;
    
    for (int i=0; i<WRITE_COUNT; i++) {
      while (!Wire.available());
      buf[i]=Wire.read();
    }
    return 0;
  }
  return -1; // not found
}

void loop() {
  // put your main code here, to run repeatedly:

}

This is not okay:

while (!Wire.available());

The waiting after a Wire.requestFrom() is a myth.

In Arduino code, we don't use endless while- or for-loops.

The Wire.endTransmission() already returns a error code that tells if the device did a ACK or NACK.
As you can see in the library of RobTillaart, the Wire.endTransmission() is used to check if the device is ready: https://github.com/RobTillaart/I2C_EEPROM/blob/master/I2C_eeprom.cpp#L373
It has a timeout of 5 milliseconds for safety.

2 Likes

Okay thanks. The Arduinio.cc Reference on the Wire library (Wire - Arduino Reference) has no mention of what Wire.endTransmission() returns. Will test this later. Hoped to avoid doing 5ms sleeps.

The initial test, writing one block of 30 bytes, then reading it back, indicated the write took about 1 ms, and read back about 3, so a 5 ms delay between them would reduce throughput significantly.

The timeout of 5 milliseconds is not used !
That is a safety feature, in case the EEPROM was disconnected from the I2C bus.
In a normal situation, as soon as the EEPROM gives no error from a Wire.endTransmission(), then the library continues.

The Arduino documentation has removed the Wire.endTransmission page. It is in limbo here: https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/

Don't use one of the numbers of the return value. If the return is 0, then it is okay. That is all you have to know.

Yes, and that one can do beginTransmission(), send zero bytes, then endTransmission() and check result, until it gets to 0, as illustrated by you first link. Very helpful, thanks. Will test it tonight. :slight_smile:

It worked perfectly for both read and write. Thanks!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.