cancel
Showing results for 
Search instead for 
Did you mean: 

HAL_ERROR calling HAL_TIM_PWM_Start_DMA to generate variable duty cycle PWM

WBach
Associate II

Hello,

i am trying to generate a fixed 800kHz PWM signal with variable duty cycle on a STM32F446RE (Nucleo-446RE). As you already might have suspected, i am intending to drive WS2812B LEDs.

For this I intend to let TIM1 run at 800kHz and have TIM_CH1 running in PWM-Mode1 outputting to CH1-Pin. Doing just this works, I get a appropriate PWM signals. But now I also have to change the duty cycle every cycle/bit.

I intend to do this with DMA. A Memory to Peripheral Request putting values from a buffer in memory into the OCR1 Register of TIM1 should alter the duty cycle. This request should be triggered by TIM1 overflowing; TIM1_UP should trigger the request.

However, testing this basic concept fails and I tried to get this working for a long time now.

To realise this concept (which is valid or isn't it?) I used the STM32CubeIDE with STM32CubeMX.

In CubeMX I configured TIM1 to be clocked at around 800kHz by setting no prescaler, a Counter Period of 225, setting Channel1 to PWM_Generation_CH1 with PWM_Mode_1.

For DMA I added a request triggered by TIM1_UP from Memory to Peripheral with a data width of half_word.

The CubeMX settings are also visible in the pictures of it:

0693W000003BfWlQAK.png

0693W000003BfVaQAK.png

To now enable DMA actually putting values from a buffer in the CCR1 register to alter the duty cycle, I looked at the HAL Documentation and tried using the function:

HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA (TIM_HandleTypeDef * htim, uint32_t Channel, uint32_t * pData, uint16_t Length)

, since its description "Starts the TIM PWM signal generation in DMA mode." and its arguments seem to be just what I'm looking for.

However, when actually executing it by calling

HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, data, 1);

it returns HAL_ERROR and well, the CCR1 value doesn't get updated.

I tried to troubleshoot it but kind of reached an impass.

I traced the origin of the return value HAL_ERROR:

HAL_TIM_PWM_Start_DMA is calling HAL_DMA_Start_IT.

If HAL_DMA_Start_IT doesn't return HAL_OK, HAL_TIM_PWM_Start_DMA returns HAL_ERROR.

In my case, HAL_DMA_Start_IT returns HAL_BUSY. This is due to its hdma->State not being HAL_DMA_STATE_READY.

I checked and instead of that, hdma->State is HAL_DMA_STATE_RESET.

So, htim1.hdma[1].State is HAL_DMA_STATE_RESET, when calling HAL_TIM_PWM_Start_DMA; In HAL_TIM_PWM_Start_DMA htim1.hdma[1] is passed to HAL_DMA_Start_IT as the argument hdma.

The described tracing above is also visible through the source of these functions and the attached image of the debugged TypeDef values:

HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length)
{
  uint32_t tmpsmcr;
 
  /* Check the parameters */
  assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
 
  if (htim->State == HAL_TIM_STATE_BUSY)
  {
    return HAL_BUSY;
  }
  else if (htim->State == HAL_TIM_STATE_READY)
  {
    if ((pData == NULL) && (Length > 0U))
    {
      return HAL_ERROR;
    }
    else
    {
      htim->State = HAL_TIM_STATE_BUSY;
    }
  }
  else
  {
    /* nothing to do */
  }
 
  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      /* 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 HAL_ERROR;
      }
 
      /* Enable the TIM Capture/Compare 1 DMA request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC1);
      break;
    }
 
    case TIM_CHANNEL_2:
    {
      /* Set the DMA compare callbacks */
      htim->hdma[TIM_DMA_ID_CC2]->XferCpltCallback = TIM_DMADelayPulseCplt;
      htim->hdma[TIM_DMA_ID_CC2]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;
 
      /* Set the DMA error callback */
      htim->hdma[TIM_DMA_ID_CC2]->XferErrorCallback = TIM_DMAError ;
 
      /* Enable the DMA stream */
      if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC2], (uint32_t)pData, (uint32_t)&htim->Instance->CCR2, Length) != HAL_OK)
      {
        return HAL_ERROR;
      }
      /* Enable the TIM Capture/Compare 2 DMA request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC2);
      break;
    }
 
    case TIM_CHANNEL_3:
    {
      /* Set the DMA compare callbacks */
      htim->hdma[TIM_DMA_ID_CC3]->XferCpltCallback = TIM_DMADelayPulseCplt;
      htim->hdma[TIM_DMA_ID_CC3]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;
 
      /* Set the DMA error callback */
      htim->hdma[TIM_DMA_ID_CC3]->XferErrorCallback = TIM_DMAError ;
 
      /* Enable the DMA stream */
      if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC3], (uint32_t)pData, (uint32_t)&htim->Instance->CCR3, Length) != HAL_OK)
      {
        return HAL_ERROR;
      }
      /* Enable the TIM Output Capture/Compare 3 request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC3);
      break;
    }
 
    case TIM_CHANNEL_4:
    {
      /* Set the DMA compare callbacks */
      htim->hdma[TIM_DMA_ID_CC4]->XferCpltCallback = TIM_DMADelayPulseCplt;
      htim->hdma[TIM_DMA_ID_CC4]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;
 
      /* Set the DMA error callback */
      htim->hdma[TIM_DMA_ID_CC4]->XferErrorCallback = TIM_DMAError ;
 
      /* Enable the DMA stream */
      if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC4], (uint32_t)pData, (uint32_t)&htim->Instance->CCR4, Length) != HAL_OK)
      {
        return HAL_ERROR;
      }
      /* Enable the TIM Capture/Compare 4 DMA request */
      __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC4);
      break;
    }
 
    default:
      break;
  }
 
  /* Enable the Capture compare channel */
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
 
  if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)
  {
    /* Enable the main output */
    __HAL_TIM_MOE_ENABLE(htim);
  }
 
  /* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
  tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
  if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
  {
    __HAL_TIM_ENABLE(htim);
  }
 
  /* Return function status */
  return HAL_OK;
}
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  HAL_StatusTypeDef status = HAL_OK;
 
  /* calculate DMA base and stream number */
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  
  /* Check the parameters */
  assert_param(IS_DMA_BUFFER_SIZE(DataLength));
 
  /* Process locked */
  __HAL_LOCK(hdma);
  
  if(HAL_DMA_STATE_READY == hdma->State)
  {
    /* Change DMA peripheral state */
    hdma->State = HAL_DMA_STATE_BUSY;
    
    /* Initialize the error code */
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    
    /* Configure the source, destination address and the data length */
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
    
    /* Clear all interrupt flags at correct offset within the register */
    regs->IFCR = 0x3FU << hdma->StreamIndex;
    
    /* Enable Common interrupts*/
    hdma->Instance->CR  |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;
    
    if(hdma->XferHalfCpltCallback != NULL)
    {
      hdma->Instance->CR  |= DMA_IT_HT;
    }
    
    /* Enable the Peripheral */
    __HAL_DMA_ENABLE(hdma);
  }
  else
  {
    /* Process unlocked */
    __HAL_UNLOCK(hdma);	  
    
    /* Return error status */
    status = HAL_BUSY;
  }
  
  return status;
}

0693W000003BfWgQAK.png

I tried to figure out, why htim1.hdma[1].State is stuck on HAL_DMA_STATE_RESET and what might be wrong or missing, tried also different functions like HAL_TIM_OC_Start_DMA or HAL_TIM_DMABurst_WriteStart.

HAL_TIM_DMABurst_WriteStart actually transferred a value from the buffer to the OCR1 register but only once, since it apparently requires HAL_TIM_DMABurst_WriteStop to be called after each / before the next transaction, which makes the usage of DMA in this case kind of pointless, since it would be less processing effort to just set the values of OCR1 with interrupts at 800kHz.

I also tried to call HAL_TIM_Base_Start_DMA, or HAL_TIM_Base_Start, or HAL_TIM_PWM_Start, or a combination of those before calling HAL_TIM_PWM_Start_DMA, but that didn't help.

