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