stm32-rs / stm32l0xx-hal

A hardware abstraction layer (HAL) for the STM32L0 series microcontrollers written in Rust
BSD Zero Clause License
96 stars 60 forks source link

Linked Upcounting Timer #114

Closed dbrgn closed 2 years ago

dbrgn commented 4 years ago

I wrote some code to link two 16 bit timers on an STM32L0 in order to form a 32 bit upcounting timer that can be used to measure relative time. The goal is to use this abstraction in order to implement the Monotonic trait for the RTIC scheduler.

Here's the current code, needs more testing & should be made more general (there are other timer pairs that allow linking), but seems to work already:

/// Two linked 16 bit timers that form a 32 bit upcounter.
pub struct LinkedTimer {
    /// Timer in master mode
    tim2: TIM2,
    /// Timer in slave mode
    tim3: TIM3,
}

impl LinkedTimer {
    /// Create a new `LinkedTimer` with TIM2 as master and TIM3 as slave.
    pub fn new(tim2: TIM2, tim3: TIM3, rcc: &mut Rcc) -> Self {
        // Enable timers
        rcc.rb.apb1enr.modify(|_, w| w.tim2en().set_bit());
        rcc.rb.apb1enr.modify(|_, w| w.tim3en().set_bit());

        // Reset timers
        rcc.rb.apb1rstr.modify(|_, w| w.tim2rst().set_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim2rst().clear_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim3rst().set_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim3rst().clear_bit());

        // Enable counter
        tim2.cr1.modify(|_, w| w.cen().set_bit());
        tim3.cr1.modify(|_, w| w.cen().set_bit());

        // In the MMS (Master Mode Selection) register, set the master mode so
        // that a rising edge is output on the trigger output TRGO every time
        // an update event is generated.
        tim2.cr2.modify(|_, w| w.mms().variant(tim2::cr2::MMS_A::UPDATE));

        // In the SMCR (Slave Mode Control Register), select the internal
        // trigger 0 (ITR0) as trigger source (TS).
        //
        // See table 76 ("TIM2/TIM3 internal trigger connection") in the
        // reference manual RM0377.
        tim3.smcr.modify(|_, w| w.ts().variant(tim2::smcr::TS_A::ITR0));
        // Set the SMS (Slave Mode Selection) register to "external clock mode 1",
        // where the rising edges of the selected trigger (TRGI) clock the counter.
        tim3.smcr.modify(|_, w| w.sms().variant(tim2::smcr::SMS_A::EXT_CLOCK_MODE));

        Self { tim2, tim3 }
    }

    fn master(&self) -> &TIM2 {
        &self.tim2
    }

    fn slave(&self) -> &TIM3 {
        &self.tim3
    }

    /// Return the current 32 bit counter value.
    pub fn get_counter(&self) -> u32 {
        let lsb = self.master().cnt.read().cnt().bits() as u32;
        let msb = self.slave().cnt.read().cnt().bits() as u32;
        (msb << 16) | lsb
    }
}

When polling the value with 10 Hz:

...
Counter m=6110 s=861, combined=56432622, delta=1600025
Counter m=33271 s=885, combined=58032647, delta=1600025
Counter m=60432 s=909, combined=59632672, delta=1600025
Counter m=22057 s=934, combined=61232697, delta=1600025
Counter m=49218 s=958, combined=62832722, delta=1600025
Counter m=10843 s=983, combined=64432747, delta=1600025
Counter m=38004 s=1007, combined=66032772, delta=1600025
...

Should this go into the stm32l0-hal? If yes, how should it be called? Should there be a trait involved that's implemented for all timers that allow linking? Or a trait for upcounting timers that cannot be reset?

If yes, should there also be logic for detecting overflows in the HAL, or should that be part of user code?

Right now there's no API in embedded-hal that matches this pattern (see also this discussion). It also cannot be combined with CountDown because resetting the timer (on start) also resets the counter.

CC @astro @hannobraun @rnestler

dbrgn commented 4 years ago

Implementation is in #115, so this should probably be discussed there.

jcard0na commented 2 years ago

Can this issue be closed?

dbrgn commented 2 years ago

Yes, good catch!