Speed up I2C on Mega 2560

Hi all,

I've been recently working on implementing an I2C slave on Arduino. This seems work fine when I test it using a raspberry pi as a master, but the issue comes when this is tested with a STM32.

The slave code:

#include <Wire.h>
#define PAGES 5
#define REGISTERS 256

uint8_t i;
uint8_t reg_addr = 0;
uint8_t reg_data;
uint8_t page_index = 0;
uint8_t reg[PAGES][REGISTERS] = {};

void setup() {
  Wire.setClock(400000);
  Wire.begin(0x73);
  Wire.onRequest(setupRead);
  Wire.onReceive(setupWrite);
}

void loop() {
  delay(1);
}

/* This function is called when a read
   event is requested by the master */
void setupRead() {
  Wire.write(reg[page_index][reg_addr]);
  reg_addr = (reg_addr + 1) % REGISTERS;
}

/* This function is called when a write
   event is requested by the master */
void setupWrite(int numBytes) {
  // first byte is the register address
  reg_addr = Wire.read();

  if (reg_addr == 0){
    changeRegisterPage();
    numBytes--;
  }

  for (i=0; i < numBytes-1; i++){
    reg_data = Wire.read();
    reg[page_index][reg_addr] = reg_data;
    reg_addr = (reg_addr + 1) % REGISTERS;
  }    
}

void changeRegisterPage(){
  reg_data = Wire.read();
  if (reg_data < PAGES){
    page_index = reg_data;
  }
}

The command used in RPi

i2cget -y 1 0x73 0x05 c

Sequence obtained with digital analyser:

Setup Write to [0x73] + ACK
0x05 + ACK
Setup Read to [0x73] + ACK
0xff + NACK

All this works fine.

The next is the sequence given between STM32 <-> Arduino Mega 2560 for the same scenario:

Sequence obtained with digital analyser between :

Setup Write to [0x73] + ACK
0x05 + ACK
Setup Read to [0x73] + NACK

When a read request is being carried the Arduino does not sent an ACK back. My guess is that it is not fast enough to read the data and write on the wire in the time this operation must be performed.

Having said this, Is there any other method to optimise this writing process and make it faster?

Thanks in advance.

All this works fine.

I disagree. Your slave code write a zero byte but you see 0xff in the analyzer.

When a read request is being carried the Arduino does not sent an ACK back. My guess is that it is not fast enough to read the data and write on the wire in the time this operation must be performed.

This is why the Arduino stretches the clock while the interrupt is running. I don't know the details of the STM32 but is it possible that it doesn't support the clock stretching feature of the I2C standard? That would explain at least part of the symptoms you see.

When an Arduino is used as a I2C Slave it can benefit from a delay between the I2C bus actions (a delay in the Master). About 1 ms should be enough.
If the STM32 I2C library does not support clock stretching then it will never work, as pylon wrote.

You can make it faster by reading up to 32 bytes. If the Slave puts 32 bytes in the buffer, then the Master can read from 0 up to 32 bytes.

With the Arduino Wire library, the Slave does not know how many bytes the Master has read. So the Master has to set the register address every time it wants to read data. That is normal for almost every sensor, so that should be no problem.

You can make the sketch easier by avoiding the pages. Use two bytes for the register address and have once piece of memory for the data. If you want to make pages, then you can do that at a higher software level.

Thank you Pylon for the answer.

I put a wrong code the first and edited later. In addition the code I'm running has the bi-dimensional array with some values that I haven't put to don't extend it too much. Let's say that the value requested by the master for the register 0x05 is 0xff at that moment.

It makes more sense that the problem is around what you said about the clock stretching. It is there any way to disable it from Arduino?

Thanks

Koepel:
When an Arduino is used as a I2C Slave it can benefit from a delay between the I2C bus actions (a delay in the Master). About 1 ms should be enough.
If the STM32 I2C library does not support clock stretching then it will never work, as pylon wrote.

You can make it faster by reading up to 32 bytes. If the Slave puts 32 bytes in the buffer, then the Master can read from 0 up to 32 bytes.

With the Arduino Wire library, the Slave does not know how many bytes the Master has read. So the Master has to set the register address every time it wants to read data. That is normal for almost every sensor, so that should be no problem.

You can make the sketch easier by avoiding the pages. Use two bytes for the register address and have once piece of memory for the data. If you want to make pages, then you can do that at a higher software level.

The master code on STM requires that page indexing and I'm afraid it can't be modified. Otherwise, that what you said about putting the data in the buffer it is not very clear for me. How can we modify that buffer if the master only uses, let's say, 2 bytes of those 32?

When someone tries to emulate a I2C chip with an Arduino, then the clock stretching is always the main issue.
The Arduino as a I2C Slave uses a combination of hardware and software for the I2C. It stretches the SCL clock to be able to run the onRequest interrupt handler (you call it "setupRead"). The clock stretching can not be disabled.

The Arduino Mega 2560 has 32 bytes buffers inside the Wire library.

Suppose a Master does a request for 2 bytes.
The Arduino Slave may fill all 32 bytes in the onRequest handler with: Wire.write( byte *, 32)
After reading two bytes, the Master stops the I2C bus transaction. The Slave has still 30 bytes in its buffer. That is no problem.

The next time the Master wants data, it should send the register address first, because the Arduino Slave does not know how many bytes the Master has read the previous time. Although that information is available, the Arduino Wire library has no function for it. Well, that is not completely true, because the Wire.availableForWrite() will tell how many bytes are still in the buffer. But that might be useless, because I don't know what the right moment is to call that function. Now I'm getting confused :grin:

After reading two bytes, the Master stops the I2C bus transaction. The Slave has still 30 bytes in its buffer. That is no problem.

The Wire library clears the buffer before calling the onRequest function. So you cannot prefill the buffer with values for the next invocation.