1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

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
This commit is contained in:
Nicolas Harel 2021-04-06 14:48:31 +02:00
parent 010ba56174
commit c710aff9c6
7 changed files with 429 additions and 12 deletions

View File

@ -1 +1,5 @@
FEATURES_REQUIRED += periph_i2c
ifneq (,$(filter ds3231_int,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio_irq
endif

View File

@ -21,6 +21,7 @@
#include <string.h>
#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);

View File

@ -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 */
/** @} */
/**

View File

@ -34,6 +34,7 @@
#include <time.h>
#include <errno.h>
#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)
*

View File

@ -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

View File

@ -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

View File

@ -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, &params);
if (res != 0) {
puts("error: unable to initialize DS3231 [I2C initialization error]");
return 1;