rtic icon indicating copy to clipboard operation
rtic copied to clipboard

Higher resolution monotonic timer (on stm32)

Open deltronix opened this issue 2 years ago • 8 comments

It seems the resolution of timer based monotonics on the stm32 is a hard limit set by this constant: const TIMER_HZ: u32 = 1_000_000;

Is there a reason for this, or could TIMER_HZ be made a generic? I would like to have a bit more resolution for my use case.

deltronix avatar Dec 06 '23 13:12 deltronix

The issue is in async executor, which require queue to be 'static' variable. But 'static' variables don't support any generics.

burrbull avatar Dec 06 '23 13:12 burrbull

@deltronix Are you sure that this would really be helpful for your usecase? I had the impression that scheduling overhead and similar already introduces noise of multiple ticks, but maybe I'm wrong.

Although I do partially agree with your reasoning, now that embedded-hal changed their DelayUs trait to DelayNs.

The limiting factor here is that for precise timing it is required that the input clock is a multiple of the timer ticks. And generating such a clock becomes harder and harder the higher we go, so 1MHz was chosen as a somewhat arbitrary tradeof.

Finomnis avatar Dec 06 '23 14:12 Finomnis

We could provide a higher resolution alternative that can be enabled via a feature flag, like we have for the systick monotonic. What resolution do you have in mind?

As a last resort, you can of course implement your own monotonic with a higher resolution. It's quite straight forward, you can take the existing one as a reference. If you need help, feel free to ask me for a review/guidance.

Finomnis avatar Dec 06 '23 14:12 Finomnis

@burrbull I have the same problem on imxrt the other way round. The monotonic we have can take any clock source but it's fixed to 1MHz; it would be useful if the user could use the low power dock in the khz range with it. Otherwise we will have to provide a low power version of it at some point.

Finomnis avatar Dec 07 '23 08:12 Finomnis

Another solution would be to move all of the monotonic code into macros that the user has to instantiate somewhere. Yet another solution would be to avoid the static nature of the thing all together, and actually instantiate a monotonic object somewhere. It would work with the interrupts just fine if we generated the static memory inside of the interrupt token macros.

All of this would require a major rework, though, and a v2 of both the rtic-monotonics and the rtic-time crate.

Finomnis avatar Dec 07 '23 08:12 Finomnis

So I did some measurements. I used the following code on an nrf52840 on a 1MHz monotonic:

#[task(local = [led])]
async fn blink(cx: blink::Context) {
    let blink::LocalResources { led, .. } = cx.local;

    let mut next_tick = Mono::now();
    let mut blink_on = false;
    loop {
        blink_on = !blink_on;
        if blink_on {
            led.set_high().unwrap();
        } else {
            led.set_low().unwrap();
        }

        next_tick += 1.micros();
        Mono::delay_until(next_tick).await;
    }
}

And got the following waveform: Screenshot 2023-12-07 103247

As you can see, it's nowhere close to 1MHz, so increasing precision is kind of pointless. Everything with a higher frequency might have to be generated via dedicated PWM timer.

Be aware that the reason this waveform is at the wrong frequency but still stable is because scheduling actually never happens, every time Mono::delay_until is called it returns immediately, because the given instant is already in the past.

If you try with 20.micros(), you can see that .awaiting happens sometimes, but not always, causing a very glitchy pattern:

Screenshot 2023-12-07 104833

This is when I try with 50.micros(). It is about the highest I managed to achieve on nrf52840 without starting to get glitches:

Screenshot 2023-12-07 105112

There might be chips where higher frequencies are stable, like the imxrt1060 family or high-performance stm32 chips, but I doubt that a scheduling precision beyond 1us would actually be stable on those either.

Finomnis avatar Dec 07 '23 09:12 Finomnis

@Finomnis Yeah I can see how scheduling overhead would make this quite pointless in that use case. I was hoping to use the higher resolution to sync to external clocks better, e.g. (USB) MIDI, through Instant's captured with the Mono::now() method. The same thing could be accomplished with an Input Capture timer I suppose, but I was hoping that this could provide a neater solution, seeing as I then could also schedule with that same Mono instance.

deltronix avatar Dec 07 '23 12:12 deltronix

Ah, for reading the time. I get that usecase. Sadly it would require a major rework :/

Finomnis avatar Dec 07 '23 12:12 Finomnis

Should get solved by #874, once merged. This makes monotonic tick rates no longer hard coded, so you can go as high as the hardware allows.

Finomnis avatar Feb 28 '24 16:02 Finomnis