SDK I2C Code. Today’s “Duh!” Story.

This entry is here as a helper (hopefully) for any other poor soul banging their head against the wall of Espressif’s I2C “master” implementation.  <TL;DR Spoiler>  When using the i2c_master.c code supplied with the IoT SDK, you must call i2c_master_gpio_init() before any other I2C functions in your code (and, obviously, your GPIOs need to be correctly defined beforehand).

ESP8266-03 with DS3231 module and DS18S20
ESP8266-03 with DS3231 module and DS18S20

I’ve been trying for days to get I2C working with an ESP8266-03 and a DS3231 RTC module (the el-cheapo, middle-kingdom one with the AT24C32 memory on the same PCB).  This was starting from scratch with I2C on the ESP8266 for me (I’d previously put together a trio of PICs talking to each other over I2C without too much trouble, so I naively thought that the DS3231 should be a doddle).

Hardware wise, I had the DS3231 hanging off a 4v5 supply and the ESP8266 running from a 3v3 linear regulator, supplied by the same 4v5 source.  The reason for this is that I intend to make the unit into a self-contained, battery-powered data-logger and I want the DS3231 to be able to control the power to the whole shebang through a small MOSFET driven from the alarm output.  The I2C bus is run through a MOSFET-based level shifter to ensure that the ESP8266 pins aren’t over-driven.

As normal, on the software side I started off with TuanPM’s totally excellent MQTT package (and if you’re not using this already, you should be!) as a starting point for my new project.  I simply added the i2c_master.c and i2c_master.h from the Espressif (IoT) SDK into the tree, modified the GPIO settings in i2c_master,h to point to the ESP8266 default I2C pins (GPIO2 for SDA and GPIO14 for SCL), added a tiny calling function into user/user_main.c and compiled.  I flashed the ESP-03 with the RTC module attached and… nothing!.   Okay, quick check… Duh, my SDA and SCL connections on the stripboard were the wrong way round.  Okay, resoldered the jumper wires, double checked with the meter and we’re good to go.  And… nothing.  Again!  My output data resolutely remained at all zeros, but the DS3231 calls (using Richard Burton’s ESP8266-ready code) didn’t throw any errors.  Double duh!

I certainly had no reason to doubt Richard’s code.  He’d only just recently done this himself and has a blog about his ongoing projects that is definitely recommended reading for anyone playing with the ESP8266.  I did have reason to suspect that the hardware might be a touch on the dodgy side though, as the DS3231 module already has a pull-up resistor pack on the I2C lines and the MOSFET level-converter also uses 10k pull-ups to both the 4v5 and 3v3 sides.  Thus the 4v5 side had the DS3231 pull-ups and the level-converter pull-ups in parallel and I suspected that the bus might not be being driven hard enough to a logical “0” on that side because of it.  Back to the soldering station, off with the SMD pull-up pack (and, while I was at it, off with the red-LED and the coin-cell “charging” diode, too…  I don’t need an LED sucking extra current from the 4v5 battery pack and the charge diode, as has been noted in lots of places on the ‘net, is a positive danger when using a normal, non-rechargeable CR2032 in the RTC).  Reconnected and yes, I know you’ve already guessed it, bugroll again.  Sigh!

Back to the soldering station.  Ripped out the original RTC module and slapped in another one.  Bugroll, repeated.

At this point I was considering popping one of the RTCs into a PIC-based board, just to check that I didn’t have two dodgy ones, but on reflection, decided that the quickest and easiest next test was to use another I2C implementation instead.  I had already seen Nathan Chantrell’s ESP8266-enabled OLED project and remembered reading that he’d used Zarya’s I2C implementation to drive the display.  Nathan’s blog is another one of my favourites and he certainly doesn’t seem to get things wrong as often as I do, so off I went to GitHub again and pulled Zarya’s I2C code.  It’s basically just two files, the i2c.c and i2c.h, which replace the i2c_master files from the SDK implementation.  First glitch… that missing word, “master”.  Richard’s code calls the i2c_master_whatever() functions, a couple of which are not compatible, argument wise, with Zarya’s implementation.  Okay, “Cheat” is my middle name, so taking the laziest course of action I just rolled half a dozen, one-line wrappers to encapsulate Zarya’s code into i2c_master_whatever() functions.  The only minor change was to make the ack and nack separate function calls (Zarya’s code sensibly uses a single ack function and passes either a 0 or a 1 depending upon whether it’s actually an ack or a nack).  Flashed and… Hurrah!!  Three cheers!  Two pints of Guinness and a packet of crisps, please!  The module burst into life and sent real data.

Mostly it sent really bad data, but at least not just those boring old zeros any more.  The memory module obviously wasn’t writing data at all and the RTC was returning some really weird values for everything except the seconds value, but the internal temperature sensor in the DS3231 was sending sensible, accurate temperatures (instead of the previous, monotonous “Bit chilly in ‘ere, innit”).  Hmmm, verrry interestink!

