cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement push-pull mode with deadtime using the HRTIM peripheral on an STM32G4

Lionking
ST Employee

Summary

This article is a step-by-step guide on how to use the push-pull mode with deadtime using the STM32G4 HRTIM (high-resolution timer). The example in this article is demonstrated using the NUCLEO-G474RE board but can be tailored to any other STM32G4 board of your choice.

Introduction

The STM32 HRTIM is a versatile timer peripheral offering high-resolution timing and flexible control. It includes a master timer synchronizing six independent timers, each with 16-bit counters, and dual outputs. These timers can operate independently or in conjunction with set/reset events triggered by various sources. Applications include power conversion, lighting, motor control, and systems requiring precise timing and synchronization.

Hardware and software prerequisites

1. Development

1.1 Push-pull mode

The push-pull logic in this mode exhibits an alternating pattern, transitioning between low and high states on each timer period. This necessitates the use of two output channels, as only one channel is active during each state. This mode is available when the timer either operates in continuous mode or in single shot mode.

The figure below from RM0440 page 909 showcases the working of push-pull mode.

Lionking_0-1738179409121.png

1.2 Deadtime in push-pull mode

In the context of push-pull configurations in HRTIM, dead time refers to a deliberate delay introduced between the deactivation of one output signal and the activation of the other. By intentionally introducing a short delay between the deactivation of one output and the activation of the other, dead time ensures that both outputs are never active at the same time.

The figure below from RM0440 page 910 shows the working of push-pull mode with a positive value of deadtime configured.Lionking_2-1738180199569.png

 

1.3 Project configuration 

We start this demonstration by creating a new project for the NUCLEO-G474RE discovery board using STM32CubeIDE, configuring the clocks and the HRTIM peripheral. Here are the steps to perform the initial configurations.

  • Open the STM32CubeIDE application

  • Choose [File] -> [New] -> [STM32 Project]

  • Search for NUCLEO-G474RE under board selector tab and click on the shown board selection and then click [Next].

Laurids_PETERSEN_0-1744716704691.png

Choose a name for the project and click on [Finish].

Laurids_PETERSEN_1-1744716725092.png

Upon the display of the "Board Project Options" dialog box, select the [Unselect All] option as we are not utilizing these software components. Click the [OK] button.

Laurids_PETERSEN_2-1744716747968.pngOnce the .ioc file is open, click on [Clear Pinouts] under the [Pinout & Configuration] drop-down menu to start the peripheral initialization from scratch.

Laurids_PETERSEN_3-1744716769797.png

Navigate to the clock configuration settings and ensure that the APB2 peripheral clocks, which the HRTIM is connected to, is set to run at 170 MHz.

Laurids_PETERSEN_4-1744716787570.png

 

1.4 HRTIM configuration 

In the "Pinout & Configuration" tab, navigate to the timers dropdown menu. Select [HRTIM1] and enable the Master Timer, Timer A1, Timer A2, and Timer C TC2 outputs.

Laurids_PETERSEN_0-1744717681642.png

By doing this, pins PA8 and PA9 are configured to serve as Output Channel 1 and 2 for Timer A and PB13 for Output channel 2 of Timer C

NOTE: The master timer does not have any output pins.

Lionking_20-1738095537491.png

1.4.1 Purpose of the enabled timers

Master timer: The master compares events within the HRTIM control the switching behavior of the push-pull outputs generated by Timer A output channels TA1 and TA2.

Timer A: The TA1 and TA2 outputs generate push-pull signals with deadtime added. Each output produces a short pulse synchronized with alternating Period events.

Timer C: TC1 is configured to toggle its output state at each period event to analyze the push-pull operation

1.4.2 Clock configuration

The HRTIM can be clocked at a maximum clock of 170 MHz using the APB2 bus.

