Basic DS3231 RTC questions

I'm starting to work with one of the DS3231 RTC modules, and am using Eric Ayars' library. Everything works, but I'd like to confirm a couple of things. First, I was concerned about problems with rollover, for instance if you read hours, and then read minutes at the moment an hour increments. But based on the data sheet, it seems the chip designers have arranged for double buffering, so that as long as you read Seconds first, and then read everything else within a second, the chip ensures you can't have a problem. From the data sheet...

The countdown chain is reset whenever the seconds register
is written. Write transfers occur on the acknowledge
from the DS3231. Once the countdown chain is reset, to
avoid rollover issues the remaining time and date registers
must be written within 1 second.

Is this correct? It seems like I'd seen references in examples about reading time and date all at once to prevent such rollover errors, but these examples were still referring to reading the registers sequentially, correct?

Second question is probably a stupid one, but when my project starts up I'd like take alternative action if for some reason the RTC was not responding. What's the best way to just test the chip, and know if there's an I2C timeout or error?

In general, the device data sheet is a much better and more reliable source of advice than random comments found on the internet.

Trust it.

I haven't used the DS3231 myself. However, the excerpt (from the datasheet?) that you quote seems to only be referring to writing to the device.

To avoid rollover problems when reading, you could always read the seconds twice, as the first and last items read. Then compare them - if the seconds haven't changed, there's no problem, if they have, the simplest thing is probably to re-read everything.

jremington:
In general, the device data sheet is a much better and more reliable source of advice than random comments found on the internet.

Trust it.

I'd agree for my first question, but for the second I need the voice of specific arduino experience. I've seen a number of articles complaining that I2C problems can cause the arduino to lockup, and I didn't write the "Wire" library, so maybe its better to ask.

alexmonro:
I haven't used the DS3231 myself. However, the excerpt (from the datasheet?) that you quote seems to only be referring to writing to the device.

To avoid rollover problems when reading, you could always read the seconds twice, as the first and last items read. Then compare them - if the seconds haven't changed, there's no problem, if they have, the simplest thing is probably to re-read everything.

Yes... that was from the datasheet. In fact the paragraph immediately before the one I quoted says reading multiple times is unnecessary (see below). But paranoid as I am, I'd still like to know of a reasonable way to TEST that the chip is working without locking up the arduino, as I've read can happen if certain I2C errors occur.

When reading or writing the time and date registers, secondary
(user) buffers are used to prevent errors when
the internal registers update. When reading the time and
date registers, the user buffers are synchronized to the
internal registers on any START and when the register
pointer rolls over to zero. The time information is read
from these secondary registers, while the clock continues
to run. This eliminates the need to reread the registers
in case the main registers update during a read.

I2C problems can cause the arduino to lockup

Yes, I2C can cause problems, as the rules are a bit vague and not all devices obey them, and the Arduino Wire library has very poor to no error handling.

It is simple enough to add timeouts to the various wait loops. In fact, it has been done more than once (I have done so for my own version), so spend a few moments with Google to look for modified versions.

I did manage to add DS3231 functionality to my project, and I now have two I2C devices... the DS3231 module and a Hitachi HD44780 LCD (16 x 2 char) via the PCF8574 backpack. In both case I'm using libraries I discovered through the forums, and of course google. The RTC uses a simplified library called DS3231-1.0.2 (LOVE when authors include their version in the folder name) and the LCD uses LiquidCrystal_I2C library, which is not the most recent LCD library, but was the first I was able to get to work, thanks to a clear example on the forum.

So after this discussion, I thought that any "fixes" try to make to the Wire library would be an attempt to re-invent a wheel, someone else will likely have done and refined over a long time. So I found a library (apptly called I2C, author Wayne Truchsess). Though it was last updated in 2011, it seems to address a lot of I2C lockup issues, timeouts, and something called "repeated start" functionality, and is supposed to be faster and require less code/data space.

