I2C throughput much lower than expected with Wire lib

Hello all.
I hope you can give me a good advice about this topic.
I have made a communication test between an Arduino UNO as I2C master and a PIC micro as slave.
I set the I2C clock speed to 400KHz hoping for a good throughput.
Here is the very simple sketch, it requests 6 bytes from the slave (and flashes the bulit-in led only when there is a failure in getting the data):

#include <Wire.h>

#define PIC_I2C_ADDRESS 8

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  //Inizializzazione I2C
  Wire.begin();
  Wire.setClock(400000);

  digitalWrite(LED_BUILTIN, LOW);
}

void loop() {
  uint8_t c;
  boolean rx = false;
 
  Wire.requestFrom(PIC_I2C_ADDRESS, 6, true);
  
  while (Wire.available()) { // slave may send less than requested
    c = Wire.read(); // receive a byte as character    
    rx = true;    
  }
  
  if (!rx) flash_led(500);
}

void flash_led(int d) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(d);
  digitalWrite(LED_BUILTIN, LOW);
  delay(d);
}

I have been watching the SDA (A4) and SCL (A5) signals in a scope and the result is disappointing!
See the attached image: as you may see it takes approx. 3.2 msec to transfer the 6 bytes and each byte transfer is preceded by almost 500 microsec with the Arduino's micro "doing something".
Hence the 400KHz I2C speed is completely voided in a very slow communication.

Actually, there is nothing in my sketch adding delay, because all the scope trace shown in the attached image is what Wire.requestFrom(PIC_I2C_ADDRESS, 6, true) does.
Therefore I am thinking that maybe the Wire library is not efficient - or I am missing something.

Does anybody have a suggestion? Thanks in advance.

Alberto

  while (Wire.available()) { // slave may send less than requested

No, the slave should not send less than requested.

Wire.requestFrom() is a complete transaction, which collects all the data requested. There is no need for the Wire.available() call. Simply load the six bytes from the receive buffer and get on with the rest of the work.

Some additional advice has been provided by forum member Koeple.

Hi jremington
Thank you for the reply.

I agree with you that the Wire.available() check in needless (I just copied it from the Wire example).
In fact, it may be seen from the attached image that the slave PIC always replies with the 6 bytes.

However, as I wrote, the 3.2msec is the time taken by the Wire.requestFrom(PIC_I2C_ADDRESS, 6, true) alone.
I am rather sure about it, by looking into the library file "Wire.cpp" one can see that "TwoWire::requestFrom()" function is a wrapper around the inner function "twi_readFrom(address, rxBuffer, quantity, sendStop)", which in turn reads the bus "quantity" times into the buffer, before my check into Wire.available(), which is not part of the delays.

So, I am looking into the possibility (if any) to speed up the bus reading process by the micro.

The scope pic is uninformative.

The bus reading process is done by the I2C controller hardware on the ATmega chip. For faster serial I/O use the SPI hardware.

I'm looking at the scope picture, but I don't know what I am looking at.
Is the SCL kept low for a long time ? Then it is probably the PIC doing clock stretching.

When the Master requests data, then the Slave can put the Master on hold to be able to get the data.
When the Slave is a microcontroller or processor, then the I2C is part hardware and part software. To be able to run a interrupt routine that prepares data to return, the Slave keeps the SCL low to put the Master on hold.

A 20 dollar USB logic analyzer will give more information.

Hi Koepel
The green trace is SDA, the yellow trace is SCL
The picture shows the read request and the subsequent 6 data transfer from the slave between the 2 blue vertical lines, spaced by 3.2ms

Koepel:
Is the SCL kept low for a long time ?

Yes, that's the issue, apparently.

I will check if I overlooked the possibility of clock streching in the PIC.

Thanks for the hint.

albtrentadue:
Therefore I am thinking that maybe the Wire library is not efficient - or I am missing something.

Does anybody have a suggestion? Thanks in advance.

I have carried out a simple data read/write operation on AT24C32 EEPROM in order to estimate the execution time of the Wire.requestFrom() instruction.

I have written 6 bytes data into the EEPROM, and I have read them back.

The theoretical time of execution of .requestFrom() code at I2C Speed of 400000 Hz should be 157 us ((9 + 6x9)*1/400000) . My experiment shows: 215 us. They are not too far off; so, it may be accepted.

#include<Wire.h>
byte dataArray[] =
{
  0x31, 0x32, 0x33, 0x34, 0x35, 0x36
};

byte recArray[6];

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  Wire.setClock(400000);
  //--------------------------
  TCCR1A = 0x00;      //normal counter
  TCCR1B = 0x00;      //TC1 is OFF
  TCNT1 = 0x0000;     //initial value
  //--------------------------
  Wire.beginTransmission(0b1010111);
  Wire.write(0x00); //address high byte
  Wire.write(0x10); //address low byte
  Wire.write(dataArray, sizeof(dataArray));
  Wire.endTransmission();
  delay(5);
  //-------------------------
}

void loop()
{
  Wire.beginTransmission(0b1010111);
  Wire.write(0x00); //address high byte
  Wire.write(0x10); //address low byte
  Wire.endTransmission();

  //---------------------------------------------------
  TCCR1B = 0x01;  //TC1 is ON at 16MHz clkTC1
  byte n = Wire.requestFrom(0b1010111, 6);  //blocking code; terminates when 6 bytes data has come in
  TCCR1B = 0x00;  //TC1 is OFF
//----------------------------------------------------

  float exeTime = (float)TCNT1*0.0625;  //1/160000000
  //Serial.println(TCNT1, DEC);   //execution time = TCNT1*1/16000000 us
  TCNT1 = 0x0000;   //TCNT1 is reset
  Serial.print("Execution time of Wire.requestFrom() = ");
  Serial.print(exeTime, 2);
  Serial.println(" us");
  //------------------------------------------------------
  
  for (int i = 0; i < n; i++)
  {
    recArray[i] = Wire.read();
    Serial.print(recArray[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
  delay(1000);
}

SM7.png

SM7.png