2025-07-18 2:04 AM
Hi there,
I would like to control WS2812B LEDs with a PWM timer and DMA.
There are various tutorials on this, but they have not helped me and I am now asking you. Because it requires slightly different configurations depending on the model (F3, F4, G4, G0), I'm a bit overwhelmed.
I start a DMA transfer, wait for the last pulse with an interrupt and stop the DMA transfer. So far so good in theory. I still have problems with the implementation.
Even the simplest DMA example without WS2812B protocol does not work for me.
TIM1 Mode + Configuration
DMA Settings
Source:
TIM_HandleTypeDef htim1;
DMA_HandleTypeDef hdma_tim1_ch4; /* never used?! */
uint32_t data[] = {
30,
80,
20,
60,
10,
50
};
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_4);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_4, (uint32_t *) data, 6);
while (1) {
/* */
}
}
The result is here:
After 4 pulses, the 5th pulse starts but didn't finish
I tried also with Data Width
Half-Word
Byte
Without stopping DMA, it's like that:
The last puls recurs:
Do you know why it's like that?
Do I need to have a different callback?
Thanks for helping...
Andreas
Solved! Go to Solution.
2025-07-19 7:46 AM
The SPI-(or UART-)based solution may be better, but the problem with the timer-based solution is, that you call a function which disables the timer output (i.e. threestates it), as witnessed by the oscilloscope track. Just don't do that. Some Cube/HAL functions may not do what you think they do just based on their name - pending more thorough documentation, they are open source so you can easily look up what are they doing, or just don't use them and program the microcontroller normally.
Here, you don't want to *stop the DMA*. Unless set to Circular, DMA stops automatically when it transfers all data it is set to transfer (in its NDTR register). What you want is a steady output from TIM, and one easy way to achieve that is by setting the respective TIMx_CCRx to zero. So, simply add one zero as an extra element in the array you are transferring using DMA to TIMx_CCRx.
JW
2025-07-19 9:35 AM
Hello @waclawek.jan,
Thank you for the explanation.
HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel)
{
// [...]
/* Disable the Peripheral */
__HAL_TIM_DISABLE(htim);
// [...]
}
uint16_t data[] = {
30,
80,
20,
60,
10,
50,
0
};
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_4, (uint32_t *) data, 6+1);
while (1) {
//
}
}
The oscilloscope gives the result
It's just strange that HAL_TIM_PWM_PulseFinishedCallback() comes 2 pulses to early. But if no one helps me, I'll just have to live with it.
2025-07-19 10:00 AM
> It's just strange that HAL_TIM_PWM_PulseFinishedCallback() comes 2 pulses to early.
I don't use Cube/HAL so don't know what's exactly HAL_TIM_PWM_PulseFinishedCallback(), but if it's called from the DMA's Transfer Complete interrupt, then that must happen before the last pulse, as the last DMA transfer is triggered by the pulse one before that last pulse (as far as I know Cube/HAL, that may be triggered by the CCx event rather than Update event, which is incorrect, but that's Cube/HAL for ya). And, as you have Compare Preload enabled, that adds one more timer cycle to the "lag".
JW