So now I'm trying to decide whether to do the necessary surgery on both these libraries. The DS3231 library? Its simple enough that I could easily substitute the (hopefully better) I2C library calls, in an hours time. The LCD library on the other hand will really require a lot of digging and testing. It does seem to route all its I2C calls through a single file where the original WIRE calls are made, but at the very least it will take more work. It would be nice if the I2C library simply duplicated all the existing WIRE calls, but thats not the case (in fact an author comment points to a version/commit where such duplication was intentionally removed).

But here's the thing... so far the project seems to work flawlessly with just the Wire library used for all I2C calls. The only time I had any "lockups" where I suspect I2C was at fault, was when I uploaded new code (which causes multiple resets). Even then it would not happen often, and it has not happened EVER during normal operation.

So all that to say, I'm not sure how to proceed. part of me says "start using the better library NOW and get used to modifying all libraries that rely on WIRE). Part of me says, since I2C was completed in 2011, maybe a better and fully compatible version of WIRE is available elsewhere, or soon will be. And then there is the old adage "don't fix it if it ain't broke".

Any Thoughts or advise?

But here's the thing... so far the project seems to work flawlessly with just the Wire library used for all I2C calls.

Then don't worry about rare failures, unless there is a compelling reason to do so.

PeterPan321:
And then there is the old adage "don't fix it if it ain't broke".

Any Thoughts or advise?

How true.
I have had an RTC and a 4x20 LCD running together, more or less continuously since 2012, and I didn't know I2C could lock up until I read this. I don't use any library for RTC but the other libraries are just what comes out of the box. I only opened this thread because I was silly enough to think it was something about telling the time, and I rather feel that the only bit worth reading in its entirety are

it has not happened EVER during normal operation.

and I note your capitals with some relief......

I've had I2C lock up when using two devices. One of them did not obey the rules and is now in the trash bin.

jremington:
Then don't worry about rare failures, unless there is a compelling reason to do so.

I guess I could EVENTUALLY take the changes I can see in the newer I2C library and incorporate them into the existing WIRE library, perhaps with a few compile time #ifDef's so I can easily select between them? Somehow I think that might be better than altering specific device libraries right? At least it should make me immune to breaking a project when one of those libraries is updated. The LCD library I'm using is pretty old.

PeterPan321:
I'm starting to work with one of the DS3231 RTC modules, and am using Eric Ayars' library.

It would help if you would provide a link, in case there is more than one version of that library floating around, or more than one library by that author.

Is this what you are using? http://physics.csuchico.edu/~eayars/code/DS3231.zip

Everything works, but I'd like to confirm a couple of things. First, I was concerned about problems with rollover, for instance if you read hours, and then read minutes at the moment an hour increments.

The solution is not to do it piecemeal.

Second question is probably a stupid one, but when my project starts up I'd like take alternative action if for some reason the RTC was not responding. What's the best way to just test the chip, and know if there's an I2C timeout or error?

Here is some of my old code I dug up. I believe it deals correctly with both issues you bring up.

#include <Wire.h>

byte ss=0, mi=0, hh=0, wd=6, dd=1, mo=1, yy=0;
 
void setup()
{
  Wire.begin();
  Serial.begin(9600);
 
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock
  // keeps running on just battery power. Once set,
  // it shouldn't need to be reset but it's a good
  // idea to make sure.
//  Wire.beginTransmission(0x68); // address DS3231
//  Wire.write(0x0E); // select register
//  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
//  Wire.endTransmission();
}
 
void loop()
{
  // ask RTC for the time
  // send request to receive data starting at register 0
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write((byte)0); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(0x68, 7); // request seven bytes (ss, mi, hh, wd, dd, mo, yy)
  // check for a reply from the RTC, and use it if we can
  if (Wire.available() >= 7) {
    // if we're here, we got a reply and it is long enough
    // so now we read the time
    ss = bcd2bin(Wire.read()); // get seconds
    mi = bcd2bin(Wire.read()); // get minutes
    hh = bcd2bin(Wire.read()); // get hours
    wd = bcd2bin(Wire.read());
    dd = bcd2bin(Wire.read());
    mo = bcd2bin(Wire.read());
    yy = bcd2bin(Wire.read());
    // show that we successfully got the time
    Serial.print("Got the time: ");
    printTime();
  }
  else {
    // if we're here, that means we were unable to read the time
    Serial.println("Unable to read time from RTC");
  }
  delay(500);
}

