cancel
Showing results for 
Search instead for 
Did you mean: 

PWM DMA to drive WS281x LED generates inconsistent period and waveform

nico23
Senior III

I'm trying to drive the PWM of an STM32G0 but the waveform generated had an inconsistent period between bit 0 and bit 1, and I can't understand why

nico23_0-1757060068608.png

I'm using TIMER 4 on Channel 2. On the main.c I'm initializing MX_DMA_Init

void MX_DMA_Init(void)
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA1_Channel2_3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}

and TIM4 configured as 

void MX_TIM4_Init(void)
{
  /* USER CODE BEGIN TIM4_Init 0 */
  HAL_TIM_Base_DeInit(&htim4);
  /* USER CODE END TIM4_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 60 - 1;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* USER CODE BEGIN TIM4_Init 2 */

  /* USER CODE END TIM4_Init 2 */
  HAL_TIM_MspPostInit(&htim4);
}

The HAL_TIM_MspPostInit has 

__HAL_RCC_GPIOD_CLK_ENABLE();
    /**TIM4 GPIO Configuration
    PD13     ------> TIM4_CH2
    PD14     ------> TIM4_CH3
    PD15     ------> TIM4_CH4
    */
    GPIO_InitStruct.Pin = OUT_uC3_Pin|OUT_uC2_Pin|OUT_uC1_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM4;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

with 

#define OUT_uC3_Pin                     GPIO_PIN_13
#define OUT_uC3_GPIO_Port               GPIOD

The led_render function correctly fills the buffer with

    // Fill with alternating pattern: HI, LO, HI, LO...
    for(uint8_t i = 0; i < WR_BUF_LEN; i++) {
        wr_buf[i] = (i % 2) ? PWM_HI : PWM_LO;
    }
    
    HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_2, (uint32_t *) wr_buf, WR_BUF_LEN);

and in the .h

#define PWM_HI (38)
#define PWM_LO (19)
// LED parameters
#define NUM_BPP (3) // WS2812B
#define NUM_PIXELS (18)
#define NUM_BYTES (NUM_BPP * NUM_PIXELS)

But for some reason I'm seeing a the strange, not correct, waveform

Am I doing something wrong?

17 REPLIES 17
waclawek.jan
Super User

What is the expected waveform and how is the observed one different?

[EDIT] Assuming timer clock is equal to system clock, 60 cycles may be sufficient for DMA, but AFAIK Cube/HAL does not use the Update event to load TIMx_CCRx but the CC event, which may leave much less time, and result in DMA not feeding the TIMx_CCRx fast enough. I don't say this *is* the problem here (as I don't know what are the symptoms, see my first question), but may be a thing to consider. Try longer times, or higher system clock frequency. 

JW

gbm
Principal

What is the declaration of wr_buf and where is it placed?

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Saket_Om
ST Employee

Hello @nico23 

Please refer to the example TIM_PWMOutput in the STM32CubeG0 package.

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

Hi @waclawek.jan and thanks for answering.

Expected waveform has a bunch of 1 and 0 encoding the LEDs colours for istance

nico23_0-1757076753094.png

Instead the one I previously upload has only zeros. 

System clock is 48Mhz (HAL_RCC_GetSysClockFreq and HAL_RCC_GetPCLK1Freq both return 48M) so the period for each beat should be around 1,25us

For a '1' bit (PWM_HI = 38):

  • High time: ~0.79μs (38/60 × 1.25μs)
  • Low time: ~0.46μs (22/60 × 1.25μs)
  • Total period: 1.25μs

For a '0' bit (PWM_LO = 19):

  • High time: ~0.40μs (19/60 × 1.25μs)
  • Low time: ~0.85μs (41/60 × 1.25μs)
  • Total period: 1.25μs

but instead I'm seeing different periods. Below a working PWM generated by DMA

nico23_1-1757076999520.png

WHat do you mean with Try longer times, or higher system clock frequency. ?

Hi @gbm and thanks for the answer

wr_buff is defined on top of adressable_LED.c (same file where led_render function is in)

// LED write buffer
#define WR_BUF_LEN (NUM_BPP * 8 * 2)
uint8_t wr_buf[WR_BUF_LEN] = { 0 };
uint_fast8_t wr_buf_p = 0;

 

gbm
Principal

Timer DMA expects 32-bit samples. You are preparing 8-bit duty values, then writing them as 32-bit words - that doesn't make too much sense - 4 values are written as single 32-bit duty value.

Sidenote: it is much easier and more memory-efficient to use UART or SPI instead of a timer for controlling WS2812-alikes.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

I'm using this method/code because it was already implemented into an STM32F091 and it worked pretty well. I'm using 8-bit as the value is always < 256 so, I would save space on storing the buffer and the value copied should be teh same, right?

I've basically ported it to the STM32G0 but I'm stuck with this issue.

About your sidenote, I didn't know about that. I'll check if PD15 could be set to use UART or SPI instead of a timer (but I think I'll have to re-write a great part of the code)

Hi @Saket_Om 

the example doesn't uses DMA. Do you suggest to avoid using it? Also, I need to drive the PWM using the buffer

@nico23 

Below is an example with DMA:

STM32CubeG0/Projects/NUCLEO-G070RB/Examples/TIM/TIM_DMA at master · STMicroelectronics/STM32CubeG0 · 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