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

1 ACCEPTED SOLUTION

Accepted Solutions
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

View solution in original post

12 REPLIES 12
Saket_Om
ST Employee

Hello @ABach.4 

Did you check the example below please:

STM32CubeG4/Projects/NUCLEO-G431KB/Examples/TIM/TIM_DMA at master · STMicroelectronics/STM32CubeG4 · GitHub

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.
Saket_Om
ABach.4
Associate II

No. Thank you. I'll check them out.

Hello @ABach.4 

I tested the example on my side. The signal starts low before the timer initialization and goes high after the timer is initialized. If you want the last pulse to behave normally (i.e., not remain high indefinitely), you need to deinitialize the timer inside the HAL_TIM_PWM_PulseFinishedCallback() function.

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.
Saket_Om

BTW: Consider using SPI to generate data stream for WS2812B. In my opinion it's easier then with timer and i would also say more reliable.

You can simply set SPI bitrate to for example 8Mb/s (bit duration 125ns), then if you send for example pattern 0b01110000, it generates 3*125=475ns pulse. Pattern 0b01111100 creates 5x125=625ns pulse. Which are the L and H signals for WS2812. You can also change bitrate to transfer multiple "pulses" in one SPI byte, or change SPI data size and clock to precise tune pulse lengths.

ABach.4
Associate II

I looked at the example TIM_DMA

STM32CubeG4/Projects/NUCLEO-G431KB/Examples/TIM/TIM_DMA at master · STMicroelectronics/STM32CubeG4 · GitHub

Here it uses circular mode, not normal mode (and another timer, TIM3_CH3):

G431KB_dma_settings.png

And the period is computed at compile or runtime rather than using the "Device Configuration Tool":

  • Clock SYSCLK/HCLK: 150 MHz
  • TIM1 CH4 configured
    • Prescaler: 0 => 150 MHZ
    • Counter Period: 8537-1 => 17.57 kHz
TIM_HandleTypeDef htim3;
DMA_HandleTypeDef hdma_tim3_ch3;

uint32_t aCCValue_Buffer[3] = {0, 0, 0};
uint32_t uwTimerPeriod  = 0;

int main(void)
{
  HAL_Init();
  BSP_LED_Init(LED2); /* does that woek?! I haven't found BSP_LED_Init() */
  SystemClock_Config();

  uwTimerPeriod = (uint32_t)((SystemCoreClock / 17570) - 1);

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM3_Init();

  aCCValue_Buffer[0] = (uint32_t)(((uint32_t) 75 * (uwTimerPeriod - 1)) / 100);
  aCCValue_Buffer[1] = (uint32_t)(((uint32_t) 50 * (uwTimerPeriod - 1)) / 100);
  aCCValue_Buffer[2] = (uint32_t)(((uint32_t) 25 * (uwTimerPeriod - 1)) / 100);

  if (HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_3, aCCValue_Buffer, 3) != HAL_OK) {
    Error_Handler();
  }

  while (1) {
    //
  }
}

static void MX_TIM3_Init(void)
{
  // [...]
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = uwTimerPeriod; /* next time I change something in IOC, it gets overwriten, right? */
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  // [...]
}

When the PWM starts, it goes infinitely. No end.

The rest is almost the same.

 

Back to my example:

In my example above, I send 6 PWM pulses, but after the 4 pulse, the 5th pulse starts but didn't finish. There is no 6th pulse anymore. So HAL_TIM_PWM_PulseFinishedCallback() comes too early, not after the 6th pulse but at the start of the 5th pulse and stop the DMA too early.

I don't care it the timer starts LOW and ends HIGH.

Source extract:

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_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_4, (uint32_t *) data, 6);
}

h100_dma_uint32t_color.png

  • Is TIM1_CH4 not good because the period doesn't fit well?
  • Should I use a different callback that fits better to DMA?
ABach.4
Associate II

One more information:

WeActStudio_STM32G431CoreBoard.jpeg

 

One option would be that I add 2 additional pulses with 0 or 100, but this is like a hack, right?

  /* 8 rather than 6 pulses */
  HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_4, (uint32_t *) data, 8);

With 0

uint32_t data[] = {
		30,
		80,
		20,
		60,
		10,
		50,
		0,
		0
};

 h100_dma_uint32t_8pulses_0.png

With 100:

uint32_t data[] = {
		30,
		80,
		20,
		60,
		10,
		50,
		100,
		100
};

 h100_dma_uint32t_8pulses_100.png

 

I try to repeate it with 3 pulses and 2 repetitions.

Could also be an option.

You are refering to SPI Mode "Half-Duplex Master" and only use MOSI? It we use a SCK that is not used: less pins.

I'll give it a try.

I was using full duplex master mode because I'm not familiar with half-duplex mode. Both SCK and MISO pin can be used to other purposes, just don't configure them as "SPI alternate function".
Nice thing on that method is fact that you dont need to care about timer stopping. SPI can be feeded by DMA without gaps like timer. Disadvantage is higher time deviations due sparse SPI bitrates options. But i've never get problems with that even on slower MCUs like STM8, AVR...


Primary understand DMA width, why is here byte or word. Exactly require equal with peripheral data =

16bit timers = half word     32bit timers = word...

Next to understand is DMA controller ends and report complete on moment last data is proceed to peripheral = not equal timer arive this time real = timer cant stop (DMA stop do it)

And last timer mode PWM1 have own rules and exist more modes as this...

Too care require HAL use for example on other as tim peripherals can HAL delegate complete callback to real send data and wait to this after DMA complete, but TIMs not doing this fo OC.