byte bcd2bin(byte x) {
  // converts from binary-coded decimal to a "regular" binary number
  return ((((x >> 4) & 0xF) * 10) + (x & 0xF)) ;
}

void printTime() {
  // just like it says on the tin
  Serial.print ("\'");
  if (yy<10) Serial.print("0"); Serial.print(yy,DEC); Serial.print("-");
  if (mo<10) Serial.print("0"); Serial.print(mo,DEC); Serial.print("-");
  if (dd<10) Serial.print("0"); Serial.print(dd,DEC); Serial.print("(");
  switch (wd) {
    case 1: Serial.print("Mon"); break;
    case 2: Serial.print("Tue"); break;
    case 3: Serial.print("Wed"); break;
    case 4: Serial.print("Thu"); break;
    case 5: Serial.print("Fri"); break;
    case 6: Serial.print("Sat"); break;
    case 7: Serial.print("Sun"); break;
    default: Serial.print("Bad"); 
  }
  Serial.print(") ");
  if (hh<10) Serial.print("0"); Serial.print(hh,DEC); Serial.print(":");
  if (mi<10) Serial.print("0"); Serial.print(mi,DEC); Serial.print(":");
  if (ss<10) Serial.print("0"); Serial.print(ss,DEC); Serial.println("");
}

odometer:
It would help if you would provide a link, in case there is more than one version of that library floating around, or more than one library by that author.

Is this what you are using? http://physics.csuchico.edu/~eayars/code/DS3231.zip

Same author, same date, yet the author had called the folder "DS3231-1.0.2", and indeed had appended some extra code to it, including some that was similar to your calls, getting multiple registers at once

odometer:
Here is some of my old code I dug up. I believe it deals correctly with both issues you bring up.

Yes I ended up doing something similar... requesting multiple registers at once, starting with seconds, since that toggles a 1 second freeze between the live registers and the ones exposed for user read, specifically to make rollover impossible. My particular project only requires day time (seconds, minutes, hours), so thats all I read or write in one shot.

Thats a good idea you added, reporting an error if the number of registers available is less than what you requested.

But you are still using the original WIRE library, and I've been lead to consider alternatives like Wayne Truchsess' I2C library, which boasts more built in lockup avoidance, faster operation, and reduced code footprint. The trouble is, the DS3231 support code seems pretty easy to trace out and rebuild your own way, using these newer calls. But doing this with the LCD library is much more complicated, and once I go down that path it makes little sense to NOT do the same for every I2C device I ever use.

So that said, it looks like the only sensible thing is to analyze Wayne's work, and build a wrapper class around it to mimic the WIRE library as closely as possible, so that ANY I2C device with a support library can make use of the better calls without modification.

Perhaps I'll write to the author and ask why HE stopped doing that.

Ok,
Here is my experience playing with i2c on Arduino using the Wire library over the past 4 years and with using the DS3231 for around 3 years in clock project. (A word clock).

You haven't said which "Arduino" you are using. Each core provides its own Wire library.
I have run into Wire library issues in the AVR, pic32 and esp8266 cores, and the SoftWire avr "soft" Wire library.
I have worked with the chipkit guys and the esp8266 to get most of the issues I ran into on those cores fixes.
There is one still outstanding on the pic32 but it is a h/w issue and so it requires a s/w work around. This issue is not something that normal sketch code will see as it occurs when probing the i2c bus looking for slaves.

On the AVR I ran into an issue that causes the Wire library to lock up. It is a case where the Wire code is trying to support multi-master but a misbehaving slave tricked the AVR into thinking another master was wanting the bus. The AVR backs off and waits for the other Master to finish. Since there was no other master, and the AVR Wire library does not timeout, the AVR wire code is hung in a loop waiting for an interrupt that never occurs. i.e. it locks up the AVR.
I have only ever used Master mode but this is the only issue I have ever seen when using the Wire library on the AVR.
And in this case it is due to a misbehaving slave. The slave responds to a read as if it were a write and that is what confuses the AVR code.

