drivers: add support for ds3231 RTC

This commit is contained in:
Hauke Petersen 2020-10-19 16:00:16 +02:00
parent fb6925a018
commit 8a31519558
6 changed files with 489 additions and 0 deletions

1
drivers/ds3231/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1 @@
FEATURES_REQUIRED += periph_i2c

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_ds3231 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_ds3231)

268
drivers/ds3231/ds3231.c Normal file
View File

@ -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 <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <string.h>
#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);
}

View File

@ -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 <hauke.petersen@fu-berlin.de>
*/
#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 */
/** @} */

162
drivers/include/ds3231.h Normal file
View File

@ -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 <hauke.petersen@fu-berlin.de>
*/
#ifndef DS3231_H
#define DS3231_H
#include <time.h>
#include <errno.h>
#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 */
/** @} */