arduino / ArduinoCore-samd

Arduino Core for SAMD21 CPU
GNU Lesser General Public License v2.1
470 stars 717 forks source link

Wire lib hangs when scanning i2c in Wire.endTransmission() #194

Open erniberni opened 7 years ago

erniberni commented 7 years ago

Hi, I tried the well known i2c scanner found on Arduino playground with a Feather M0. Unfortunately the program hangs while testing error = Wire.endTransmission(); In many libraries (for ex. the BME280 lib) this test is used to check if the sensor (based on it's i2c address) is present. On Arduino and ESP the i2c scanner is working.

spiderkeys commented 7 years ago

Hi @erniberni

Your program is getting hung in one of two places: https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/SERCOM.cpp#L478 https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/SERCOM.cpp#L488

I've reviewed Arduino's I2CM implementation and the Wire library calls that are made to perform a scan, and there are a few ways that you can end up locked up.

  1. Starting with what is most likely the cause of your woes, the Feather M0 does not have built in pullup resistors on the I2C pins, so you need to manually add your own. If you don't, bad things are bound to happen that will leave the SAMD's I2C peripheral in a sad state. 4.7K resistors should be fine, but it all depends on your bus capacitance and the speed at which you're trying to clock the I2C. If you have already verified the electrical integrity of your buses, then read on.

  2. Double check to make sure you initialized the I2C peripheral before using it (Wire.begin()).

  3. When using a proper setup (I2C peripheral has been properly initialized and your physical buses are electrically to spec), the failure mechanism is that you have a slave playing bad actor on your bus as a result of how the scan is implemented (it could be holding down SDA or SCL forever). I don't think this is your failure case, as you report that it is working properly with the same code on a different board (and what I assume are the same sensors). However, this still could be the issue, because I am not sure how the Atmel EDBG chip responds to being scanned (being addressed with a write command and then immediately receiving a stop condition). If you have a logic probe, you can quickly determine if this is happening. If the EDBG chip decides to act up after being scanned, you could get stuck in one of those loops forever and you'll see either SDA or SCL held low as the SAMD and EDBG wait for things to happen that never will.

Unfortunately, there is no timeout concept in any of the Arduino board I2C drivers, so unless you have a 100% perfect setup, you can expect an application hangup in the event of certain classes of errors. Even if you have a watchdog and the chip resets, there is no guarantee that the error condition will disappear (the slave could still be holding down lines). In Arduino's defense, timeouts aren't part of the I2C spec, but then again, they don't even call it I2C, and it is not difficult to tease an I2C lockup out of most configurations. My philosophy is that I2C failures should not block your application, so timeouts should be employed to allow the user to decide what to do in the event of I2C conditions that led to a timeout. Some community members have added I2C timeouts for the various boards. For the SAMD21, you can check out RIOT OS's I2C driver for the SAMD21, however it also does not implement an I2CM driver that is fully robust, so I am working through a patch for them as well at the moment. It also does not conform to the Wire API, so it is not plug and play by any means.

All in all, the SAMD21 I2C peripheral is a complex beast that is almost too "smart" for its own good. I have yet to see anyone successfully implement a robust driver for it, and I believe this is mostly because the documentation for the peripheral is sometimes vague, conflicting, and leaves certain things out. I feel like I am getting pretty close with a combination of Arduino's driver, RIOT's driver, and my own custom driver that I wrote, so hopefully in the not too distant future, I can provide a standalone I2CM library that can be used to substitute wire or otherwise help to update the Arduino SAMD core and wire libraries.

erniberni commented 7 years ago

@spiderkeys Thank you for your detailed explanation. As I tried to test if the sensor is present, a missing sensor breakout also means that also the pullups are missing (btw: you wrote "pulldown"). That leads to an undefined bad electrical situation on the SDA, SCL lines while testing. If I place pullups on the busses only, the test is working. Then I tried to read with digitalRead(SDA) if a pullup is present or not, but it seems that only the pinMode(SDA, INPUT) before wire.begin would clean the bad electrical situation on the bus. So maybe this would prevent a hanging in the code. I will do more tests.

spiderkeys commented 7 years ago

Whoops, yea I meant pullup. Don't know what possessed me to write that; will edit my post to prevent confusion for anyone else.

sandeepmistry commented 7 years ago

@erniberni any updates on this?

erniberni commented 7 years ago

No, I didn't find a solution.

Gusev-Roman commented 3 years ago

my Mega2560 hangs on endTransmission() when slave device is off, It seems unpowered device forms pull-down on i2c lines, but this is not good if firmware is toatlly hangs.

Nielsbishere commented 2 years ago

I'm running into the same issue, any fix for this?