I've spent quite a bit of time playing with various h/w and s/w i2c libraries (while developing my hd44780 library) and I haven't had any lockup or issues with them in normal use unless there was a h/w issue like incorrect pullups , bad power, bad wires, yanking or inserting slaves, or misbehaving slaves.
I've not attempted to use slave or multiple masters.

I built a word clock that uses a DS3231, and htk1633 (which is also i2c) to run the LEDs in a matrix to light the letters.
The processor was a Teensy2. The clock ran continuously for just over 2 years with no issues (I'm currently making mods to it).
Internally I use unix epoch time (I ALWAYS use unix epoch time as it is the most efficient way to track time and time calculations and adjustments are super easy).
The time is tracked using the Time/TimeLib library the RTC is accessed using the DS3231RTC library .
The Time library syncs with the RTC every 5 minutes through the DS3232RTC library.
The htk1633 is accessed over i2c using the Adafruit_LED backpack library which uses the Wire library.

Granted this is a very limited test case, but so far I haven't seen any issues that would cause me to seek out another i2c library other than than the bundled Wire library.

The project has recently been modified to use a esp8266.
There are lots of options available when using that module since it has built in wifi.
You really don't even need an RTC with that module as you can sync to an NTP sever.
The current implementation of the modified wordclock project I'm playing with still uses the Time/TimeLib library, the DS3231RTC but now uses the WifiManager to attach to the local network then uses the timezoneapi.io sever to get the local time to set the local time in the RTC.
timezonapi.io is very cool. It can get your local time from your location which you can give it in many forms from just a city, an address, GPS coordinates, or it can figure it out based on your IP address - which is what my clock uses.
The wordclock syncs to timezonapi every day at 2:00am to catch the DST changes.
This makes things nice as this clock has no LCD or any user interface other than two buttons to set the time.

Alternatively, I think it would be better and much simpler to drop the RTC and the Time/TimeLib libraries and use the built in esp8266 NTP support and the bundled gnu proper time libraries.
That way, you can use the WifiManager to also get a gnu TZ timezone string. Once you hand that TZ string to the time library functions, the built in NTP support will track time and you have access to the proper unix time functions like localtime(), ctime() etc... which will do any/all needed timezone and DST adjustments. All with no need for any 3rd party time libraries or RTC.
The only reason I'm not jumping on that right now is that for this wordclock is I'm not yet ready to depend on a network and there is a fall back mode to using the RTC (which is how it worked when it used the Teensy2) and for the time being it is easier to use the Time/TimeLib and RTC code I already had when just using the RTC and no network.
For future projects, I'll look at moving to using full NTP and TZ string support.

There are curently some issues in the esp8266 ntp library in how it attempts to handle timezone offsets.
They really screwed it up. I've been talking to the developers/maintainers of the esp8266 core and I'm in the process of putting together a patch/pull request to fix it.
But if you avoid all the timezone offset stuff in the ntp library and let the gnu time library handle it using the TZ string, it all works as it should. The TZ string and the normal unix time library functions is by far the best way to handle time, timezone and DST adjustments. The APIs have existed for more than 3 decades and they "just work".

The AVR tools (and hence the Arduino IDE) includes the gnu time library functions, but IMO they f*&* up big time.
They do use epoch time, but they changed the epoch from Jan 1, 1970 to Jan 1, 2000.
This really didn't buy them much other eliminating a few compares during leap year calculations; however it creates many issues since it is the wrong epoch. They also screwed up their leap year calculations after year 2100.

For i2c backpack LCD support, I use my hd44780 library which auto locations the i2c address and auto configures the pin mappings and active backlight level.

--- bill

While we're on the subject of misbehaving I2C devices:
It is possible (due to power issues or what-have-you) for a DS3231 to malfunction: for example, it might set itself to a garbage date and/or time. I've had this happen to me. If I am particularly concerned about the possibility of this happening, I will perform sanity checks on the received date and time information. For example, as we are now in the year 2018, it might be a good idea to reject as garbage any date containing a year from '00 to '17, along with any time associated with such a date. Also, as it is not particularly hard to calculate the correct day of the week for any date, the day of the week can serve as a sort of checksum for the other date information. Yes, I know, this doesn't help us if only the time of day gets messed up, but it's a start, at least.

The DS3231 contains a status bit to indicate if timekeeping was interrupted from something like low or loss of power.

odometer:
... Yes, I know, this doesn't help us if only the time of day gets messed up, but it's a start, at least.

You can check if it's dark, and check the direction of the light if it's not. That'll give you am/pm and day or night. I understand there ways to check the time the sky is light vs dark over a few days to obtain the date ± a day or so. That gives you about 6 hours / year, which is within 0.07% of the true time of year.

Am I right on this?

bperrybap:
Ok,
Here is my experience playing with i2c on Arduino using the Wire library over the past 4 years and with using the DS3231 for around 3 years in clock project. (A word clock).

You haven't said which "Arduino" you are using. Each core provides its own Wire library.
I have run into Wire library issues in the AVR, pic32 and esp8266 cores, and the SoftWire avr "soft" Wire library.

...

Well first of all, thanks for the detailed write up. My apologies, the arduino I'm using are all NANOs, atmega328, 16mhz.

The most important things I'm gleaning from all this is that (1) as an I2C master, I'm unlikely to run into lockup issues, (2) the lockup problems are usually caused by misbehaving slaves, and the better choice in these cases (as pointed out by another poster) is to refrain from using such devices, in favor of better ones.

I've also run many tests at this point, banging much more heavily on both my LCD and my RTS, and not once have I even seen a missed read or write, and certainly no lockups. The only exception has been an occasional lockup when I reset the board or upload new code, which causes two resets (never figured out why 2, and don't need to re-open that discussion). I suppose there are times when the reset occurs right in the middle of an I2C exchange. I don't expect any improved wire library to be able to fix that, and I can probably live with that if its the only problem case.

