cancel
Showing results for 
Search instead for 
Did you mean: 

Generation of PWM wave with DMA

krunal2411
Associate II

Hello all,

Good day!

I am working on a project to drive WS2812B RGB leds using STM32F030R8.

These leds needs precisely timed signals of 800KHz.

I am using a timer in PWM generation mode with DMA.

These leds require each bit of data to be 1.25us long.

When we have to send '1' then the High time needs to be (2/3)rd of 1.25us, and for sending '0' high time needs to be (1/3)rd of 1.25us.

I am storing data for LED in LEDBuffer array and pass it to DMA to send in coordination with timer. But it is not generating the waves as per requirements.

In order to verify my code, I tried sending only 1 byte from LEDBuffer, and it works as expected, but the same doesn't work for more than 1 byte.

I have attached my project and CubeMX file.

I will be really very grateful if someone can help me out of this.

Thanking you in anticipation.

5 REPLIES 5
T J
Lead

that's work

first error message is that you are not using the correct firmware version...

so not easy to check your stuff.

I dont have hours to put to it.

basically you would use the Timer overflow to trigger the dma to push the new PWM value into the timer

have done this with Clive's help, you could offer him some beer and pizza money or if you want a super job some proper funds.

krunal2411
Associate II

Hi T J,

Thanks for reply.

I appreciate your time to look into it.

I am using CubeMX version 5.1 and Cube version FW_F0 V1.9.0.

I checked "Add necessary files as a reference in the toolchain project" while creating the attachment with CubeMX and I guess it may not compile as it does not have all the HAL libraries. Generally, I have a practice to attach all the code on forum to make it easier for others to understand, what I am trying to achieve.

I surely do not want community members to do my work, but rather just to guide me where I have gone wrong.

Please pardon if I failed to follow community rules, I am a newbie to STM, but good at Embedded programming.

To make it simple, following is my initialization:

// For Timer in PWM mode:
static void MX_TIM3_Init(void)
{
 /* USER CODE BEGIN TIM3_Init 0 */
 
 /* USER CODE END TIM3_Init 0 */
 
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_OC_InitTypeDef sConfigOC = {0};
 
 /* USER CODE BEGIN TIM3_Init 1 */
 
 /* USER CODE END TIM3_Init 1 */
 
 htim3.Instance = TIM3;
 htim3.Init.Prescaler = 0;
 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim3.Init.Period = 59;
 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
 if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
 {
  Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &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(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
  Error_Handler();
 }
 /* USER CODE BEGIN TIM3_Init 2 */
 /* USER CODE END TIM3_Init 2 */
 HAL_TIM_MspPostInit(&htim3);
}

//DMA Initialization:
static void MX_DMA_Init(void) 
{
 /* DMA controller clock enable */
 __HAL_RCC_DMA1_CLK_ENABLE();
 /* DMA interrupt init */
 
 /* DMA1_Channel4_5_IRQn interrupt configuration */
 HAL_NVIC_SetPriority(DMA1_Channel4_5_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel4_5_IRQn);
}
 
 
void DMA1_Channel4_IRQHandler(void) 
{
	HAL_DMA_IRQHandler(htim3.hdma[TIM_DMA_ID_CC1]);
}

After filling data to the buffer I just start the DMA transfer using following command.

if(HAL_OK != HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t *)LEDbuffer, LED_BUFFER_SIZE))

I have configured, DMA in circular mode, Memory to Peripheral with word as the data width.

I understand that I need to use Timer Overflow to trigger dma push.

But I am not able to figure out which command should I use to do this.

> I understand that I need to use Timer Overflow to trigger dma push.

> But I am not able to figure out which command should I use to do this.

Timer overflow generates a so called Update event in the timer. You use that event as DMA trigger by setting TIMx_DIER.UDE bit. When the Update event occurs, timer sets TIMx_SR.UIF; if TIMx_DIER.UIE is set, and interrupt is invoked; if TIMx_DIER.UDE is set, a DMA transfer is triggered.

But, you appear to have used the Channel 1 Capture-Compare event for the DMA trigger, rather than the Update event; in that case TIMx_DIER.CC1DE is the bit enabling the DMA trigger.

I don't speak the Cube/HAL gobbledygook. You can use the above information to check in debugger, whether everything is set properly.

> void DMA1_Channel4_IRQHandler(void)