Unfortunately though, this still left me in something of a quandry.  Now I knew my hardware was capable of working to some extent, but was the bad data a result of my inept programming of the wrappers, or did I still have a hardware issue of some sort, or (perish the thought) was Zarya’s I2C implementation incompatible with the SDK (which has been updated several times since Zarya originally published his code) or Richards DS3231 driver?  Okay, there are a couple of other I2C implementations out there and one of them, by EADF, implements a replacement i2c_master with a significantly easier method of specifying the GPIO defines than the original, SDK master (EADF is the guy who also brought us “easygpio”, so this should be no surprise).  Off to GitHub again.  “https://github.com/eadf/esp8266_i2c_master”. Download. change out the files. compile, flash and… bugroll again.  Aaaargh!  Now I’m starting to doubt my own sanity.  I trust EADF like a brother.  He’s never let me down where the ESP8266 is concerned.  What’s going on here?

I spent more time looking at the two implementations (Zarya’s and EADF’s) side by side to try and work out what was different and it eventually struck me that there was one extra function in EADF’s code which, while missing from Zarya’s, was also present in the SDK version —  i2c_master_gpio_init(). So I finally check the much maligned and utterly useless SDK documentation and, yes, there it is, labelled as “Function: set GPIO in i2c master mode“, nothing else.  Looking at the code itself, it’s obvious that the function does pretty much nothing, other than the all-important job of setting the GPIO pins to the correct mode for I2C (and Espressif have even helpfully commented their code with “//open drain” on those lines), before calling the I2C initialize function.  So, the documentation, despite it’s shortcomings, isn’t so utterly useless after all (just bloody useless!) and a quick edit of my calling code to replace i2c_master_init() with i2c_master_gpio_init() caused it all to spring back into life again… this time with sensible data coming from the RTC and the AT24C32 memory chip, too.  Yip-finally-ee!

Bottom line (same as the top one)…  When using the i2c_master.c code supplied with the IoT SDK, you must call i2c_master_gpio_init() before any other I2C functions in your code (and, obviously, your GPIOs need to be correctly defined beforehand).  Failure to do so will cause hours of frustration (this seems to be my standard state when trying to do almost anything with the ESP8266 nowadays) and will also cause your significant other (and possibly the neighbours) to complain about the unrestrained use of Anglo-Saxon and unwarranted cat kicking.

7 thoughts on “SDK I2C Code. Today’s “Duh!” Story.

  1. Aha!!! Very nice! A biiig thank you! 🙂

    Do you have a working code for some i2c device, maybe for your DS3231? I.e., a very simple example, which has some communication between master (esp) and slave.

    I have some MCP23017 and ADT7420, but I can’t get an ACK from them 😦 But maybe I made some “general” I2C handshake mistake with these ACK and Start and Stop…. so an example could clarify…

    Thanks
    Pesce

    ps: My oscilloscope should arrive early february, till then, I am “blind”… so I cannot check the hardware stuff…

    Like

    • Pesce,

      I’ve been cheating a little bit since that post was published. I got very sick of fighting the SDK and the memory limitations (trying to shoehorn everything into the ESP8266, only to have the whole thing explode again when including a different library or driver), so I went off and tried the Arduino-IDE when it appeared a few months ago. I’m -not- and Arduino guy (as I may have mentioned a few hundred times already in various places 🙂 ), but the toolchain just worked, reliably, straight out of the box for me. The IDE is horrible, so I write code with “vi” and then use the IDE to compile and load. It works flawlessly and I will never, willingly, go back to using the original SDK.

      [Update – …but the original, non-Arduino tree is now available at https://github.com/PuceBaboon/ESP8266-I2C-Fail]

      So, the upshot of that is that I migrated to using the Arduino “Wire” library for I2C code and, like the rest of the libraries, it is very simple to use and just works. Initialize with your selected GPIO pins and you’re done.

      I was looking round for a DS3231 library and came across Petre Rodan’s repository at https://github.com/rodan/ds3231 which has an examples directory with several working examples (one of which almost mirrored my existing code), so I’d recommend Petre’s repository as a good starting point.

      If you want my original, SDK-based code (messy, but working) you’re welcome to it; just drop me another note and I’ll put it up on GitHub. I definitely won’t be doing any more work on it though; the Arduino-IDE route is much less painful.

      -John-

      Like

      • Hi John!

        I implemented an i2c master in (inline) assembly for the esp8266 🙂

        Works perfect up to SCL speeds of 1 MHz, tested with several i2c devices. Tonight, I decided to make my repo public: https://github.com/pasko-zh/brzo_i2c

        For further documentation see the wiki, the use of the library is rather straight forward, I guess 😉 For my needs I decided to move away from the typical wire library syntax and use “i2c transactions” instead.

        It is written and tested with the Arduino tool chain, but since it is plain C and assembly, it should compile with the native tool chain, too.

        The code has many many comments explaining mainly i2c related stuff–so it looks a bit “ugly”.

        paško

        Like

      • Paško,

        Many thanks for letting us know about this. The I2C SDK article is one of the most consistently popular pages on the site, so there are definitely a lot of people looking for answers to I2C problems.

        -John-

        Like

  2. Hi John.

    I’m trying to get this module to play ball with my esp8266 12E.

    Do you think you could drop your bodged sdk code up on github.
    I would really appreciate having a look.

    Thanks.
    James.

    Like

Leave a comment