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

Timer::start panics #227

Open knoellle opened 1 year ago

knoellle commented 1 year ago

The Timer::start() implementation breaks if the ticks value calculated inside the start function is an exact multiple of (1 << 16).

Numerical example

If ticks is 1 << 16 == 65536 then psc, which is (ticks - 1) / (1 << 16) becomes 0 because the division result is ever so slightly less than one and thus trucated. Now we calculate u16(ticks / u32(psc + 1)). But since psc is 0,psc + 1 is 1, so ticks / u32(psc + 1) is still 65536, which is then cast to a u16. But since 65536 is 1 << 16, it doesn't fit in the u16 range from 0 to 1 << 16 - 1 and we unwrapp an Err(Overflow) value.

Clock configuration example

Pretty simple actually, the MSI clock source outputs frequencies that (except for MSI range 0) are all multiple of 1 << 16. For example, MSI clock source of range 1 produces a 65536 Hz clock. When attempting to start a timer with frequency 1 with this clock, the error occurs.

Code example

use cast::{u16, u32, Error};

// simplified version of calculations inside Timer::start()
fn timer_settings(sysclk: u32, frequency: u32) -> Result<(u16, u16), Error> {
    let ticks = sysclk / frequency;
    println!("{}", ticks % (1 << 16));
    let psc = u16((ticks - 1) / (1 << 16))?;
    let arr = u16(ticks / u32(psc + 1))?;

    Ok((psc, arr))
}

fn main() {
    // all of these return Err()
    for n in 1..100 {
        println!("{n} {:?}", timer_settings((2 << 16) * n, n).unwrap_err());
    }
    // all of these return Ok()
    for n in 1..100 {
        println!("{n} {:?}", timer_settings((2 << 16) * n - 1, n).unwrap());
    }
}

Possible solution

I'm not 100% sure on this, but I think u16(ticks / u32(psc + 1)) can be changed to u16((ticks - 1) / u32(psc + 1)), such that both divisions have the same numerator.