From c710aff9c6a9f888b07c345c152cec5e3a36b862 Mon Sep 17 00:00:00 2001 From: Nicolas Harel Date: Tue, 6 Apr 2021 14:48:31 +0200 Subject: [PATCH] drivers/ds3231: add alarm support tests/driver_ds3231 drivers/ds3231: add alarm support with IRQ drivers/ds3231: alarm support and documentation drivers/ds3231: alarm interrupt with mutex drivers/ds3231: alarm, _unlock function fixup! drivers/ds3231: add alarm support --- drivers/ds3231/Makefile.dep | 4 + drivers/ds3231/ds3231.c | 189 ++++++++++++++++++++++++- drivers/ds3231/include/ds3231_params.h | 13 +- drivers/include/ds3231.h | 158 ++++++++++++++++++++- makefiles/pseudomodules.inc.mk | 3 + tests/driver_ds3231/Makefile | 3 + tests/driver_ds3231/main.c | 71 +++++++++- 7 files changed, 429 insertions(+), 12 deletions(-) diff --git a/drivers/ds3231/Makefile.dep b/drivers/ds3231/Makefile.dep index e67057d463..71ecd389dd 100644 --- a/drivers/ds3231/Makefile.dep +++ b/drivers/ds3231/Makefile.dep @@ -1 +1,5 @@ FEATURES_REQUIRED += periph_i2c + +ifneq (,$(filter ds3231_int,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq +endif \ No newline at end of file diff --git a/drivers/ds3231/ds3231.c b/drivers/ds3231/ds3231.c index 2d0459c608..2a8bf752c9 100644 --- a/drivers/ds3231/ds3231.c +++ b/drivers/ds3231/ds3231.c @@ -21,6 +21,7 @@ #include #include "bcd.h" +#include "mutex.h" #include "ds3231.h" #define ENABLE_DEBUG 0 @@ -75,11 +76,11 @@ #define CTRL_BBSQW 0x40 #define CTRL_CONV 0x20 #define CTRL_RS2 0x10 -#define CTRL_RS1 0x80 +#define CTRL_RS1 0x08 #define CTRL_RS (CTRL_RS2 | CTRL_RS1) -#define CTRL_INTCN 0x40 -#define CTRL_A2IE 0x20 -#define CTRL_A1IE 0x10 +#define CTRL_INTCN 0x04 +#define CTRL_A2IE 0x02 +#define CTRL_A1IE 0x01 #define CTRL_AIE (CTRL_A2IE | CTRL_A1IE) /* status register bitmaps */ @@ -141,6 +142,13 @@ static int _clrset(const ds3231_t *dev, uint8_t reg, return 0; } +#if IS_USED(MODULE_DS3231_INT) +static void _unlock(void *m) +{ + mutex_unlock(m); +} +#endif + int ds3231_init(ds3231_t *dev, const ds3231_params_t *params) { int res; @@ -149,8 +157,13 @@ int ds3231_init(ds3231_t *dev, const ds3231_params_t *params) memset(dev, 0, sizeof(ds3231_t)); dev->bus = params->bus; +#if IS_USED(MODULE_DS3231_INT) + /* write interrupt pin configuration */ + dev->int_pin = params->int_pin; +#endif + /* en or disable 32KHz output */ - if (params->opt & DS2321_OPT_32KHZ_ENABLE) { + if (params->opt & DS3221_OPT_32KHZ_ENABLE) { res = _clrset(dev, REG_STATUS, 0, STAT_EN32KHZ, 1, 0); } else { @@ -160,8 +173,8 @@ int ds3231_init(ds3231_t *dev, const ds3231_params_t *params) return -EIO; } - /* disable interrupts and configure backup battery */ - uint8_t clr = (CTRL_A1IE | CTRL_A2IE); + /* Configure backup battery */ + uint8_t clr = 0; uint8_t set = 0; /* if configured, start the oscillator */ if (params->opt & DS3231_OPT_BAT_ENABLE) { @@ -171,6 +184,14 @@ int ds3231_init(ds3231_t *dev, const ds3231_params_t *params) set = CTRL_EOSC; } + /* if configured, enable the interrupts (no SQW output) */ + if (params->opt & DS3231_OPT_INTER_ENABLE) { + set |= CTRL_INTCN; + } + else { + clr |= CTRL_INTCN; + } + return _clrset(dev, REG_CTRL, clr, set, 0, 1); } @@ -236,6 +257,160 @@ int ds3231_set_time(const ds3231_t *dev, const struct tm *time) return 0; } +#if IS_USED(MODULE_DS3231_INT) +int ds3231_await_alarm(ds3231_t *dev) +{ + mutex_t mutex = MUTEX_INIT_LOCKED; + + assert(dev != NULL); + assert(gpio_is_valid(dev->int_pin)); + + if (gpio_init_int(dev->int_pin, GPIO_IN, GPIO_FALLING, _unlock, &mutex) < 0) { + return -EIO; + } + + /* wait for alarm */ + mutex_lock(&mutex); + + gpio_irq_disable(dev->int_pin); + + uint8_t status, tmp; + _read(dev, REG_STATUS, &status, 1, 1, 0); + + /* clear interrupt flags */ + tmp = status & ~(STAT_A1F | STAT_A2F); + if (_write(dev, REG_STATUS, &tmp, 1, 0, 1) < 0) { + return -EIO; + } + + return status & (STAT_A1F | STAT_A2F); +} +#endif + +int ds3231_set_alarm_1(const ds3231_t *dev, struct tm *time, + ds3231_alm_1_mode_t trigger) +{ + uint8_t raw[A1_REG_NUMOF]; + uint8_t a1mx_mask[A1_REG_NUMOF]; + + /* A1M1, A1M2, A1M3 and A1M4 are set accordingly to the trigger type */ + a1mx_mask[0] = (trigger & 0x01) << 7; + a1mx_mask[1] = (trigger & 0x02) << 6; + a1mx_mask[2] = (trigger & 0x04) << 5; + a1mx_mask[3] = (trigger & 0x08) << 4; + + raw[0] = ((bcd_from_byte(time->tm_sec) & (MASK_SEC10 | MASK_SEC)) + | a1mx_mask[0]); + raw[1] = ((bcd_from_byte(time->tm_min) & (MASK_MIN10 | MASK_MIN)) + | a1mx_mask[1]); + /* note: we always set the hours in 24-hour format */ + raw[2] = ((bcd_from_byte(time->tm_hour) & (MASK_H20H10 | MASK_HOUR)) + | a1mx_mask[2]); + raw[3] = ((bcd_from_byte(time->tm_wday + 1) & (MASK_DATE10 | MASK_DATE)) + | a1mx_mask[3]); + + /* write alarm configuration to device */ + if (_write(dev, REG_A1_SEC, raw, A1_REG_NUMOF, 1, 1) < 0) { + return -EIO; + } + + /* activate alarm 1 in case it was not */ + if (_clrset(dev, REG_CTRL, 0, CTRL_A1IE, 1, 1) < 0){ + return -EIO; + } + + return 0; +} + +int ds3231_set_alarm_2(const ds3231_t *dev, struct tm *time, + ds3231_alm_2_mode_t trigger) +{ + uint8_t raw[A2_REG_NUMOF]; + uint8_t a2mx_mask[A2_REG_NUMOF]; + + /*A2M2, A2M3 and A2M4 are set accordingly to the trigger type */ + a2mx_mask[0] = (trigger & 0x01) << 7; + a2mx_mask[1] = (trigger & 0x02) << 6; + a2mx_mask[2] = (trigger & 0x04) << 5; + + raw[0] = ((bcd_from_byte(time->tm_min) & (MASK_MIN10 | MASK_MIN)) + | a2mx_mask[0]); + /* note: we always set the hours in 24-hour format */ + raw[1] = ((bcd_from_byte(time->tm_hour) & (MASK_H20H10 | MASK_HOUR)) + | a2mx_mask[1]); + raw[2] = ((bcd_from_byte(time->tm_wday + 1) & (MASK_DATE10 | MASK_DATE)) + | a2mx_mask[2]); + + /* write alarm configuration to device */ + if (_write(dev, REG_A2_MIN, raw, A2_REG_NUMOF, 1, 1) < 0) { + return -EIO; + } + + /* activate alarm 2 in case it was not */ + if (_clrset(dev, REG_CTRL, 0, CTRL_A2IE, 1, 1) < 0){ + return -EIO; + } + + return 0; +} + +int ds3231_toggle_alarm_1(const ds3231_t *dev, bool enable) +{ + if (enable){ + return _clrset(dev, REG_CTRL, 0, CTRL_A1IE, 1, 1); + } + else{ + return _clrset(dev, REG_CTRL, CTRL_A1IE, 0, 1, 1); + } +} + +int ds3231_toggle_alarm_2(const ds3231_t *dev, bool enable) +{ + if (enable){ + return _clrset(dev, REG_CTRL, 0, CTRL_A2IE, 1, 1); + } + else{ + return _clrset(dev, REG_CTRL, CTRL_A2IE, 0, 1, 1); + } +} + +int ds3231_clear_alarm_1_flag(const ds3231_t *dev) +{ + return _clrset(dev, REG_STATUS, STAT_A1F, 0, 1, 1); +} + +int ds3231_clear_alarm_2_flag(const ds3231_t *dev) +{ + return _clrset(dev, REG_STATUS, STAT_A2F, 0, 1, 1); + +} + +int ds3231_get_alarm_1_flag(const ds3231_t *dev, bool *flag) +{ + uint8_t raw; + int res = _read(dev, REG_STATUS, &raw, 1, 1, 1); + if (res != 0) { + return -EIO; + } + + *flag = (raw & STAT_A1F); + + return res; +} + +int ds3231_get_alarm_2_flag(const ds3231_t *dev, bool *flag) +{ + uint8_t raw; + int res = _read(dev, REG_STATUS, &raw, 1, 1, 1); + if (res != 0) { + return -EIO; + } + + *flag = (raw & STAT_A2F) >> 1; + + return res; +} + int ds3231_get_aging_offset(const ds3231_t *dev, int8_t *offset) { return _read(dev, REG_AGING_OFFSET, (uint8_t *)offset, 1, 1, 1); diff --git a/drivers/ds3231/include/ds3231_params.h b/drivers/ds3231/include/ds3231_params.h index f7588aa8c6..07e0c4f530 100644 --- a/drivers/ds3231/include/ds3231_params.h +++ b/drivers/ds3231/include/ds3231_params.h @@ -32,11 +32,20 @@ extern "C" { #ifndef DS3231_PARAM_OPT #define DS3231_PARAM_OPT (DS3231_OPT_BAT_ENABLE) #endif +#ifndef DS3231_PARAM_INT_PIN +#define DS3231_PARAM_INT_PIN (GPIO_UNDEF) +#endif #ifndef DS3231_PARAMS +#if IS_USED(MODULE_DS3231_INT) #define DS3231_PARAMS { .bus = DS3231_PARAM_I2C, \ - .opt = DS3231_PARAM_OPT, } -#endif + .opt = DS3231_PARAM_OPT, \ + .int_pin = DS3231_PARAM_INT_PIN} +#else /* MODULE_DS3231_INT */ +#define DS3231_PARAMS { .bus = DS3231_PARAM_I2C, \ + .opt = DS3231_PARAM_OPT} +#endif /* MODULE_DS3231_INT */ +#endif /* DS3231_PARAMS */ /** @} */ /** diff --git a/drivers/include/ds3231.h b/drivers/include/ds3231.h index 675a272369..be4c1c042d 100644 --- a/drivers/include/ds3231.h +++ b/drivers/include/ds3231.h @@ -34,6 +34,7 @@ #include #include +#include "periph/gpio.h" #include "periph/i2c.h" #ifdef __cplusplus @@ -45,19 +46,52 @@ extern "C" { */ #define DS3231_I2C_ADDR 0x68 +/** + * @brief Alarm flags returned by the ds3231_await_alarm function + * @{ + */ +#define DS3231_FLAG_ALARM_1 0x01 +#define DS3231_FLAG_ALARM_2 0x02 +/** @} */ + /** * @brief Configuration options */ enum { - DS3231_OPT_BAT_ENABLE = 0x01, /* enable backup battery on startup */ - DS2321_OPT_32KHZ_ENABLE = 0x02, /* enable 32KHz output */ + DS3231_OPT_BAT_ENABLE = 0x01, /**< enable backup battery on startup */ + DS3221_OPT_32KHZ_ENABLE = 0x02, /**< enable 32KHz output */ + DS3231_OPT_INTER_ENABLE = 0x04, /**< enable the interrupt control */ }; +/** + * @brief Alarm trigger type of alarm 1 for DS3231 devices + */ +typedef enum { + DS3231_AL1_TRIG_PER_S = 0x0F, /**< alarm once per second */ + DS3231_AL1_TRIG_S = 0x0E, /**< alarm when seconds match */ + DS3231_AL1_TRIG_M_S = 0x0C, /**< alarm when minutes and seconds match */ + DS3231_AL1_TRIG_H_M_S = 0x08, /**< alarm when H/M/S match */ + DS3231_AL1_TRIG_D_H_M_S = 0x00, /**< alarm when D/H/M/S match */ +} ds3231_alm_1_mode_t; + +/** + * @brief Alarm trigger type of alarm 2 for DS3231 devices + */ +typedef enum { + DS3231_AL2_TRIG_PER_M = 0x07, /**< alarm once per minute */ + DS3231_AL2_TRIG_M = 0x06, /**< alarm when minutes match */ + DS3231_AL2_TRIG_H_M = 0x04, /**< alarm when hours and minutes match */ + DS3231_AL2_TRIG_D_H_M_S = 0x00, /**< alarm when D/H/M match */ +} ds3231_alm_2_mode_t; + /** * @brief Device descriptor for DS3231 devices */ typedef struct { i2c_t bus; /**< I2C bus the device is connected to */ +#if IS_USED(MODULE_DS3231_INT) + gpio_t int_pin; /**< alarm interrupt pin */ +#endif /* MODULE_DS3231_INT */ } ds3231_t; /** @@ -66,8 +100,16 @@ typedef struct { typedef struct { i2c_t bus; /**< I2C bus the device is connected to */ uint8_t opt; /**< additional options */ +#if IS_USED(MODULE_DS3231_INT) + gpio_t int_pin; /**< alarm interrupt pin */ +#endif /* MODULE_DS3231_INT */ } ds3231_params_t; +#if IS_USED(MODULE_DS3231_INT) +typedef void (*ds3231_alarm_cb_t)(void *); + +#endif /* MODULE_DS3231_INT */ + /** * @brief Initialize the given DS3231 device * @@ -79,6 +121,24 @@ typedef struct { */ int ds3231_init(ds3231_t *dev, const ds3231_params_t *params); +#if IS_USED(MODULE_DS3231_INT) +/** + * @brief Initialize the GPIO alarm interrupt + * + * This function initializes the pin defined as the interrupt pin in the + * initialization parameters of the device then blocks until an alarm is + * triggered. + * + * @note This function is only available when module `ds3231_int` is enabled. + * + * @param[in] dev device descriptor of DS3231 device + * + * @return status of A1F and A2F on success + * @return -EIO if unable to initialize GPIO interrupt + */ +int ds3231_await_alarm(ds3231_t *dev); +#endif /* MODULE_DS3231_INT */ + /** * @brief Get date and time from the device * @@ -101,6 +161,100 @@ int ds3231_get_time(const ds3231_t *dev, struct tm *time); */ int ds3231_set_time(const ds3231_t *dev, const struct tm *time); +/** + * @brief Set alarm 1 of the device + * + * @param[in] dev DS3231 device descriptor + * @param[in] time target date and time + * @param[in] trigger alarm 1 trigger type + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_set_alarm_1(const ds3231_t *dev, struct tm *time, + ds3231_alm_1_mode_t trigger); + +/** + * @brief Set alarm 2 of the device + * + * @param[in] dev DS3231 device descriptor + * @param[in] time target date and time + * @param[in] trigger alarm 2 trigger type + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_set_alarm_2(const ds3231_t *dev, struct tm *time, + ds3231_alm_2_mode_t trigger); + +/** + * @brief Clear alarm 1 flag (A1F) + * + * @param[in] dev DS3231 device descriptor + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_clear_alarm_1_flag(const ds3231_t *dev); + +/** + * @brief Clear alarm 2 flag (A2F) + * + * @param[in] dev DS3231 device descriptor + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_clear_alarm_2_flag(const ds3231_t *dev); + +/** + * @brief Get the state of alarm 1 flag (A1F) + * + * @note This function is not needed when ds3231_await_alarm is used + * + * @param[in] dev DS3231 device descriptor + * @param[out] flag Current value of the flag + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_get_alarm_1_flag(const ds3231_t *dev, bool *flag); + +/** + * @brief Get the state of alarm 2 flag (A2F) + * + * @note This function is not needed when ds3231_await_alarm is used + * + * @param[in] dev DS3231 device descriptor + * @param[out] flag Current value of the flag + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_get_alarm_2_flag(const ds3231_t *dev, bool *flag); + +/** + * @brief Enable/Disable alarm 1 interrupt on the device + * + * @param[in] dev DS3231 device descriptor + * @param[in] enable True to enable alarm, false to disable it + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_toggle_alarm_1(const ds3231_t *dev, bool enable); + +/** + * @brief Enable/Disable alarm 2 interrupt on the device + * + * @param[in] dev DS3231 device descriptor + * @param[in] enable True to enable alarm, false to disable it + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_toggle_alarm_2(const ds3231_t *dev, bool enable); + /** * @brief Get the configured aging offset (see datasheet for more information) * diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index a4acda742a..6837dd75d2 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -205,6 +205,9 @@ PSEUDOMODULES += cc1100 PSEUDOMODULES += cc1100e PSEUDOMODULES += cc1101 +# include variants of ds3231 drivers as pseudo modules +PSEUDOMODULES += ds3231_int + # interrupt variant of the HMC5883L driver PSEUDOMODULES += hmc5883l_int diff --git a/tests/driver_ds3231/Makefile b/tests/driver_ds3231/Makefile index fb8eb35773..e1f39baf7a 100644 --- a/tests/driver_ds3231/Makefile +++ b/tests/driver_ds3231/Makefile @@ -4,4 +4,7 @@ USEMODULE += ds3231 USEMODULE += xtimer USEMODULE += shell +#USEMODULE += ds3231_int +#CFLAGS +="-DDS3231_PARAM_INT_PIN=(GPIO_PIN(1,2))" + include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_ds3231/main.c b/tests/driver_ds3231/main.c index b1ddcaadf9..1216112fcf 100644 --- a/tests/driver_ds3231/main.c +++ b/tests/driver_ds3231/main.c @@ -258,8 +258,74 @@ static int _cmd_test(int argc, char **argv) return 1; } + /* clear all existing alarm flag */ + res = ds3231_clear_alarm_1_flag(&_dev); + if (res != 0) { + puts("error: unable to clear alarm flag"); + return 1; + } + + /* get time to set up next alarm*/ + res = ds3231_get_time(&_dev, &time); + if (res != 0) { + puts("error: unable to read time"); + return 1; + } + + time.tm_sec += TEST_DELAY; + mktime(&time); + + /* set alarm */ + res = ds3231_set_alarm_1(&_dev, &time, DS3231_AL1_TRIG_H_M_S); + if (res != 0) { + puts("error: unable to program alarm"); + return 1; + } + +#ifdef MODULE_DS3231_INT + + /* wait for an alarm with GPIO interrupt */ + res = ds3231_await_alarm(&_dev); + if (res < 0){ + puts("error: unable to program GPIO interrupt or to clear alarm flag"); + } + + if (!(res & DS3231_FLAG_ALARM_1)){ + puts("error: alarm was not triggered"); + } + puts("OK"); return 0; + +#else + + /* wait for the alarm to trigger */ + xtimer_sleep(TEST_DELAY); + + bool alarm; + + /* check if alarm flag is on */ + res = ds3231_get_alarm_1_flag(&_dev, &alarm); + if (res != 0) { + puts("error: unable to get alarm flag"); + return 1; + } + + if (alarm != true){ + puts("error: alarm was not triggered"); + } + + /* clear alarm flag */ + res = ds3231_clear_alarm_1_flag(&_dev); + if (res != 0) { + puts("error: unable to clear alarm flag"); + return 1; + } + + puts("OK"); + return 0; + +#endif } static const shell_command_t shell_commands[] = { @@ -279,7 +345,10 @@ int main(void) puts("DS3231 RTC test\n"); /* initialize the device */ - res = ds3231_init(&_dev, &ds3231_params[0]); + ds3231_params_t params= ds3231_params[0]; + params.opt = DS3231_OPT_BAT_ENABLE; + params.opt |= DS3231_OPT_INTER_ENABLE; + res = ds3231_init(&_dev, ¶ms); if (res != 0) { puts("error: unable to initialize DS3231 [I2C initialization error]"); return 1;