cancel
Showing results for 
Search instead for 
Did you mean: 

Timer PWM with DMA stops too early on STM32G431

ABach.4
Associate II

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.

  • Clock SYSCLK/HCLK: 170 MHz
  • TIM1 CH4 configured
    • Prescaler: 170-1 => 1 MHZ
    • Counter Preiod: 100-1 => 10 kHz
  • DMA
    • Memory -> Peripheral
    • Data Width: Word (32 bit)
  • 6 PWM pulses (= 600 us) should be sent out

TIM1 Mode + Configuration

tim1_mode.png

tim1_conf1.png

tim1_conf2.png

DMA Settings

dma_settings.png

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

h100_dma_uint32t.png

I tried also with Data Width

Half-Word

h100_dma_uint16t.png

Byte

h100_dma_uint8t.png

Without stopping DMA, it's like that:

The last puls recurs:

h100_dma_uint32t_nostop.png

Do you know why it's like that?

Do I need to have a different callback?

Thanks for helping...

Andreas

12 REPLIES 12
waclawek.jan
Super User

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

Hello @waclawek.jan,

Thank you for the explanation.

  • HAL_TIM_PWM_Stop_DMA() disables the peripheral and threestates it, maybe with the macro __HAL_TIM_DISABLE(). I'm not sure.
HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel)
{
    // [...]

    /* Disable the Peripheral */
    __HAL_TIM_DISABLE(htim);

    // [...]
}
  • In DMA Mode "Normal" the DMA stops after the specific count. Because I didn't use 0 but 50 as the last pulse, the DMA stopped but the TIM_CH4 stays with the last pulse forever.
  • Add one zero as the last pulse solves the problem
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

h100_dma_uint16t_single.png

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.

waclawek.jan
Super User

> 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