At this point, the only compelling reason I may have for making the newer I2C library totally replace WIRE, is to have the benefit of its alleged more compact code and faster execution. I'll have to do some experimenting with it to see if its worthwhile. But for now I think the existing WIRE library is good enough for at least "version 1" of my current project.

bperrybap:
For i2c backpack LCD support, I use my hd44780 library which auto locations the i2c address and auto configures the pin mappings and active backlight level.

--- bill

I probably don't have the best HD44780 library. Something written by "Francisco Malpartida". But I'm using it because of all the HD44780 libraries out there, this was the first one where I could get the LCD to do anything with any of their examples. Not saying this would be the case with your library, but it seems people get so deep into making their libraries versatile, they forget to write a complete "getting started" guide to steer a new user through real world setups. Then they further complicate things by putting their ZIP file inside a TAR file, which itself is inside a ZIP. Don't get me started. But I digress.

I'd like to see your HD44780 library, though you didn't mention whether it uses the original WIRE lib or not (maybe I missed it)? But bottom line, any device library is used as a shortcut to a bigger task, which is your project that uses the device. The down side is that if a user thinks theyneed to do things differently, they can easily lose a ton of time trying to understand how the library works, and few people document their libraries with enough comments to do such modifications efficiently.

bperrybap:
The DS3231 contains a status bit to indicate if timekeeping was interrupted from something like low or loss of power.

I'll have to check into that. But with the batter connected, as in my modules, it probably wouldn't happen. At least it seems to me, from the data sheet, that the transition from input voltage to battery voltage should not interfere with an I2C operation.

PeterPan321:
I've also run many tests at this point, banging much more heavily on both my LCD and my RTS, and not once have I even seen a missed read or write, and certainly no lockups. The only exception has been an occasional lockup when I reset the board or upload new code, which causes two resets (never figured out why 2, and don't need to re-open that discussion).

For the AVR based boards that use the DTR signal (some use RTS - which is actually better)
during auto reset the board may actually be getting 3 resets:

  • when the host opens the serial port (from using DTR instead of RTS)
  • The IDE does one (depending on version of the IDE)
  • The "arduino" protocol in avrdude drops DTR and toggles RTS to trigger auto reset before uploading starts

