From 69e3f08a051f17b8a223b30ad39d9bec1758d702 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Fri, 27 Dec 2024 17:46:49 +0100 Subject: [PATCH] [stm32] Add RTC peripheral --- README.md | 25 ++ src/modm/platform/clock/stm32/module.lb | 7 +- .../platform/core/stm32/startup_platform.c.in | 12 +- src/modm/platform/rtc/stm32/module.lb | 43 +++ src/modm/platform/rtc/stm32/rtc.cpp | 19 ++ src/modm/platform/rtc/stm32/rtc.hpp.in | 169 +++++++++++ src/modm/platform/rtc/stm32/rtc_impl.hpp.in | 271 ++++++++++++++++++ 7 files changed, 541 insertions(+), 5 deletions(-) create mode 100644 src/modm/platform/rtc/stm32/module.lb create mode 100644 src/modm/platform/rtc/stm32/rtc.cpp create mode 100644 src/modm/platform/rtc/stm32/rtc.hpp.in create mode 100644 src/modm/platform/rtc/stm32/rtc_impl.hpp.in 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..0a0162e034 --- /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 = "Real Time Clock (RTC)" + +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/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..385d91455a --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc.hpp.in @@ -0,0 +1,169 @@ +/* + * 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; + + static time_point + now() noexcept; + + 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: + // Optimized version that returns seconds since epoch + static std::time_t + time_t(); + + static struct timeval + timeval(); + + static modm::DateTime + dateTime(); + + static void + setDateTime(const modm::DateTime &dt); + +public: + static void + enable(); + + static void + disable(); + + template< class SystemClock > + static bool + initialize(); + + /** + * Synchronized to a remote clock with a high degree of precision + * + * @tparam Rep + * an arithmetic type representing the number of ticks + * @tparam Period + * a std::ratio representing the tick period (i.e. the number of second's fractions per tick) + * + * @param delay The amount of time to delay (or advance) the + * @param waitCycle Number of cycles to wait for the INITF bit to be set. (default = 2048) + * + * @return True on success + */ + // template< typename Rep, typename Period > + // static bool + // synchronize(std::chrono::duration delay, uint32_t waitCycles = 2048); + +private: + /// Unlock RTC register write protection + static void + unlock(); + + /// Lock RTC register write protection + static void + lock(); +%# +%% if with_ssr + static uint16_t +%% else + static void +%% endif + read(); + + static void + update_cache(); + + struct Data + { + union + { + struct + { + 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{}; +%# +%% 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 + 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..4946c2828d --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc_impl.hpp.in @@ -0,0 +1,271 @@ +/* + * 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_INITF RTC_ISR_INITF +# define RTC_ICSR_RSF RTC_ISR_RSF +# define RTC_ICSR RTC->ISR +#endif + + +namespace modm::platform +{ + +template< class SystemClock > +bool +Rtc::initialize() +{ + 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; +%# +%% 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(); + 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 = (c_prediv_a << RTC_PRER_PREDIV_A_Pos) | (c_prediv_s << RTC_PRER_PREDIV_S_Pos); + + // Configure 24 hour format + RTC->CR &= ~RTC_CR_FMT; + + // Exit the initialization mode by clearing the INIT bit + RTC_ICSR = 0; + lock(); + // wait until the RTC registers are synchronized + while (not (RTC_ICSR & RTC_ICSR_RSF)) __NOP(); + + return true; +} +%# +%% 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 + constexpr uint16_t milliseconds{0}; + read(); +%% 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 ? 0u : (data.year - 2u) / 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{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 * 1000l}; +%% else + return {Rtc::time_t(), 0}; +%% endif +} + +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.hour().count()) << RTC_TR_HU_Pos) & (RTC_TR_HT_Msk | RTC_TR_HU_Msk)) | + ((toBcd(dateTime.minute().count()) << RTC_TR_MNU_Pos) & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) | + ((toBcd(dateTime.second().count()) << RTC_TR_SU_Pos) & (RTC_TR_ST_Msk | RTC_TR_SU_Msk)); +%# +%% if with_ssr + RTC->SSR = ms2t(dateTime.millisecond().count()); +%% endif +%# + RTC_ICSR = 0; + lock(); + while (not (RTC_ICSR & RTC_ICSR_RSF)) __NOP(); +} + +/* +template< typename Rep, typename Period > +bool +Rtc::synchronize(std::chrono::duration delay, uint32_t waitCycles) +{ + // Check that SS[15] = 0 in order to ensure that no overflow will occur, before initiating a shift operation. + if (((RTC->SSR & RTC_SSR_SS_Msk) & (Bit15 << RTC_SSR_SS_Pos)) == 0 && RTC->CR & RTC_CR_REFCKON == 0) { + unlock(); + + if (0 <= delay.count()) { + uint32_t subfs = delay.count() * Period::num * ck_spre / Period::den; + RTC->SHIFTR = (subfs << RTC_SHIFTR_SUBFS_Pos) & RTC_SHIFTR_SUBFS_Msk; + } + else { + uint32_t subfs = ck_spre + delay.count() * Period::num * ck_spre / Period::den; + RTC->SHIFTR = RTC_SHIFTR_ADD1S | ((subfs << RTC_SHIFTR_SUBFS_Pos) & RTC_SHIFTR_SUBFS_Msk); + } + + lock(); + + // Wait until pending shift operation is performed before returning + while (RTC_ICSR & RTC_ICSR_SHPF) + if (--waitCycles == 0) return false; + + return true; + } + return false; +} +*/ + +// ---------------------------------------------------------------------------- +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