Known defect in Arduino I2C code causes hangup problems

Posted 20 August 2018

06 July 2020 Update

Miracle of miracles!  Arduino finally got off their collective asses and decided to do something about the well-known, well-documented, and long-ignored I2C hangup bug.  Thanks to Grey Christoforo of Oxford, England for submitting the pull request that started the ball rolling.  See https://github.com/arduino/ArduinoCore-avr/pull/107 for all the gory details.  However, in a bizarre outcome, the implementation of the needed timeouts isn’t implemented by default! You have to modify your code to add a call to a new function, like the following:

Note that you have to explicitly add a timeout value (1000 in my example above) or the timeout feature will still not be enabled! The ‘true’ parameter tells the library to reset the I2C bus if a timeout is detected – surely something you will want to do.

I’m currently working on a ‘before/after’ post to demonstrate that the new timeout feature actually works with real hardware scenarios.  However, due to the intermittent nature of the I2C hangup bug, it takes a while (hours/days) to grind through enough iterations to excite the bug reliably, so it may be a while before I have a good demonstration

One last thing; at some point the examples in C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\Wire\examples (on my Win 10 machine) will probably be updated/expanded to show how to properly implement the new timeout feature, but this has not happened yet AFAICT.

Stay tuned!

In my continuing quest to add relative heading sensing to Wall-E2, my autonomous wall-following robot, I have been trying to make the Invensense MPU-6050 module sold by DFRobots work on my robot.

In my last post on this topic, I had finally figured out that the program lockup problems I had been experiencing were due to a well-known-but-never-fixed bug in twi.c the low-level code associated with the Arduino I2C library.   This utility program has a number of while() loops used to send and receive bytes across the I2C bus, and every one of them is prone to deadlock when the device(s) on the other end of the bus misbehaves at all.   Then the while() loop never exits, and whatever program is running dies a horrible death.

The weird thing about this problem is that it has been known for at least a decade (yep – 10 years!!!), and has actually been fixed multiple times by multiple people over this period, but the fixes have never made it into the ‘official’ Arduino Wire library.   This makes  NO SENSE, as the Wire library code is open-source, and is available on GitHub.   I thought the whole idea behind open-source code and GitHub was that others could contribute code fixes in a reliable revision-tracked way, so that when someone finds a bug, it can be fixed quickly and then propagated out to all users.   Apparently the guys at Arduino never got the memo, because I found it impossible to get a ‘Pull Request’ containing the bug fix through the code-maintainer’s gauntlet.

Thinking this was just a logistics problem that I could solve with just a few hours of elbow grease, and would be a good training exercise for other open-source collaboration projects, I decided to take a swing at this problem myself – how hard could it be?

  • I thoroughly researched the technical issues, made the changes to my local copies of Wire.cpp/h and twi.c/h, and verified that they indeed fully solved the hangup problems
  • Found the releveant Arduino Wire library source tree on GitHub
  • Forked the Arduino Wire library source tree to my own GitHub Account
  • Cloned my fork of the Arduino Wire Library to my PC
  • Made all the relevant changes to my local repo, tested the result, and pushed the changes to my GitHub repo.
  • Created a ‘Pull Request’ with all the changes, with a descriptive note

By this time, I had expended a LOT of time, but that was OK as I had learned a lot that would pay off in future efforts, and besides I was finished – I thought!

Then I got a very nice email from the Arduino maintainer of the Wire library, listing all the things I had done wrong, and making it clear that the changes wouldn’t be merged into the ‘official’ Wire library until all was correct to their satisfaction.   When I looked at the list of problems, I realized most of it was about ‘whitespace’ mismatches between my submission and the official version.   Now, I don’t know about you, but I stopped thinking about whitespace a decade or so ago, when it became clear that whitespace was just a figment of the programmer’s mind, and had NOTHING WHATSOEVER to do with how well or poorly the code actually worked.   Now I was being asked to manually correct all the literally hundreds/thousands of places where my code had 2 spaces and the ‘official’ code had 3!   So, if I wanted this bugfix to get into the main distribution, I was going to   have to spend a HUGE amount of time dealing with nit-picking aesthetics that have nothing whatsoever to do with anything but somebody’s misplaced idea of right and wrong with respect to whitespace, for source files that are rarely, if ever, viewed by 99% of the Arduino programming community.   I mean, this would be like refusing to make a small, but important change to the maintenance manual for a car because the shop technician’s penmanship wasn’t up to par!   What is penmanship going to matter when known defects aren’t corrected?