depending the speed of your host, the Arduino may have time to start running in between these.

I suppose there are times when the reset occurs right in the middle of an I2C exchange. I don't expect any improved wire library to be able to fix that, and I can probably live with that if its the only problem case.

If resetting the Arduino creates an I2C issue it is likely to be an issue on the slave side since the Arduino side is completely reset - which is both s/w and h/w.
What is more likely is the possibility that there can be i2c issues at power up (vs reset)
it is possible that if your setup() routine immediately jumps on the i2c bus that the AVR is up and running but the slaves are not because the voltage has to rise up to full vcc level and the AVR can potentially start running sooner than the slaves.
It is also possible that weak pullups are being used on the i2c signals that the i2c signals have not had enough time to fully pull up to vcc.
If possible it is best to put the i2c initialization after other initialization or even put in a 50ms delay to give everything some time to stabilize. The hd44780 library does have a delay in its begin() to avoid any issues like this.

I probably don't have the best HD44780 library. Something written by "Francisco Malpartida". But I'm using it because of all the HD44780 libraries out there, this was the first one where I could get the LCD to do anything with any of their examples. Not saying this would be the case with your library, but it seems people get so deep into making their libraries versatile, they forget to write a complete "getting started" guide to steer a new user through real world setups. Then they further complicate things by putting their ZIP file inside a TAR file, which itself is inside a ZIP.

I'm very familiar with that library as I worked with fm on it for a while.
You should always make sure to download that library from fm's bitbucket site and then do the manual install.
All the released versions of the library are available in zip files.
That library was originally done quite some time ago. Long before there was any sort of library manager in the IDE.
Back then all 3rd party library installations were manual.
The library was done as a LiquidCrystal library replacement. This allowed it transparently replace the bundled LiquidCrystal library with no changes to the sketch code that used LiquidCrystal.
Unfortunately, that now means that it can never use IDE library manager to make installation easier.

I'd like to see your HD44780 library, though you didn't mention whether it uses the original WIRE lib or not (maybe I missed it)?

I created hd44780 to essentially be similar to fm's library only better.
For LCDs that use i2c backpacks it offers a "plug and play" capability by providing auto self configuration.
You don't have to configure the i2c address, pin mappings, or backlight active level. The library figures it out by probing the device.
The hd44780 library internally is quite different from any of the other LCD libraries out there in that it allows the host processor to run in parallel with the LCD. This increases performance as the host is not doing blind delays but only delays when necessary and only the needed amount of time. The host only delays if the host wants to send another command and the previous command has not yet finished.
The hd44780 library can be installed directly from the IDE library manger using the cloud install.
If you install it make sure to read the documentation as it describes the API functions (LiquidCrystal compatible with some extensions) and where to find the examples for each i/o class.
The i/o class for i2c backpacks that use an i/o expander is hd44780_I2Cexp
It also includes a diagnostic sketch I2CexpDiag to test the i2c signals and the LCD internal memory to verify that the library is properly communicating with the LCD module.

Currently, the hd44780_I2Cexp i/o class depends on a Wire compatible library with an object named "Wire".
It can be made to work with software i2c libraries by simply declaring an i2c object named Wire.
I have tested this with a couple soft i2c libraries.
The downside of this is that you can't use the hardware Wire library for some slaves and then a soft i2c library for hd44780 because of this limitation.

I'm looking at doing a re-factoring of the library to make things much better.
(or perhaps a new simpler library that is just for LCDs with PCF8574 backpacks which would reduce the code size as well)
In the future the sketch will be able to specify the name of the i2c library object.
So as long as the object provides a Wire library compatible API, the user will be able to use any i2c object.
This is the way things should work and will once and for all solve the issue of i2c libraries and using multiple i2c buses.
It requires using a templated C++ class which uses a different structure than nearly all Arduino libraries are using today (all code must be in the .h file and no .cpp file)
hd44780 is already mostly doing this so moving all the way to allow templating is the next step.

--- bill