Library for Real Time Clock module MCP97410 based on Library for DS1307

Fork of RTC-DS1307 by Henry Leinen

Rtc_Mcp97410.cpp

Committer:
charly
Date:
2015-03-08
Revision:
13:10e564536e23
Parent:
12:88f82e47b6a1

File content as of revision 13:10e564536e23:

#include "mbed.h"
#include "Rtc_Mcp97410.h"

#ifndef DEBUG
#define DEBUG
#endif
#include "debug.h"

const char *Rtc_Mcp97410::m_weekDays[] = { "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };


Rtc_Mcp97410::Rtc_Mcp97410(I2C* i2c)
{

    _i2c = i2c;
    if (_i2c == NULL)
        error("Rtc_Mcp97410: no I2C specified");

    // Set the frequency to standard 100kHz
    // MCP97410 can handle full 400kHz-speed!
    //_i2c->frequency(100000);
}

Rtc_Mcp97410::~Rtc_Mcp97410()
{

}

bool Rtc_Mcp97410::setTime(Time_rtc& time, bool start, bool thm)
{
    char buffer[7];
    INFO("reading clock registers to write the new time : %d:%d:%d\n", time.hour, time.min, time.sec);
    if (!readRTC(ADDR_SEC,buffer,7)) {
        ERR("Failed to read from RTC\n");
        return false;
    }
    //  Now update only the time part (saving the existing flags)
    if (start) {
        buffer[ADDR_SEC] |= START_32KHZ;
    } else {
        buffer[ADDR_SEC] &= ~START_32KHZ;
    }
    buffer[ADDR_SEC] = (buffer[ADDR_SEC]& START_32KHZ) | (decimalToBcd(time.sec)& ~START_32KHZ);
    buffer[ADDR_MIN] = decimalToBcd(time.min);
    if (thm) {
        //  AM PM format
        buffer[ADDR_HOUR] = HOUR_12 | (time.hour>12 ? (0x20 | ((decimalToBcd(time.hour-12)))) : decimalToBcd(time.hour));
    } else {
        // 24 hours format: HOUR_12 is 0
        buffer[ADDR_HOUR] = decimalToBcd(time.hour) & 0x3F;
    }
    // bit 3 of register 03 on MCP97410 is VBATEN (different to DS1307)!
    // should be set to 1 to enable Battery-Backup
    buffer[ADDR_DAY] = VBATEN | (time.wday & 0x07);
    buffer[ADDR_DATE] = decimalToBcd(time.date);
    buffer[ADDR_MNTH] = decimalToBcd(time.mon);
    buffer[ADDR_YEAR] = decimalToBcd(time.year-2000);
    INFO("Writing new time and date data to RTC\n");
    if (!writeRTC(ADDR_SEC, buffer, 7) ) {
        ERR("Failed to write the data to RTC!\n");
        return false;
    }
    return true;
}

bool Rtc_Mcp97410::getTime(Time_rtc& time)
{
    char buffer[7];
    bool thm = false;

    INFO("Getting time from RTC\n");
    if (!readRTC(ADDR_SEC, buffer, 7) ) {
        //  Failed to read
        ERR("Failed to read from RTC\n");
        return false;
    }
    thm = ((buffer[ADDR_HOUR] & HOUR_12) == HOUR_12);
    time.sec = bcdToDecimal(buffer[ADDR_SEC]&0x7F);
    time.min = bcdToDecimal(buffer[ADDR_MIN]);
    if (thm) {
        // in 12-hour-mode, we need to add 12 hours if PM bit is set
        time.hour = Rtc_Mcp97410::bcdToDecimal( buffer[ADDR_HOUR] & 0x1F );
        if ((buffer[ADDR_HOUR] & PM) == PM)
            time.hour += 12;
    } else {
        time.hour = Rtc_Mcp97410::bcdToDecimal( buffer[ADDR_HOUR] & 0x3F );
    }
    time.wday = buffer[ADDR_DAY] & 0x07;
    INFO("OSCRUN:%0d PWRFAIL:%0d VBATEN:%0d\n", (buffer[ADDR_STAT]>>5) & 0x01, buffer[ADDR_STAT]>>4 & 0x01, buffer[ADDR_STAT]>>3 & 0x01);
    time.date = Rtc_Mcp97410::bcdToDecimal( buffer[ADDR_DATE]);
    time.mon = Rtc_Mcp97410::bcdToDecimal( buffer[ADDR_MNTH]);
    time.year = Rtc_Mcp97410::bcdToDecimal(buffer[ADDR_YEAR]) + 2000;   //  plus hundret is because RTC is giving the years since 2000, but std c struct tm needs years since 1900

    return true;
}


bool Rtc_Mcp97410::startClock()
{
    char strtStop;

    INFO ("Reading clock start/stop register value\n");
    if (!readRTC(ADDR_SEC, &strtStop, 1)) {
        ERR("Failed to read clock start stop register !\n");
        return false;
    }
    //set bit
    strtStop |= START_32KHZ;


    INFO("Writing back start/stop register value\n");
    if (!writeRTC(ADDR_SEC, &strtStop, 1)) {
        ERR("Failed to write the start stop register !\n");
        return false;
    }

    INFO("Start/stop register value successfully written\n");
    return true;
}

bool Rtc_Mcp97410::stopClock()
{
    char strtStop;

    INFO ("Reading clock start/stop register value\n");
    if (!readRTC(ADDR_SEC, &strtStop, 1)) {
        ERR("Failed to read clock start stop register !\n");
        return false;
    }
    //clear bit
    strtStop &= ~START_32KHZ;

    INFO("Writing back start/stop register value\n");
    if (!writeRTC(ADDR_SEC, &strtStop, 1)) {
        ERR("Failed to write the start stop register !\n");
        return false;
    }

    INFO("Start/stop register value successfully written\n");
    return true;
}

bool Rtc_Mcp97410::setSquareWaveOutput(bool ena, SqwRateSelect_t rs)
{
    char reg;
    INFO("Reading register value first\n");

    if (!readRTC(ADDR_CTRL,&reg, 1)) {
        ERR("Failed to read RTC register value !\n");
        return false;
    }
    INFO("[Reg:0x07] = %02x\n", reg);

    //  preserve the OUT control bit while writing the frequency and enable bits
    reg = (reg & OUT_PIN) | (ena ? 0x10 : 0) | ((char)rs & 0x03);

    INFO("Writing back register value\n");
    INFO("[Reg:0x07] = %02x\n", reg);

    if (!writeRTC(ADDR_CTRL, &reg, 1)) {
        ERR("Failed to write register value !\n");
        return false;
    }

    INFO("Successfully changed the square wave output.\n");
    return true;
}



bool Rtc_Mcp97410::read(int control_byte, int address, char *buffer, int len)
{
    char buffer2[2] = {(char)address, 0};

//    _i2c->start();
    if (_i2c->write(control_byte, buffer2, 1) != 0) {
        ERR("Failed to write register address on RTC or EEPROM!\n");
        _i2c->stop();
        return false;
    }
    if (_i2c->read(control_byte, buffer, len) != 0) {
        ERR("Failed to read register !\n");
        return false;
    }
    _i2c->stop();

    INFO("Successfully read %d bytes from RTC or EEPROM\n", len);
    return true;
}

bool Rtc_Mcp97410::readRTC(int address, char *buffer, int len)
{
    return read(ADDR_RTCC, address,buffer, len);
}

bool Rtc_Mcp97410::readEEPROM(int address, char *buffer, int len)
{
    return read(ADDR_EEPROM, address,buffer, len);
}

bool Rtc_Mcp97410::write(int control_byte, int address, char *buffer, int len)
{
    char buffer2[10];
    buffer2[0] = address&0xFF;
    for (int i = 0 ; i < len ; i++)
        buffer2[i+1] = buffer[i];

//    _i2c->start();
    if (_i2c->write(control_byte, buffer2, len+1) != 0) {
        ERR("Failed to write data to rtc or EEPROM\n");
        _i2c->stop();
        return false;
    }
    _i2c->stop();
    INFO("Successfully written %d bytes to RTC or EEPROM\n", len);
    return true;
}


bool Rtc_Mcp97410::writeRTC(int address, char *buffer, int len)
{
    return write(ADDR_RTCC, address,buffer, len);
}

bool Rtc_Mcp97410::writeEEPROM(int address, char* buffer, int len)
{
    if (len <=8) {
        return write(ADDR_EEPROM, address,buffer, len);
    } else {
        ERR("Can write maximum 8 Bytes to EEPROM in one call\n");
        return false;
    }
}


uint8_t Rtc_Mcp97410::readTrim()
{
    uint8_t  trim;
    if (readRTC(ADDR_CAL, (char *) &trim, 1)) {
        INFO("OSCTRIM: %02X \n", trim);
        return trim;
    } else {
        return 0;
    }
}

bool Rtc_Mcp97410::incTrim()
{
    uint8_t  trim;
    uint8_t  trim_val;
    uint8_t  trim_sign;

    readRTC(ADDR_CAL, (char *) &trim, 1);
    trim_val = 0x7F&trim;
    trim_sign= trim >> 7;
    if (trim_sign == 1) {
        // positive: inc
        if (trim_val < 0x7F) {
            trim_val = trim_val++;
        }
    } else {
        // negative: dec
        if (trim_val != 0) {
            trim_val = trim_val--;
        } else {
            // change sign
            trim_sign = 1;
        }
    }
    trim = trim_sign <<7 | trim_val;
    INFO("incTrim: New TRIM: %02X\n", trim);
    return writeRTC(ADDR_CAL, (char *) &trim, 1);
}

bool Rtc_Mcp97410::decTrim()
{
    uint8_t  trim;
    uint8_t  trim_val;
    uint8_t  trim_sign;

    readRTC(ADDR_CAL, (char *) &trim, 1);
    trim_val = 0x7F&trim;
    trim_sign= trim >> 7;
    if (trim_sign == 0) {
        // negative: inc
        if (trim_val < 0x7F) {
            trim_val = trim_val++;
        }
    } else {
        // positive: dec
        if (trim_val != 0) {
            trim_val = trim_val--;
        } else {
            // change sign
            trim_sign = 0;
        }
    }
    trim = trim_sign <<7 | trim_val;
    INFO("decTrim: New TRIM: %02X\n", trim);
    return writeRTC(ADDR_CAL, (char *) &trim, 1);
}

bool Rtc_Mcp97410::disableEEPROMWrite()
{
    // protect all EEPROM from write operations
    // set STATUS_REGISTER 0xFF bits 2 and 3
    char reg = 0x0C;
    return write(ADDR_EEPROM, ADDR_EEPROM_SR,&reg, 1);
}


bool Rtc_Mcp97410::enableEEPROMWrite()
{
    // enable  all EEPROM write operations
    // unset STATUS_REGISTER 0xFF bits 2 and 3
    char reg = 0x00;
    return write(ADDR_EEPROM, ADDR_EEPROM_SR,&reg, 1);
}

bool Rtc_Mcp97410::readEUI48(uint8_t* eui48)
{
    //EUI48 start at EEPROM-address 0xF2
    return(readEEPROM(0xF2,(char *) eui48,6));
}

bool Rtc_Mcp97410::writeEUI48(uint8_t* eui48)
{
    //EUI48 start at EEPROM-address 0xF2
    return(writeEEPROM(0xF2,(char *) eui48,6));
}

bool Rtc_Mcp97410::readEUI64(uint8_t* eui64)
{
    //EUI64 start at EEPROM-address 0xF2
    return(readEEPROM(0xF0,(char *) eui64,8));
}


bool Rtc_Mcp97410::writeEUI64(uint8_t* eui64)
{
    //EUI64 start at EEPROM-address 0xF2
    return(writeEEPROM(0xF0,(char *) eui64,8));
}

bool Rtc_Mcp97410::unlockEUI()
{
    // unlock the special EEPROM area
    // write 0x55 and then 0xAA to the EEUNLOCK-register
    char reg =0x00;
    bool result = true;

    reg = 0x55;
    result &= writeRTC(ADDR_ULID,&reg,1);
    reg = 0xAA;
    result &= writeRTC(ADDR_ULID,&reg,1);
    if (result) {
        INFO("Successfully UNLOCKED the EUI-Area in EEPROM\n");
        return true;
    } else {
        ERR("Error in UNLOCKING the EUI-Area in EEPROM\n");
        return false;
    }
}

RtcCls::RtcCls(I2C* i2c, PinName sqw, bool bUseSqw)
    : Rtc_Mcp97410(i2c), m_sqw(sqw), m_bUseSqw(bUseSqw), m_bAlarmEnabled(false), m_alarmfunc(NULL)
{
    Time_rtc t;
    //  query time from device
    getTime(t);
    //  sync the time with MBED RTC
    struct tm now = {t.sec, t.min, t.hour, t.date, t.mon-1, t.year-1900};
    m_time = mktime(&now);
    set_time(m_time);

    //  Only register the callback and start the SQW if requested to do so. Otherwise the system
    //  will use the MBED built-in RTC.
    if (m_bUseSqw) {
        //  start the wave
        setSquareWaveOutput(true, RS1Hz);
        //  register callback from now on the time will be maintained by the square wave input
        m_sqw.rise(this, &RtcCls::_callback);
    }
}

void RtcCls::_callback(void)
{
//    INFO("Tick!");
    //  Simply increase the number of seconds
    m_time++;
//    if (m_bAlarmEnabled && (m_time == m_alarmTime)) {
//        if (m_alarmfunc != NULL)
//            m_alarmfunc();
//        m_bAlarmEnabled = false;
//    }
}

time_t RtcCls::getTime()
{
    //  when not using the HW support, we have to query the RTC chip. Other wise we can just return out stored value
    if (!m_bUseSqw) {
        Time_rtc t;
        getTime(t);
        struct tm now = {t.sec, t.min, t.hour, t.date, t.mon-1, t.year-1900};
        m_time = mktime(&now);
        INFO("getting time %02d.%02d.%04d %02d:%02d:%02d Ticks=%08lx", t.date, t.mon, t.year, t.hour, t.min, t.sec, m_time);
    } else {
        INFO("getting time Ticks=%08lx", m_time);
    }
    return m_time;
}

void RtcCls::setTime(time_t t)
{
    Time_rtc tim;
    struct tm *now;
    now = localtime(&t);

    tim.sec = now->tm_sec;
    tim.min = now->tm_min;
    tim.hour = now->tm_hour;
    tim.date = now->tm_mday;
    tim.mon = now->tm_mon+1;
    tim.year = now->tm_year + 1900;
    tim.wday = now->tm_wday +1;

    setTime( tim, true, true);
    set_time(t);
}