From 782cdfd11dff058e0bf275d0814fdb229d7a9700 Mon Sep 17 00:00:00 2001 From: Bhuvanchandra Date: Wed, 13 Jul 2016 02:03:23 +0000 Subject: [PATCH] extras: ds3231: Add support for DS3231 real-time clock (RTC) Add support for DS3231 extremely accurate I2C real-time clock (RTC). Signed-off-by: Bhuvanchandra DV --- extras/ds3231/LICENSE | 22 +++ extras/ds3231/component.mk | 9 ++ extras/ds3231/ds3231.c | 296 +++++++++++++++++++++++++++++++++++++ extras/ds3231/ds3231.h | 187 +++++++++++++++++++++++ 4 files changed, 514 insertions(+) create mode 100644 extras/ds3231/LICENSE create mode 100644 extras/ds3231/component.mk create mode 100644 extras/ds3231/ds3231.c create mode 100644 extras/ds3231/ds3231.h diff --git a/extras/ds3231/LICENSE b/extras/ds3231/LICENSE new file mode 100644 index 0000000..3e60637 --- /dev/null +++ b/extras/ds3231/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Richard A Burton +Copyright (c) 2016 Bhuvanchandra DV + +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. diff --git a/extras/ds3231/component.mk b/extras/ds3231/component.mk new file mode 100644 index 0000000..46d9b12 --- /dev/null +++ b/extras/ds3231/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/ds3231 + +# expected anyone using bmp driver includes it as 'ds3231/ds3231.h' +INC_DIRS += $(ds3231_ROOT).. + +# args for passing into compile rule generation +ds3231_SRC_DIR = $(ds3231_ROOT) + +$(eval $(call component_compile_rules,ds3231)) diff --git a/extras/ds3231/ds3231.c b/extras/ds3231/ds3231.c new file mode 100644 index 0000000..87ce805 --- /dev/null +++ b/extras/ds3231/ds3231.c @@ -0,0 +1,296 @@ +/* Driver for DS3231 high precision RTC module + * + * Part of esp-open-rtos + * Copyright (C) 2015 Richard A Burton + * Copyright (C) 2016 Bhuvanchandra DV + * MIT Licensed as described in the file LICENSE +*/ + +#include "ds3231.h" +#include "espressif/esp_common.h" +#include "espressif/sdk_private.h" +#include "esp8266.h" + +#include "i2c/i2c.h" + +/* Convert normal decimal to binary coded decimal */ +static inline uint8_t decToBcd(uint8_t dec) +{ + return(((dec / 10) * 16) + (dec % 10)); +} + +/* Convert binary coded decimal to normal decimal */ +static inline uint8_t bcdToDec(uint8_t bcd) +{ + return(((bcd / 16) * 10) + (bcd % 16)); +} + +/* Send a number of bytes to the rtc over i2c + * returns true to indicate success + */ +static inline bool ds3231_send(uint8_t *data, uint8_t len) +{ + return i2c_slave_write(DS3231_ADDR, data, len); +} + +/* Read a number of bytes from the rtc over i2c + * returns true to indicate success + */ +static inline bool ds3231_recv(uint8_t *data, uint8_t len) +{ + return i2c_slave_read(DS3231_ADDR, data[0], data, len); +} + +bool ds3231_setTime(struct tm *time) +{ + uint8_t data[8]; + + /* start register */ + data[0] = DS3231_ADDR_TIME; + /* time/date data */ + data[1] = decToBcd(time->tm_sec); + data[2] = decToBcd(time->tm_min); + data[3] = decToBcd(time->tm_hour); + data[4] = decToBcd(time->tm_wday + 1); + data[5] = decToBcd(time->tm_mday); + data[6] = decToBcd(time->tm_mon + 1); + data[7] = decToBcd(time->tm_year - 100); + + return ds3231_send(data, 8); +} + +bool ds3231_setAlarm(uint8_t alarms, struct tm *time1, uint8_t option1, struct tm *time2, uint8_t option2) +{ + int i = 0; + uint8_t data[8]; + + /* start register */ + data[i++] = (alarms == DS3231_ALARM_2 ? DS3231_ADDR_ALARM2 : DS3231_ADDR_ALARM1); + + /* alarm 1 data */ + if (alarms != DS3231_ALARM_2) { + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SEC ? decToBcd(time1->tm_sec) : DS3231_ALARM_NOTSET); + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMIN ? decToBcd(time1->tm_min) : DS3231_ALARM_NOTSET); + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMINHOUR ? decToBcd(time1->tm_hour) : DS3231_ALARM_NOTSET); + data[i++] = (option1 == DS3231_ALARM1_MATCH_SECMINHOURDAY ? (decToBcd(time1->tm_wday + 1) & DS3231_ALARM_WDAY) : + (option1 == DS3231_ALARM1_MATCH_SECMINHOURDATE ? decToBcd(time1->tm_mday) : DS3231_ALARM_NOTSET)); + } + + /* alarm 2 data */ + if (alarms != DS3231_ALARM_1) { + data[i++] = (option2 >= DS3231_ALARM2_MATCH_MIN ? decToBcd(time2->tm_min) : DS3231_ALARM_NOTSET); + data[i++] = (option2 >= DS3231_ALARM2_MATCH_MINHOUR ? decToBcd(time2->tm_hour) : DS3231_ALARM_NOTSET); + data[i++] = (option2 == DS3231_ALARM2_MATCH_MINHOURDAY ? (decToBcd(time2->tm_wday + 1) & DS3231_ALARM_WDAY) : + (option2 == DS3231_ALARM2_MATCH_MINHOURDATE ? decToBcd(time2->tm_mday) : DS3231_ALARM_NOTSET)); + } + + return ds3231_send(data, i); +} + +/* Get a byte containing just the requested bits + * pass the register address to read, a mask to apply to the register and + * an uint* for the output + * you can test this value directly as true/false for specific bit mask + * of use a mask of 0xff to just return the whole register byte + * returns true to indicate success + */ +bool ds3231_getFlag(uint8_t addr, uint8_t mask, uint8_t *flag) +{ + uint8_t data[1]; + + /* get register */ + data[0] = addr; + if (ds3231_send(data, 1) && ds3231_recv(data, 1)) { + /* return only requested flag */ + *flag = (data[0] & mask); + return true; + } + + return false; +} + +/* Set/clear bits in a byte register, or replace the byte altogether + * pass the register address to modify, a byte to replace the existing + * value with or containing the bits to set/clear and one of + * DS3231_SET/DS3231_CLEAR/DS3231_REPLACE + * returns true to indicate success + */ +bool ds3231_setFlag(uint8_t addr, uint8_t bits, uint8_t mode) +{ + uint8_t data[2]; + + data[0] = addr; + /* get status register */ + if (ds3231_send(data, 1) && ds3231_recv(data+1, 1)) { + /* clear the flag */ + if (mode == DS3231_REPLACE) + data[1] = bits; + else if (mode == DS3231_SET) + data[1] |= bits; + else + data[1] &= ~bits; + + if (ds3231_send(data, 2)) { + return true; + } + } + + return false; +} + +bool ds3231_getOscillatorStopFlag(bool *flag) +{ + uint8_t f; + + if (ds3231_getFlag(DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, &f)) { + *flag = (f ? true : false); + return true; + } + + return false; +} + +inline bool ds3231_clearOscillatorStopFlag() +{ + return ds3231_setFlag(DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, DS3231_CLEAR); +} + +inline bool ds3231_getAlarmFlags(uint8_t *alarms) +{ + return ds3231_getFlag(DS3231_ADDR_STATUS, DS3231_ALARM_BOTH, alarms); +} + +inline bool ds3231_clearAlarmFlags(uint8_t alarms) +{ + return ds3231_setFlag(DS3231_ADDR_STATUS, alarms, DS3231_CLEAR); +} + +inline bool ds3231_enableAlarmInts(uint8_t alarms) +{ + return ds3231_setFlag(DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS | alarms, DS3231_SET); +} + +inline bool ds3231_disableAlarmInts(uint8_t alarms) +{ + /* Just disable specific alarm(s) requested + * does not disable alarm interrupts generally (which would enable the squarewave) + */ + return ds3231_setFlag(DS3231_ADDR_CONTROL, alarms, DS3231_CLEAR); +} + +inline bool ds3231_enable32khz() +{ + return ds3231_setFlag(DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_SET); +} + +inline bool ds3231_disable32khz() +{ + return ds3231_setFlag(DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_CLEAR); +} + +inline bool ds3231_enableSquarewave() +{ + return ds3231_setFlag(DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_CLEAR); +} + +inline bool ds3231_disableSquarewave() +{ + return ds3231_setFlag(DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_SET); +} + +bool ds3231_setSquarewaveFreq(uint8_t freq) +{ + uint8_t flag = 0; + + if (ds3231_getFlag(DS3231_ADDR_CONTROL, 0xff, &flag)) { + /* clear current rate */ + flag &= ~DS3231_CTRL_SQWAVE_8192HZ; + /* set new rate */ + flag |= freq; + + return ds3231_setFlag(DS3231_ADDR_CONTROL, flag, DS3231_REPLACE); + } + return false; +} + +bool ds3231_getRawTemp(int16_t *temp) +{ + uint8_t data[2]; + + data[0] = DS3231_ADDR_TEMP; + if (ds3231_send(data, 1) && ds3231_recv(data, 2)) { + *temp = (int16_t)(int8_t)data[0] << 2 | data[1] >> 6; + return true; + } + + return false; +} + +bool ds3231_getTempInteger(int8_t *temp) +{ + int16_t tInt; + + if (ds3231_getRawTemp(&tInt)) { + *temp = tInt >> 2; + return true; + } + + return false; +} + +bool ds3231_getTempFloat(float *temp) +{ + int16_t tInt; + + if (ds3231_getRawTemp(&tInt)) { + *temp = tInt * 0.25; + return true; + } + + return false; +} + +bool ds3231_getTime(struct tm *time) +{ + uint8_t data[7]; + + /* start register address */ + data[0] = DS3231_ADDR_TIME; + if (!ds3231_send(data, 1)) { + return false; + } + + /* read time */ + if (!ds3231_recv(data, 7)) { + return false; + } + + /* convert to unix time structure */ + time->tm_sec = bcdToDec(data[0]); + time->tm_min = bcdToDec(data[1]); + if (data[2] & DS3231_12HOUR_FLAG) { + /* 12H */ + time->tm_hour = bcdToDec(data[2] & DS3231_12HOUR_MASK); + /* AM/PM? */ + if (data[2] & DS3231_PM_FLAG) time->tm_hour += 12; + } else { + /* 24H */ + time->tm_hour = bcdToDec(data[2]); + } + time->tm_wday = bcdToDec(data[3]) - 1; + time->tm_mday = bcdToDec(data[4]); + time->tm_mon = bcdToDec(data[5] & DS3231_MONTH_MASK) - 1; + time->tm_year = bcdToDec(data[6]) + 100; + time->tm_isdst = 0; + + // apply a time zone (if you are not using localtime on the rtc or you want to check/apply DST) + //applyTZ(time); + + return true; + +} + +void ds3231_Init(uint8_t scl, uint8_t sda) +{ + i2c_init(scl, sda); +} diff --git a/extras/ds3231/ds3231.h b/extras/ds3231/ds3231.h new file mode 100644 index 0000000..06cdb62 --- /dev/null +++ b/extras/ds3231/ds3231.h @@ -0,0 +1,187 @@ +/* Driver for DS3231 high precision RTC module + * + * Part of esp-open-rtos + * Copyright (C) 2015 Richard A Burton + * Copyright (C) 2016 Bhuvanchandra DV + * MIT Licensed as described in the file LICENSE +*/ + +#ifndef __DS3231_H__ +#define __DS3231_H__ + +#include +#include +#include + +#define DS3231_ADDR 0x68 + +#define DS3231_STAT_OSCILLATOR 0x80 +#define DS3231_STAT_32KHZ 0x08 +#define DS3231_STAT_BUSY 0x04 +#define DS3231_STAT_ALARM_2 0x02 +#define DS3231_STAT_ALARM_1 0x01 + +#define DS3231_CTRL_OSCILLATOR 0x80 +#define DS3231_CTRL_SQUAREWAVE_BB 0x40 +#define DS3231_CTRL_TEMPCONV 0x20 +#define DS3231_CTRL_SQWAVE_4096HZ 0x10 +#define DS3231_CTRL_SQWAVE_1024HZ 0x08 +#define DS3231_CTRL_SQWAVE_8192HZ 0x18 +#define DS3231_CTRL_SQWAVE_1HZ 0x00 +#define DS3231_CTRL_ALARM_INTS 0x04 +#define DS3231_CTRL_ALARM2_INT 0x02 +#define DS3231_CTRL_ALARM1_INT 0x01 + +#define DS3231_ALARM_WDAY 0x40 +#define DS3231_ALARM_NOTSET 0x80 + +#define DS3231_ADDR_TIME 0x00 +#define DS3231_ADDR_ALARM1 0x07 +#define DS3231_ADDR_ALARM2 0x0b +#define DS3231_ADDR_CONTROL 0x0e +#define DS3231_ADDR_STATUS 0x0f +#define DS3231_ADDR_AGING 0x10 +#define DS3231_ADDR_TEMP 0x11 + +#define DS3231_12HOUR_FLAG 0x40 +#define DS3231_12HOUR_MASK 0x1f +#define DS3231_PM_FLAG 0x20 +#define DS3231_MONTH_MASK 0x1f + +enum { + DS3231_SET = 0, + DS3231_CLEAR, + DS3231_REPLACE +}; + +enum { + DS3231_ALARM_NONE = 0, + DS3231_ALARM_1, + DS3231_ALARM_2, + DS3231_ALARM_BOTH +}; + +enum { + DS3231_ALARM1_EVERY_SECOND = 0, + DS3231_ALARM1_MATCH_SEC, + DS3231_ALARM1_MATCH_SECMIN, + DS3231_ALARM1_MATCH_SECMINHOUR, + DS3231_ALARM1_MATCH_SECMINHOURDAY, + DS3231_ALARM1_MATCH_SECMINHOURDATE +}; + +enum { + DS3231_ALARM2_EVERY_MIN = 0, + DS3231_ALARM2_MATCH_MIN, + DS3231_ALARM2_MATCH_MINHOUR, + DS3231_ALARM2_MATCH_MINHOURDAY, + DS3231_ALARM2_MATCH_MINHOURDATE +}; + +/* Set the time on the rtc + * timezone agnostic, pass whatever you like + * I suggest using GMT and applying timezone and DST when read back + * returns true to indicate success + */ +bool ds3231_setTime(struct tm *time); + +/* Set alarms + * alarm1 works with seconds, minutes, hours and day of week/month, or fires every second + * alarm2 works with minutes, hours and day of week/month, or fires every minute + * not all combinations are supported, see DS3231_ALARM1_* and DS3231_ALARM2_* defines + * for valid options you only need to populate the fields you are using in the tm struct, + * and you can set both alarms at the same time (pass DS3231_ALARM_1/DS3231_ALARM_2/DS3231_ALARM_BOTH) + * if only setting one alarm just pass 0 for tm struct and option field for the other alarm + * if using DS3231_ALARM1_EVERY_SECOND/DS3231_ALARM2_EVERY_MIN you can pass 0 for tm stuct + * if you want to enable interrupts for the alarms you need to do that separately + * returns true to indicate success + */ +bool ds3231_setAlarm(uint8_t alarms, struct tm *time1, uint8_t option1, struct tm *time2, uint8_t option2); + +/* Check if oscillator has previously stopped, e.g. no power/battery or disabled + * sets flag to true if there has been a stop + * returns true to indicate success + */ +bool ds3231_getOscillatorStopFlag(bool *flag); + +/* Clear the oscillator stopped flag + * returns true to indicate success + */ +bool ds3231_clearOscillatorStopFlag(); + +/* Check which alarm(s) have past + * sets alarms to DS3231_ALARM_NONE/DS3231_ALARM_1/DS3231_ALARM_2/DS3231_ALARM_BOTH + * returns true to indicate success + */ +bool ds3231_getAlarmFlags(uint8_t *alarms); + +/* Clear alarm past flag(s) + * pass DS3231_ALARM_1/DS3231_ALARM_2/DS3231_ALARM_BOTH + * returns true to indicate success + */ +bool ds3231_clearAlarmFlags(uint8_t alarm); + +/* enable alarm interrupts (and disables squarewave) + * pass DS3231_ALARM_1/DS3231_ALARM_2/DS3231_ALARM_BOTH + * if you set only one alarm the status of the other is not changed + * you must also clear any alarm past flag(s) for alarms with + * interrupt enabled, else it will trigger immediately + * returns true to indicate success + */ +bool ds3231_enableAlarmInts(uint8_t alarms); + +/* Disable alarm interrupts (does not (re-)enable squarewave) + * pass DS3231_ALARM_1/DS3231_ALARM_2/DS3231_ALARM_BOTH + * returns true to indicate success + */ +bool ds3231_disableAlarmInts(uint8_t alarms); + +/* Enable the output of 32khz signal + * returns true to indicate success + */ +bool ds3231_enable32khz(); + +/* Disable the output of 32khz signal + * returns true to indicate success + */ +bool ds3231_disable32khz(); + +/* Enable the squarewave output (disables alarm interrupt functionality) + * returns true to indicate success + */ +bool ds3231_enableSquarewave(); + +/* Disable the squarewave output (which re-enables alarm interrupts, but individual + * alarm interrupts also need to be enabled, if not already, before they will trigger) + * returns true to indicate success + */ +bool ds3231_disableSquarewave(); + +/* Set the frequency of the squarewave output (but does not enable it) + * pass DS3231_SQUAREWAVE_RATE_1HZ/DS3231_SQUAREWAVE_RATE_1024HZ/DS3231_SQUAREWAVE_RATE_4096HZ/DS3231_SQUAREWAVE_RATE_8192HZ + * returns true to indicate success + */ +bool ds3231_setSquarewaveFreq(uint8_t freq); + +/* Get the raw value + * returns true to indicate success + */ +bool ds3231_getRawTemp(int16_t *temp); + +/* Get the temperature as an integer + * returns true to indicate success + */ +bool ds3231_getTempInteger(int8_t *temp); + +/* Get the temerapture as a float (in quarter degree increments) + * returns true to indicate success + */ +bool ds3231_getTempFloat(float *temp); + +/* Get the time from the rtc, populates a supplied tm struct + * returns true to indicate success + */ +bool ds3231_getTime(struct tm *time); +void ds3231_Init(uint8_t scl, uint8_t sda); + +#endif