embassy icon indicating copy to clipboard operation
embassy copied to clipboard

embassy-stm32 - Unsplit timer types for more control

Open usbalbin opened this issue 1 month ago • 2 comments

Hi!

We are looking to use embassy-stm32 hal for a couple of projects, all which will use the hardware timers for different things. One of the things is triac control for soft starting an AC-motor. For that, we are thinking "one pulse mode", restart timer on ETR input, set output on compare match, reset on period etc. Perhaps even load the next pulse width from the DMA which is given the ramp profile as an array.

However that does not really map to any of the timer types available in embassy-stm32/src/timer.

From what I can tell most of that is available but not on the same type, I would need some combination of for example

  • one_pulse.rs (trigger input and single shot mode)
  • simple_pwm.rs (output to pin and DMA support)

In my mind "one pulse", "pwm output" and "input capture" does not need to be mutually exclusive. I understand if the majority of users are happy with the different types and the discoverability is better that way. However it would be nice to have something slightly higher than the pac level as exposed in the low level timer type but still having the control to do what I want.

What do you think about moving down a lot of logic from the different types and putting it directly on the low level type so as to make that more useful(and delegate to the inner type in the higher level types so as to keep the same API) and/or perhaps add new timer type (insert too-many-standards-meme here) which can do all the things.

Coming from stm32g4xx-hal, and to some extent stm32h5xx-hal, where there is quite extensive use of builder types for setting up peripherals with lots of options. From what I have seen so far, embassy does not seem to do that quite as often. Is there any particular reason for this or would you think something like this could work?

For example

let mut triac_ctrl = MySwissArmyKnifeTimer::new(p.TIM1)
    //.frequency()
    .prescaler(...)
    .period(...)
    .ch1_out(output_pin, initial_duty)
    //.ch1_out_compl(...)
    .trigger(zero_detect_pin)
    .enable_oneshot_mode()
    .output_compare_mode(OutputCompareMode::PwmMode2)
    .init();

triac_ctrl.waveform_up(dma, MY_SOFT_START_RAMP_PROFILE).await;

Or do you have any other suggestions? :) I suppose some of the the things above could be made into methods on the final type to mutate it after it has been initialized and the things that have to be done before could be put into some Config struct if you prefer?

cc @denenilsson

usbalbin avatar Nov 10 '25 17:11 usbalbin

low_level::Timer is available for this purpose. you can build a custom timer class that encapsulates low_level::Timer for a custom application. As for altering the high level timers to a different API, the most important property of the high level timers is that they are easy to use. If you can propose a better API while keeping that in mind, I am open to accepting it.

xoviat avatar Nov 10 '25 23:11 xoviat

Might have overdone the type level shenanigans a bit but #4895 is some sort of rough PoC thing to at least start a discussion.

usbalbin avatar Nov 16 '25 11:11 usbalbin