I would like to include my main.c but then the post would be to long... The whole project is attached as a zip file.

I tried my best to describe the problem and display my troubleshooting information, i hope it is understandable.

Do you know what I am missing or doing wrong?

18 REPLIES 18
TDK
Guru

HAL_DMA_STATE_RESET indicates the DMA has not been initialized.

There should be a call to HAL_DMA_Init within HAL_TIM_Base_MspInit which will initialize the DMA. CubeMX should generate this code for you within "stm32f4xx_hal_msp.c".

If you feel a post has answered your question, please click "Accept as Solution".
TDK
Guru

I think the underlying issue is you have DMA set up for the update event, not for the CH1. When you add a DMA request, select TIMx_CH1 instead of TIMx_UP.

If you feel a post has answered your question, please click "Accept as Solution".
WBach
Associate II

Thank You for your quick reply.

I checked and there is indeed a call to HAL_DMA_Init within HAL_TIM_Base_MspInit, which also gets called on startup.

However there is one thing that seems wrong/weird to me.

When looking at the source of HAL_TIM_Base_MspInit, more specifically the call to HAL_DMA_Init, the argument being passed to HAL_DMA_Init startles me:

if (HAL_DMA_Init(&hdma_tim1_up) != HAL_OK)
    {
      Error_Handler();
    }

The argument hdma_tim1_up is not the same as htim1.hdma[1], which causes the problem.

These are the TypeDef values of hdma_tim1_up and htim1.hdma[1]:

0693W000003Bfc5QAC.png

From looking at the Instance value and the StreamBaseAdress I would assume they are not the same or am I wrong?

From looking at the debugged TypeDef values, hdma_tim1_up.State seems so be HAL_DMA_STATE_READY, while htim1.hdma[1].State is HAL_DMA_STATE_RESET.

Do You understand this?

EDIT: Not asking whether You can comprehend what I'm writing but what's the problem here.

WBach
Associate II

Thank you for the reply.

This looks quite promising, I am currently testing it and it works so far.

However, it confuses me, since the DMA Request/Transfer should occur, when TIM1 overflows (hence TIM1_UP), and not when CH1 matches.

I will test which is the case but the name of the request "CH1" hints to be when CH1 matches, which would be wrong for my principle.

I will test, which it is or alternatively, can you tell me/do you know?

Ok, it seems to be triggered when CH1 matches... But I need it to trigger when TIM1 overflows. How would that be achieved?

TDK
Guru

> However, i still would ask, what is the sense behind that naming scheme then?

I believe the "CH1" name indicates the DMA is going to service CH1 in some manner, either transferring data from or (in this case) to the CCR1 register. Whereas update just means it's triggered by the update, but not necessarily doing anything in particular.

You can have multiple DMA streams each serving a different channel on the same timer, so it would be confusing if they were not separated by name somehow.

If you feel a post has answered your question, please click "Accept as Solution".
WBach
Associate II

Sorry for editing my message without leaving the old part.

For record: Originally I wrote that it triggered on TIM1 overflowing and that my issue would be solved but I only thought so because I made a mistake checking that, in actuality it triggers on CH1 matching.

But I need it to trigger when TIM1 overflows. How would that be achieved? When selecting TIM1_UP I get the aforementioned issues.

TDK
Guru

This suggests you're doing the right calls:

https://github.com/STMicroelectronics/STM32CubeF4/blob/b5abca20c9676b04f8d2885a668a9b653ee65705/Projects/STM32F412G-Discovery/Examples/TIM/TIM_DMA/Src/main.c

Or at least I can't spot the difference. It'd be odd for their example project not to work, although I didn't try it.

The CCR1 register can be preloaded, so it may not matter precisely when it's updated as the actual update will occur at... update.

If you feel a post has answered your question, please click "Accept as Solution".
prain
Senior III

Does application enter DMA transfer complete​ ISR? Usually DMA HAL libraries go from busy state to ready in corresponding ISR. Put a break point in DMA ISR to check.