cancel
Showing results for 
Search instead for 
Did you mean: 

Changing PWM parameters in the middle of operation causes incorrect timing behavior?

IJoe.1
Associate II

Good day everybody,

In my current application (IR signal generation) I need to change the PWM width for every bit that I want to send. In my implementation (seen below) I change the ARR and CCR1 values for the durations that I need, and then I generate an update event with TIM16->EGR |= TIM_EGR_UG; to reset the timer, and then I start it in PWM mode with the HAL_TIM_PWM_Start() function.

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM16)
	{	
		toggleflag = 1;
	}
}
 
void Send_PowerNEC(uint32_t code)
{
		// 1. header for the IR: 4.5ms chirp, 4.5ms 0
		TIM16->CCR1 = 36000;
		TIM16->ARR = 54000;
		TIM16->CNT = 0;
		TIM16->EGR |= TIM_EGR_UG;
		__HAL_TIM_ENABLE_IT(&htim16, TIM_IT_UPDATE);
		HAL_TIM_PWM_Start(&htim16, TIM_CHANNEL_1);
		toggleflag = 0;
		while(toggleflag == 0);
		toggleflag = 0;
		HAL_TIM_PWM_Stop(&htim16, TIM_CHANNEL_1);
		TIM16->EGR  |= TIM_EGR_UG;
	
		// 2. sending the string 
		uint32_t tosend = 0x0045FF00;
		for(int i=0; i<32; i++)
		{
			if(tosend & 1)
			{
					TIM16->CCR1 = 2240;
					TIM16->ARR = 9000-1;
					TIM16->CNT = 0;
					__HAL_TIM_ENABLE_IT(&htim16, TIM_IT_UPDATE);
					TIM16->EGR  |= TIM_EGR_UG;
					HAL_TIM_PWM_Start(&htim16, TIM_CHANNEL_1);
					toggleflag = 0;
					while(toggleflag == 0);
					toggleflag = 0;
			}
			else
			{
					TIM16->CCR1 = 2240; 
					TIM16->ARR = 4500-1;
					TIM16->CNT = 0;
					__HAL_TIM_ENABLE_IT(&htim16, TIM_IT_UPDATE);
					TIM16->EGR  |= TIM_EGR_UG;
					toggleflag = 0;
					while(toggleflag == 0);
					toggleflag = 0;
			}
			HAL_TIM_PWM_Stop(&htim16, TIM_CHANNEL_1);
			TIM16->EGR  |= TIM_EGR_UG;
			tosend >>= 1;
		}
}

The timer clock is set to 8MHz and the prescaler is 1, and PWM is set to mode 2, so for the first signal (ARR=54000, CCR1=36000) I must get a 1 for 9ms and a 0 for 4.5ms, and I indeed get the correct output on the MCU pin. However sadly here is where the accuracy ends.

From then on, if the LSB bit is 1 I set ARR=9000 and CCR1=2240, and if the LSB bit is zero I set ARR=4500 and CCR1=2240. So theoretically I must get 560us of 1 in both cases and 1.68ms and 560us of 0 on the pin. However after observing the PWM output pin with a logic analyzer, I get this:

0693W00000QMXGKQA5.pngwhich means that the output timings are incorrect.

What can cause this problem? The PWM output pin only works correctly the first time and when I change the CCR1 and ARR values and generate an update event afterwards, I get incorrect results.

Thanks for your time. Also here is my timer configuration:

static void MX_TIM16_Init(void)
{
 
  /* USER CODE BEGIN TIM16_Init 0 */
 
  /* USER CODE END TIM16_Init 0 */
 
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
 
  /* USER CODE BEGIN TIM16_Init 1 */
 
  /* USER CODE END TIM16_Init 1 */
  htim16.Instance = TIM16;
  htim16.Init.Prescaler = 1;
  htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim16.Init.Period = 18000-1;
  htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim16.Init.RepetitionCounter = 0;
  htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim16) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim16, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim16, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM16_Init 2 */
 
  /* USER CODE END TIM16_Init 2 */
  HAL_TIM_MspPostInit(&htim16);
}

1 ACCEPTED SOLUTION

Accepted Solutions

Which STM32?

If you run it at 8MHz, the code between individual pulses may be significant - especially if you call Cube/HAL functions and don't use compiler optimization.

Also, seting EGR.UG triggers the interrupt immediately.

You should enable preload on both ARR (ARPE) and CCR, then write ARR and CCR (and NOTHING ELSE, ie. not CNT nor EGR) as soon as you detect update (which you may detect directly by reading SR, interrupt is not needed at all).

JW

View solution in original post

3 REPLIES 3

Which STM32?

If you run it at 8MHz, the code between individual pulses may be significant - especially if you call Cube/HAL functions and don't use compiler optimization.

Also, seting EGR.UG triggers the interrupt immediately.

You should enable preload on both ARR (ARPE) and CCR, then write ARR and CCR (and NOTHING ELSE, ie. not CNT nor EGR) as soon as you detect update (which you may detect directly by reading SR, interrupt is not needed at all).

JW

MM..1
Chief II

Your primary fail is idea about control precise timed signal with start stop PWM.

This corrupt your timing .

Normal work is precalculate array CCR and in ISR load new item or use DMA.

Javier1
Principal

I solved this by changing ARR/CCR only inside the IRQ from the PWM timer completion.

we dont need to firmware by ourselves, lets talk