cancel
Showing results for 
Search instead for 
Did you mean: 

Implement PWM dithering 1 bit

sam_cheng
Associate II

Hi

I am working on stm32f070. The STM32F0 system clock = 48MHz,

I need to set the PWM frequency on 23437Hz.

Therefore, PWM resolution = 48MHz / 23437Hz = 2048 (11bit)

How to use PWM dithering technique to reach 12-bit resolution on PWM frequency 23.4KHz?

Preliminary thoughts PWMResolution + PWMDither_Resolution = 23.4kHz(11-bit resolution) + 1bits dithering resolution could be implemented to reach 12-bit resolution.

I have referred the example code X-CUBE-PWM-DITHR https://www.st.com/en/embedded-software/x-cube-pwm-dithr.html#overview

and document https://www.st.com/resource/en/application_note/dm00119042-pwm-resolution-enhancement-through-a-dithering-technique-for-stm32-advancedconfiguration-generalpurpose-and-lite-timers-stmicroelectronics.pdf

and this question https://community.st.com/s/question/0D50X0000BvhhhASQQ/bug-in-xcubepwmdithr?t=1594794784965

seems did not talk adjust duty cycle detail.

My question:

1.I need to enable two PWM channel dithering on TIM3 and TIM15. Can PWM Dithering be used on two TIM3/TIM15 on STM32F0 at the same time?

2. How to adjust the duty cycle in PWM Dithering?

3.How can I add only one extra bit of resolution using the dithering technique?

Can someone help me? Thanks in advance.

5 REPLIES 5
  1. Yes. You need separate DMA channels for both, but you can have them in 'F070.
  2. You recalculate the table of compare values, which is transferred using DMA into the TIMx_CCRx
  3. In the same way as you add any bits. Here, the table is only 2 values long, which are transferred alternately using DMA into TIMx_CCRx.

Note, that you'll have a low-frequency content in the output,.

JW

Hi @Community member​ ,

Thanks for your reply.

I tried to modify the example code X-CUBE-PWM-DITHR, I do not understand why aDitherTable recalculate the table of values max is 2047.

Please look the image of the oscilloscope on duty cycle 2047.

0693W000001t0NjQAI.jpg

AN4507 Page 6 say

PWM-Effective_Resolution = PWM-Resolution + PWM-Dither_Resolution =11+1=12 bits=4095.

Therefore, from my understanding, duty cycle max should 4095.

bellow are the parts of the example code which were modified

/* USER CODE BEGIN PD */

#define DITHER_TABLE_FIRST_PART     ((uint32_t*) aDitherTable)

#define DITHER_TABLE_SECOND_PART    ((uint32_t*) aDitherTable+2)

#define TIMER_PERIOD ((uint32_t)2047)

/* USER CODE BEGIN PV */

uint32_t aDitherTable[4];

uint32_t DitherIndex = 0;

uint32_t DCycleIndex = 0;

uint32_t UpCounting = 1;

int main(void)

{

 /* USER CODE BEGIN 1 */

 /* USER CODE END 1 */

 /* MCU Configuration--------------------------------------------------------*/

 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

 HAL_Init();

 /* USER CODE BEGIN Init */

 /* USER CODE END Init */

 /* Configure the system clock */

 SystemClock_Config();

 /* USER CODE BEGIN SysInit */

 /* USER CODE END SysInit */

 /* Initialize all configured peripherals */

 MX_GPIO_Init();

 MX_DMA_Init();

 MX_TIM1_Init();

 /* USER CODE BEGIN 2 */

  

 /* Initialize the first part of the DitherTable with the needed duty cycle and dithering value */

 UpdateDitherTable(DITHER_TABLE_FIRST_PART, 0, 0);

  

 /* Configure the TIM CCR1 update in DMA mode */

 TIM1_PWM_UPDATE_DMA(&htim1, (uint32_t*) aDitherTable, 2);

  

 /* Start the TIM1 base */

 HAL_TIM_Base_Start(&htim1);

  /* Starts the PWM1 signal generation */

 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

 /* USER CODE END 2 */

 /* Infinite loop */

 /* USER CODE BEGIN WHILE */

 while (1)

 {

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

 }

 /* USER CODE END 3 */

}

/* USER CODE BEGIN 4 */

/**

 * @brief Update Dither Table

 * @param None

 * @retval None

 */

void UpdateDitherTable(uint32_t *pDitherTable, uint32_t DutyCycle, uint32_t DitherV)

{

 uint32_t table_index;

 if (DitherV > 1

 {

  DitherV = 1;

 }

 for (table_index = 0; table_index <= 1; table_index++)

 {

  if(table_index < DitherV)

  {

   pDitherTable[table_index] = DutyCycle + 1;

  }

  else

  {

   pDitherTable[table_index] = DutyCycle;

  }

 }

}

/**

 * @brief Update Indexes 

 * @param None

 * @retval None

 */

void UpdateIndex(void)

{

 if(DitherIndex < 1)

 {

  DitherIndex++;

 }

 else

 {

  DitherIndex = 0;

   

  if(UpCounting == 1)

  {  

   if(DCycleIndex < TIMER_PERIOD)

   {

    DCycleIndex++;

   }

   else

   {

    UpCounting = 0;

   }

  }

  else

  {

   if(DCycleIndex > 0)

   {

    DCycleIndex--;

   }

   else

   {

    UpCounting = 1;

   }

  }

 }

}

/**

 * @brief Configure the TIM CCR1 update in DMA mode.

 * @param htim : TIM handle

 * @param pData: The source Buffer address.

 * @param Length: The length of data to be transferred from memory to peripheral.

 * @retval HAL status

*/

HAL_StatusTypeDef TIM1_PWM_UPDATE_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length)

