2020-07-15 12:17 AM
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 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.
2020-07-15 05:12 AM
Note, that you'll have a low-frequency content in the output,.
JW
2020-07-16 01:57 AM
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.
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 */
}
}
2020-07-16 03:29 AM
Please correct me if my reading is incorrect.
2020-07-16 04:17 AM
> 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
2020-07-17 01:24 AM
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.