pololu / vl53l0x-arduino

Pololu Arduino library for VL53L0X time-of-flight distance sensor
https://www.pololu.com/product/2490
Other
346 stars 162 forks source link

Reboot loop when I2C Bus is not in a good state #50

Open hsaturn opened 3 years ago

hsaturn commented 3 years ago

Hello

I'm experiencing a boot loop in certain circumstances I'm trying to discover.

The ESP8266 continuously reboot very quickly.

As far as I know, the problem arise only when these conditions are met together 1 - The Chip (Vl53) is in a kind of bad state 2 - We attempt to change the timing budget (sensor.setMeasurementTimingBudget(20000); right after init.

My reduced sketch is

sensor.setTimeout(500); if (!sensor.init()) { ... } sensor.setMeasurementTimingBudget(20000);

hsaturn commented 3 years ago

Back

Finally I've found a first issue

The reboot is due to a division by 0 in cpp VL53L0X::timeoutMicrosecondsToMclks( ... )

=> I suggest to replace the formula with that one

return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / (macro_period_ns ? macro_period_ns : 1));

In my (big) project, I do not want anything to reboot my ESP ! The division by 0 is a side effect of a bad initialisation of the chip. Ok, this fix only allows to protect from unwanted reboots when in a bad state, but I think the lib shouldn't divide by 0 even when in a bad state due to the programmer. Another (better) fix would be to check that the chip was well ::init(). But I find that the ternary operator is enough.

I'm still searching if this is possible to initialise the chip when it is in a bad state. Maybe it is impossible...

hsaturn commented 3 years ago

The problem during the initialization is that the VL53 does not respond when asking for its identification

if (readReg(IDENTIFICATION_MODEL_ID) != 0xEE) { return false; 

I've tried to add a pause during startup, or init the module with Adafruit : no success... The only way seems to power off / on

My I2C Scanner did not find the chip.

Still searching ... Even if I think that I won't find interresting things now

hsaturn commented 3 years ago

I think I've found the problem which is an I2C bus problem (And have renamed the Tittle of this issue since the solution could be usefull to others).

The solution to my problem to reset the I2C bus with that function. The solution comes from this page : http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html

Imho, this function should be part of the Wire.h library, and shoud be call each time Wire.begin() is called. (But this would make bin larger, and boot time slower...)

int I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
  TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
  pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
  pinMode(SCL, INPUT_PULLUP);

  delay(2500);  // Wait 2.5 secs. This is strictly only necessary on the first power
  // up of the DS3231 module to allow it to initialize properly,
  // but is also assists in reliable programming of FioV3 boards as it gives the
  // IDE a chance to start uploaded the program
  // before existing sketch confuses the IDE by sending Serial data.

  boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
  if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master. 
    return 1; //I2C bus error. Could not clear SCL clock line held low
  }

  boolean SDA_LOW = (digitalRead(SDA) == LOW);  // vi. Check SDA input.
  int clockCount = 20; // > 2x9 clock

  while (SDA_LOW && (clockCount > 0)) { //  vii. If SDA is Low,
    clockCount--;
  // Note: I2C bus is open collector so do NOT drive SCL or SDA high.
    pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
    pinMode(SCL, OUTPUT); // then clock SCL Low
    delayMicroseconds(10); //  for >5uS
    pinMode(SCL, INPUT); // release SCL LOW
    pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
    // do not force high as slave may be holding it low for clock stretching.
    delayMicroseconds(10); //  for >5uS
    // The >5uS is so that even the slowest I2C devices are handled.
    SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
    int counter = 20;
    while (SCL_LOW && (counter > 0)) {  //  loop waiting for SCL to become High only wait 2sec.
      counter--;
      delay(100);
      SCL_LOW = (digitalRead(SCL) == LOW);
    }
    if (SCL_LOW) { // still low after 2 sec error
      return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
    }
    SDA_LOW = (digitalRead(SDA) == LOW); //   and check SDA input again and loop
  }
  if (SDA_LOW) { // still low
    return 3; // I2C bus error. Could not clear. SDA data line held low
  }

  // else pull SDA line low for Start or Repeated Start
  pinMode(SDA, INPUT); // remove pullup.
  pinMode(SDA, OUTPUT);  // and then make it LOW i.e. send an I2C Start or Repeated start control.
  // When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
  /// A Repeat Start is a Start occurring after a Start with no intervening Stop.
  delayMicroseconds(10); // wait >5uS
  pinMode(SDA, INPUT); // remove output low
  pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
  delayMicroseconds(10); // x. wait >5uS
  pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
  pinMode(SCL, INPUT);
  return 0; // all ok
}
kevin-pololu commented 3 years ago

Hi, hsaturn.

Thanks for sharing what you found out about this issue. Have you seen this happen when using other I2C devices with the ESP8266 or does it seem relatively unique to the VL53L0X? Is there something specific that seems to trigger the bus problems (like loose connections or a particular startup sequence)?

Kevin

hsaturn commented 3 years ago

Hi Kevin

Yet, I only have a bunch of VL530X on my I2C bus (8 of them actually).

And for your 2nd question, yes, there is a specific thing that triggers the problem. Here is the best method I've found to reproduce it

1 - Minimal sketch with your library and the fastest possible read of the VL53 in loop(). setup : init and Serial.print in case of error loop: read the distance from VL

In that situation, the I2C bus is constantly used and there are more chance to cause the problem

2 - Simply press the Reset button until the init error occurs.

I havn't saved the sketch but it looks like the example of your lib (continuous)

hsaturn commented 3 years ago

Kevin I really suggest you to avoid the division by 0, this avoids reboots at very low cost.

The next problem I'll have is to change the default address of the VL53 (which is 0x29). I need this because I have eight VL53 connected to my device.

Adafruit lib propose this by using

lox.begin(new_adress);

I haven't search yet if your libray has this feature.

kevin-pololu commented 3 years ago

I will try to see if I can reproduce the problem and if there is anything we can change to make it better. Checking for a divide by 0 is easy so I'll look at putting that in too; at that point the configuration will probably be messed up and the sensor might be left in a bad state, but I think it's still worth doing to avoid the ESP8266 rebooting.

This library does let you change the address with the setAddress() function (see the readme on the main page of the repository).

hsaturn commented 3 years ago

the sensor might be left in a bad state

Yes, and this is expected (because init() already said that things went wrong and for some reasons, I've removed your infinite while loop of example Continuous.ino => division by 0 after).

setAddress(),

Thanks ! I did not notice.

Yet, I'm playing with your excellent VL53L1X library. Another great one. I'm trying to understand how to implement that great lidar thing. -> I will probably open a discussion there to share my experience.

hsaturn commented 3 years ago

Kevin I forgot to say that the problem may occur ONLY with the ESP8266 !

SK-StYleZ commented 1 year ago

tried the 'devision by zero' fix, but still stuck in the boot loop. I2C_ClearBus() did help in my case, thank you @hsaturn