It supports clock prescalers that can multiply the APB clock value by 1, 2, 4, 8, 16, or 32, or divide the APB clock value by 2 or 4. Therefore, the maximum fHRTIM clock achievable is 5.44 GHz when using a multiplication prescaler of 32 with an APB2 base clock of 170 MHz. Conversely, the lowest frequency attainable, given an APB2 clock of 170 MHz, is 42.5 MHz when using a prescaler divider of 4.

The maximum value that the period register can support varies when using multiplication prescaler values, as the least significant bits become insignificant.Lionking_0-1738105874195.png

The maximum programmable period values for each multiplication factor are shown in the table below.

CKPSC[2:0]

Multiplier value

Maximum period value

000

x32

0xFFDF

001

x16

0xFFEF

010

x8

0xFFF7

011

x4

0xFFFB

100

x2

0xFFFD 

101

x1

0xFFFD 

By selecting a multiplier prescaler of 32, the maximum period value is 0xFFDF. We use the period value as 0xE000 which results in the PWM frequency of fHRCK / Configured_period_value = 5.44 GHz / 0xE000 = 94866 Hz. For simplicity, we use the same clock configuration for all the enabled timers. The changes to be done under the HRTIM configuration sections for the timers are shown below: 

Laurids_PETERSEN_6-1744716837752.png

 

Laurids_PETERSEN_7-1744716863963.png

 

Laurids_PETERSEN_8-1744716894017.png

 

With the clock configurations complete, let us proceed to configure the other parameters of the timers.

1.4.3 Master timer configuration

Enable the compare Unit 1 and configure the compare value as 0x5000

 

Laurids_PETERSEN_9-1744716933348.png

1.4.4 Timer A configuration

Under the "Timing Unit" section, enable the push pull mode.

Laurids_PETERSEN_10-1744716985888.png

Change the number of active set and reset sources to 1.

Next, configure the set and reset events for the outputs signals TA1 and TA2, each of which becomes active on the alternating period event due to the push-pull mode.

OUTPUT Set event Reset event
TA1 Timer period Master compare unit 1 
TA2 Timer period Master compare unit 1 

Laurids_PETERSEN_11-1744717003978.png

 

Note: STM32CubeMX v6.13.0 or earlier does not support code generation for push-pull mode with deadtime functionality. Therefore, the configuration for the deadtime unit of Timer A is manually added to the code, which is demonstrated later in this article.

1.4.5 Timer C configuration

The output TC2 must be toggled with each period event to analyze the push-pull behavior. Therefore, configure the source of set and reset events to trigger by the timer period event.

Laurids_PETERSEN_12-1744717090776.png

With all configurations complete, we can now generate the code by clicking on the "Device Configuration Tool code generation" icon.

Laurids_PETERSEN_13-1744717180577.png

1.5 Code to add in the main.c file 

The code to add in-between the respective user code comments for the main.c file is noted below.

/* USER CODE BEGIN 2 */

  HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA1 + HRTIM_OUTPUT_TC2 + HRTIM_OUTPUT_TA2);
  HAL_HRTIM_WaveformCountStart(&hhrtim1,HRTIM_TIMERID_TIMER_A + HRTIM_TIMERID_TIMER_C + HRTIM_TIMERID_MASTER);

  /* USER CODE END 2 */

Since the deadtime configuration was not enabled in the .ioc file earlier due to CubeMX v1.13.0 limitations, we will manually modify the MX_HRTIM1_Init(void) function code. The additional code to be inserted is placed between the comments "Extra Code Begin" and "Extra Code End," as shown below.

Note: The extra code resides outside the designated "USER CODE BEGIN/END" sections. Consequently, it is discarded during project regeneration, necessitating manual reinsertion of the code.

