From cd2b86c4b10f979f4cdf843ed8f7157bd7862667 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sat, 29 Feb 2020 01:01:20 +0100 Subject: [PATCH] rtt_rtc: add RTT based RTC implementation --- drivers/Makefile.dep | 8 ++ drivers/rtt_rtc/Makefile | 1 + drivers/rtt_rtc/rtt_rtc.c | 188 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 drivers/rtt_rtc/Makefile create mode 100644 drivers/rtt_rtc/rtt_rtc.c diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 4f98703452..dc2887b2aa 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -558,6 +558,14 @@ ifneq (,$(filter rgbled,$(USEMODULE))) USEMODULE += color endif +ifneq (,$(filter rtt_rtc,$(USEMODULE))) + # Unit tests will use a mock implementation + ifeq (,$(UNIT_TESTS)) + FEATURES_REQUIRED += periph_rtt + endif + FEATURES_PROVIDED += periph_rtc +endif + ifneq (,$(filter rn2%3,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_uart diff --git a/drivers/rtt_rtc/Makefile b/drivers/rtt_rtc/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/rtt_rtc/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/rtt_rtc/rtt_rtc.c b/drivers/rtt_rtc/rtt_rtc.c new file mode 100644 index 0000000000..1ff5c5a3b6 --- /dev/null +++ b/drivers/rtt_rtc/rtt_rtc.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 Benjamin Valentin + * + * 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_periph_rtc + * @{ + * + * @file + * @brief Basic RTC implementation based on a RTT + * + * @note Unlike a real RTC, this emulated version is not guaranteed to keep + * time across reboots or deep sleep. + * If your hardware provides the means to implement a real RTC, always + * prefer that over this emulated version! + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include + +#include "periph/rtc.h" +#include "periph/rtt.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define RTT_SECOND (RTT_FREQUENCY) + +#define _RTT(n) ((n) & RTT_MAX_VALUE) + +#define RTT_SECOND_MAX (RTT_MAX_VALUE/RTT_FREQUENCY) + +#define TICKS(x) ( (x) * RTT_SECOND) +#define SECONDS(x) (_RTT(x) / RTT_SECOND) + +/* Place counter in .noinit section if no backup RAM is available. + This means the date is undefined at cold boot, but will likely still + survive a reboot. */ +#ifndef BACKUP_RAM +#define BACKUP_RAM __attribute__((section(".noinit"))) +#endif + +static uint32_t rtc_now BACKUP_RAM; /**< The RTC timestamp when the last RTT alarm triggered */ +static uint32_t last_alarm BACKUP_RAM; /**< The RTT timestamp of the last alarm */ + +static uint32_t alarm_time; /**< The RTC timestamp of the (user) RTC alarm */ +static rtc_alarm_cb_t alarm_cb; /**< RTC alarm callback */ +static void *alarm_cb_arg; /**< RTC alarm callback argument */ + +static void _rtt_alarm(void *arg); + +/* convert RTT counter into RTC timestamp */ +static inline uint32_t _rtc_now(uint32_t now) +{ + return rtc_now + SECONDS(now - last_alarm); +} + +static inline void _set_alarm(uint32_t now, uint32_t next_alarm) +{ + rtt_set_alarm(now + next_alarm, _rtt_alarm, NULL); +} + +/* This calculates when the next alarm should happen. + Always chooses the longest possible period min(alarm, overflow) + to minimize the amount of wake-ups. */ +static void _update_alarm(uint32_t now) +{ + uint32_t next_alarm; + + last_alarm = TICKS(SECONDS(now)); + + /* no alarm or alarm beyond this period */ + if ((alarm_cb == NULL) || + (alarm_time < rtc_now) || + (alarm_time - rtc_now > RTT_SECOND_MAX)) { + next_alarm = RTT_SECOND_MAX; + } else { + /* alarm triggers in this period */ + next_alarm = alarm_time - rtc_now; + } + + /* alarm triggers NOW */ + if (next_alarm == 0) { + next_alarm = RTT_SECOND_MAX; + alarm_cb(alarm_cb_arg); + } + + _set_alarm(now, TICKS(next_alarm)); +} + +/* the RTT alarm callback */ +static void _rtt_alarm(void *arg) +{ + (void) arg; + + uint32_t now = rtt_get_counter(); + rtc_now = _rtc_now(now); + + _update_alarm(now); +} + +void rtc_init(void) +{ + /* only update last_alarm on cold boot */ + if (last_alarm == 0) { + last_alarm = rtt_get_counter(); + } + + _set_alarm(last_alarm, TICKS(RTT_SECOND_MAX)); +} + +int rtc_set_time(struct tm *time) +{ + rtc_tm_normalize(time); + + /* disable alarm to prevent race condition */ + rtt_clear_alarm(); + + uint32_t now = rtt_get_counter(); + rtc_now = rtc_mktime(time); + + /* calculate next wake-up period */ + _update_alarm(now); + + return 0; +} + +int rtc_get_time(struct tm *time) +{ + uint32_t prev = rtc_now; + + /* repeat calculation if an alarm triggered in between */ + do { + uint32_t now = rtt_get_counter(); + uint32_t tmp = _rtc_now(now); + + rtc_localtime(tmp, time); + } while (prev != rtc_now); + + return 0; +} + +int rtc_get_alarm(struct tm *time) +{ + rtc_localtime(alarm_time, time); + + return 0; +} + +int rtc_set_alarm(struct tm *time, rtc_alarm_cb_t cb, void *arg) +{ + /* disable alarm to prevent race condition */ + rtt_clear_alarm(); + + uint32_t now = rtt_get_counter(); + + alarm_time = rtc_mktime(time); + alarm_cb_arg = arg; + alarm_cb = cb; + + rtc_now = _rtc_now(now); + _update_alarm(now); + + return 0; +} + +void rtc_clear_alarm(void) +{ + alarm_cb = NULL; +} + +void rtc_poweron(void) +{ + rtt_poweron(); +} + +void rtc_poweroff(void) +{ + rtt_poweroff(); +}