What is this? If you want to write an interrupt handler, you must make sure its name matches that in the vector table, which is in the startup code. There's a DMA1_Channel4_5_IRQHandler() function in your stm32f0xx_it.c, so I guess that's the real ISR.

JW

T J
Lead

lets see if this answer appears in the thread...

sick of answering and it doesn't make it to the forum...

thanks @waclawek.jan JW, you have the patience of a rock...

this work is still in progress, but it runs...

I get 6 different PWM duty cycles in a row then repeats

the frequency remains stable.

I use the cube but I still have to run this code :

   __HAL_DMA_DISABLE(&hdma_tim2_up);

   /* TIM2 DMA Init */

   /* TIM2_UP Init */

   hdma_tim2_up.Instance = DMA1_Channel2;

   hdma_tim2_up.Init.Request = DMA_REQUEST_4;

   hdma_tim2_up.Init.Direction = DMA_MEMORY_TO_PERIPH;

   hdma_tim2_up.Init.PeriphInc = DMA_PINC_DISABLE;

   hdma_tim2_up.Init.MemInc = DMA_MINC_ENABLE;

   hdma_tim2_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;

   hdma_tim2_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

   hdma_tim2_up.Init.Mode = DMA_NORMAL;   //DMA_CIRCULAR;                     //

   hdma_tim2_up.Init.Priority = DMA_PRIORITY_LOW;

   if (HAL_DMA_Init(&hdma_tim2_up) != HAL_OK)

   {

       Error_Handler();

   }

setPWMTimers();

   SET_BIT(htim2.Instance->DIER, TIM_DIER_UDE); 

   htim2.Instance->CCR1 = 0;    // Pwm to drive controller 

void setPWMTimers(void)

{

  int MinimumPeriod = (500 / 6 / periodSegments) * 6 * periodSegments;                

   MinimumPeriod &= 0xffff;

   htim16.Instance->CCR1 = MinimumPeriod * 3 / 6;      //Phase 1

   htim16.Instance->ARR = MinimumPeriod * 6 / 6;       // 16bit only

   htim15.Instance->CCR2 = MinimumPeriod * 2 / 6;     //Phase 2

   htim15.Instance->ARR = MinimumPeriod * 5 / 6;

   htim1.Instance->CCR4 = MinimumPeriod * 2 / 6;      //Phase 3

   htim1.Instance->CCR3 = MinimumPeriod * 1 / 6;

   htim1.Instance->ARR = MinimumPeriod * 4 / 6;

   htim2.Instance->ARR = (MinimumPeriod ) / periodSegments;           // Pwm to drive controller

   htim2.Instance->CCR1 = (MinimumPeriod) / periodSegments / 8;     

   htim2.Instance->CCR2 = (MinimumPeriod) / periodSegments / 8;    

   // Dir

  // Brake

   uint32_t period =   htim2.Instance->ARR;

   for (int i = 0; i < 256; i++)

       percentPWM[i] = (period * i) >> 8;   // linear slider

   int sourceCounter = 0;

   for (int j = 0; j < periodSegments; j++)

       PWMCycle[j] = 0;

   PWMCycle[0] = period;

   PWMCycle[1] = period*.9;

   PWMCycle[2] = period*.7;

   PWMCycle[3] = period*.5;

   PWMCycle[4] = period*.3;

   PWMCycle[5] = period*.1;

   HAL_StatusTypeDef DMAStatus = HAL_DMA_Start(&hdma_tim2_up, &PWMCycle, &htim2.Instance->CCR1, periodSegments);

   if(DMAStatus != HAL_OK)

   {

       // Transfer error in reception process

       //_Error_Handler(__FILE__, __LINE__);

       setCursor1(1, 160);

       sprintf(string, "HAL_DMA_Start Failed\n");

       puts1(string);       

   }

   else

   {

       setCursor1(1, 160);

       sprintf(string, "HAL_DMA_Start OK!\n");

       puts1(string);

   }

  start_Timer2();                                //   dma1c2.ccr enable

}

krunal2411
Associate II

Hi,

Thanks a lot @waclawek.jan JW and @Community member​ J for your suggestions.

Finally I got it working.

The problem was with defining the data type.

I had initialized DMA for memory to peripheral type with word as the data width for both.

But I initialized by LED_Buffer as uint8_t.

As i said, it was working good for transferring 1 byte but not for more than 1 byte.

Thus I tried changing the datatype of LED_Buffer to uint32_t and it worked as expected.

Thanks a lot to both of you for taking time to look into my problem.

Have a good day!