diff --git a/README.md b/README.md index d3629a3a55..4981c582bc 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,31 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ +RTC +✅ +✅ +○ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +○ +○ +○ +○ +○ +✕ +✕ +✕ + SPI ✅ ✅ diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 31fc0313a1..833fd9c508 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -187,6 +187,9 @@ def build(env): nper = "DSI" if "Eth" in all_peripherals and per == "ETHMAC": per = "Eth" + if "Rtc" in all_peripherals and per == "RTCAPB": + per = "RTC" + nper = "RTCAPB" # Fix USBOTG OTG if target.family == "u5" and per == "OTG": per = "Usbotgfs" @@ -200,7 +203,9 @@ def build(env): if per.capitalize() not in all_peripherals: continue if "EN" in mode: - rcc_enable[per.capitalize()] = (nper, mode["EN"]) + kw = per.capitalize() + if kw not in rcc_enable: + rcc_enable[kw] = (nper, mode["EN"]) if "RST" in mode: rcc_reset[per.capitalize()] = (nper, mode["RST"]) diff --git a/src/modm/platform/core/stm32/startup_platform.c.in b/src/modm/platform/core/stm32/startup_platform.c.in index 338a68ce8c..fd540adf1c 100644 --- a/src/modm/platform/core/stm32/startup_platform.c.in +++ b/src/modm/platform/core/stm32/startup_platform.c.in @@ -61,13 +61,17 @@ __modm_initialize_platform(void) // Enable Data Tighly Coupled Memory (DTCM) and backup SRAM (BKPSRAM) RCC->AHB1ENR |= RCC_AHB1ENR_DTCMRAMEN | RCC_AHB1ENR_BKPSRAMEN; %% elif target.family in ["g0", "g4", "l4", "l5"] -%% if target.family in ["l4", "g4"] + %% if target.family in ["l4", "g4"] RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN; -%% elif target.family != "g0" -#ifdef PWR_CR2_IOSV + %% elif target.family in ["g0"] + RCC->APBENR1 |= RCC_APBENR1_PWREN; + %% else +#ifdef RCC_APB1ENR1_PWREN RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN; #endif -%% endif + %% endif + // Enable access to RTC and Backup registers + PWR->CR1 |= PWR_CR1_DBP; #ifdef PWR_CR2_IOSV // Enable VDDIO2 diff --git a/src/modm/platform/rtc/stm32/module.lb b/src/modm/platform/rtc/stm32/module.lb new file mode 100644 index 0000000000..8412cee84f --- /dev/null +++ b/src/modm/platform/rtc/stm32/module.lb @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:rtc" + module.description = FileReader("module.md") + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("rtc:stm32*") or device.identifier.family in ["f1"]: + return False + + module.depends( + ":cmsis:device", + ":platform:rcc", + ":architecture:register", + ":math:calendar", + ) + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/rtc" + target = env[":target"].identifier + env.substitutions = { + # F1, F2, L1 do not have the RTC->SSR register. + # (Some L1 device do have a SSR field, but the CMSIS headers are inconsistent). + "with_ssr": target.family not in ["f1", "f2", "l1"], + # F2, L1 have a smaller PREDIV_S register field. + "bits_prediv_s": 13 if target.family in ["f2", "l1"] else 15, + } + env.template("rtc.hpp.in") + env.template("rtc_impl.hpp.in") + env.copy("rtc.cpp") diff --git a/src/modm/platform/rtc/stm32/module.md b/src/modm/platform/rtc/stm32/module.md new file mode 100644 index 0000000000..f3a8a3ec66 --- /dev/null +++ b/src/modm/platform/rtc/stm32/module.md @@ -0,0 +1,85 @@ +# Real Time Clock (RTC) + +The STM32 RTC implements a full calendar in hardware to provide a date and time +in binary-coded decimal (BCD) format. Several optimized methods are provided to +provide an efficient conversion from this hardware format to a software +representation. + +The epoch of the RTC is chosen to be the 1st of January 1970 to be compatible +with UNIX timestamps. Since the year is limited to two BCD digits, the RTC will +roll over in 2070. + +Note that the RTC hardware has no support for time zones, so you have to handle +that in software. + + +## Initialization + +The RTC keeps running during a reset of the microcontroller when the backup +domain is powered. To prevent clock drift, the `initialize()` function will +check if the RTC is already running and only initialize the prescaler differs +from the programmed one. If the return value is `false` the RTC was already +initialized and running: + +```cpp +struct SystemClock +{ + static constexpr uint32_t Rtc = 32'768; +}; +const bool inited = Rtc::initialize(); +if (not inited) { /* RTC was already running from before reset */ } +``` + +To always initialize the RTC, set the `forced` argument to `true`: + +```cpp +Rtc::initialize(true); +``` + +To give the RTC an initial date and time, use the `setDateTime()` function. You +can use the compile time as a basic reference time, and only set the time +forward to not reset the time on every reset: + +```cpp +constexpr auto cdt = modm::DateTime::fromBuildTime(); +if (Rtc::dateTime() < cdt) Rtc::setDateTime(cdt); +``` + + +## Accessing Date and Time + +The RTC hardware provides the date and time in BCD format which can be +atomically read out with the `dateTime()` function, which returns a `DateTime` +object that can be used to access the individual date and time components: + +```cpp +const auto dt = Rtc::dateTime(); +dt.year_month_day(); +dt.day_of_year(); +dt.weekday(); + +dt.hours(); +dt.minutes(); +dt.seconds(); +dt.subseconds(); + +// prints ISO encoding: 2024-12-22 18:39:21.342 +MODM_LOG_INFO << dt << modm::endl; + +// Efficient conversion to std::tm +const std::tm tm = dt.tm(); +``` + +Please note that while the `DateTime` object provides methods to compute to +seconds and milliseconds since epoch, these are slow and should be avoided. +Instead, use the `Rtc::now()`, `Rtc::time_t()` and `Rtc::timeval()` functions +to access optimized and cached conversion methods which are much faster: + +```cpp +const Rtc::time_point tp = Rtc::now(); +// instead of Rtc::dateTime().time_since_epoch(); +const std::time_t tt = Rtc::time_t(); +// instead of Rtc::dateTime().time_t(); +const struct timeval tv = Rtc::timeval(); +// instead of Rtc::dateTime().timeval(); +``` diff --git a/src/modm/platform/rtc/stm32/rtc.cpp b/src/modm/platform/rtc/stm32/rtc.cpp new file mode 100644 index 0000000000..0eeb9522fa --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "rtc.hpp" + +extern "C" int +_gettimeofday(struct timeval *tp, void *) +{ + *tp = modm::platform::Rtc::timeval(); + return 0; +} diff --git a/src/modm/platform/rtc/stm32/rtc.hpp.in b/src/modm/platform/rtc/stm32/rtc.hpp.in new file mode 100644 index 0000000000..8fa4d6fc5c --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc.hpp.in @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_RTC_HPP +#define MODM_STM32_RTC_HPP + +#include +#include +#include + +#include +#include + +namespace modm::platform +{ + +/** + * Real Time Clock (RTC) control for STM32 devices + * + * @author Niklas Hauser + * @author Rasmus Kleist Hørlyck Sørensen + * @ingroup modm_platform_rtc + */ +class Rtc : public modm::PeripheralDriver +{ +public: +%% if with_ssr + using duration = std::chrono::milliseconds; +%% else + using duration = std::chrono::seconds; +%% endif + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static constexpr bool is_steady = true; + + /// Reads the RTC atomically and converts it to a time_point + static time_point + now() noexcept; + + /// Consider using the optimized `Rtc::time_t()` function directly instead! + static std::time_t + to_time_t(const time_point& t) noexcept + { + return std::time_t(duration_cast(t.time_since_epoch()).count()); + } + + static time_point + from_time_t(std::time_t t) noexcept + { + using from_t = std::chrono::time_point; + return time_point_cast(from_t(std::chrono::seconds(t))); + } + +public: + /// Reads the RTC and converts to seconds in an optimized way + static std::time_t + time_t(); + + /// Reads the RTC and converts to `struct timeval` in an optimized way + static struct timeval + timeval(); + + /// Reads the RTC and returns the split up calendar and time values in an optimized way + static modm::DateTime + dateTime(); + + /// Sets the RTC time + static void + setDateTime(const modm::DateTime &dt); + +public: + /// Enable the RTC clock + static void + enable(); + + /// Initialize the RTC clock + template< class SystemClock > + requires requires { SystemClock::Rtc; } + static bool + initialize(bool forced=false); + + /// Disable the RTC clock + static void + disable(); + +private: + /// Unlock RTC register write protection + static void + unlock(); + + /// Lock RTC register write protection + static void + lock(); + + /// Read the RTC registers and store them in the data field. +%% if with_ssr + /// Returns the milliseconds converted from the SSR register + static uint16_t +%% else + static void +%% endif + read(); + + /// Update the cached seconds if necessary + static void + update_cache(); + + struct Data + { + union + { + struct + { + /// milliseconds are NOT cached + uint8_t second; + uint8_t minute; + uint8_t hour; + } modm_packed; + uint32_t time32; + }; + union + { + struct + { + uint8_t weekday; + uint8_t day; + uint8_t month; + uint8_t year; + } modm_packed; + uint32_t date32; + }; + }; + + static inline Data data{}; + + /// Cached computed values +%% if with_ssr + static inline uint64_t cache_time_milliseconds{}; +%% endif + static inline uint32_t cache_time_seconds{}; + static inline uint32_t cache_date_seconds{}; + static inline uint32_t cache_time{}; + static inline uint32_t cache_date{}; +%% if with_ssr +%# + /// Function points for efficient implementations of the SSR <=> millisecond conversion + static inline uint32_t (*t2ms)(uint32_t) = [](uint32_t) { return 0ul; }; + static inline uint32_t (*ms2t)(uint32_t) = [](uint32_t) { return 0ul; }; +%% endif +%# + static constexpr uint16_t epoch{1970}; +}; + +} // namespace modm::platform + +#include "rtc_impl.hpp" + +#endif // MODM_STM32_RTC_HPP diff --git a/src/modm/platform/rtc/stm32/rtc_impl.hpp.in b/src/modm/platform/rtc/stm32/rtc_impl.hpp.in new file mode 100644 index 0000000000..b49ee10b1e --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc_impl.hpp.in @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include + +// Fix the inconsistent naming in ST's register files +#ifdef RTC_ICSR_INIT +# define RTC_ICSR RTC->ICSR +#else +# define RTC_ICSR_INIT RTC_ISR_INIT +# define RTC_ICSR_INITS RTC_ISR_INITS +# define RTC_ICSR_INITF RTC_ISR_INITF +# define RTC_ICSR_RSF RTC_ISR_RSF +# define RTC_ICSR RTC->ISR +#endif + + +namespace modm::platform +{ + +template< class SystemClock > +requires requires { SystemClock::Rtc; } +bool +Rtc::initialize(bool forced) +{ + constexpr auto result = modm::PrescalerCounter::from_linear(SystemClock::Rtc, 1_Hz, 1ul << {{bits_prediv_s}}, 128, 1); + modm::PeripheralDriver::assertBaudrateInTolerance< result.frequency, 1_Hz, 0.f >(); + constexpr uint32_t c_prediv_a = result.prescaler - 1; + constexpr uint32_t c_prediv_s = result.counter - 1; + constexpr uint32_t prer = (c_prediv_a << RTC_PRER_PREDIV_A_Pos) | (c_prediv_s << RTC_PRER_PREDIV_S_Pos); +%% if with_ssr +%# + // Manually optimize division away for common LSE frequencies: + switch(result.counter) + { + // 32kHz: prediv_a=128, prediv_s=250 -> 1000/250 = 4 + case 250: t2ms = [](uint32_t ticks) -> uint32_t { return (c_prediv_s - ticks) << 2; }; break; + // 32.768kHz: prediv_a=128, prediv_s=256 -> 1000/256 = 1000 >> 8 + case 256: t2ms = [](uint32_t ticks) -> uint32_t { return ((c_prediv_s - ticks) * 1000ul) >> 8; }; break; + // 40kHz: prediv_a=125, prediv_s=320 -> 1000/320 = 25 >> 3 + case 320: t2ms = [](uint32_t ticks) -> uint32_t { return ((c_prediv_s - ticks) * 25ul) >> 3; }; break; + // Otherwise just do the division + default: t2ms = [](uint32_t ticks) -> uint32_t { return ((c_prediv_s - ticks) * 1000ul) / (c_prediv_s + 1); }; break; + } + // other way around for setting the SSR register + ms2t = [](uint32_t ms) -> uint32_t { return c_prediv_s - ((c_prediv_s + 1) * ms) / 1000ul; }; +%% endif +%# + enable(); + // Do not initialize again to prevent clock drift + if (not forced and RTC_ICSR & RTC_ICSR_INITS and RTC->PRER == prer) return false; + + unlock(); + // Enter initialization mode + RTC_ICSR = RTC_ICSR_INIT; + + // Wait until initialization phase mode is entered when INITF bit is set + while (not (RTC_ICSR & RTC_ICSR_INITF)) __NOP(); + + // To generate a 1 Hz clock for the calendar counter, program both the prescaler factors + RTC->PRER = prer; + + // Configure 24 hour format + RTC->CR &= ~RTC_CR_FMT; + + // Exit the initialization mode + RTC_ICSR = 0; + lock(); + + // wait until the RTC registers are synchronized + while (not (RTC_ICSR & RTC_ICSR_RSF)) __NOP(); + + return true; +} + +inline void +Rtc::setDateTime(const modm::DateTime &dateTime) +{ + unlock(); + RTC_ICSR = RTC_ICSR_INIT; + // Wait until initialization phase mode is entered when INITF bit is set + while (not (RTC_ICSR & RTC_ICSR_INITF)) __NOP(); + + RTC->DR = ((toBcd(int(dateTime.year()) - epoch) << RTC_DR_YU_Pos) & (RTC_DR_YT_Msk | RTC_DR_YU_Msk)) | + ((toBcd(unsigned(dateTime.month())) << RTC_DR_MU_Pos) & (RTC_DR_MT_Msk | RTC_DR_MU_Msk)) | + ((toBcd(unsigned(dateTime.day())) << RTC_DR_DU_Pos) & (RTC_DR_DT_Msk | RTC_DR_DU_Msk)) | + ((dateTime.weekday().iso_encoding() << RTC_DR_WDU_Pos) & RTC_DR_WDU_Msk); + + RTC->TR = ((toBcd(dateTime.hours().count()) << RTC_TR_HU_Pos) & (RTC_TR_HT_Msk | RTC_TR_HU_Msk)) | + ((toBcd(dateTime.minutes().count()) << RTC_TR_MNU_Pos) & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) | + ((toBcd(dateTime.seconds().count()) << RTC_TR_SU_Pos) & (RTC_TR_ST_Msk | RTC_TR_SU_Msk)); +%# +%% if with_ssr + RTC->SSR = ms2t(dateTime.subseconds().count()); +%% endif +%# + RTC_ICSR = 0; + lock(); + while (not (RTC_ICSR & RTC_ICSR_RSF)) __NOP(); +} +%# +%% if with_ssr +inline uint16_t +%% else +inline void +%% endif +Rtc::read() +{ + const uint32_t tr = RTC->TR; +%% if with_ssr + const uint16_t ssr = RTC->SSR; +%% endif + const uint32_t dr = RTC->DR; + + data.year = modm::fromBcd((dr & (RTC_DR_YT_Msk | RTC_DR_YU_Msk)) >> RTC_DR_YU_Pos); + data.month = modm::fromBcd((dr & (RTC_DR_MT_Msk | RTC_DR_MU_Msk)) >> RTC_DR_MU_Pos); + data.day = modm::fromBcd((dr & (RTC_DR_DT_Msk | RTC_DR_DU_Msk)) >> RTC_DR_DU_Pos); + + data.weekday = (dr & RTC_DR_WDU_Msk) >> RTC_DR_WDU_Pos; + + data.hour = modm::fromBcd((tr & (RTC_TR_HT_Msk | RTC_TR_HU_Msk)) >> RTC_TR_HU_Pos); + data.minute = modm::fromBcd((tr & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) >> RTC_TR_MNU_Pos); + data.second = modm::fromBcd((tr & (RTC_TR_ST_Msk | RTC_TR_SU_Msk)) >> RTC_TR_SU_Pos); +%% if with_ssr +%# + return t2ms(ssr); +%% endif +} + +inline modm::DateTime +Rtc::dateTime() +{ +%% if with_ssr + const auto milliseconds = read(); +%% else + read(); + constexpr uint16_t milliseconds{0}; +%% endif + return DateTime(data.year + epoch, data.month, data.day, data.hour, data.minute, data.second, milliseconds, data.weekday); +} + +inline void +Rtc::update_cache() +{ + static constexpr uint32_t seconds_per_day{24*60*60}; + static constexpr uint32_t seconds_per_year{365*seconds_per_day}; + static constexpr uint16_t m2d[] = {0, /* 1-index shortcut */ + 0, 31, 59 /* or 60 if leap year */, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + if (cache_date != data.date32) + { + uint16_t day_of_year = m2d[data.month] + data.day - 1u; + // Every forth year from 1972 until 2068 is a leap year + if ((data.year & 0b11) == 0b10 and data.month > 2u) day_of_year++; + // We must not count the leap day of the current leap year, since that's already part of day_of_year! + const uint8_t leap_days_since_epoch = (data.year + 2u - 1u) / 4u; + + // won't overflow since year≤100 -> less than 3.2e9 ≤ max(uint32_t) + cache_date_seconds = data.year * seconds_per_year + + (leap_days_since_epoch + day_of_year) * seconds_per_day; + cache_date = data.date32; + } + if (cache_time != data.time32) + { + cache_time_seconds = cache_date_seconds + + (data.hour * 60ul + data.minute) * 60ul + data.second; +%% if with_ssr + // but this will overflow therefore use of ull + cache_time_milliseconds = cache_time_seconds * 1000ull; +%% endif + cache_time = data.time32; + } +} + +inline Rtc::time_point +Rtc::now() +{ +%% if with_ssr + const auto milliseconds = read(); + update_cache(); + return time_point{duration{cache_time_milliseconds + milliseconds}}; +%% else + return time_point{duration{Rtc::time_t()}}; +%% endif +} + +inline std::time_t +Rtc::time_t() +{ + read(); + update_cache(); + return cache_time_seconds; +} + +inline struct timeval +Rtc::timeval() +{ +%% if with_ssr + const auto milliseconds = read(); + update_cache(); + return {cache_time_seconds, milliseconds * 1000}; +%% else + return {Rtc::time_t(), 0}; +%% endif +} + +// ---------------------------------------------------------------------------- +void inline +Rtc::unlock() +{ + // Unlock the write protection on the protected RTC registers. + RTC->WPR = 0xCA; + RTC->WPR = 0x53; + __DSB(); +} + +void inline +Rtc::lock() +{ + // Lock the write protection on the protected RTC registers. + RTC->WPR = 0xFF; +} + +void inline +Rtc::enable() +{ + Rcc::enable(); +} + +void inline +Rtc::disable() +{ + Rcc::disable(); +} + +} // namespace modm::platform + +#undef RTC_ICSR