I’ve been using the DS3231 RTC module as one means of reducing the consumption of battery-powered ESP8266 sensors. The idea being to set up an alarm on the DS3231 which will take the (DS3231) interrupt pin low when triggered. That pin is connected to the gate of a P-channel MOSFET, which functions as a power switch for the battery supply to the ESP8266. Once powered, the ESP takes a sensor reading, connects to the network and sends the reading and then programs a new alarm into the DS3231, clearing the interrupt and thus turning its own power off. This actually works very well. The DS3231 module runs on its own button-cell battery and the ESP on three AA alkaline batteries (fed via a low quiescent current, LDO 3v3 regulator). The standard set up will run for many months and this can be extended by using the RTC memory as a data store and only initializing the ESP wireless to send on every 10th (or whatever) reading of the sensor.
I recently came across a very similar implementation on the LowPowerLab’s forum, where user “TomWS” published a circuit using the TI TPL5110 “Nano-power system timer” to drive the MOSFET (instead of the DS3231 in my implementation). The advantages are simplification (the TPL5110 only needs a resistor to set the delay between power-ons, no programming) and a pin saving (the TPL5110 only requires a single GPIO “done” signal from the ESP, as opposed to the two pins required for the i2c bus connection to the DS3231). The TPL5110’s timing range is a little more restricted than the DS3231, with effective delays between fractions of a second and 2-hours. Having said that though, that range probably covers the usage requirements of most hobby applications.
The TPL5110 module (which includes the P-channel MOSFET) is available from LowPowerLab’s web site for about $5. The TPL5110 chip itself is available from Mouser/Digikey, etc for slightly more than a dollar.
Recently I read a post on the ESP8266 forum where the author was attempting to use the powf() function call, but running out of memory during compile. That sounded very familiar to me, as I’ve been experimenting with the DS3231 RTC clock module recently (inside an MQTT application) and found that if I tried to use any of the standard “time.h” library functions, such as gmtime() or localtime(), my compile would also bomb with the infamous “iram1_0_seg” out-of-memory errors. This would happen no matter how many struct tm‘s worth of memory I freed up in the code, to the point where using time.h calls from within MQTT seemed to be an unworkable combination, even with the eagle.app.v6.ld modifications for .literal. and .text. storage.
I don’t have a “silver bullet” answer to this problem (and, so far, according to the thread referenced above, neither does anyone else), but the latest SDK (V1.2 as of the time of writing) does seem to help, although not enough for most people, including me (see page 2 of the thread). In the end I modified a cut-down version of an Arduino C++ time.h lookalike (mem’s Arduino Playground Time.h) to add the functionality I wanted and still have memory left over for other things.
Kudos to mem for the original Arduino Time.h and my apologies for the terrible, slash-and-burn mess I’ve made of it. Gomen-ne! My exceedingly ugly versions are available here for anyone who is really, really desperate:- ESP8266/time.h_hacks
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_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.
Richard Burton has a new-ish blog with some interesting, clock related stuff. Half of the blog is devoted to his restoration of an actual, mechanical turret clock and the other half is a very nice collection of his C code for the ESP8266, mainly related to the DS3231 RTC. As well as the driver for the DS3231 (and the EEPROM usually found on the same PCB with these devices), he also has a simple NTP client (for setting the time) and timezone support.