DS3231 External RTC I2C
Dependents: HumidityController
DS3231.cpp
- Committer:
- techstep
- Date:
- 2014-08-06
- Revision:
- 1:1607610a4ee9
- Parent:
- 0:e4fbbd93299b
File content as of revision 1:1607610a4ee9:
/***************************************************************************** * Type : C++ * File : DS3231.cpp * Dec. : DS3231 + AT24C32 IIC Module Precision RTC Module Memory Module * Copyright (c) 2013-2014, Bird Techstep, tbird_th@hotmail.com * * Remark Original codr from DS3231 Library [Arduino] * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. *****************************************************************************/ #include "DS3231.h" #define CLOCK_ADDRESS 0x68 #define ADDR char(CLOCK_ADDRESS<<1) #define SINCE 1900 // Constructor DS3231::DS3231(PinName sda_pin, PinName scl_pin) : _i2c(sda_pin, scl_pin) { _i2c.frequency(100000); } /***************************************** Public Functions *****************************************/ void DS3231::getTime(uint8_t& year, uint8_t& month, uint8_t& date, uint8_t& DoW, uint8_t& hour, uint8_t& minute, uint8_t& second) { uint8_t tempBuffer; bool PM; bool h12; char data[7]; char cmd; cmd = 0x00; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, data, 7 ); second = bcdToDec(data[0]); minute = bcdToDec(data[1]); tempBuffer = bcdToDec(data[2]); h12 = tempBuffer & 0x40; if (h12) { PM = tempBuffer & 0x20; hour = bcdToDec(tempBuffer & 0x1F); } else { hour = bcdToDec(tempBuffer & 0x3F); } DoW = bcdToDec(data[3]); date = bcdToDec(data[4]); month = bcdToDec(data[5] & 0x7F); year = bcdToDec(data[6]); } int8_t DS3231::getSecond(void) { char data; char cmd; cmd = 0x00; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); return(bcdToDec(data)); } uint8_t DS3231::getMinute(void) { char data; char cmd; cmd = 0x01; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); return(bcdToDec(data)); } uint8_t DS3231::getHour(bool& h12, bool& PM) { char temp_buffer; char hour; char cmd; cmd = 0x02; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &temp_buffer, 1 ); h12 = temp_buffer & 0x40; if (h12) { PM = temp_buffer & 0x20; hour = bcdToDec(temp_buffer & 0x1F); } else { hour = bcdToDec(temp_buffer & 0x3F); } return hour; } uint8_t DS3231::getDoW(void) { char data; char cmd; cmd = 0x03; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); return(bcdToDec(data)); } uint8_t DS3231::getDate(void) { char data; char cmd; cmd = 0x04; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); return(bcdToDec(data)); } uint8_t DS3231::getMonth(bool& Century) { char temp_buffer; char cmd; cmd = 0x05; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &temp_buffer, 1 ); Century = temp_buffer & 0x80; return (bcdToDec(temp_buffer & 0x7F)) ; } uint8_t DS3231::getYear(void) { char data; char cmd; cmd = 0x06; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); return(bcdToDec(data)); } void DS3231::setSecond(uint8_t Second) { // Sets the seconds // This function also resets the Oscillator Stop Flag, which is set // whenever power is interrupted. char cmd[2]; cmd[0] = 0x00; cmd[1] = decToBcd(Second); _i2c.write(ADDR, cmd, 2); // Clear OSF flag uint8_t temp_buffer = readControlByte(1); writeControlByte((temp_buffer & 0x7F), 1); } void DS3231::setMinute(uint8_t Minute) { // Sets the minutes char cmd[2]; cmd[0] = 0x01; cmd[1] = decToBcd(Minute); _i2c.write(ADDR, cmd, 2); } void DS3231::setHour(uint8_t Hour) { // Sets the hour, without changing 12/24h mode. // The hour must be in 24h format. bool h12; // Start by figuring out what the 12/24 mode is char data; char cmd; cmd = 0x02; _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &data, 1 ); h12 = (data & 0x40); // if h12 is true, it's 12h mode; false is 24h. if (h12) { // 12 hour if (Hour > 12) { Hour = decToBcd(Hour-12) | 0x60; } else { Hour = decToBcd(Hour) & 0xDF; } } else { // 24 hour Hour = decToBcd(Hour) & 0xBF; } char cmdHour[2]; cmdHour[0] = 0x02; cmdHour[1] = Hour; _i2c.write(ADDR, cmdHour, 2); } void DS3231::setDoW(uint8_t DoW) { // Sets the Day of Week char cmd[2]; cmd[0] = 0x03; cmd[1] = decToBcd(DoW); _i2c.write(ADDR, cmd, 2 ); } void DS3231::setDate(uint8_t Date) { // Sets the Date char cmd[2]; cmd[0] = 0x04; cmd[1] = decToBcd(Date); _i2c.write(ADDR, cmd, 2 ); } void DS3231::setMonth(uint8_t Month) { // Sets the month char cmd[2]; cmd[0] = 0x05; cmd[1] = decToBcd(Month); _i2c.write(ADDR, cmd, 2 ); } void DS3231::setYear(uint8_t Year) { // Sets the year char cmd[2]; cmd[0] = 0x06; cmd[1] = decToBcd(Year); _i2c.write(ADDR, cmd, 2 ); } void DS3231::setClockMode(bool h12) { // sets the mode to 12-hour (true) or 24-hour (false). // One thing that bothers me about how I've written this is that // if the read and right happen at the right hourly millisecnd, // the clock will be set back an hour. Not sure how to do it better, // though, and as long as one doesn't set the mode frequently it's // a very minimal risk. // It's zero risk if you call this BEFORE setting the hour, since // the setHour() function doesn't change this mode. char temp_buffer; char cmd; cmd = 0x02; // Start by reading byte 0x02. _i2c.write(ADDR, &cmd, 1 ); _i2c.read( ADDR, &temp_buffer, 1 ); // Set the flag to the requested value: if (h12) { temp_buffer = temp_buffer | 0x40; } else { temp_buffer = temp_buffer & 0xBF; } // Write the byte _i2c.write(ADDR, &cmd, 1 ); _i2c.write(temp_buffer); } float DS3231::getTemperature(void) { // Checks the internal thermometer on the DS3231 and returns the // temperature as a floating-point value. char temp[2]; char cmd; cmd = 0x11; _i2c.write(ADDR, &cmd, 1); _i2c.read( ADDR, temp, 2); return float(temp[0]) + 0.25*(temp[1]>>6); } void DS3231::getA1Time(uint8_t& A1Day, uint8_t& A1Hour, uint8_t& A1Minute, uint8_t& A1Second, uint8_t& AlarmBits, bool& A1Dy, bool& A1h12, bool& A1PM) { uint8_t temp_buffer; char data[4]; char cmd; cmd = 0x07; _i2c.write(ADDR, &cmd, 1); _i2c.read( ADDR, data, 4); temp_buffer = data[0]; // Get A1M1 and A1 Seconds A1Second = bcdToDec(temp_buffer & 0x7F); // put A1M1 bit in position 0 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>7; temp_buffer = data[1]; // Get A1M2 and A1 minutes A1Minute = bcdToDec(temp_buffer & 0x7F); // put A1M2 bit in position 1 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>6; temp_buffer = data[2]; // Get A1M3 and A1 Hour // put A1M3 bit in position 2 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>5; // determine A1 12/24 mode A1h12 = temp_buffer & 0x40; if (A1h12) { A1PM = temp_buffer & 0x20; // determine am/pm A1Hour = bcdToDec(temp_buffer & 0x1F); // 12-hour } else { A1Hour = bcdToDec(temp_buffer & 0x3F); // 24-hour } temp_buffer = data[3]; // Get A1M4 and A1 Day/Date // put A1M3 bit in position 3 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>4; // determine A1 day or date flag A1Dy = (temp_buffer & 0x40)>>6; if (A1Dy) { // alarm is by day of week, not date. A1Day = bcdToDec(temp_buffer & 0x0F); } else { // alarm is by date, not day of week. A1Day = bcdToDec(temp_buffer & 0x3F); } } void DS3231::getA2Time(uint8_t& A2Day, uint8_t& A2Hour, uint8_t& A2Minute, uint8_t& AlarmBits, bool& A2Dy, bool& A2h12, bool& A2PM) { uint8_t temp_buffer; char data[3]; char cmd; cmd = 0x0B; _i2c.write(ADDR, &cmd, 1); _i2c.read( ADDR, data, 4); //I2C.requestFrom(CLOCK_ADDRESS, 3); temp_buffer = data[0]; // Get A2M2 and A2 Minutes A2Minute = bcdToDec(temp_buffer & 0x7F); // put A2M2 bit in position 4 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>3; temp_buffer = data[1]; // Get A2M3 and A2 Hour // put A2M3 bit in position 5 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>2; // determine A2 12/24 mode A2h12 = temp_buffer & 0x40; if (A2h12) { A2PM = temp_buffer & 0x20; // determine am/pm A2Hour = bcdToDec(temp_buffer & 0x1F); // 12-hour } else { A2Hour = bcdToDec(temp_buffer & 0x3F); // 24-hour } temp_buffer = data[2]; // Get A2M4 and A1 Day/Date // put A2M4 bit in position 6 of DS3231_AlarmBits. AlarmBits = AlarmBits | (temp_buffer & 0x80)>>1; // determine A2 day or date flag A2Dy = (temp_buffer & 0x40)>>6; if (A2Dy) { // alarm is by day of week, not date. A2Day = bcdToDec(temp_buffer & 0x0F); } else { // alarm is by date, not day of week. A2Day = bcdToDec(temp_buffer & 0x3F); } } void DS3231::setA1Time(uint8_t A1Day, uint8_t A1Hour, uint8_t A1Minute, uint8_t A1Second, uint8_t AlarmBits, bool A1Dy, bool A1h12, bool A1PM) { // Sets the alarm-1 date and time on the DS3231, using A1* information uint8_t temp_buffer; char data[4]; data[0] = 0x07; // A1 starts at 07h data[1] = decToBcd(A1Second) | ((AlarmBits & 0x01) << 7); // Send A1 second and A1M1 data[2] = decToBcd(A1Minute) | ((AlarmBits & 0x02) << 6); // Send A1 Minute and A1M2 // Figure out A1 hour if (A1h12) { // Start by converting existing time to h12 if it was given in 24h. if (A1Hour > 12) { // well, then, this obviously isn't a h12 time, is it? A1Hour = A1Hour - 12; A1PM = true; } if (A1PM) { // Afternoon // Convert the hour to BCD and add appropriate flags. temp_buffer = decToBcd(A1Hour) | 0x60; } else { // Morning // Convert the hour to BCD and add appropriate flags. temp_buffer = decToBcd(A1Hour) | 0x40; } } else { // Now for 24h temp_buffer = decToBcd(A1Hour); } temp_buffer = temp_buffer | ((AlarmBits & 0x04)<<5); // A1 hour is figured out, send it //I2C.write(temp_buffer); _i2c.write(temp_buffer); // Figure out A1 day/date and A1M4 temp_buffer = ((AlarmBits & 0x08)<<4) | decToBcd(A1Day); if (A1Dy) { // Set A1 Day/Date flag (Otherwise it's zero) temp_buffer = temp_buffer | 0x40; } data[3] = temp_buffer; _i2c.write(ADDR, data, 4); } void DS3231::setA2Time(uint8_t A2Day, uint8_t A2Hour, uint8_t A2Minute, uint8_t AlarmBits, bool A2Dy, bool A2h12, bool A2PM) { // Sets the alarm-2 date and time on the DS3231, using A2* information uint8_t temp_buffer; char data[4]; data[0] = 0x0B; // A2 starts at 0Bh data[1] = decToBcd(A2Minute) | ((AlarmBits & 0x10) << 3); // Send A2 Minute and A2M2 // Figure out A2 hour if (A2h12) { // Start by converting existing time to h12 if it was given in 24h. if (A2Hour > 12) { // well, then, this obviously isn't a h12 time, is it? A2Hour = A2Hour - 12; A2PM = true; } if (A2PM) { // Afternoon // Convert the hour to BCD and add appropriate flags. temp_buffer = decToBcd(A2Hour) | 0x60; } else { // Morning // Convert the hour to BCD and add appropriate flags. temp_buffer = decToBcd(A2Hour) | 0x40; } } else { // Now for 24h temp_buffer = decToBcd(A2Hour); } // add in A2M3 bit temp_buffer = temp_buffer | ((AlarmBits & 0x20)<<2); // A2 hour is figured out, send it data[2] = temp_buffer; // Figure out A2 day/date and A2M4 temp_buffer = ((AlarmBits & 0x40)<<1) | decToBcd(A2Day); if (A2Dy) { // Set A2 Day/Date flag (Otherwise it's zero) temp_buffer = temp_buffer | 0x40; } data[3] = temp_buffer; _i2c.write(ADDR, data, 4); } void DS3231::turnOnAlarm(uint8_t Alarm) { // turns on alarm number "Alarm". Defaults to 2 if Alarm is not 1. uint8_t temp_buffer = readControlByte(0); // modify control byte if (Alarm == 1) { temp_buffer = temp_buffer | 0x05; } else { temp_buffer = temp_buffer | 0x06; } writeControlByte(temp_buffer, 0); } void DS3231::turnOffAlarm(uint8_t Alarm) { // turns off alarm number "Alarm". Defaults to 2 if Alarm is not 1. // Leaves interrupt pin alone. uint8_t temp_buffer = readControlByte(0); // modify control byte if (Alarm == 1) { temp_buffer = temp_buffer & 0xFE; } else { temp_buffer = temp_buffer & 0xFD; } writeControlByte(temp_buffer, 0); } bool DS3231::checkAlarmEnabled(uint8_t Alarm) { // Checks whether the given alarm is enabled. uint8_t result = 0x0; uint8_t temp_buffer = readControlByte(0); if (Alarm == 1) { result = temp_buffer & 0x01; } else { result = temp_buffer & 0x02; } return result; } bool DS3231::checkIfAlarm(uint8_t Alarm) { // Checks whether alarm 1 or alarm 2 flag is on, returns T/F accordingly. // Turns flag off, also. // defaults to checking alarm 2, unless Alarm == 1. uint8_t result; uint8_t temp_buffer = readControlByte(1); if (Alarm == 1) { // Did alarm 1 go off? result = temp_buffer & 0x01; // clear flag temp_buffer = temp_buffer & 0xFE; } else { // Did alarm 2 go off? result = temp_buffer & 0x02; // clear flag temp_buffer = temp_buffer & 0xFD; } writeControlByte(temp_buffer, 1); return result; } void DS3231::enableOscillator(bool TF, bool battery, uint8_t frequency) { // turns oscillator on or off. True is on, false is off. // if battery is true, turns on even for battery-only operation, // otherwise turns off if Vcc is off. // frequency must be 0, 1, 2, or 3. // 0 = 1 Hz // 1 = 1.024 kHz // 2 = 4.096 kHz // 3 = 8.192 kHz (Default if frequency byte is out of range) if (frequency > 3) frequency = 3; // read control byte in, but zero out current state of RS2 and RS1. uint8_t temp_buffer = readControlByte(0) & 0xE7; if (battery) { // turn on BBSQW flag temp_buffer = temp_buffer | 0x40; } else { // turn off BBSQW flag temp_buffer = temp_buffer & 0xBF; } if (TF) { // set ~EOSC to 0 and INTCN to zero. temp_buffer = temp_buffer & 0x7B; } else { // set ~EOSC to 1, leave INTCN as is. temp_buffer = temp_buffer | 0x80; } // shift frequency into bits 3 and 4 and set. frequency = frequency << 3; temp_buffer = temp_buffer | frequency; // And write the control bits writeControlByte(temp_buffer, 0); } void DS3231::enable32kHz(bool TF) { // turn 32kHz pin on or off uint8_t temp_buffer = readControlByte(1); if (TF) { // turn on 32kHz pin temp_buffer = temp_buffer | 0x08; } else { // turn off 32kHz pin temp_buffer = temp_buffer & 0xF7; } writeControlByte(temp_buffer, 1); } bool DS3231::oscillatorCheck() { // Returns false if the oscillator has been off for some reason. // If this is the case, the time is probably not correct. uint8_t temp_buffer = readControlByte(1); bool result = true; if (temp_buffer & 0x80) { // Oscillator Stop Flag (OSF) is set, so return false. result = false; } return result; } /***************************************** Private Functions *****************************************/ uint8_t DS3231::decToBcd(uint8_t val) { // Convert normal decimal numbers to binary coded decimal return ( (val/10*16) + (val%10) ); } uint8_t DS3231::bcdToDec(uint8_t val) { // Convert binary coded decimal to normal decimal numbers return ( (val/16*10) + (val%16) ); } uint8_t DS3231::readControlByte(bool which) { // Read selected control byte // first byte (0) is 0x0e, second (1) is 0x0f char data; char cmd[2]; cmd[0] = 0x0F; cmd[1] = 0x0E; if (which) { // second control byte _i2c.write(ADDR, &cmd[0], 1 ); } else { // first control byte _i2c.write(ADDR, &cmd[1], 1 ); } _i2c.read( ADDR, &data, 1 ); return data; } void DS3231::writeControlByte(uint8_t control, bool which) { // Write the selected control byte. // which=false -> 0x0e, true->0x0f. char data1[2]; char data2[2]; data1[0] = 0x0F; data1[1] = control; data2[0] = 0x0E; data2[1] = control; if (which) { _i2c.read( ADDR, data1, 2); } else { _i2c.read( ADDR, data2, 2); } }