Search code examples
timerstm32dmapwmlibopencm3

Orchestration of DMA transfer to PWM peripheral on STM32


I have a STM32F411 board and want to output a PWM signal using timers/PWM/DMA: once every 2 ms a, 16 pulses are sent, each representing one bit. Each pulse has a period of ~3 microseconds with varying duty cycle.

To change the duty cycle, I write to a 32-bit output capture/compare register (TIMx->CCRx) via a DMA transfer from a buffer in memory (the buffer contains 16 32-bit integers). Essentially, the output is HIGH as long as the timer's counter is smaller than this register value, then it changes to LOW until the timer overflows.

I have two questions:

  1. What happens if the DMA transfer is not perfectly synchronized with the PWM output? For example, if the first two CCR values transferred via DMA arrive during the first pulse period. Is the 2nd value that arrived "thrown away" or does the PWM peripheral store it and use it for the 2nd pulse?
  2. How can I make the PWM peripheral only output 16 pulses, then set the line to LOW, and wait until the next 16 pulses need to be transferred? At the moment, I'm only able to continuously output the pulses but I'm stuck with implementing the delay between the pulse trains.

I'd appreciate any pointers!

Btw, I use libopencm3.


Solution

    1. TIMx_CCRx is a register. If you write two values into it, the second overwrites the first.

    However, you can have TIMx_CCRx preload switched on, if the related TIMx_CCMRx.OCxPE bit is set. In that case, processor or DMA is not writing to the "working" TIMx_CCRx register (against which TIMx_CNT is compared), but into an "intermediate" register (ST uses a different nomenclature, but IMO that's more confusing). The value from "intermediate" register is copied into the "working" register upon the next Update event (usually upon the timer "rollover", when TIMx_CNT reaches TIMx_ARR and then goes to 0). But even in this case, if you write twice to TIMx_CCRx within one TIM period, both writes go into the "intermediate" register and the second one overwrites the first one.

    But you don't need to be worried by two writes per period at all. Unless you run at some extreme clocks, the DMA mechanism together with the above preload ensures, that there's only one DMA transfer per period. Just don't trigger the DMA by the CC itself (as I see so often in examples and in Cube/HAL), but by Update.

    1. Add one "stopper" period at the end, with 0 duty cycle. That makes the timer output permanently low, even if the timer keeps running.