Skip to content

Commit

Permalink
[stm32] Add RTC peripheral
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Dec 29, 2024
1 parent cee61d6 commit 7b4645f
Show file tree
Hide file tree
Showing 8 changed files with 601 additions and 5 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,31 @@ Please [discover modm's peripheral drivers for your specific device][discover].
<td align="center">✕</td>
<td align="center">✕</td>
</tr><tr>
<td align="left">RTC</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">○</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">✕</td>
<td align="center">✕</td>
<td align="center">✕</td>
</tr><tr>
<td align="left">SPI</td>
<td align="center">✅</td>
<td align="center">✅</td>
Expand Down
7 changes: 6 additions & 1 deletion src/modm/platform/clock/stm32/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"])

Expand Down
12 changes: 8 additions & 4 deletions src/modm/platform/core/stm32/startup_platform.c.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions src/modm/platform/rtc/stm32/module.lb
Original file line number Diff line number Diff line change
@@ -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")
85 changes: 85 additions & 0 deletions src/modm/platform/rtc/stm32/module.md
Original file line number Diff line number Diff line change
@@ -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<SystemClock>();
if (not inited) { /* RTC was already running from before reset */ }
```
To always initialize the RTC, set the `forced` argument to `true`:
```cpp
Rtc::initialize<SystemClock>(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();
```
19 changes: 19 additions & 0 deletions src/modm/platform/rtc/stm32/rtc.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
167 changes: 167 additions & 0 deletions src/modm/platform/rtc/stm32/rtc.hpp.in
Original file line number Diff line number Diff line change
@@ -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 <chrono>
#include <ctime>
#include <sys/time.h>

#include <modm/architecture.hpp>
#include <modm/math/calendar/date_time.hpp>

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<Rtc, duration>;
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<std::chrono::seconds>(t.time_since_epoch()).count());
}

static time_point
from_time_t(std::time_t t) noexcept
{
using from_t = std::chrono::time_point<Rtc, std::chrono::seconds>;
return time_point_cast<duration>(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
Loading

0 comments on commit 7b4645f

Please sign in to comment.