Skip to content
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

Add rtc module #93

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 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
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub mod pwm;
#[cfg(feature = "device-selected")]
pub mod rcc;
#[cfg(feature = "device-selected")]
pub mod rtc;
#[cfg(feature = "device-selected")]
pub mod serial;
#[cfg(feature = "device-selected")]
pub mod spi;
Expand Down
85 changes: 84 additions & 1 deletion src/rcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use core::cmp;

use crate::stm32::{
rcc::{self, cfgr},
RCC,
PWR, RCC,
};

use crate::flash::ACR;
use crate::rtc::RTCSrc;
use crate::time::Hertz;

/// Extension trait that constrains the `RCC` peripheral
Expand Down Expand Up @@ -45,6 +46,87 @@ pub struct Rcc {
pub cfgr: CFGR,
}

impl Rcc {
teskje marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn enable_rtc(&mut self, src: &RTCSrc) {
match src {
RTCSrc::LSI => self.enable_lsi(),
teskje marked this conversation as resolved.
Show resolved Hide resolved
RTCSrc::HSE => self.enable_hse(false),
RTCSrc::LSE => self.enable_lse(false),
}
self.unlock_rtc();
self.bdcr().modify(|_, w| {
w
// RTC Backup Domain reset bit set high
.bdrst()
.set_bit()
});
self.bdcr().modify(|_, w| {
w
// RTC clock source selection
.rtcsel()
.bits(*src as u8)
// Enable RTC
.rtcen()
.set_bit()
// RTC backup Domain reset bit set low
.bdrst()
.clear_bit()
});
}

pub(crate) fn unlock_rtc(&mut self) {
let pwr = unsafe { &(*PWR::ptr()) };
self.apb1.enr().modify(|_, w| {
w
// Enable the backup interface by setting PWREN
.pwren()
.set_bit()
});
pwr.cr.modify(|_, w| {
w
// Enable access to the backup registers
.dbp()
.set_bit()
});

while pwr.cr.read().dbp().bit_is_clear() {}
}

pub(crate) fn enable_lsi(&mut self) {
self.csr().write(|w| w.lsion().set_bit());
while self.csr().read().lsirdy().bit_is_clear() {}
}

pub(crate) fn enable_hsi(&mut self) {
self.cr().write(|w| w.hsion().set_bit());
while self.cr().read().hsirdy().bit_is_clear() {}
}

pub(crate) fn enable_hse(&mut self, bypass: bool) {
self.cr()
.write(|w| w.hseon().set_bit().hsebyp().bit(bypass));
while self.cr().read().hserdy().bit_is_clear() {}
}

pub(crate) fn enable_lse(&mut self, bypass: bool) {
self.bdcr()
.write(|w| w.lseon().set_bit().lsebyp().bit(bypass));
while self.bdcr().read().lserdy().bit_is_clear() {}
}

pub(crate) fn cr(&mut self) -> &rcc::CR {
unsafe { &(*RCC::ptr()).cr }
}

pub(crate) fn csr(&mut self) -> &rcc::CSR {
unsafe { &(*RCC::ptr()).csr }
}

pub(crate) fn bdcr(&mut self) -> &rcc::BDCR {
unsafe { &(*RCC::ptr()).bdcr }
}
}

