cancel
Showing results for 
Search instead for 
Did you mean: 

PWM DMA stops after 2 pulses when preload is disabled, (does not when compare register preload is enabled)

Johi
Senior III

I am experimenting with PWM DMA on STM32F407VET6. The code creates pulses of increasing width.

If I enable the output compare preload, the number of pulses is 2 instead of 6.

The first pulse at startup is the default setting from the configuration in MX and not the first DMA value. This makes sense as preload of output compare means to load when timer = 0 (if I am not mistaken).

To make sure PWM values are not one off, I disabled the preload of the output compare register. In that case I get only 2 pulses and pulse generation stops? Why do I get only 2 pulses in that case?

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
	printf("HAL_TIM_PWM_PulseFinishedCallback: Transfer completed\n");
}
 
 
/........................../
Main loop extract:
 
  for (int i=0; i<6;i++)
	 pwmData[i] = 1+5*i;
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
	  uint8_t i = sizeof(pwmData)/sizeof(uint32_t);
	  printf("Sending data %d pulses\n",i);
	  HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1,(uint32_t *) pwmData, 6);
	  HAL_Delay(1000);
  }


_legacyfs_online_stmicro_images_0693W00000bida9QAA.png
_legacyfs_online_stmicro_images_0693W00000bida4QAA.pngEdit: It seems that the transfer complete interrupt is coming to early in the case without preload.

1 ACCEPTED SOLUTION

Accepted Solutions

> how to make sure the first pulse is what the first DMA word sent specifies.

Before starting DMA, load the desired value into CCRx before setting preload, or with preload already set, generate Update manually.

You can generate manually events (including Update) by setting respective bits in TIMx_EGR.

JW

View solution in original post

9 REPLIES 9
Johi
Senior III


_legacyfs_online_stmicro_images_0693W00000bidjfQAA.png 

Preload register must be used in PWM mode, then the question remains, how to make sure the first pulse is what the first DMA word sent specifies.

If you'd use higher resolution on the oscilloscope - or just slower system clock - you'd see 5 pulses within that longer one, spaced closely apart.

If you want understanding, Cube is open source and you can look into it to see what it does - or just simply don't use it and write your own code, which you have full control of.

HAL_TIM_PWM_Start_DMA() sets the DMA request (trigger) to come from the CCx event. I don't know your pulse width values, but say ARR=1000 and the array contains 100, 200, 300: so, without CCx preload, when you have a CCx event at CNT=100, output goes low but DMA loads 200 into CCx which in turn makes output go high until CNT=200 when output goes low and DMA loads 300, so CCx goes again high until CNT=300.

JW

> how to make sure the first pulse is what the first DMA word sent specifies.

Before starting DMA, load the desired value into CCRx before setting preload, or with preload already set, generate Update manually.

You can generate manually events (including Update) by setting respective bits in TIMx_EGR.

JW

Johi
Senior III

@Community member​ 

Thank you for the advice. I modified HAL_TIM_PWM_Start_DMA() (see line 51/52 below) in line with your analysis and now the proper results appear.

But now the next topic comes: How do I keep this modification because CubeMX overwrites my changes each time I change the hardware? (I can do the same adjustment in my own code, but this is much cleaner at first sight).

Second question: can these 2 lines be considered as a bug fix?

HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length)
{
  HAL_StatusTypeDef status = HAL_OK;
  uint32_t tmpsmcr;
 
  /* Check the parameters */
  assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
 
  /* Set the TIM channel state */
  if (TIM_CHANNEL_STATE_GET(htim, Channel) == HAL_TIM_CHANNEL_STATE_BUSY)
  {
    return HAL_BUSY;
  }
  else if (TIM_CHANNEL_STATE_GET(htim, Channel) == HAL_TIM_CHANNEL_STATE_READY)
  {
    if ((pData == NULL) && (Length > 0U))
    {
      return HAL_ERROR;
    }
    else
    {
      TIM_CHANNEL_STATE_SET(htim, Channel, HAL_TIM_CHANNEL_STATE_BUSY);
    }
  }
  else
  {
    return HAL_ERROR;
  }
 
  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      /* initialize register    */
      /* Set the DMA compare callbacks */
      htim->hdma[TIM_DMA_ID_CC1]->XferCpltCallback = TIM_DMADelayPulseCplt;
      htim->hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;
 
      /* Set the DMA error callback */
      htim->hdma[TIM_DMA_ID_CC1]->XferErrorCallback = TIM_DMAError ;
 
      /* Enable the DMA stream */
      if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)pData, (uint32_t)&htim->Instance->CCR1,
                           Length) != HAL_OK)
      {
        /* Return error status */
        return HAL_ERROR;
      }
 
      /* initialize CCR1 and trigger update cycle*/
	  htim->Instance->CCR1=pData[0];
      htim->Instance->EGR|=TIM_EGR_UG;
 
      /* Enable the TIM Capture/Compare 1 DMA request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC1);
      break;
    }

And third:


_legacyfs_online_stmicro_images_0693W00000bidvWQAQ.pngIf I disable output compare preload, the pulses are indeed very close to each other. I still wonder why. For sure it is not my timing setting that is at the origin of this weird result because if I enable preload, with the same settings I get the right result below.


_legacyfs_online_stmicro_images_0693W00000bidvbQAA.png(APB1 Clock is 40 MHz so Using pre-scaler the base of the timer is 2.5kHz,

The counter counts internally to 0-99 so the timer counts at 25 Hz or 40 ms period.) 

Anyway, maybe I should stick with my second post: follow the spec, and do not ask too many questions :).

The best approach is to have a thorough understanding of what's happening, and then write code accordingly, avoiding Cube/HAL which imposes what its authors deem "usual usage" on you.

Try to draw yourself a timing diagram, containing CNT, the "buffer-CCRx" (in case of CCR preload on) and "acting-CCRx", DMA's internal memory pointer, and from that the output waveform. See e.g. last post here for example - it may be not illustrative for you, you may want to draw it yourself.

JW

Johi
Senior III

@Community member​ 

Reading the datasheet, I already figured out that probably the timer seemed to start before the DMA value entered the CCRx. This seemed probable as the sheet indicated that preload was transferred to the operational register at the update event.

So tried to build a wrapper around HAL_TIM_PWM_Start_DMA and succeeded in fixing the first bug manually programming the first cycle manually and then let the DMA kick in. But then other annomalies started to show up leading again to abnormalities.

Conclusion: you are 100% right, I will have to let go of CMSIS and switch to LL. It is really pity that a high level and generic approach leads to these results in practice.

> switch to LL

Why? What do you expect from LL? It's mostly just renaming registers, so if you want to check something in the RM, you need to translate the LL names to those used in RM (and the CMSIS-mandated device header).

I see the lure of being able to click in CubeMX, but IMO it's not worth it.

JW

Johi
Senior III

@Community member​ 

>I see the lure of being able to click in CubeMX, but IMO it's not worth it.

All depends on background, but putting HAL generated code next to LL generated code and of course the RM together with some good books is a context that gives different insights to how the peripherals of the STM work. But you have a point in saying that one reads the RM and then has to find the function in the LL that implements the desired modification in the register. To be honest after 2 weeks of experimenting with LL, it could be that LL also goes out of the window and only the headers will survive...

in the first cycle the last value got lost, say we expected {1, 2, 3, 4} and we got {1, 2, 3} and now 4 is in the CCR register already, so, from the next cycle there comes {4, 1, 2, 3}. It seems that the FINISHED_IT triggerred right after the last CCR is loaded.