static void MX_HRTIM1_Init(void)
{

	/* USER CODE BEGIN HRTIM1_Init 0 */

	/* USER CODE END HRTIM1_Init 0 */

	HRTIM_TimeBaseCfgTypeDef pTimeBaseCfg = {0};
	HRTIM_TimerCfgTypeDef pTimerCfg = {0};
	HRTIM_CompareCfgTypeDef pCompareCfg = {0};
	HRTIM_TimerCtlTypeDef pTimerCtl = {0};
	HRTIM_OutputCfgTypeDef pOutputCfg = {0};

	/* USER CODE BEGIN HRTIM1_Init 1 */

	/* USER CODE END HRTIM1_Init 1 */
	hhrtim1.Instance = HRTIM1;
	hhrtim1.Init.HRTIMInterruptResquests = HRTIM_IT_NONE;
	hhrtim1.Init.SyncOptions = HRTIM_SYNCOPTION_NONE;
	if (HAL_HRTIM_Init(&hhrtim1) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_DLLCalibrationStart(&hhrtim1, HRTIM_CALIBRATIONRATE_3) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_PollForDLLCalibration(&hhrtim1, 10) != HAL_OK)
	{
		Error_Handler();
	}
	pTimeBaseCfg.Period = 0xE000;
	pTimeBaseCfg.RepetitionCounter = 0x00;
	pTimeBaseCfg.PrescalerRatio = HRTIM_PRESCALERRATIO_MUL32;
	pTimeBaseCfg.Mode = HRTIM_MODE_CONTINUOUS;
	if (HAL_HRTIM_TimeBaseConfig(&hhrtim1, HRTIM_TIMERINDEX_MASTER, &pTimeBaseCfg) != HAL_OK)
	{
		Error_Handler();
	}
	pTimerCfg.InterruptRequests = HRTIM_MASTER_IT_NONE;
	pTimerCfg.DMARequests = HRTIM_MASTER_DMA_NONE;
	pTimerCfg.DMASrcAddress = 0x0000;
	pTimerCfg.DMADstAddress = 0x0000;
	pTimerCfg.DMASize = 0x1;
	pTimerCfg.HalfModeEnable = HRTIM_HALFMODE_DISABLED;
	pTimerCfg.InterleavedMode = HRTIM_INTERLEAVED_MODE_DISABLED;
	pTimerCfg.StartOnSync = HRTIM_SYNCSTART_DISABLED;
	pTimerCfg.ResetOnSync = HRTIM_SYNCRESET_DISABLED;
	pTimerCfg.DACSynchro = HRTIM_DACSYNC_NONE;
	pTimerCfg.PreloadEnable = HRTIM_PRELOAD_DISABLED;
	pTimerCfg.UpdateGating = HRTIM_UPDATEGATING_INDEPENDENT;
	pTimerCfg.BurstMode = HRTIM_TIMERBURSTMODE_MAINTAINCLOCK;
	pTimerCfg.RepetitionUpdate = HRTIM_UPDATEONREPETITION_DISABLED;
	pTimerCfg.ReSyncUpdate = HRTIM_TIMERESYNC_UPDATE_UNCONDITIONAL;
	if (HAL_HRTIM_WaveformTimerConfig(&hhrtim1, HRTIM_TIMERINDEX_MASTER, &pTimerCfg) != HAL_OK)
	{
		Error_Handler();
	}
	pCompareCfg.CompareValue = 0x5000;
	if (HAL_HRTIM_WaveformCompareConfig(&hhrtim1, HRTIM_TIMERINDEX_MASTER, HRTIM_COMPAREUNIT_1, &pCompareCfg) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_TimeBaseConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pTimeBaseCfg) != HAL_OK)
	{
		Error_Handler();
	}
	pTimerCtl.UpDownMode = HRTIM_TIMERUPDOWNMODE_UP;
	pTimerCtl.DualChannelDacEnable = HRTIM_TIMER_DCDE_DISABLED;
	if (HAL_HRTIM_WaveformTimerControl(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pTimerCtl) != HAL_OK)
	{
		Error_Handler();
	}
	pTimerCfg.InterruptRequests = HRTIM_TIM_IT_NONE;
	pTimerCfg.DMARequests = HRTIM_TIM_DMA_NONE;
	pTimerCfg.PushPull = HRTIM_TIMPUSHPULLMODE_ENABLED;
	pTimerCfg.FaultEnable = HRTIM_TIMFAULTENABLE_NONE;
	pTimerCfg.FaultLock = HRTIM_TIMFAULTLOCK_READWRITE;

	//Extra Code Begin
	//pTimerCfg.DeadTimeInsertion = HRTIM_TIMDEADTIMEINSERTION_DISABLED;
	pTimerCfg.DeadTimeInsertion = HRTIM_TIMDEADTIMEINSERTION_ENABLED;
	//Extra Code End

	pTimerCfg.DelayedProtectionMode = HRTIM_TIMER_A_B_C_DELAYEDPROTECTION_DISABLED;
	pTimerCfg.UpdateTrigger = HRTIM_TIMUPDATETRIGGER_NONE;
	pTimerCfg.ResetTrigger = HRTIM_TIMRESETTRIGGER_NONE;
	pTimerCfg.ResetUpdate = HRTIM_TIMUPDATEONRESET_DISABLED;
	if (HAL_HRTIM_WaveformTimerConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pTimerCfg) != HAL_OK)
	{
		Error_Handler();
	}
	pTimerCfg.PushPull = HRTIM_TIMPUSHPULLMODE_DISABLED;

	//Extra Code Begin
	pTimerCfg.DeadTimeInsertion = HRTIM_TIMDEADTIMEINSERTION_DISABLED;
	//Extra Code End

	if (HAL_HRTIM_WaveformTimerConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_C, &pTimerCfg) != HAL_OK)
	{
		Error_Handler();
	}

	//Extra Code Begin
	pDeadTimeCfg.Prescaler = HRTIM_TIMDEADTIME_PRESCALERRATIO_MUL8;
	pDeadTimeCfg.RisingValue = 200;
	pDeadTimeCfg.RisingSign = HRTIM_TIMDEADTIME_RISINGSIGN_POSITIVE;
	pDeadTimeCfg.RisingLock = HRTIM_TIMDEADTIME_RISINGLOCK_WRITE;
	pDeadTimeCfg.RisingSignLock = HRTIM_TIMDEADTIME_RISINGSIGNLOCK_WRITE;
	pDeadTimeCfg.FallingValue = 200;
	pDeadTimeCfg.FallingSign = HRTIM_TIMDEADTIME_FALLINGSIGN_POSITIVE;
	pDeadTimeCfg.FallingLock = HRTIM_TIMDEADTIME_FALLINGLOCK_WRITE;
	pDeadTimeCfg.FallingSignLock = HRTIM_TIMDEADTIME_FALLINGSIGNLOCK_WRITE;
	if (HAL_HRTIM_DeadTimeConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, &pDeadTimeCfg) != HAL_OK)
	{
		Error_Handler();
	}
	//Extra Code End

	pOutputCfg.Polarity = HRTIM_OUTPUTPOLARITY_HIGH;
	pOutputCfg.SetSource = HRTIM_OUTPUTSET_TIMPER;
	pOutputCfg.ResetSource = HRTIM_OUTPUTRESET_MASTERCMP1;
	pOutputCfg.IdleMode = HRTIM_OUTPUTIDLEMODE_NONE;
	pOutputCfg.IdleLevel = HRTIM_OUTPUTIDLELEVEL_INACTIVE;
	pOutputCfg.FaultLevel = HRTIM_OUTPUTFAULTLEVEL_NONE;
	pOutputCfg.ChopperModeEnable = HRTIM_OUTPUTCHOPPERMODE_DISABLED;
	pOutputCfg.BurstModeEntryDelayed = HRTIM_OUTPUTBURSTMODEENTRY_REGULAR;
	if (HAL_HRTIM_WaveformOutputConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA1, &pOutputCfg) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_WaveformOutputConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA2, &pOutputCfg) != HAL_OK)
	{
		Error_Handler();
	}
	pOutputCfg.ResetSource = HRTIM_OUTPUTRESET_TIMPER;
	if (HAL_HRTIM_WaveformOutputConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_C, HRTIM_OUTPUT_TC2, &pOutputCfg) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_TimeBaseConfig(&hhrtim1, HRTIM_TIMERINDEX_TIMER_C, &pTimeBaseCfg) != HAL_OK)
	{
		Error_Handler();
	}
	if (HAL_HRTIM_WaveformTimerControl(&hhrtim1, HRTIM_TIMERINDEX_TIMER_C, &pTimerCtl) != HAL_OK)
	{
		Error_Handler();
	}
	/* USER CODE BEGIN HRTIM1_Init 2 */

	/* USER CODE END HRTIM1_Init 2 */
	HAL_HRTIM_MspPostInit(&hhrtim1);

}

