cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F4 DMA messing with my PWM, what am I missing?

Fishmaster257
Associate III

Hello,

I am having a problem with my STM32F369l-DISCO board, as the title says it has to do with the DMA and PWM output.

So I need a 800kHz PWM signal for some SK6812 LEDs, I already did achieve this with an STM32L433CCT on a custom board using this guide: https://www.thevfdcollective.com/blog/stm32-and-sk6812-rgbw-led

I set up my Project with CubeMX, the DMA setup there looked like this:

0693W000006Ho12QAC.pngwith this I got my desired DMA/PWM-frequency when using:

 HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1,"Data","Lengt");

Now as I said I switched to a STM32F469l-DISCO board.

There the setup looked like this:

0693W000006Ho3mQAC.pngMy clocks like this:

0693W000006HoFYQA0.pngwith a Timer period of 112-1 resulting in ~800kHz

I also tried it without FIFO, but then I am not able to select memorysize BYTE. It doesn´t really matter, it made almost no difference. To get to my Problem, when trying to output my PWM signal with this code from the guide above:

#define PWM_HI (56) //50%DC
#define PWM_LO (28)	//25%DC
 
// LED parameters
#define NUM_BPP (4) // SK6812
#define NUM_PIXELS2 (8) ///number of LEDs controlled by tim2 
#define NUM_BYTES (NUM_BPP * NUM_PIXELS2) 
 
#define WR_BUF_LEN2 (NUM_BPP * 8 * 2)//Tim2
uint8_t wr_buf2[WR_BUF_LEN2] = {0};
uint_fast8_t wr_buf_p2 = 0;
 
uint8_t rgb_arr2[NUM_BYTES2]={0};///1 BYTE per colour, max  value 255. 4 BYTE= 1 LED
 
void led_render2() {
  if(wr_buf_p2 != 0 || hdma_tim2_ch1.State != HAL_DMA_STATE_READY) {
    // Ongoing transfer, cancel!
    for(uint8_t i = 0; i < WR_BUF_LEN2; ++i) wr_buf2[i] = 0;
    wr_buf_p2 = 0;
    HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
    return;
  }
  // Ooh boi the first data buffer half (and the second!)
  for(uint_fast8_t i = 0; i < 8; ++i) {
    wr_buf2[i     ] = PWM_LO << (((rgb_arr2[0] << i) & 0x80) > 0);
    wr_buf2[i +  8] = PWM_LO << (((rgb_arr2[1] << i) & 0x80) > 0);
    wr_buf2[i + 16] = PWM_LO << (((rgb_arr2[2] << i) & 0x80) > 0);
    wr_buf2[i + 24] = PWM_LO << (((rgb_arr2[3] << i) & 0x80) > 0);
    wr_buf2[i + 32] = PWM_LO << (((rgb_arr2[4] << i) & 0x80) > 0);
    wr_buf2[i + 40] = PWM_LO << (((rgb_arr2[5] << i) & 0x80) > 0);
    wr_buf2[i + 48] = PWM_LO << (((rgb_arr2[6] << i) & 0x80) > 0);
    wr_buf2[i + 56] = PWM_LO << (((rgb_arr2[7] << i) & 0x80) > 0);
  }
 
  HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)wr_buf2, WR_BUF_LEN2);
  wr_buf_p2 = 2; // Since we're ready for the next buffer
}

I get this signal on an oscilloscope:

0693W000006HoLHQA0.jpgbut when using this code to directly set dutycycle without DMA it looks different:

__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,56);
 
 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);

0693W000006HoLCQA0.jpgas you can see this results in the desired output signal. I am pretty sure this has something to either with my DMA setup or my approach on feeding the data to the DMA, yet I am not sure what the problem exactly ist. I tried it with FIFO an almost every burst/threshold combination there is yet it didn´t work. I also looked at the datasheet for the DMA without success though.

This is the code, CubeMX generates for my Timer (should be correct):

static void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 111;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &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(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

And this the init code for my DMA, which is probably not 100% fitting:

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TIM1)
  {
    //the same code but for TIM1
 }
  else if(htim_base->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */
 
  /* USER CODE END TIM2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
 
    /* TIM2 DMA Init */
    /* TIM2_CH1 Init */
    hdma_tim2_ch1.Instance = DMA1_Stream5;
    hdma_tim2_ch1.Init.Channel = DMA_CHANNEL_3;
    hdma_tim2_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim2_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // I tried manually setting this to Byte, without any change to the PWM signal though
    hdma_tim2_ch1.Init.Mode = DMA_CIRCULAR;
    hdma_tim2_ch1.Init.Priority = DMA_PRIORITY_LOW;
    hdma_tim2_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  //  hdma_tim2_ch1.Init.FIFOMode = DMA_FIFOMODE_ENABLE;  //with FIFO
   // hdma_tim2_ch1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    //hdma_tim2_ch1.Init.MemBurst = DMA_MBURST_SINGLE;
    //hdma_tim2_ch1.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_Init(&hdma_tim2_ch1) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_CC1],hdma_tim2_ch1);
 
  /* USER CODE BEGIN TIM2_MspInit 1 */
 
  /* USER CODE END TIM2_MspInit 1 */
  }
 
}

I hope I didn´t forget something important. Any hint on what I am doing wrong will help greatly, as I don´t know what else to try.

1 ACCEPTED SOLUTION

Accepted Solutions

There are several differences between the single-port DMA in 'L4 and dual-port DMA in 'F4, but what affects you here is, that the single-port DMA, when set to byte on read side and word to write side, it zero-extends (pads) the byte into word; whereas the dual-port DMA won't (in particular, it will read 4 bytes on the read side and assemble them into a single word on the write side, if the FIFO is used; it will simply ignore the memory-side data setting if FIFO is not used, using the peripheral-side setting for both sides).

So, you have to do that "manually", by declaring the source array as words

uint32_t wr_buf2[WR_BUF_LEN2] = {0};

I don't use Cube/CubeMX, so I might've overlooked something else, too.

JW

View solution in original post

2 REPLIES 2

There are several differences between the single-port DMA in 'L4 and dual-port DMA in 'F4, but what affects you here is, that the single-port DMA, when set to byte on read side and word to write side, it zero-extends (pads) the byte into word; whereas the dual-port DMA won't (in particular, it will read 4 bytes on the read side and assemble them into a single word on the write side, if the FIFO is used; it will simply ignore the memory-side data setting if FIFO is not used, using the peripheral-side setting for both sides).

So, you have to do that "manually", by declaring the source array as words

uint32_t wr_buf2[WR_BUF_LEN2] = {0};

I don't use Cube/CubeMX, so I might've overlooked something else, too.

JW

You dear sir are simply awesome!!!

That's it the whole deal, now it works again like a charm.

Have a great day !! Thank you so much I have been struggling with this DMA for four days and now it finally works :^)