From 8a31519558b568d5ec1b71f4f2ac0544437362e0 Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Mon, 19 Oct 2020 16:00:16 +0200 Subject: [PATCH] drivers: add support for ds3231 RTC --- drivers/ds3231/Makefile | 1 + drivers/ds3231/Makefile.dep | 1 + drivers/ds3231/Makefile.include | 2 + drivers/ds3231/ds3231.c | 268 +++++++++++++++++++++++++ drivers/ds3231/include/ds3231_params.h | 55 +++++ drivers/include/ds3231.h | 162 +++++++++++++++ 6 files changed, 489 insertions(+) create mode 100644 drivers/ds3231/Makefile create mode 100644 drivers/ds3231/Makefile.dep create mode 100644 drivers/ds3231/Makefile.include create mode 100644 drivers/ds3231/ds3231.c create mode 100644 drivers/ds3231/include/ds3231_params.h create mode 100644 drivers/include/ds3231.h diff --git a/drivers/ds3231/Makefile b/drivers/ds3231/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/ds3231/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/ds3231/Makefile.dep b/drivers/ds3231/Makefile.dep new file mode 100644 index 0000000000..e67057d463 --- /dev/null +++ b/drivers/ds3231/Makefile.dep @@ -0,0 +1 @@ +FEATURES_REQUIRED += periph_i2c diff --git a/drivers/ds3231/Makefile.include b/drivers/ds3231/Makefile.include new file mode 100644 index 0000000000..7787803bd0 --- /dev/null +++ b/drivers/ds3231/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_ds3231 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_ds3231) diff --git a/drivers/ds3231/ds3231.c b/drivers/ds3231/ds3231.c new file mode 100644 index 0000000000..2d0459c608 --- /dev/null +++ b/drivers/ds3231/ds3231.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup drivers_ds3231 + * @{ + * + * @file + * @brief DS3231 RTC driver implementation + * + * @author Hauke Petersen + * + * @} + */ + +#include + +#include "bcd.h" +#include "ds3231.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* some metadata about the devices registers */ +#define DATE_REG_NUMOF 7U +#define A1_REG_NUMOF 4U +#define A2_REG_NUMOF 3U + +/* register addresses */ +#define REG_SEC 0x00 +#define REG_MIN 0x01 +#define REG_HOUR 0x02 +#define REG_DAY 0x03 +#define REG_DATE 0x04 +#define REG_MONTH 0x05 +#define REG_YEAR 0x06 +#define REG_A1_SEC 0x07 +#define REG_A1_MIN 0x08 +#define REG_A1_HOUR 0x09 +#define REG_A1_DAYDATE 0x0a +#define REG_A2_MIN 0x0b +#define REG_A2_HOUR 0x0c +#define REG_A2_DAYDATE 0x0d +#define REG_CTRL 0x0e +#define REG_STATUS 0x0f +#define REG_AGING_OFFSET 0x10 +#define REG_TEMP_MSB 0x11 +#define REG_TEMP_LSB 0x12 + +/* general register bitmasks */ +#define MASK_SEC10 0x70 +#define MASK_SEC 0x0f +#define MASK_MIN10 0x70 +#define MASK_MIN 0x0f +#define MASK_H10 0x10 +#define MASK_AMPM_H20 0x20 +#define MASK_H12_H24 0x40 +#define MASK_H20H10 (MASK_H10 | MASK_AMPM_H20) +#define MASK_HOUR 0x0f +#define MASK_DAY 0x07 +#define MASK_DATE10 0x30 +#define MASK_DATE 0x0f +#define MASK_MONTH10 0x10 +#define MASK_MONTH 0x0f +#define MASK_CENTURY 0x80 +#define MASK_DY_DT 0x60 + +/* control register bitmaps */ +#define CTRL_EOSC 0x80 +#define CTRL_BBSQW 0x40 +#define CTRL_CONV 0x20 +#define CTRL_RS2 0x10 +#define CTRL_RS1 0x80 +#define CTRL_RS (CTRL_RS2 | CTRL_RS1) +#define CTRL_INTCN 0x40 +#define CTRL_A2IE 0x20 +#define CTRL_A1IE 0x10 +#define CTRL_AIE (CTRL_A2IE | CTRL_A1IE) + +/* status register bitmaps */ +#define STAT_OSF 0x80 +#define STAT_EN32KHZ 0x08 +#define STAT_BSY 0x04 +#define STAT_A2F 0x02 +#define STAT_A1F 0x01 +#define STAT_AF (STAT_A2F | STAT_A1F) + +static int _read(const ds3231_t *dev, uint8_t reg, uint8_t *buf, size_t len, + int acquire, int release) +{ + int res; + + if (acquire && i2c_acquire(dev->bus)) { + return -EIO; + } + res = i2c_read_regs(dev->bus, DS3231_I2C_ADDR, reg, buf, len, 0); + if (res < 0) { + i2c_release(dev->bus); + return -EIO; + } + if (release) { + i2c_release(dev->bus); + } + return 0; +} + +static int _write(const ds3231_t *dev, uint8_t reg, uint8_t *buf, size_t len, + int acquire, int release) +{ + if (acquire && i2c_acquire(dev->bus)) { + return -EIO; + } + if (i2c_write_regs(dev->bus, DS3231_I2C_ADDR, reg, buf, len, 0) < 0) { + i2c_release(dev->bus); + return -EIO; + } + if (release) { + i2c_release(dev->bus); + } + return 0; +} + +static int _clrset(const ds3231_t *dev, uint8_t reg, + uint8_t clr_mask, uint8_t set_mask, int acquire, int release) +{ + uint8_t old; + uint8_t new; + + if (_read(dev, reg, &old, 1, acquire, 0) < 0) { + return -EIO; + } + new = ((old &= ~clr_mask) | set_mask); + if (_write(dev, reg, &new, 1, 0, release) < 0) { + return -EIO; + } + return 0; +} + +int ds3231_init(ds3231_t *dev, const ds3231_params_t *params) +{ + int res; + + /* write device descriptor */ + memset(dev, 0, sizeof(ds3231_t)); + dev->bus = params->bus; + + /* en or disable 32KHz output */ + if (params->opt & DS2321_OPT_32KHZ_ENABLE) { + res = _clrset(dev, REG_STATUS, 0, STAT_EN32KHZ, 1, 0); + } + else { + res = _clrset(dev, REG_STATUS, STAT_EN32KHZ, 0, 1, 0); + } + if (res != 0) { + return -EIO; + } + + /* disable interrupts and configure backup battery */ + uint8_t clr = (CTRL_A1IE | CTRL_A2IE); + uint8_t set = 0; + /* if configured, start the oscillator */ + if (params->opt & DS3231_OPT_BAT_ENABLE) { + clr |= CTRL_EOSC; + } + else { + set = CTRL_EOSC; + } + + return _clrset(dev, REG_CTRL, clr, set, 0, 1); +} + +int ds3231_get_time(const ds3231_t *dev, struct tm *time) +{ + uint8_t raw[DATE_REG_NUMOF]; + + /* read date registers */ + if (_read(dev, REG_SEC, raw, DATE_REG_NUMOF, 1, 1) < 0) { + return -EIO; + } + + /* convert data to struct tm */ + time->tm_sec = bcd_to_byte(raw[REG_SEC]); + time->tm_min = bcd_to_byte(raw[REG_MIN]); + if (raw[REG_HOUR] & MASK_H12_H24) { + time->tm_hour = bcd_to_byte(raw[REG_HOUR] & (MASK_HOUR | MASK_H10)); + if (raw[REG_HOUR] & MASK_AMPM_H20) { + time->tm_hour += 12; + } + } + else { + time->tm_hour = bcd_to_byte(raw[REG_HOUR] & (MASK_HOUR | MASK_H20H10)); + } + time->tm_mday = bcd_to_byte(raw[REG_DATE]); + time->tm_mon = bcd_to_byte(raw[REG_MONTH] & ~MASK_CENTURY) - 1; + time->tm_year = bcd_to_byte(raw[REG_YEAR]); + if (raw[REG_MONTH] & MASK_CENTURY) { + time->tm_year += 100; + } + time->tm_wday = bcd_to_byte(raw[REG_DAY]) - 1; + + return 0; +} + +int ds3231_set_time(const ds3231_t *dev, const struct tm *time) +{ + uint8_t raw[DATE_REG_NUMOF]; + + /* some validity checks */ + if (time->tm_year > 200) { + return -ERANGE; + } + + /* convert struct tm to raw BDC data */ + raw[REG_SEC] = bcd_from_byte(time->tm_sec); + raw[REG_MIN] = bcd_from_byte(time->tm_min); + /* note: we always set the hours in 24-hour format */ + raw[REG_HOUR] = bcd_from_byte(time->tm_hour); + raw[REG_DAY] = bcd_from_byte(time->tm_wday + 1); + raw[REG_DATE] = bcd_from_byte(time->tm_mday); + raw[REG_MONTH] = bcd_from_byte(time->tm_mon + 1); + raw[REG_YEAR] = bcd_from_byte(time->tm_year % 100); + if (time->tm_year > 100) { + raw[REG_MONTH] |= MASK_CENTURY; + } + + /* write time to device */ + if (_write(dev, REG_SEC, raw, DATE_REG_NUMOF, 1, 1) < 0) { + return -EIO; + } + + return 0; +} + +int ds3231_get_aging_offset(const ds3231_t *dev, int8_t *offset) +{ + return _read(dev, REG_AGING_OFFSET, (uint8_t *)offset, 1, 1, 1); +} + +int ds3231_set_aging_offset(const ds3231_t *dev, int8_t offset) +{ + return _write(dev, REG_AGING_OFFSET, (uint8_t *)&offset, 1, 1, 1); +} + +int ds3231_get_temp(const ds3231_t *dev, int16_t *temp) +{ + uint8_t raw[2]; + int res = _read(dev, REG_TEMP_MSB, raw, 2, 1, 1); + if (res != 0) { + return -EIO; + } + *temp = ((((int16_t)raw[0] << 8) | raw[1]) >> 6) * 25; + return 0; +} + +int ds3231_enable_bat(const ds3231_t *dev) +{ + return _clrset(dev, REG_CTRL, CTRL_EOSC, 0, 1, 1); +} + +int ds3231_disable_bat(const ds3231_t *dev) +{ + return _clrset(dev, REG_CTRL, 0, CTRL_EOSC, 1, 1); +} diff --git a/drivers/ds3231/include/ds3231_params.h b/drivers/ds3231/include/ds3231_params.h new file mode 100644 index 0000000000..f7588aa8c6 --- /dev/null +++ b/drivers/ds3231/include/ds3231_params.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_ds3231 + * @{ + * + * @file + * @brief Default configuration for DS3231 devices + * + * @author Hauke Petersen + */ +#ifndef DS3231_PARAMS_H +#define DS3231_PARAMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Default configuration parameters for the DS3231 driver + * @{ + */ +#ifndef DS3231_PARAM_I2C +#define DS3231_PARAM_I2C I2C_DEV(0) +#endif +#ifndef DS3231_PARAM_OPT +#define DS3231_PARAM_OPT (DS3231_OPT_BAT_ENABLE) +#endif + +#ifndef DS3231_PARAMS +#define DS3231_PARAMS { .bus = DS3231_PARAM_I2C, \ + .opt = DS3231_PARAM_OPT, } +#endif +/** @} */ + +/** + * @brief DS3231 configuration + */ +static const ds3231_params_t ds3231_params[] = +{ + DS3231_PARAMS +}; + +#ifdef __cplusplus +} +#endif + +#endif /* DS3231_PARAMS_H */ +/** @} */ diff --git a/drivers/include/ds3231.h b/drivers/include/ds3231.h new file mode 100644 index 0000000000..675a272369 --- /dev/null +++ b/drivers/include/ds3231.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_ds3231 DS3231 Real Time Clock + * @ingroup drivers_sensors + * @brief Driver for the Maxim DS3231 extremely accurate RTC + * + * # About + * This module implements a device driver for Maxim DS3231 RTC. + * + * # Implementation status + * The current implementation does only support reading and setting of time + * registers as well as reading the temperature register and configuring the + * aging offset. + * + * Setting alarms and configuring the square wave output is not yet supported. + * + * @{ + * @file + * @brief Interface definition for the Maxim DS3231 RTC + * + * @author Hauke Petersen + */ + +#ifndef DS3231_H +#define DS3231_H + +#include +#include + +#include "periph/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default address of DS3231 sensors + */ +#define DS3231_I2C_ADDR 0x68 + +/** + * @brief Configuration options + */ +enum { + DS3231_OPT_BAT_ENABLE = 0x01, /* enable backup battery on startup */ + DS2321_OPT_32KHZ_ENABLE = 0x02, /* enable 32KHz output */ +}; + +/** + * @brief Device descriptor for DS3231 devices + */ +typedef struct { + i2c_t bus; /**< I2C bus the device is connected to */ +} ds3231_t; + +/** + * @brief Set of configuration parameters for DS3231 devices + */ +typedef struct { + i2c_t bus; /**< I2C bus the device is connected to */ + uint8_t opt; /**< additional options */ +} ds3231_params_t; + +/** + * @brief Initialize the given DS3231 device + * + * @param[out] dev device descriptor of the targeted device + * @param[in] params device configuration + * + * @return 0 on success + * @return -EIO if no DS3231 device was found + */ +int ds3231_init(ds3231_t *dev, const ds3231_params_t *params); + +/** + * @brief Get date and time from the device + * + * @param[in] dev DS3231 device descriptor + * @param[out] time current date and time from on device + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_get_time(const ds3231_t *dev, struct tm *time); + +/** + * @brief Set date and time of the device + * + * @param[in] dev DS3231 device descriptor + * @param[in] time target date and time + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_set_time(const ds3231_t *dev, const struct tm *time); + +/** + * @brief Get the configured aging offset (see datasheet for more information) + * + * @param[in] dev DS3231 device descriptor + * @param[out] offset aging offset + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_get_aging_offset(const ds3231_t *dev, int8_t *offset); + +/** + * @brief Set the aging offset (see datasheet for more information) + * + * @param[in] dev DS3231 device descriptor + * @param[in] offset aging offset + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_set_aging_offset(const ds3231_t *dev, int8_t offset); + +/** + * @brief Get temperature from the device + * + * @param[in] dev DS3231 device descriptor + * @param[out] temp current value of the temperature register [in centi °C] + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_get_temp(const ds3231_t *dev, int16_t *temp); + +/** + * @brief Enable the backup battery + * + * @param[in] dev DS3231 device descriptor + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_enable_bat(const ds3231_t *dev); + +/** + * @brief Disable the backup battery + * + * @param[in] dev DS3231 device descriptor + * + * @return 0 on success + * @return -EIO on I2C communication error + */ +int ds3231_disable_bat(const ds3231_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* DS3231_H */ +/** @} */