{

 /* Set the DMA Period elapsed callback */

 htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = TIM1_DMAPeriodElapsedCplt;

 /* Set the DMA half period transfer callback */

 htim->hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = HalfTransferComplete;

   

 /* Set the DMA error callback */

 htim->hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback = HAL_TIM_DMAError;

  

 /* Enable the DMA channel */

 HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)pData, (uint32_t)&htim->Instance->CCR1, Length);

  

 /* Enable the TIM Update DMA request */

 __HAL_TIM_ENABLE_DMA(htim, TIM_DMA_UPDATE);

 /* Return function status */

 return HAL_OK;

}

/**

 * @brief TIM DMA Period Elapse complete callback. 

 * @param hdma : pointer to DMA handle.

 * @retval None

 */

void TIM1_DMAPeriodElapsedCplt(DMA_HandleTypeDef *hdma)

{

 TIM_HandleTypeDef* htim = ( TIM_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;

  

 htim->State= HAL_TIM_STATE_READY;

  

 HAL_TIM_PeriodElapsedCallback(htim);

}

/**

 * @brief Period elapsed callback in non blocking mode 

 * @param TimHandle : TIM handle

 * @retval None

 */

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *TimHandle)

{

  /* Update the Dither generation table */

  if(UpCounting == 1)

  {

   UpdateDitherTable(DITHER_TABLE_SECOND_PART, DCycleIndex, DitherIndex);

  }

  else

  {

   UpdateDitherTable(DITHER_TABLE_SECOND_PART, DCycleIndex, 0);

  }

  UpdateIndex();

}

/**

 * @brief DMA half conversion complete callback

 * @note  This function is executed when the transfer half complete interrupt 

 *     is generated

 * @retval None

 */

void HalfTransferComplete(DMA_HandleTypeDef *DmaHandle)

{

 /* Update the Dither generation table */

  if(UpCounting == 1)

  {

    UpdateDitherTable(DITHER_TABLE_FIRST_PART, DCycleIndex, DitherIndex);

  }

  else

  {

    UpdateDitherTable(DITHER_TABLE_FIRST_PART, DCycleIndex, 0);

  }

  UpdateIndex();

}

/* USER CODE END 4 */

-------------------------------------------

TIM.c

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* tim_pwmHandle)

{

 if(tim_pwmHandle->Instance==TIM1)

 {

 /* USER CODE BEGIN TIM1_MspInit 0 */

 /* USER CODE END TIM1_MspInit 0 */

  /* TIM1 clock enable */

  __HAL_RCC_TIM1_CLK_ENABLE();

  

  /* TIM1 DMA Init */

  /* TIM1_CH3_UP Init */

  hdma_tim1_ch3_up.Instance = DMA1_Channel5;

  hdma_tim1_ch3_up.Init.Direction = DMA_MEMORY_TO_PERIPH;

  hdma_tim1_ch3_up.Init.PeriphInc = DMA_PINC_DISABLE;

  hdma_tim1_ch3_up.Init.MemInc = DMA_MINC_ENABLE;

  hdma_tim1_ch3_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

  hdma_tim1_ch3_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

  hdma_tim1_ch3_up.Init.Mode = DMA_CIRCULAR;

  hdma_tim1_ch3_up.Init.Priority = DMA_PRIORITY_LOW;

  if (HAL_DMA_Init(&hdma_tim1_ch3_up) != HAL_OK)

  {

   Error_Handler();

  }

  /* Several peripheral DMA handle pointers point to the same DMA handle.

   Be aware that there is only one channel to perform all the requested DMAs. */

  //__HAL_LINKDMA(tim_pwmHandle,hdma[TIM_DMA_ID_CC3],hdma_tim1_ch3_up);

  __HAL_LINKDMA(tim_pwmHandle,hdma[TIM_DMA_ID_UPDATE],hdma_tim1_ch3_up);

 /* USER CODE BEGIN TIM1_MspInit 1 */

 /* USER CODE END TIM1_MspInit 1 */

 }

}

Please correct me if my reading is incorrect.

> why aDitherTable recalculate the table of values max is 2047

Because the table contains values to be loaded into TIMx_CCRx, so they can't be higher than TIMx_ARR.

Maybe you should reconsider why would you need to use dithering at all, simply using TIMx_ARR=4095 and coping with the resulting lower PWM frequency.

JW

Because we can hear audible noise from the LED driver PWM.

Normal human ears can hear sounds between 20Hz and 20KHz.

Using TIMx_CCRx=TIMx_ARR=4095, PWM frequency = 48MHz / 4095 = 11.72KHz. Also, we can hear 11KHz noise from the LED driver PWM.

Therefore, we set the PWM frequency on 23437Hz and hope the resolution keep 12 bits.

PWM resolution = 48MHz / 23437Hz = 2048 (11bit),

Preliminary thoughts PWM_Resolution + PWM_Dither_Resolution = 11bits(23.4kHz) + 1bits dithering resolution could be implemented to reach 12-bit resolution.