So, I thought about that some more, and I came to realize why this I2C hangup bug has been around for so long – nobody’s pull request has ever made it through the ‘penmanship contest’ gauntlet; the Arduino maintainers are more interested in penmanship than in fixing clearly defective code that has (and still is) causing grief for anyone who tries to use the I2C bus.   My personal response to this problem was “screw them – I’m not going to spend all that effort just to please someone’s weird affection for whitespace, especially since my local copy of these files has already been fixed.

With just a little bit of searching, I found Steve Bian’s ‘SBWire’ library with timeouts added to all the while() loops in twi.c, and was quickly able to ascertain that Steve’s library did indeed solve my hangup problems.   Moreover, Steve actually answered my emails, and is undoubtedly much more open to open-source collaboration than the guys at Arduino.

The sad thing about all this is that Arduino is not doing themselves any favors by making themselves part of the problem rather than part of the solution. If they aren’t going to actively maintain their baseline code distribution, it (and Arduino) will become irrelevant as users find other ways around the obstacles.

Frank

25 August update:

So, I did the same thing with Shuning (Steve) Bain’s SBWire library that I had done with Arduino’s Wire library.   Forked his repo, cloned it to my PC, made the small changes I wanted, pushed to my repo, and created a pull request.    Two Days later, Shuning had merged my changes into the library.   Now I do realize that SBWire isn’t ARduino Wire, so maybe a ‘higher standard’ might be justified for the ‘gold standard’ I2C library.   However, I think we could all agree that EIGHT FRIGGIN’ YEARS  of known defects is probably a bit much!

So, my advice, if you’ve been having problems with I2C hangups, is to throw the Arduino Wire library in the nearest trashcan and use Shuning’s SBWire library

26 August Update:

I have been running SBWire on a little I2C test board, and I left it running over the weekend while my wife and I were away on a trip.   When I came back, some 95 hours later, the board was still running merrily.   I did note that the ‘lockup counter’ (the number of times the standard Wire library code would have locked up) stood at 14, or about once every 7 hours or so.   Actually I’m a bit surprised by this number, as in my personal experience the Wire library never lasted more than about 2 hours before locking up.

Just another reason to dump the Arduino Wire library and use something useful like SBWire ;-).

