Go Down

Topic: Arduino as I2C Slave returns sometimes 0 bytes instead of 4. (Read 12807 times) previous topic - next topic

Erdin

Jun 16, 2013, 10:36 am Last Edit: Jun 23, 2013, 10:01 am by Erdin Reason: 1
Hi,

I have an Arduino as I2C Slave which returns 4 byte after a request from the Master.
However sometimes 0 bytes are received by the Master.

I run into this problem with an ATmega8L as Slave, but the same problem happens with a Arduino Uno as Slave. A Mega 2560 is the Master with onboard pull-up resistors of 10k.
The Slave acts as a sensor, with a (simulated) register address that has to be set first.

Changing the pull-up resistors from 10k to 2k doesn't change anything.
Adding 470pF to SDA or SCL doesn't change it.
Adding more decoupling capacitors or different ground paths makes no difference.
Using a ATmegaL at 8MHz makes it worse.
Using 5V or 3.3V (with level shifter) for that ATMegaL doesn't make a difference.

The problem gets a lot worse if don't release the bus after the (simulated) register address is set.
   Wire.endTransmission(); -> 1 in 50 to 1 in 1000 is wrong.
   Wire.endTransmission(false); -> they are all wrong !

Code for Master
(the Master has 'avail' being zero, no other errors)

Code: [Select]

// TEST_i2c_Master
// Arduino 1.0.5

#include <Wire.h>

int good = 0;

void setup()
{
  Serial.begin( 9600);
  Serial.println( "Start");
 
  Wire.begin();


void loop()
{
  Wire.beginTransmission( 0x2B);
  Wire.write( 0x11); // for the (simulated) register address.
  byte error = Wire.endTransmission(false); // hold bus
  if( error != 0)
  {
    Serial.print("endTransmission error = ");
    Serial.println( error);
  }
  else
  {
    Wire.requestFrom( 0x2B, 4, true);  // release bus after this
    byte avail = Wire.available();
    if( avail != 4)
    {
      Serial.print( "avail = ");
      Serial.println( avail);
      Serial.print( "good = ");
      Serial.println( good);
      good = 0;
    }
    else
    {
      char buffer[10];
      buffer[0] = Wire.read();
      buffer[1] = Wire.read();
      buffer[2] = Wire.read();
      buffer[3] = Wire.read();
      if( buffer[0] != 'A' || buffer[1] != 'B' || buffer[2] != 'C' || buffer[3] != 'D')
      {
        Serial.print( "data : ");
        buffer[4] = '\0';
        Serial.println( buffer);
      }
      else
      {
        good++;
      }
    }
  }
  delay( random( 2, 1500));
}



Code for Slave
(the Slave does not detect an error)

Code: [Select]

// TEST_i2c_Slave
// Arduino 1.0.5

#include <Wire.h>

volatile byte error = 0;

void setup()
{
  Serial.begin( 9600);
  pinMode( 13, OUTPUT);
 
  Wire.begin( 0x2B);

  Wire.onReceive( receiveEvent);
  Wire.onRequest( requestEvent);


void loop()
{
  if( error != 0)
  {
    digitalWrite( 13, HIGH);
    Serial.print("error = ");
    Serial.println( error);
    error = 0;
  }
}

void receiveEvent( int howMany)
{
  if( howMany != 1)
  {
    error = 100;
    digitalWrite( 13, HIGH);
  }
 
  Wire.read(); // read the (simulated) register address
}

void requestEvent( void)
{
  byte n;
 
  n = Wire.write( (const byte *) "ABCD", 4);
  if( n != 4)
  {
    error = 110;
    digitalWrite( 13, HIGH);
  }
}



UPDATE 1: I completely rewrote this post, since I was able to replicate it with normal Arduino boards.
Notified this as a bug, http://forum.arduino.cc/index.php?topic=172831.0

UPDATE 2: This fixes the problem with the "Wire.endTransmission(false);" causing it to fail always.
https://github.com/arduino/Arduino/issues/848
The timing problem that occurs only now and then (and more with 8MHz I2C slave) still exists. The bug notified on Github:
https://github.com/arduino/Arduino/issues/1477

UPDATE 3: The sketch above has been tested on Teensy 2.0 (ATmega32U4) and Teensy 3.0 (MK20DX128 ARM chip). The problem when using the repeated start with "Wire.endTransmission(false);" also exists in the library for Teensy 2.0, but without it there is no error at all. Teensy 3.0 runs also without that error.
I don't know what to think of it: I have always an error within a number of minutes, but the Teensy 2.0 with avr chip doesn't have them.

pjrc

I modified your code slightly, to print the number of times the test passed and the number of times failed.

It's been running on a pair of Teensy 2.0 boards for many hours, currently up to this:

Code: [Select]

good response, ok=101325, err=0




Quote

// TEST_i2c_Master
// Arduino 1.0.5
// http://forum.arduino.cc/index.php?topic=172296.0

#include <Wire.h>

void setup()
{
  Serial.begin(9600);
  Serial.println("Start");
  Wire.begin();
}  

unsigned long okcount=0, errcount=0;

void loop()
{
  Wire.beginTransmission(0x2B);
  Wire.write(0x11); // for the (simulated) register address.
  
  //byte error = Wire.endTransmission(false); // hold bus (errors)
  byte error = Wire.endTransmission(); // release bus (works)
  
  if( error != 0) {
    Serial.print("endTransmission error = ");
    Serial.print(error);
    errcount++;
  } else {
    Wire.requestFrom( 0x2B, 4, true);  // release bus after this
    byte avail = Wire.available();
    if (avail != 4) {
      Serial.print("avail = ");
      Serial.print( avail);
      errcount++;
    } else {
      char buffer[10];
      buffer[0] = Wire.read();
      buffer[1] = Wire.read();
      buffer[2] = Wire.read();
      buffer[3] = Wire.read();
      if( buffer[0] != 'A' || buffer[1] != 'B' || buffer[2] != 'C' || buffer[3] != 'D') {
        Serial.print("bad data : ");
        buffer[4] = '\0';
        Serial.print(buffer);
        errcount++;
      } else {
        Serial.print("good response");
        okcount++;
      }
    }
  }
  Serial.print(", ok=");
  Serial.print(okcount);
  Serial.print(", err=");
  Serial.print(errcount);
  Serial.println();
  delay(random( 2, 1500));
}




and...
Quote

// TEST_i2c_Slave
// Arduino 1.0.5
// http://forum.arduino.cc/index.php?topic=172296.0

#include <Wire.h>

volatile byte error = 0;

const int led = LED_BUILTIN;

void setup()
{
  Serial.begin( 9600);
  pinMode(led, OUTPUT);
  
  Wire.begin(0x2B);

  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}  

void loop()
{
  if (error != 0) {
    digitalWrite(led, HIGH);
    Serial.print("error = ");
    Serial.println( error);
    error = 0;
  }
}

void receiveEvent( int howMany)
{
  if (howMany != 1) {
    error = 100; 
    digitalWrite(led, HIGH);
  }
  
  Wire.read(); // read the (simulated) register address
}

void requestEvent( void)
{
  byte n;
  
  n = Wire.write((const byte *)"ABCD", 4);
  if (n != 4) {
    error = 110;
    digitalWrite(led, HIGH);
  }
}



pjrc

#2
Jun 23, 2013, 03:06 pm Last Edit: Jun 23, 2013, 03:17 pm by Paul Stoffregen Reason: 1
I hooked up a couple official Arduino boards.  The slave code is running on a Mega 2560 and the master is running on an Uno R1.

So far, it's looking like no errors:

Code: [Select]

good response, ok=524, err=0


EDIT: looks like the Mega2560 & Uno are having occasional problems....

Code: [Select]

good response, ok=1253, err=2
good response, ok=1254, err=2
good response, ok=1255, err=2
avail = 0, ok=1255, err=3
good response, ok=1256, err=3
good response, ok=1257, err=3
good response, ok=1258, err=3



Erdin

#3
Jun 23, 2013, 06:35 pm Last Edit: Jun 23, 2013, 09:06 pm by Erdin Reason: 1
Thanks for your time and effort.

What you found is the problem: 'avail' being zero.

I had made changes to the I2C library, so I downloaded a fresh 1.0.5 and used your sketches with Mega 2560 (R1 or R2) as Slave and Uno (R1 or R2) as Master.
Would you believe it, the error occurs all the time! Only 1 in 100 was good.

I added a delay in the Master between Wire.endtransmission() and Wire.requestFrom(). That seems to solve the problem.
Code: [Select]

  byte error = Wire.endTransmission(); // release bus (works)
  delay(1);              // --->  seems to fix timing problem
  ...


Instead of that "delay(1);", I tried changing the delay at the bottom of the loop to "delay(random( 200, 1500));", that did not fix the problem.

I don't understand the Wire library very well, but at least this is a workaround. I will add this to my bug report on Github, https://github.com/arduino/Arduino/issues/1477

To find the minimal delay, I reduced the sketch and found that about 6 microseconds is enough with a 16MHz Master and 16MHz Slave.
According to the timing info by Nick Gammon ( http://gammon.com.au/interrupts ), one or two interrupt service routines could be executed in 6 microseconds.
Code: [Select]

...
Wire.endTransmission();      // release bus (works)
delayMicroseconds( 6);        // seems to fix the timing problem.
Wire.requestFrom( 0x2B, 4, true);  // release bus after this
...

pjrc

I added a 10us delay and ran it on the 2 Arduino boards.  So far, 2885 tests with no errors.

pjrc

It's strange that your boards showed so many errors.  On these two Arduino boards, it seems to be about 1 error in every 300 to 400 tests.

I can't get the error to happen on any Teensy board, even the AVR ones.  But Teensy2 does have a much more efficient timer0 interrupt for millis() and the USB communication doesn't interrupt the CPU for every byte like Arduino Uno's hardware serial does.  Since adding a small delay makes this work on Arduino, it seems doubly strange the problem never happens on Teensy2 where interrupts aren't taking as much CPU time.

Hopefully someone will investigate and truly get to the bottom of what's going on here?

Erdin

#6
Jun 23, 2013, 10:02 pm Last Edit: Jun 23, 2013, 10:19 pm by Erdin Reason: 1
Yes, let's hope so.
I'm not investigating it further. I just use the delay.
Thanks for all you help.

I have this also without use of the Serial library and without USB connection.
I made the system led turn on (in the Master and in the Slave) if that error occurs, to be able to run it without my computer.
Perhaps that optimization for timer0 has effect on this.

Is this the file for millis ? The normal Arduino library seems the same for the Arduino Uno and Arduino Leonardo.
https://github.com/arduino/Arduino/blob/master/hardware/arduino/cores/arduino/wiring.c

This came to my mind: The functions endTransmission() and requestFrom() need time for the parameters and stack and so. Suppose there will be a total time of 4us for that. With de delay of 6us, the total between the 'real' action is 10us. That is remarkable, since 10us is a clock pulse of the I2C at 100kHz.

wayneft

Quote

UPDATE 1: I completely rewrote this post, since I was able to replicate it with normal Arduino boards.
Notified this as a bug, http://forum.arduino.cc/index.php?topic=172831.0

It's not a bug per se but an aspect of the hardware for the Atmega 328.  Per the datasheet on page 326, there is a parameter called tBUF which is the Bus free time between a STOP and START condition and is spec'd at 4.7us for 100kHz bus speed and 1.3us for bus speeds above 100kHz.  It works very well using the repeated start instead, but you need to modify the Wire library on the slave device to handle the repeated starts.

Erdin

#8
Jul 04, 2013, 11:36 am Last Edit: Jul 04, 2013, 11:38 am by Erdin Reason: 1
With a 'fixed' library (commented out the line for the stop) I still had that problem.
Could it be that a repeated start also needs such a delay ?

I also had this problem with 5us delay and only with an Arduino as Slave.

I still think that it is a bug in the Wire library. The library should take care of this situation.

tttwww

Just for your info and the search engines:  ;)

I have a working solution between an Arduino UNO (slave) and the Raspberry PI (with kernel parameter, I2C transfer handling etc....)

See at Raspberry PI Forum / I2C clock stretching


Go Up