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).
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_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.