cancel
Showing results for 
Search instead for 
Did you mean: 

PWM behavior when changing deadtime during operation

kbouchard
Associate

Hello!

I'm having some issue while updating the deadtime of the PWMs on Advance timer TIM1 during PWM operation is running.
I'm attaching the scopeshot with the Channel 1 and 1N pulses, during deadtime register(DTGbin) update ,the deadtime is zero. 

In the scope shots we can see the dead time before and after the deadtime update where the CH1 signal stays high for a longer duration, the pulses also seem to cross (x) switch at the wrong time also. I observe this happening at some deadtime updates but not every update.

Would you know why it does this or how to fix it?

My application needs to have large deadtime in the PWMs during startup which I ramp down to small value during normal operation to limit the current in converter.

Thank you,

photo_pwm_deadtime_1.jpg

photo_pwm_deadtime_2.jpg

 

Here is the configuration of the Timer 1 for the PWMs ( i have 3 channels but could only capture 1 in scope)

/**
  * @brief TIM1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
  htim1.Init.Period = 2000;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 750;
  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(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.Pulse = 500;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.Pulse = 250;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 50;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
  sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
  sBreakDeadTimeConfig.Break2Filter = 0;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);

}


I also have 2 interrupts, one that changes the compare value of the PWMs, the second to change the deadtime value.

volatile uint32_t counter = 0;
#define ISR_FREQUENCY 50000
#define SIGNAL_FREQUENCY 60
#define OFFSET 0.5
#define AMPLITUDE 0.45
#define PI 3.14159265
#define PERIOD_TIM 2000

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Instance == TIM2)
	{

        double phase = 2 * PI * SIGNAL_FREQUENCY * counter / ISR_FREQUENCY;
        double value1 = OFFSET + AMPLITUDE * sin(phase);
        double value2 = OFFSET + AMPLITUDE * sin(phase + 2 * PI / 3);
        double value3 = OFFSET + AMPLITUDE * sin(phase + 4 * PI / 3);
        // Use value1, value2, and value3 here
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint32_t )(value1*PERIOD_TIM));			/*! Phase A*/
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, (uint32_t )(value2*PERIOD_TIM)); 			/*! Phase B*/
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, (uint32_t )(value3*PERIOD_TIM)); 			/*! Phase C*/

        counter++;
        if (counter >= 833) counter = 0;

	}
	if (htim->Instance == TIM3)
	{
		
		if(newdeadtime > 200){
			newdeadtime = newdeadtime-150;
		}
		else{
    	newdeadtime=20000;
    	}
    	  Set_DeadTime(newdeadtime);
	}
}

  

And here is the function to change the Deadtime value using register.

/**
 * @brief  Set_DeadTime: Accordingly with DT input variable expressed in seconds provide a better DTG[7:0] configuration of TIMx_BDTR
 *
 * @PAram  DT_TimeVal: Dead time value expressed in nano seconds ex: 20 000 max
 * @retval returns
 * @note  Function linked to  Calc_DeadTimRange function
 * @note  See  RM0433 Rev8 (pg1374)
 * @note  RangeX
 RangeA -> DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS.
 RangeB -> DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS.
 RangeC -> DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS.
 RangeD -> DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS.
 * @warning LOCK config must be disable to obtain effect of DTG_bin Setting
 * Only Compatible for Advance Timer 1
 */
