on 2025-05-20 9:00 AM
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.
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.
NUCLEO-G474RE board
STM32CubeIDE v1.17.0 or later
STM32Cube MCU Package for STM32G4 series v1.6.0 or later for generating the HAL/LL code using STM32CubeIDE.
USB A to USB micro B cable
Logic analyzer or oscilloscope
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.
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.
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].
Choose a name for the project and click on [Finish].
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.
Once the .ioc file is open, click on [Clear Pinouts] under the [Pinout & Configuration] drop-down menu to start the peripheral initialization from scratch.
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.
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.
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.
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
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.
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:
With the clock configurations complete, let us proceed to configure the other parameters of the timers.
Enable the compare Unit 1 and configure the compare value as 0x5000
Under the "Timing Unit" section, enable the push pull mode.
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 |
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.
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.
With all configurations complete, we can now generate the code by clicking on the "Device Configuration Tool code generation" icon.
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.
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.
Connect the logic analyzer or oscilloscope probes to pins PA8, PA9, and PB13. Then, click on the [Run] icon to start the process.
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.
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
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
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!
Here are some useful links that can help with your development on HRTIM: