Arduino I2C Slave to hold I2C bus when buffered writing to EEPROM

I have an Arduino as I2C Slave. I can write to it at 8kbyte/s.
But when I write to the EEPROM of the Slave, the I2C speed is only 300 byte/s.
I want to know how to implement buffered writing to the EEPROM.

In the Slave, the function receiveEvent() is set with Wire.onReceive(), and it receives the bytes that are written by the Master.
Normally I would write to the EEPROM in the function receiveEvent().
That will slow down the I2C transfer to 300byte/s. That is very slow.
I could write to a buffer in ram, and write to the EEPROM in the background, in the loop() function.
That is however a problem for the next I2C data. I have to hold the I2C bus while that buffer is not empty yet.
I can not do that in receiveEvent() since that is an interrupt function, and the EEPROM is written in the background.

Is there a way to implement a buffer for writing to the EEPROM ?

Is there a way to implement a buffer for writing to the EEPROM ?

Yes, there is. Implement a circular buffer (Circular buffer - Wikipedia). You have to stop interrupts while change the buffer pointers but while writing to the EEPROM (that's a slow operation) you're able to receive.
Just keep in mind that you cannot save more than 300 bytes per second to the EEPROM because writing a byte to the EEPROM takes approx. 3ms. So if you get short bursts of data you can keep in RAM you can speed that up but if you're receiving a continuous stream of data, you won't get beyond 300 B/s.

With an ATmega8 writing the EEPROM is even slower, 125 byte per second.

The same problem occurs with a circular buffer.
The Arduino is a I2C slave, and incoming data from the I2C Master is handled in an interrupt routine.
If the circular buffer is getting full in the interrupt routine, it is not possible to wait for the buffer to get empty, since it never gets empty while waiting in an interrupt routine.

Does that mean your actual question was "Can the EEPROM write be made faster"? The answer is: no, because the hardware needs so much time.

No, I want to use an extra buffer for the EEPROM.write, to be able to write a small amount of data via the I2C bus faster.
With normal 100kHz I2C bus, I can write 8kbyte/s to a memory location in the I2C slave, and only 300byte/s to EEPROM.

The I2C could have a "ready" flag. The Master could read the flag if the I2C slave is ready to accept new data for the buffer (the ram buffer for data for the EEPROM.write).
That will also slow down the data transfer for a small amount of data.

My goal is to hold the I2C bus, or slow it down in case the Master wants to transfer more data than the size of the buffer.

I think that the Wire library does not allow this situation.

Have you considered page write mode? I use this in the Cosa EEPROM abstraction. Below is a short presentation; http://cosa-arduino.blogspot.com/2013/04/object-oriented-eeprom-device-driver.html

Cheers!

This is about an Arduino as I2C slave, and I write data to the EEPROM of the Arduino via I2C. The Arduino simulates an I2C EEPROM.

The page write is something very close to the buffered write to EEPROM.

But implementing it, seems not possible.

Erdin:
This is about an Arduino as I2C slave, and I write data to the EEPROM of the Arduino via I2C. The Arduino simulates an I2C EEPROM.

Opps! Sorry about that. Barking up the wrong tree. Your issue is a very interesting challange. Need to think a bit about how I would go about solving that.

Hum, guess you need to reject the I2C write operation if the buffer is not yet empty (i.e. the EEPROM update is not yet completed). Guess that requires rewriting/modifying the I2C ISR for the slave. Or adding a callback to validate the state.

Cheers!

I think that the Wire library does not allow this situation.

Correct but the hardware theoretically allows it. So if you write your own TWI routines you can implement that (clock stretching). You even might send a NAK to the master if the write buffer is full.

Thank you pylon and kowalski !

The final conclusion is that is can not be done with a normal sketch.
Changing the library is too complicated for me.
Sending a NAK would result in something like polling, and it makes the code longer.
I stick with the slow 300byte/s even if I only write a few bytes.

If it becomes important (if I need the bandwidth also for other I2C devices) I might check some other I2C libraries.

I checked the Wire/twi in the Arduino library to see where to put the modification. In the file ../libraries/Wire/utility/twi.c you can find the ISR(TWI_vect), the interrupt handler. Approximately in the middle of the function there is the Slave Receiver section (line 439 in 1.0.3).

I believe that you could add a callback to a function that checks if the application is ready to receive and if not sends a NACK. The section looks like this now:

    // Slave Receiver
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      // enter slave receiver mode
      twi_state = TWI_SRX;
      // indicate that rx buffer can be overwritten and ack
      twi_rxBufferIndex = 0;
      twi_reply(1);
      break;

And could be modified to something like:

    // Slave Receiver
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      // call slave ready check
      if (!twi_slave_ready_callback()) { 
         twi_reply(0);
         return;
      ]     
      // enter slave receiver mode
      twi_state = TWI_SRX;
      // indicate that rx buffer can be overwritten and ack
      twi_rxBufferIndex = 0;
      twi_reply(1);
      break;

The modification is also needed in the Slave Transmitter section. I will need to implement something like this in my project Cosa. It is a very interesting usecase and challenge that you came up with.

Cheers!

Thank you, I did take a look at twi.c. However, I don't understand that part, so I rather not change it.
I did see a total of 5 buffers in twi.c and Wire.cpp. So some double-buffering is already going on.