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.
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.
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.
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.
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);
}