The prescaler value used influences the deadtime resolution and maximum absolute deadtime values. In this article, we use a prescaler value of 0x000, which multiplies the frequency of the HRTIM (fHRTIM) by a factor of 8. This results in a deadtime generator clock value (fDTG) of 1.36 GHz, or a deadtime period (tDTG) of 0.735 ns.

With a positive deadtime sign for both the falling and rising edges, the TA1 and TA2 outputs are delayed relative to the period event, causing the TA1/TA2 outputs to be low for a short duration with respect to the period event (TC2). The maximum configurable deadtime rising/falling value is 511, but in our example, we configure it to 200. The deadtime calculations for different prescaler values, based on the configured deadtime value of 200, are shown below:

In our example fHRTIM = 170. 

DTPRSC[2:0] tDTG (1 / fDTG) tDTG (ns) tDTx max |tDTx| max (µs)
000 tHRTIM / 8 0.73 200 * tDTG 0.146
001 tHRTIM / 4 1.47 200 * tDTG 0.29
010 tHRTIM / 2 2.94 200 * tDTG 0.59
011 tHRTIM  5.89 200 * tDTG 1.18
100 2 * tHRTIM 11.76 200 * tDTG 2.35
101 4 * tHRTIM 23.53 200 * tDTG 4.70
110 8 * tHRTIM 47.06 200 * tDTG 9.41
111 16 * tHRTIM 94.12 200 * tDTG 18.82

