HAL_ERROR calling HAL_TIM_PWM_Start_DMA to generate variable duty cycle PWM
- July 29, 2020
- 18 replies
- 13924 views
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:


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;
}
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?
