Skip to content

Instantly share code, notes, and snippets.

@jeje
Created March 12, 2012 10:53

Revisions

  1. jeje created this gist Mar 12, 2012.
    302 changes: 302 additions & 0 deletions DS3231.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,302 @@
    // DS3231 Class is by Seeed Technology Inc(http://www.seeedstudio.com) and used
    // in Seeeduino Stalker v2.1 for battery management(MCU power saving mode)
    // & to generate timestamp for data logging. DateTime Class is a modified
    // version supporting day-of-week.

    // Original DateTime Class and its utility code is by Jean-Claude Wippler at JeeLabs
    // http://jeelabs.net/projects/cafe/wiki/RTClib
    // Released under MIT License http://opensource.org/licenses/mit-license.php

    #include <Wire.h>
    #include <avr/pgmspace.h>
    #include "DS3231.h"
    #include <Arduino.h>

    #define SECONDS_PER_DAY 86400L

    ////////////////////////////////////////////////////////////////////////////////
    // utility code, some of this could be exposed in the DateTime API if needed

    static uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };

    // number of days since 2000/01/01, valid for 2001..2099
    static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
    if (y >= 2000)
    y -= 2000;
    uint16_t days = d;
    for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
    if (m > 2 && y % 4 == 0)
    ++days;
    return days + 365 * y + (y + 3) / 4 - 1;
    }

    static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
    return ((days * 24L + h) * 60 + m) * 60 + s;
    }

    static uint8_t conv2d(const char* p) {
    uint8_t v = 0;
    if ('0' <= *p && *p <= '9')
    v = *p - '0';
    return 10 * v + *++p - '0';
    }

    ////////////////////////////////////////////////////////////////////////////////
    // DateTime implementation - ignores time zones and DST changes
    // NOTE: also ignores leap seconds, see http://en.wikipedia.org/wiki/Leap_second

    DateTime::DateTime (long t) {
    ss = t % 60;
    t /= 60;
    mm = t % 60;
    t /= 60;
    hh = t % 24;
    uint16_t days = t / 24;
    uint8_t leap;
    for (yOff = 0; ; ++yOff) {
    leap = yOff % 4 == 0;
    if (days < 365 + leap)
    break;
    days -= 365 + leap;
    }
    for (m = 1; ; ++m) {
    uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
    if (leap && m == 2)
    ++daysPerMonth;
    if (days < daysPerMonth)
    break;
    days -= daysPerMonth;
    }
    d = days + 1;
    }

    DateTime::DateTime (uint16_t year, uint8_t month, uint8_t date, uint8_t hour, uint8_t min, uint8_t sec, uint8_t wd) {
    if (year >= 2000)
    year -= 2000;
    yOff = year;
    m = month;
    d = date;
    hh = hour;
    mm = min;
    ss = sec;
    wday = wd;
    }

    // A convenient constructor for using "the compiler's time":
    // DateTime now (__DATE__, __TIME__);
    // NOTE: using PSTR would further reduce the RAM footprint
    DateTime::DateTime (const char* date, const char* time) {
    // sample input: date = "Dec 26 2009", time = "12:34:56"
    yOff = conv2d(date + 9);
    // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
    switch (date[0]) {
    case 'J': m = date[1] == 'a' ? 1 : m = date[2] == 'n' ? 6 : 7; break;
    case 'F': m = 2; break;
    case 'A': m = date[2] == 'r' ? 4 : 8; break;
    case 'M': m = date[2] == 'r' ? 3 : 5; break;
    case 'S': m = 9; break;
    case 'O': m = 10; break;
    case 'N': m = 11; break;
    case 'D': m = 12; break;
    }
    d = conv2d(date + 4);
    hh = conv2d(time);
    mm = conv2d(time + 3);
    ss = conv2d(time + 6);
    }

    long DateTime::get() const {
    uint16_t days = date2days(yOff, m, d);
    return time2long(days, hh, mm, ss);
    }

    static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); }
    static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); }

    ////////////////////////////////////////////////////////////////////////////////
    // RTC DS3231 implementation

    uint8_t DS3231::readRegister(uint8_t regaddress)
    {
    Wire.beginTransmission(DS3231_ADDRESS);
    Wire.write(regaddress);
    Wire.endTransmission();

    Wire.requestFrom(DS3231_ADDRESS, 1);
    return Wire.read();
    }

    void DS3231::writeRegister(uint8_t regaddress,uint8_t value)
    {
    Wire.beginTransmission(DS3231_ADDRESS);
    Wire.write(regaddress);
    Wire.write(value);
    Wire.endTransmission();
    }

    uint8_t DS3231::begin(void) {

    unsigned char ctReg=0;
    ctReg |= 0b00011100;
    writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address
    delay(10);

    // set the clock to 24hr format
    uint8_t hrReg = readRegister(DS3231_HOUR_REG);
    hrReg &= 0b10111111;
    writeRegister(DS3231_HOUR_REG, hrReg);

    delay(10);

    return 1;
    }

    //Adjust the time-date specified in DateTime format
    //writing any non-existent time-data may interfere with normal operation of the RTC
    void DS3231::adjust(const DateTime& dt) {

    Wire.beginTransmission(DS3231_ADDRESS);
    Wire.write((uint8_t) DS3231_SEC_REG); //beginning from SEC Register address

    Wire.write(bin2bcd(dt.second()));
    Wire.write(bin2bcd(dt.minute()));
    Wire.write(bin2bcd((dt.hour()) & 0b10111111)); //Make sure clock is still 24 Hour
    Wire.write(dt.dayOfWeek());
    Wire.write(bin2bcd(dt.date()));
    Wire.write(bin2bcd(dt.month()));
    Wire.write(bin2bcd(dt.year() - 2000));
    Wire.endTransmission();

    }

    //Read the current time-date and return it in DateTime format
    DateTime DS3231::now() {
    Wire.beginTransmission(DS3231_ADDRESS);
    Wire.write((uint8_t) 0x00);
    Wire.endTransmission();

    Wire.requestFrom(DS3231_ADDRESS, 8);
    uint8_t ss = bcd2bin(Wire.read());
    uint8_t mm = bcd2bin(Wire.read());

    uint8_t hrreg = Wire.read();
    uint8_t hh = bcd2bin((hrreg & ~0b11000000)); //Ignore 24 Hour bit

    uint8_t wd = Wire.read();
    uint8_t d = bcd2bin(Wire.read());
    uint8_t m = bcd2bin(Wire.read());
    uint16_t y = bcd2bin(Wire.read()) + 2000;

    return DateTime (y, m, d, hh, mm, ss, wd);
    }

    //Enable periodic interrupt at /INT pin. Supports only the level interrupt
    //for consistency with other /INT interrupts. All interrupts works like single-shot counter
    //Use refreshINTA() to re-enable interrupt.
    void DS3231::enableInterrupts(uint8_t periodicity)
    {

    unsigned char ctReg=0;
    ctReg |= 0b00011101;
    writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address

    switch(periodicity)
    {
    case EverySecond:
    writeRegister(DS3231_AL1SEC_REG, 0b10000000 ); //set AM1
    writeRegister(DS3231_AL1MIN_REG, 0b10000000 ); //set AM2
    writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //set AM3
    writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4

    break;

    case EveryMinute:
    writeRegister(DS3231_AL1SEC_REG, 0b00000000 ); //Clr AM1
    writeRegister(DS3231_AL1MIN_REG, 0b10000000 ); //set AM2
    writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //set AM3
    writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4

    break;

    case EveryHour:
    writeRegister(DS3231_AL1SEC_REG, 0b00000000 ); //Clr AM1
    writeRegister(DS3231_AL1MIN_REG, 0b00000000 ); //Clr AM2
    writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //Set AM3
    writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4

    break;
    }
    }

    //Enable HH/MM/SS interrupt on /INTA pin. All interrupts works like single-shot counter
    void DS3231::enableInterrupts(uint8_t hh24, uint8_t mm, uint8_t ss)
    {
    unsigned char ctReg=0;
    ctReg |= 0b00011101;
    writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address

    writeRegister(DS3231_AL1SEC_REG, 0b00000000 | bin2bcd(ss) ); //Clr AM1
    writeRegister(DS3231_AL1MIN_REG, 0b00000000 | bin2bcd(mm)); //Clr AM2
    writeRegister(DS3231_AL1HOUR_REG, (0b00000000 | (bin2bcd(hh24) & 0b10111111))); //Clr AM3
    writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4
    }

    //Disable Interrupts. This is equivalent to begin() method.
    void DS3231::disableInterrupts()
    {
    begin(); //Restore to initial value.
    }

    //Clears the interrrupt flag in status register.
    //This is equivalent to preparing the DS3231 /INT pin to high for MCU to get ready for recognizing the next INT0 interrupt
    void DS3231::clearINTStatus()
    {
    // Clear interrupt flag
    uint8_t statusReg = readRegister(DS3231_STATUS_REG);
    statusReg &= 0b11111110;
    writeRegister(DS3231_STATUS_REG, statusReg);

    }

    //force temperature sampling and converting to registers. If this function is not used the temperature is sampled once 64 Sec.
    void DS3231::convertTemperature()
    {
    // Set CONV
    uint8_t ctReg = readRegister(DS3231_CONTROL_REG);
    ctReg |= 0b00100000;
    writeRegister(DS3231_CONTROL_REG,ctReg);


    //wait until CONV is cleared. Indicates new temperature value is available in register.
    do
    {
    //do nothing
    } while ((readRegister(DS3231_CONTROL_REG) & 0b00100000) == 0b00100000 );

    }

    //Read the temperature value from the register and convert it into float (deg C)
    float DS3231::getTemperature()
    {
    int temperatureCelsius;
    float fTemperatureCelsius;
    uint8_t tUBYTE = readRegister(DS3231_TMP_UP_REG); //Two's complement form
    uint8_t tLRBYTE = readRegister(DS3231_TMP_LOW_REG); //Fractional part

    if(tUBYTE & 0b10000000) //check if -ve number
    {
    tUBYTE ^= 0b11111111;
    tUBYTE += 0x1;
    fTemperatureCelsius = tUBYTE + ((tLRBYTE >> 6) * 0.25);
    fTemperatureCelsius = fTemperatureCelsius * -1;
    }
    else
    {
    fTemperatureCelsius = tUBYTE + ((tLRBYTE >> 6) * 0.25);
    }

    return (fTemperatureCelsius);

    }