/// AMBA High-performance Bus (AHB) registers
pub struct AHB {
_0: (),
Expand Down Expand Up @@ -151,6 +233,7 @@ mod usb_clocking {
}

/// Clock configuration
#[derive(Clone, Copy)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

We must not allow cloning of the CFGR struct. This struct is responsible for making sure that the clocks are only configured once. When you call freeze() it consumes the struct and thereby ensures the clocks won't change anymore, so other code can rely on that. If you make CFGR clonable you could suddenly have multiple instances of this struct around, which would allow you to change the clocks and break the assumption that they are stable

pub struct CFGR {
hse: Option<u32>,
hclk: Option<u32>,
Expand Down
172 changes: 172 additions & 0 deletions src/rtc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use crate::rcc::Rcc;
use crate::stm32::RTC;
use crate::time::{Date, Time, U32Ext};

/**
teskje marked this conversation as resolved.
Show resolved Hide resolved
Interface to the real time clock
**/

pub struct Rtc {
regs: RTC,
}

impl Rtc {
/**
teskje marked this conversation as resolved.
Show resolved Hide resolved
Initializes the RTC
**/
// Sets default clock source to LSI, since LSE is not included on STM32f3 discovery boards
pub fn rtc(regs: RTC, src: RTCSrc, rcc: &mut Rcc) -> Self {
let mut result = Rtc { regs };

rcc.enable_rtc(&src);

result.regs.cr.modify(|_, w| {
w
// sets hour format to 24 hours
.fmt()
.clear_bit()
});

// Prescalers set to produce a 1 hz signal
let (prediv_s, prediv_a) = match src {
Copy link
Member

Choose a reason for hiding this comment

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

Where do these numbers come from? Have you considered that HSE is variable and can differ between boards?

Copy link
Author

Choose a reason for hiding this comment

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

These numbers came from AN4795:

temp

I like the idea of a function that takes care of choosing the correct prescalers to produce a 1 hz RTC clock source. This is possible for LSI and LSE, but the fact that HSE frequency can vary (I read that the HSE frequency can be between 4 and 32 Mhz) makes that a challenge.

The concept of making some methods "visible" given a parameter (the RTCSrc) sounds like a good solution, if it is possible.

For example :

if RTCSrc::LSE or RTCSrc::LSI, make the following method visible....

fn set_1hz_frequency{
   /// set prescalers to produce 1hz clock source
}

else, there is a more general function set_frequency that is always visible that would be used if the user is using an HSE of any frequency. The user would then supply prescalers to set appropriate frequency.

I think I am not using the correct words to search to see if this feature of making methods "visible" or "invisible" exists, because I am not able to find anything online. Can something like this be accomplished?

Copy link
Member

Choose a reason for hiding this comment

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

Ah interesting to know. I would like to see a comment or make these tuples for fixed "input" frequencies (meaning LSI LSE) constants.

I have difficulties to find the AN4795 Application note, can you provide a link?

Copy link
Author

@smedellin90 smedellin90 May 18, 2020

Choose a reason for hiding this comment

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

Copy link
Author

@smedellin90 smedellin90 May 18, 2020

Choose a reason for hiding this comment

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

Because HSE clock frequencies can vary, perhaps the user should choose a source and prescaler values to produce the desired rtc input clock speed through a configuration structure.

eg.

// rtc module


/// RTC clock input source
#[derive(Clone, Copy)]
pub enum RTCSrc {
    LSE = 0b01,
    LSI = 0b10,
    HSE = 0b11,
}

/// RTC configuration struct
pub struct cfgRTC {
    src: RTCSrc,
    prediv_s: u16,
    prediv_a: u8,
}

impl cfgRTC {
    pub fn new(source: RTCSrc, prescaler_sync: u16, prescaler_async: u8) 
        -> cfgRTC 
        {
            let config  = cfgRTC {
                src: source,
                prediv_s: prescaler_sync,
                prediv_a: prescaler_async,
            };
            config
        }
}


pub struct Rtc {
    regs: RTC,
}

impl Rtc {
    pub fn rtc(regs: RTC, cfg: cfgRTC, rcc: &mut Rcc) -> Self {
        /// initialization of rtc with desired clock source and input frequency
    }
// etc etc
}

RTCSrc::LSI => (311_u32, 127_u32),
RTCSrc::HSE => (62992_u32, 127_u32),
RTCSrc::LSE => (255_u32, 127_u32),
};

let raw_bits: u32 = prediv_s | (prediv_a << 16);
result.modify(|regs| {
regs.prer.write(|w| unsafe { w.bits(raw_bits) });
smedellin90 marked this conversation as resolved.
Show resolved Hide resolved
});
result
}

pub fn set_time(&mut self, time: &Time) {
let (ht, hu) = bcd2_encode(time.hours);
let (mnt, mnu) = bcd2_encode(time.minutes);
let (st, su) = bcd2_encode(time.seconds);
self.modify(|regs| {
regs.tr.write(|w| unsafe {
w.ht()
.bits(ht)
.hu()
.bits(hu)
.mnt()
.bits(mnt)
.mnu()
.bits(mnu)
.st()
.bits(st)
.su()
.bits(su)
.pm()
.clear_bit()
});
regs.cr.modify(|_, w| w.fmt().bit(time.daylight_savings));
teskje marked this conversation as resolved.
Show resolved Hide resolved
});
}

pub fn set_date(&mut self, date: &Date) {
let (yt, yu) = bcd2_encode(date.year - 1970);
let (mt, mu) = bcd2_encode(date.month);
let (dt, du) = bcd2_encode(date.day);

self.modify(|regs| {
regs.dr.write(|w| unsafe {
w.dt()
.bits(dt)
.du()
.bits(du)
.mt()
.bit(mt > 0)
.mu()
.bits(mu)
.yt()
.bits(yt)
.yu()
.bits(yu)
.wdu()
.bits(date.day as u8)
});
});
}

pub fn get_time(&self) -> Time {
let timer = self.regs.tr.read();
Time::new(
bcd2_decode(timer.ht().bits(), timer.hu().bits()).hours(),
bcd2_decode(timer.mnt().bits(), timer.mnu().bits()).minutes(),
bcd2_decode(timer.st().bits(), timer.su().bits()).seconds(),
self.regs.cr.read().fmt().bit(),
teskje marked this conversation as resolved.
Show resolved Hide resolved
)
}

pub fn get_date(&self) -> Date {
let date = self.regs.dr.read();
Date::new(
(bcd2_decode(date.yt().bits(), date.yu().bits()) + 1970).year(),
bcd2_decode(date.mt().bit() as u8, date.mu().bits()).month(),
bcd2_decode(date.dt().bits(), date.du().bits()).day(),
)
}

pub fn get_week_day(&self) -> u8 {
teskje marked this conversation as resolved.
Show resolved Hide resolved
self.regs.dr.read().wdu().bits()
}

fn modify<F>(&mut self, mut closure: F)
teskje marked this conversation as resolved.
Show resolved Hide resolved
where
F: FnMut(&mut RTC) -> (),
smedellin90 marked this conversation as resolved.
Show resolved Hide resolved
{
// Disable write protection
self.regs.wpr.write(|w| unsafe { w.bits(0xCA) });
self.regs.wpr.write(|w| unsafe { w.bits(0x53) });
// Enter init mode
let isr = self.regs.isr.read();
if isr.initf().bit_is_clear() {
self.regs.isr.write(|w| w.init().set_bit());
while self.regs.isr.read().initf().bit_is_clear() {}
}
// Invoke closure
closure(&mut self.regs);
// Exit init mode
self.regs.isr.write(|w| w.init().clear_bit());
// wait for last write to be done
while !self.regs.isr.read().initf().bit_is_clear() {}
}
}

/// RTC clock input source
#[derive(Clone, Copy)]
pub enum RTCSrc {
teskje marked this conversation as resolved.
Show resolved Hide resolved
LSE = 0b01,
LSI = 0b10,
HSE = 0b11,
}

pub trait RtcExt {
fn constrain(self, rcc: &mut Rcc) -> Rtc;
}

impl RtcExt for RTC {
fn constrain(self, rcc: &mut Rcc) -> Rtc {
Rtc::rtc(self, RTCSrc::LSI, rcc)
}
}

fn bcd2_encode(word: u32) -> (u8, u8) {
smedellin90 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, if I understand correctly, this function converts 92 to 9 and 2. If that's true then you could massively simplify it to:

(word / 10, word % 10)

But there also needs to be a (panicking) check that word is smaller than 100.

let mut value = word as u8;
let mut bcd_high: u8 = 0;
while value >= 10 {
bcd_high += 1;
value -= 10;
}
let bcd_low = ((bcd_high << 4) | value) as u8;
(bcd_high, bcd_low)
}

fn bcd2_decode(fst: u8, snd: u8) -> u32 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly, this can be simplified to:

fst * 10 + snd

let value = snd | fst << 4;
let value = (value & 0x0F) + ((value & 0xF0) >> 4) * 10;
value as u32
}
Loading