uint16_t Set_DeadTime(uint16_t DT_TimeVal)
{
	uint32_t F_TIM1 = HAL_RCC_GetPCLK1Freq();		    // Get the frequency of the peripheral clock  ///SHOULD be INPUT CLK
	float T_TIM1 = 1.0f / (float)(F_TIM1*2);			// Calculate the time period of one clock cycle
	float XtDesired = (1e-9 * (float) DT_TimeVal) / (T_TIM1*4);// Calculate the desired number of clock cycles for the dead time
	uint32_t DTG_bin = 1; /*!< DTG[7:0] Dead-time generator setup (bits of TIMx_BDTR) expressed in HEX*/
	uint32_t ckdValue = TIM1->CR1 & TIM_CR1_CKD_CHECK_MASK;


	XtDesired = (uint32_t)XtDesired;
	XtVal = XtDesired;
	if(XtDesired <= 127){
		DTG_bin = floor(XtDesired);
	}
	else if(XtDesired >= 128 && XtDesired < 256){
		if(XtDesired == 128){
			DTG_bin = (ceil((XtDesired-(64*2))/2)) + 128;
		}
		else{
			DTG_bin = (floor((XtDesired-(64*2))/2)) + 128;
		}
	}
	else if(XtDesired >= 256 && XtDesired < 512){
		if(XtDesired == 256){
			DTG_bin = (ceil((XtDesired-(32*8))/8)) + 192;
		}
		else{
			DTG_bin = (floor((XtDesired-(32*8))/8)) + 192;
		}
	}
	else if(XtDesired >= 512 && XtDesired <= 1008)
	{
		if(XtDesired == 512){
			DTG_bin = (ceil((XtDesired-(32*16))/16)) + 224;
		}
		else{
			DTG_bin = (floor((XtDesired-(32*16))/16)) + 224;
		}
	}
	else{
		// bad value set default to 500ns
		DTG_bin = 100;
	}

	
	TIM1->BDTR &= (TIM_BDTR_DTG_CLEAR_MASK);		// Clear the lower 8 bits of the BDTR register
	TIM1->BDTR |= (DTG_bin<<TIM_BDTR_DTG_Pos);      // Set the lower 8 bits of the BDTR register to the dead-time generator setup
	DTGVal = DTG_bin;


	return DTG_bin;
}

 

1 ACCEPTED SOLUTION

Accepted Solutions

Which STM32?

I have little experience with deadtime, but generally, this:

 

	TIM1->BDTR &= (TIM_BDTR_DTG_CLEAR_MASK);		// Clear the lower 8 bits of the BDTR register
	TIM1->BDTR |= (DTG_bin<<TIM_BDTR_DTG_Pos);      // Set the lower 8 bits of the BDTR register to the dead-time generator setup

 

is incorrect, in that there is a period when deadtime is set to zero.

So, read in the current BDTR value into a variable, modify it in that variable (both AND and OR), and write back the result.

I don't think this fixes your problem but you should do that anyway.

As BDTR fields are not preloaded, if you want to avoid any problem, generate an interrupt by a different channel somewhere in the middle between the edges, and change BDTR there. Have the computation done beforehand, so that the latency is as short as possible (Cube/HAL is not  helping in this).

JW

 

View solution in original post

2 REPLIES 2

Which STM32?

I have little experience with deadtime, but generally, this:

 

	TIM1->BDTR &= (TIM_BDTR_DTG_CLEAR_MASK);		// Clear the lower 8 bits of the BDTR register
	TIM1->BDTR |= (DTG_bin<<TIM_BDTR_DTG_Pos);      // Set the lower 8 bits of the BDTR register to the dead-time generator setup

 

is incorrect, in that there is a period when deadtime is set to zero.

So, read in the current BDTR value into a variable, modify it in that variable (both AND and OR), and write back the result.

I don't think this fixes your problem but you should do that anyway.

As BDTR fields are not preloaded, if you want to avoid any problem, generate an interrupt by a different channel somewhere in the middle between the edges, and change BDTR there. Have the computation done beforehand, so that the latency is as short as possible (Cube/HAL is not  helping in this).

JW

 

I was indeed setting the deadtime to 0 and then setting the deadtime at the following instruction.
After changing both lines you mentioned to the following :

TIM1->BDTR = (TIM1->BDTR & TIM_BDTR_DTG_CLEAR_MASK) | (DTG_bin & TIM_BDTR_DTG_SET_MASK) ;

I no longer could see the issue and the deadtime was respected. 

Thank you for your help it was much appreciated,
KB