Skip to content

embassy-stm32: split rtc low-power wakeup and time functions #4191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions embassy-stm32/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ pub struct Config {
/// RCC config.
pub rcc: rcc::Config,

/// RTC config.
pub rtc: Option<rtc::RtcConfig>,

/// Enable debug during sleep and stop.
///
/// May increase power consumption. Defaults to true.
Expand Down Expand Up @@ -283,6 +286,7 @@ impl Default for Config {
fn default() -> Self {
Self {
rcc: Default::default(),
rtc: None,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dirbaio should we have the default behaviour be this (no RTC setup) or use the RtcConfig defaults?

#[cfg(dbgmcu)]
enable_debug_during_sleep: true,
#[cfg(any(stm32l4, stm32l5, stm32u5))]
Expand Down Expand Up @@ -602,10 +606,19 @@ fn init_hw(config: Config) -> Peripherals {

rcc::init(config.rcc);

if let Some(rtc_config) = config.rtc {
rtc::init(cs, rtc_config);
}

// must be after rcc init
#[cfg(feature = "_time-driver")]
time_driver::init(cs);

#[cfg(feature = "low-power")]
if let Some(_) = config.rtc {
time_driver::get_driver().set_rtc(rtc::get_rtc_control());
}

#[cfg(feature = "low-power")]
{
crate::rcc::REFCOUNT_STOP2 = 0;
Expand Down
23 changes: 17 additions & 6 deletions embassy-stm32/src/rtc/low_power.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use super::{bcd2_to_byte, DateTimeError, Rtc, RtcError};
use core::cell::Cell;

use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;

use super::{bcd2_to_byte, DateTimeError, Rtc, RtcError, RtcTimeProvider};
use crate::peripherals::RTC;
use crate::rtc::SealedInstance;

Expand Down Expand Up @@ -112,10 +117,16 @@ impl WakeupPrescaler {
}
}

impl Rtc {
impl super::RtcControl {
pub(crate) const fn new() -> Self {
Self {
stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)),
}
}

/// Return the current instant.
fn instant(&self) -> Result<RtcInstant, RtcError> {
self.time_provider().read(|_, tr, ss| {
RtcTimeProvider { _private: () }.read(|_, tr, ss| {
let second = bcd2_to_byte((tr.st(), tr.su()));

RtcInstant::from(second, ss).map_err(RtcError::InvalidDateTime)
Expand All @@ -139,15 +150,15 @@ impl Rtc {
unsafe { crate::rcc::get_freqs() }.rtc.to_hertz().unwrap();

let requested_duration = requested_duration.as_ticks().clamp(0, u32::MAX as u64);
let rtc_hz = Self::frequency().0 as u64;
let rtc_hz = Rtc::frequency().0 as u64;
let rtc_ticks = requested_duration * rtc_hz / TICK_HZ;
let prescaler = WakeupPrescaler::compute_min((rtc_ticks / u16::MAX as u64) as u32);

// adjust the rtc ticks to the prescaler and subtract one rtc tick
let rtc_ticks = rtc_ticks / prescaler as u64;
let rtc_ticks = rtc_ticks.clamp(0, (u16::MAX - 1) as u64).saturating_sub(1) as u16;

self.write(false, |regs| {
super::Rtc::write_with_cs(cs, false, |regs| {
regs.cr().modify(|w| w.set_wute(false));

#[cfg(any(
Expand Down Expand Up @@ -193,7 +204,7 @@ impl Rtc {
if RTC::regs().cr().read().wute() {
trace!("rtc: stop wakeup alarm at {}", instant);

self.write(false, |regs| {
super::Rtc::write_with_cs(cs, false, |regs| {
regs.cr().modify(|w| w.set_wutie(false));
regs.cr().modify(|w| w.set_wute(false));

Expand Down
75 changes: 50 additions & 25 deletions embassy-stm32/src/rtc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ mod datetime;

#[cfg(feature = "low-power")]
mod low_power;

#[cfg(feature = "low-power")]
use core::cell::Cell;

#[cfg(feature = "low-power")]
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
#[cfg(feature = "low-power")]
use embassy_sync::blocking_mutex::Mutex;
#[cfg(feature = "low-power")]
use low_power::RtcInstant;

use core::sync::atomic::{AtomicBool, Ordering};

use critical_section::CriticalSection;

use self::datetime::{day_of_week_from_u8, day_of_week_to_u8};
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
Expand Down Expand Up @@ -113,11 +117,24 @@ impl RtcTimeProvider {

/// RTC driver.
pub struct Rtc {
#[cfg(feature = "low-power")]
stop_time: Mutex<CriticalSectionRawMutex, Cell<Option<low_power::RtcInstant>>>,
_private: (),
}

/// RTC low-power driver.
#[cfg(feature = "low-power")]
pub(crate) struct RtcControl {
stop_time: Mutex<CriticalSectionRawMutex, Cell<Option<RtcInstant>>>,
}

/// RTC low-power driver.
#[cfg(feature = "low-power")]
static RTC_CONTROL: RtcControl = RtcControl::new();

#[cfg(feature = "low-power")]
pub(crate) fn get_rtc_control() -> &'static RtcControl {
&RTC_CONTROL
}

/// RTC configuration.
#[non_exhaustive]
#[derive(Copy, Clone, PartialEq)]
Expand Down Expand Up @@ -149,32 +166,40 @@ pub enum RtcCalibrationCyclePeriod {
Seconds32,
}

impl Rtc {
/// Create a new RTC instance.
pub fn new(_rtc: Peri<'static, RTC>, rtc_config: RtcConfig) -> Self {
#[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))]
crate::rcc::enable_and_reset::<RTC>();
static IS_INIT: AtomicBool = AtomicBool::new(false);

let mut this = Self {
#[cfg(feature = "low-power")]
stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)),
_private: (),
};
pub(crate) fn init(cs: CriticalSection, rtc_config: RtcConfig) {
if IS_INIT.load(Ordering::Relaxed) {
return;
}

let frequency = Self::frequency();
let async_psc = ((frequency.0 / rtc_config.frequency.0) - 1) as u8;
let sync_psc = (rtc_config.frequency.0 - 1) as u16;
#[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))]
crate::rcc::enable_and_reset::<RTC>();

this.configure(async_psc, sync_psc);
let frequency = Rtc::frequency();
let async_psc = ((frequency.0 / rtc_config.frequency.0) - 1) as u8;
let sync_psc = (rtc_config.frequency.0 - 1) as u16;

// Wait for the clock to update after initialization
#[cfg(not(rtc_v2f2))]
{
let now = this.time_provider().read(|_, _, ss| Ok(ss)).unwrap();
while now == this.time_provider().read(|_, _, ss| Ok(ss)).unwrap() {}
}
Rtc::configure(cs, async_psc, sync_psc);

this
// Wait for the clock to update after initialization
#[cfg(not(rtc_v2f2))]
{
let time_provider = RtcTimeProvider { _private: () };
let now = time_provider.read(|_, _, ss| Ok(ss)).unwrap();
while now == time_provider.read(|_, _, ss| Ok(ss)).unwrap() {}
}

IS_INIT.store(true, Ordering::Relaxed);
}

impl Rtc {
/// Create a new RTC instance.
pub fn new(_rtc: Peri<'static, RTC>) -> Self {
if !IS_INIT.load(Ordering::Relaxed) {
panic!("RTC not initialized");
}
Self { _private: () }
}

fn frequency() -> Hertz {
Expand Down
74 changes: 47 additions & 27 deletions embassy-stm32/src/rtc/v2.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
use critical_section::CriticalSection;
use stm32_metapac::rtc::vals::{Osel, Pol};

use super::SealedInstance;
use crate::pac::rtc::Rtc;
use crate::peripherals::RTC;

/// Write to the RTC without a mutual reference to the RTC instance.
/// Not used outside of this module
fn unchecked_write<F, R>(init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
let r = RTC::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
r.wpr().write(|w| w.set_key(0xca));
r.wpr().write(|w| w.set_key(0x53));

// true if initf bit indicates RTC peripheral is in init mode
if init_mode && !r.isr().read().initf() {
// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode
r.isr().modify(|w| w.set_init(true));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.isr().read().initf() {}
}

let result = f(r);

if init_mode {
r.isr().modify(|w| w.set_init(false)); // Exits init mode
}

// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(0xff));
result
}

#[allow(dead_code)]
impl super::Rtc {
/// Applies the RTC config
/// It this changes the RTC clock source the time will be reset
pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) {
self.write(true, |rtc| {
pub(super) fn configure(_cs: CriticalSection, async_psc: u8, sync_psc: u16) {
unchecked_write(true, |rtc| {
rtc.cr().modify(|w| {
#[cfg(not(rtc_v2f2))]
w.set_bypshad(true);
Expand Down Expand Up @@ -93,35 +127,21 @@ impl super::Rtc {
})
}

pub(super) fn write<F, R>(&self, init_mode: bool, f: F) -> R
/// Write to the RTC with a mutual reference to the RTC instance.
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
let r = RTC::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
r.wpr().write(|w| w.set_key(0xca));
r.wpr().write(|w| w.set_key(0x53));

// true if initf bit indicates RTC peripheral is in init mode
if init_mode && !r.isr().read().initf() {
// to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode
r.isr().modify(|w| w.set_init(true));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.isr().read().initf() {}
}

let result = f(r);

if init_mode {
r.isr().modify(|w| w.set_init(false)); // Exits init mode
}
unchecked_write(init_mode, f)
}

// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(0xff));
result
/// Write to the RTC with a critical section.
/// Used in the initialisation of the RTC
pub(super) fn write_with_cs<F, R>(_cs: CriticalSection, init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
unchecked_write(init_mode, f)
}
}

Expand Down
67 changes: 43 additions & 24 deletions embassy-stm32/src/rtc/v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,38 @@ use crate::pac::rtc::Rtc;
use crate::peripherals::RTC;
use crate::rtc::SealedInstance;

/// Write to the RTC without a critical section.
/// Not used outside of this module
fn unchecked_write<F, R>(init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
let r = RTC::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
r.wpr().write(|w| w.set_key(Key::DEACTIVATE1));
r.wpr().write(|w| w.set_key(Key::DEACTIVATE2));

if init_mode && !r.icsr().read().initf() {
r.icsr().modify(|w| w.set_init(true));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.icsr().read().initf() {}
}

let result = f(r);

if init_mode {
r.icsr().modify(|w| w.set_init(false)); // Exits init mode
}

// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(Key::ACTIVATE));

result
}

impl super::Rtc {
/// Applies the RTC config
/// It this changes the RTC clock source the time will be reset
Expand Down Expand Up @@ -95,34 +127,21 @@ impl super::Rtc {
})
}

pub(super) fn write<F, R>(&self, init_mode: bool, f: F) -> R
/// Write to the RTC with a mutual reference to the RTC instance.
pub(super) fn write<F, R>(&mut self, init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
let r = RTC::regs();
// Disable write protection.
// This is safe, as we're only writin the correct and expected values.
r.wpr().write(|w| w.set_key(Key::DEACTIVATE1));
r.wpr().write(|w| w.set_key(Key::DEACTIVATE2));

if init_mode && !r.icsr().read().initf() {
r.icsr().modify(|w| w.set_init(true));
// wait till init state entered
// ~2 RTCCLK cycles
while !r.icsr().read().initf() {}
}

let result = f(r);

if init_mode {
r.icsr().modify(|w| w.set_init(false)); // Exits init mode
}

// Re-enable write protection.
// This is safe, as the field accepts the full range of 8-bit values.
r.wpr().write(|w| w.set_key(Key::ACTIVATE));
unchecked_write(init_mode, f)
}

result
/// Write to the RTC with a critical section.
/// Used in the initialisation of the RTC
pub(super) fn write_with_cs<F, R>(cs: CriticalSection, init_mode: bool, f: F) -> R
where
F: FnOnce(crate::pac::rtc::Rtc) -> R,
{
unchecked_write(init_mode, f)
}
}

Expand Down
Loading