Based on the configurations and calculations, we should expect a deadtime value of approximately 0.146 us, which we verify shortly.
With the correct configurations now applied to the HRTIM initialization function, we can proceed with compiling the project.

2. Results

Connect your board to the PC using a USB cable. Compile, load, and debug the binary file by clicking on the button shown below. The programming process should be complete without any errors.

Laurids_PETERSEN_14-1744717223459.png

Connect the logic analyzer or oscilloscope probes to pins PA8, PA9, and PB13. Then, click on the [Run] icon to start the process.

Laurids_PETERSEN_15-1744717237459.png

High-level analysis of the oscilloscope captures indicates that the push-pull operation is functioning as designed. TA1 and TA2 outputs exhibit the expected alternating activity synchronized with the period events.

Lionking_0-1738124278237.png

Zooming in on TA1 and the master timer we can see the deadtime to be 148 ns. This is an approximate of ~146 ns which we found in the calculations earlier

Lionking_1-1738124593971.png

Subsequently, zooming in on TA2 and the master Timer we can see the deadtime to be 146 ns which verifies the push-pull mode with deadtime functionality

Lionking_2-1738124847904.png

Conclusion

With this knowledge, you are now equipped to effectively utilize the STM32G4's HRTIM for push-pull operation, including the implementation of deadtime. Best of luck with your development!

Related links

Here are some useful links that can help with your development on HRTIM:

Version history
Last update:
‎2025-05-06 2:45 AM
Updated by: