April 4, 2011

DS3231 Real-Time Clock

I've used the DS1307 Real-Time Clock (RTC) for a few projects in the past, but I'm currently working on several datalogger projects that use both RTC and SD-card. The problem is that the SD card won't survive 5V, and the DS1307 won't work at 3.3V. That particular clock chip needs a minimum of 4.5V according to both the datasheet and some inadvertent "experimental verification".

Rex Belli (One of my students and an all-around bright guy, contact me if you're hiring a summer intern) pointed me towards the DS3231 as a possible replacement. It has several advantages over the DS1307:
  • It runs fine on either 3.3V or 5V.
  • It has a built-in oscillator: no external crystal required.
  • It has two built-in alarms that can drive an interrupt pin, so if you just need a periodic interrupt signal this chip can in many cases do the job without a microcontroller.
  • It' rated to 2 minutes per year (max) drift. (My best DS1307 clock drifts about 2 minutes per month!)
One disadvantage is that the DS3231 doesn't have the eprom storage that the DS1307 has, but you can cheat and store seven bytes in the alarm registers if you had to.

I wrote an Arduino library for it, so if you want to use this clock chip with that microcontroller it makes things a bit easier.

Here's the library: DS3231.zip
The header file (DS3231.h) is extensively commented, and there are several example sketches included as well. Enjoy! If you use the library for anything interesting, send me an email. I'd be happy to hear what's been done with it.

13 comments:

  1. The DS3232 has all the features of the DS3231, plus it has 240 bytes of storage, just like the DS1307.

    http://kennethfinnegan.blogspot.com/2010/02/dot-matrix-arduino-clock.html

    ReplyDelete
  2. Thank you, I search a lot to find how to set time.

    ReplyDelete
  3. Thanks. This is very handy for me. I am making a simple 7-segment LED clock with the 3231.

    I found a minor problem in SetClockMode. You have:
    // Set the flag to the requested value:
    if (h12) {
    temp_buffer = temp_buffer | 0x01000000;
    } else {
    temp_buffer = temp_buffer & 0x10111111;
    }
    I am sure you meant those bit masks to be in binary not hex, like 0b01000000.

    Thanks for the code,
    David

    ReplyDelete
  4. Thanks for catching that, David. I've posted an update.

    I also added a getTime() function which reads all components of time in one call. This eliminates the chance of rollover error should you call, say, getHour() at 1:59:59.999 and getMinute() at 2:00:00.001. (That combination would tell you it's 1:00 instead of 2:00.) If you need just one component of time, use the appropriate getWhatever(); but if you need everything it'd probably be best to use getTime(). Thanks Andy Wickert for pointing out the need for this!

    -ea

    ReplyDelete
  5. Thanks for the library.

    I seem to have a small problem with it, and wondered if you had any idea what I was doing wrong?

    I'm setting it using the arduino Time library functions - casting ints to bytes:
    Clock.setClockMode(false); // set to 24h
    Clock.setYear((byte)(year()-2000));
    Clock.setMonth((byte)month());
    Clock.setDate((byte)day());
    Clock.setDoW((byte)weekday());
    Clock.setHour((byte)hour());
    Clock.setMinute((byte)minute());
    Clock.setSecond((byte)second());

    ...then getting the time later using this code:
    byte byear, bmonth, bdate, bDoW, bhour, bminute, bsecond;
    Clock.getTime(byear, bmonth, bdate, bDoW, bhour, bminute, bsecond);
    String ts = "";
    ts+=(int)bhour; ts+=":"; ts+=(int)bminute; ts+=":"; ts+=(int)bsecond;

    In some cases the hour isn't the expected number, for example I'm expecting 17 and getting 11.

    I've been using the DS1307 library for the DS3231, but prefer yours if I can work out what I'm doing wrong!

    Thanks
    Andy Betsworth

    ReplyDelete
  6. Andy--
    Beats me. Your code looks good, I don't see a problem with it, and I can't reproduce the problem here.

    Anyone else have a suggestion?
    -ea

    ReplyDelete
  7. I managed to work around the problem by resorting to:
    byte byear, bmonth, bdate, bDoW, bhour, bminute, bsecond;
    bool b1,b2;
    Clock.getTime(byear, bmonth, bdate, bDoW, bhour, bminute, bsecond);
    bhour = Clock.getHour(b1,b2);
    String ts = "";
    ts+=(int)bhour; ts+=":"; ts+=(int)bminute; ts+=":"; ts+=(int)bsecond;

    So calling the getHour function separately doesn't cause the problem... odd though, I can't pin the problem down, and the rollover issue still potentially applies with my code.

    ReplyDelete
  8. Possibly you are not setting the hours register correctly. If you wanted to set the hours to 17 and you are reading 11 then it seems you may not be converting the hours to Packed BCD (http://en.wikipedia.org/wiki/Packed_BCD#Packed_BCD) before setting the register.

    If you converted decimal 17 to hex then of course that would be 0x11 and if read out of the hours register in the intended manner that would then be a value of 11. However to correctly load the hours register you need to convert decimal 17 to Packed BCD which is a hex value of 0x17. The decimal 10s are stored in the upper 4 bits and the decimal units are stored in the lower 4 bits. Each nibble is converted to and from decimal as a separate conversion. Hope that makes sense. Refer the DS3231 datasheet.

    ReplyDelete
  9. I am using the excellent library with a LoL Shield, and the 2 seem to conflict. If anyone else is using this combo successfully, please speak up ;-)

    thanks for a great library!

    ReplyDelete
  10. Excellent Library, however BCD to decimal conversion is being done twice for the hours value, leading to a 6 hour time difference after 16:00 when using 24 hour clock...

    Change line 35 of DS3231.cpp to the following
    tempBuffer = Wire.read(); // removed 1st bcdtodec bit

    as it is done again on line 39 or 41 (depending on 12 / 24h clock)

    Steve W

    ReplyDelete
  11. Re: the problem with getTime()
    There is a bug in this function.
    This statement:
    tempBuffer = bcdToDec(Wire.read());

    should not be calling the bcdToDec function. It should be:
    tempBuffer = Wire.read();

    as it is in getHour().

    Other than that, thanks very much for the library :-)

    Pete

    ReplyDelete
  12. Thanks to all who found the multiple BCD-Dec conversion error. The library has been updated to reflect this correction.

    ReplyDelete
  13. Hi Dr. Ayars

    A very versitile clock.

    Im no programmer but managed to get the code compiling and it is running .. to a point.

    1. i dont have a 3 or 4 pin r-encoder, is it possible to have 2 separate p-buttons instead? im sure the code will have to be changed.

    I have assumed from the "english" in the code that it is only one alarm per day of the week.

    The when setting the time the year seems to have a code that ranges from 00 to I9 and not 00 to 99 and then back to 00.

    If there is a much / recent updated code would this be available?

    Im more of a tinkerer doing this to understand what else i do with the Arduino.

    Many Thanks for the code and your time


    Barry

    ReplyDelete