9 thoughts on “Known defect in Arduino I2C code causes hangup problems

  1. ufcret

    Hello! I just started working with Arduino a few months ago and I’m currently doing a project with four mpu6050s (which I multiplexed with a tca9548A). I did experience lockups and after searching through forums and tutorials on how to fix this (and failing to do so), I finally came across your post, and thought that this might be the fix. However, switching to SBWire made the sketch freeze even faster than before, so I’m wondering if SBWire still is a good fix?

    Reply
    1. paynterf Post author

      ufcret,

      Sorry to hear you are having problems with your 6050’s and I2C. If changing to SBWire didn’t solve your hangup issue, then it is probable that there is something else going on rather (or maybe in addition to) than I2C lockups. I just finished a project to integrate a MPU6050 module back onto my 2-motor wall-following robot. After solving some RFI/EMI problems caused by the motor driver, I found the MPU6050 to be quite reliable – see my more recent posts.

      I recommend you simplify your setup to make it easier to find out where the real problem lies. For instance, maybe construct the simplest possible Arduino sketch that does nothing but poll the device ID of just one MPU6050, and let it run for several hours. Fix the I2C problem first, and then move on to fixing whatever other problem you have. SBWire will certainly eliminate the known lockup problems with the stock I2C library, so if you are still having lockups you have something else going on. I call this the “cut the problem in half” troubleshooting technique ;-).

      Frank

      Reply
  2. Al Warner

    Hello. Saw your Arduino forum attempts to improve the Wire library and share your frustration. I have a Mega based solar control system that has been up and running for about 10 years. However, I keep getting an occasional hang or RTC mis-reads that requires resetting the Mega and therefore my constant monitoring. If you’re interested in gory details and thrashing about, see https://forums.adafruit.com/viewtopic.php?f=8&t=59804

    My I2C bus services a DS1307 Real Time Clock on a Adafruit logger board and a 4X20 LCD. Simple, right? So I followed your lead and downloaded the SBWire library. Changed my program #include to SBWire, but it won’t compile. So I picked up somewhere, that there may be Wire library calls in either the Adafruit RTC library or Malpartida LCD library. Can you tell me exactly what needs to be done to implement SBWire so the program compiles?

    Reply
  3. paynterf Post author

    Adafruit’s RTClib does indeed use the Wire library, and that is pretty much guaranteed to hangup over time. I also use the DS1307 RTC and the Adafruit RTClib, but I have modified my copy to use SBWIRE instead of Wire.h. I submitted a pull request to Adafruit over a year ago to have them add the SBWIRE option to their master copy, but nothing came of it.

    The modification is very simple; add

    #define USE_SBWIRE to the top of RTClib.h

    Then modify RTClib.cpp, just after #include RTClib.h so that it looks like the following

    #ifdef USE_SBWIRE
    #include SBWIRE.h
    #else
    #include Wire.h
    #endif

    Note that if you are using a later version of RTClib, you may have to use the following code instead:
    #include “RTClib.h”

    #ifdef __AVR_ATtiny85__
    #include
    #define Wire TinyWireM
    #elif defined USE_SBWIRE //Uncomment this definition in RTClib.h to avoid Wire library lockups
    #include
    #else
    #include
    #endif

    Hope this helps,

    Frank

    Reply
    1. Al Warner

      So I’m ready to try this now … after yet another RTC mis-read that takes me “back to the future” year 2038.
      Modified the .h file.
      However, the top of the .cpp looks like this:
      #ifdef __AVR_ATtiny85__
      #include
      #define Wire TinyWireM
      #else
      #include
      #endif

      #include “RTClib.h”
      #ifdef __AVR__
      #include
      #elif defined(ESP8266)
      #include
      #elif defined(ARDUINO_ARCH_SAMD)
      // nothing special needed
      #elif defined(ARDUINO_SAM_DUE)
      #define PROGMEM
      #define pgm_read_byte(addr) (*(const unsigned char *)(addr))
      #define Wire Wire1
      #endif

      After I used Wordpad to edit the file as follows, it did not compile . Compile error message for .cpp was Exit Status 1 at #include SBWIRE.h.
      #ifdef __AVR_ATtiny85__
      #include
      #define Wire TinyWireM
      #else
      #include
      #endif

      #include “RTClib.h”
      #ifdef USE_SBWIRE
      #include SBWIRE.h
      #else
      #include Wire.h
      #endif

      #ifdef __AVR__
      #include
      #elif defined(ESP8266)
      #include
      #elif defined(ARDUINO_ARCH_SAMD)
      // nothing special needed
      #elif defined(ARDUINO_SAM_DUE)
      #define PROGMEM
      #define pgm_read_byte(addr) (*(const unsigned char *)(addr))
      #define Wire Wire1
      #endif

      While I’m not familiar with #compiler methodology, I thought this was simple “if/else if” replacement logic. Also don’t understand the blank #include’s after #ifdef __AVR_ATtiny85__

      Reply
  4. paynterf Post author

    I re-submitted the pull request (https://github.com/adafruit/RTClib/pull/160) to Adafruit, and it has passed all the preliminary checks. If the changes are merged into the library, then all you have to do is update your local copy of the RTC library from the Adafruit site (https://github.com/adafruit/RTClib) and uncomment the ‘#define USE_SBWIRE’ line in RTClib.h. Of course you’ll also need to download/install the SBWIRE library (https://github.com/freespace/SBWire).

    Hope this helps,

    Frank

    Reply
  5. Pingback: I2C Hangup bug cured! Miracle of Miracles! Film at 11! | Paynter's Palace

  6. John

    I only just came across this post, and until now have never known about the wire fix. It’s been driving me insane for almost 10 years now. I only occasionally got past it playing whack-a-mole with my code, and sometimes I have just reluctantly hit reset whenever it crashed. I can’t believe they haven’t set it by default. That’s why it has taken me 3 years to finally find a fix. I’ve been googling this all day, and only just found the lucky string of keywords that has solved my problem. Let’s hope I don’t forget about it before the next I2C project comes along. Thank you for your work and reporting.

    Reply
    1. paynterf Post author

      Thanks for the kind words, and I’m glad something I posted made your life better. I too had become very frustrated with Arduino.org’s reluctance to fix this problem. Ironically, it took the C-19 pandemic and the specter of the infamous I2C hangup bug killing patients on Arduino-powered ventilators to get them off their collective asses :).

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *