IronOS icon indicating copy to clipboard operation
IronOS copied to clipboard

Timers - Going full hardware?

Open sandmanRO opened this issue 2 years ago • 4 comments

Before getting to the timers, I would say that this started with a question from Ralim regarding the optimum frequency of the TIM3 CH1 output against the AC coupling between the TIM3 CH1 output (routed on TS100 to Port B Bit 4 of STM32 chipset) and the power circuitry. I did not have a good answer at that time as honestly, I did not put too much thought on this while rushing to complete my custom build. Nevertheless, the question is very important and deservers the proper addressing. I took the time to investigate before attempting an answer. In short TIM3 CH1 output (PB4) is connected to the base of Q2 transistor via an RC derivative circuit (or high pass filter if you like) made out of a 10nF capacitor and 1kOhm resistor. The cutoff frequency of the RC filter is about 16kHz (15.9 something kHz). However, in my tests I could not see any difference in the overall outcome no matter if the TIM3 CH1 PWM cycle frequency was set at 5kHz or 20kHz. It took me a while to understand what is going on and then it struck me: the TIM3 CH1 output is a square wave so, in theory, it is the superposition of an "infinity" of harmonics, multiple of base frequency and various amplitudes, so even if we go way lower than the 16kHz cutoff frequency, at least some of the higher harmonics would still pass the filter without significant attenuation and with enough amplitude to fully open the Q2 transistor that in turn would put the gate of power MOSFETS to ground, etc. So… my 'educated' answer would that the currently used 10kHz base frequency for TIM3 CH1 PWM output works just fine even if apparently lower than the AC coupling cutoff frequency. With this introduction I will address now the note on timers going full hardware.


Currently the TIM3 CH1 is software modulated by being started / stopped from within the TIM2 CH4 Period / Pulse complete interrupts callbacks. Make no mistake, the current implementation works flawlessly. Still, while revisiting the timers, I was wondering why not using hardware gating for controlling the TIM3 CH1 PWM output, that is, instead of using software modulation via interrupts we would use TIM2 CH4 PWM Pulse as an internal gate for TIM3 CH1 clock. Unfortunately, the STM & HAL documentation is not clear (if at all) regarding how the actual internal gating should be done so I spent several frustrating days just to sort out something as trivial as gating one timer with another. It turns out to be pretty simple once you figure out how it should be done....so there we go:

  1. Step one: configure the master output of TIM2 to mimic the TIM2 CH4 output. sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC4REF;

The full code below:

static void TIM2_Init(void) { // Channel 1 triggers the ADC at the end of pulse // Channel 4 hardware modulates (gates) TIM3 CH1 PWM output TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC;

htim2.Instance = TIM2;
htim2.Init.Prescaler = 8000 - 1; // 8 MHz timer clock / 8000 = 1 kHz tick rate

htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = TIM_PERIOD;						// 200-1
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; 	// 8 MHz
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
htim2.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&htim2);

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

HAL_TIM_PWM_Init(&htim2);
HAL_TIM_OC_Init(&htim2);

sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC4REF;			// TIM2 Master output mimics the TIM2 CH4 output (used as gate for TIM3 clock)
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);

sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC.Pulse = 180;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
sConfigOC.Pulse = 0;	// default off
HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4);

HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4); //plain start - no pulse complete interrupt
HAL_NVIC_SetPriority(TIM2_IRQn, 15, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);

}

  1. Step two: configure the TIM3 as gated slave of TIM2. The critical line of code is this: sSlaveConfig.InputTrigger = TIM_TS_ITR1; Where this comes from...well, it’s something buried within STM32 documentation. I reproduce in the code below a copy of the table I came across (see full code below). It tells us what it shall be used depending which is the slave timer and which is the master timer.

// TIM3 init function (should be called before TIM2 init so TIM3 would be ready and waiting by the time TIM2 starts) static void TIM3_Init(void) { // TIM3 CH1 generates the actual power control signal. It can NOT be continuous as this is AC coupled with // the power MOSFET circuitry so we go with a 50% duty cycle. The AC coupling is done via a capacitor of 10nF // (C10) in conjunction with a resistor of 1kOHm (R13). This leads to a cutoff frequency of about 15.9kHz so // will go with a 20kHz signal, although, since the signal is a square wave it should not be a problem to open // the switching transistor (Q2) even with a smaller frequency square wave. As a note, a square wave signal // is made out of an "infinity" of high frequency harmonics (multiple of base frequency) out of which at // least some of them would have an amplitude high enough to fully open the Q2 transistor.

TIM_ClockConfigTypeDef  sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_SlaveConfigTypeDef	sSlaveConfig;
TIM_OC_InitTypeDef      sConfigOC;

htim3.Instance = TIM3;
htim3.Init.Prescaler = 8 - 1;									// 8MHz / 8 = 1MHz tick rate
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 50 - 1;										// 1MHz tick rate / 50 ticks = 20kHz PWM full cycle frequency
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;				// 8MHz
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim3);

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);

HAL_TIM_PWM_Init(&htim3);
HAL_TIM_OC_Init(&htim3);

sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);

sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 25 - 1; 										// Period 50 ticks / Pulse Width 25 ticks (50% duty cycle)
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);

// Slave	<--	Master Timer w/ Corresponding Trigger Code **********************
// 				ITR0(TS=000)	ITR1(TS=001)	ITR2(TS=010)	ITR3(TS=011)	*	<- (Master) Trigger Source
// TIM2		<--	TIM1			TIM8			TIM3			TIM4			*
// TIM3		<--	TIM1			TIM2			TIM5			TIM4			*
// TIM4		<--	TIM1			TIM2			TIM3			TIM8			*
// TIM5		<--	TIM2			TIM3			TIM4			TIM8			*
//*******************************************************************************
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
sSlaveConfig.InputTrigger = TIM_TS_ITR1; 						//Timer 3 Slave, Timer 2 Master - see table
HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

}

That’s it. Now the TIM3 CH1 will generate a 50% duty cycle, 20kHz square wave signal while (and only while) the TIM2 CH4 pulses out (indirectly, TIM2 CH4 pulse is gating the TIM3 CH1 clock) so we can remove the TIM3 CH1 start / stop calls from HAL_TIM_PeriodElapsedCallback() and HAL_TIM_PWM_PulseCompleteCallback() as no longer needed (in fact HAL_TIM_PWM_PulseCompleteCallback() could be removed completely as TM2 CH4 is no longer using interrupts).

In the end, will these changes improve the overall behavior of the existing implementation? No, not really. But I thought that maybe somebody else is contemplating the idea and will find this information remotely useful.

sandmanRO avatar Sep 22 '21 16:09 sandmanRO

You are a better person than I am at figuring this out; I've tried at least twice to get this working properly. ❤️ I will give this a look and probably do this as I have wanted to get this to work for years but never quite got there.

Ralim avatar Sep 23 '21 06:09 Ralim

Thank you, Sir! (again). I wish I could say I enjoyed doing this but I can not. It was such a frustrating experience for me. Several times I thought I would give up. The only thing that kept me going was I 'knew' it was possible. In my early search I came across an oscilloscope screenshot that was looking tantalizing close to what we are trying to achieve here but unfortunately the picture's only comment was that the output was generated using "MXCube" (I believe this is some old (?) STM software utility). I ended up even looking over STM documentation written in Chinese (while I know nothing about Chinese). That table I was referring to actually came from such a document: 20190709230712252 I'm not joking, if somebody knows someone that works for Google Translate, please tell him/her I owe them a big one.

sandmanRO avatar Sep 23 '21 08:09 sandmanRO

I think I have tried at least three times in the past to get this to work and given up after some hours. Even now I'm probably going to not touch this just yet as I know its going to be a time sink to test and validate 😓

The ST docs are sometimes great and sometimes utterly useless. I'm surprised the Chinese docs are better, but also not surprised at all 😢

MXCube is probably a mess around of CubeMX

I Will get this in there as its been on the bucket list for ages. Going to public an RC though first to get some attention to testing the rest of the changes that have gone in recently

Ralim avatar Sep 25 '21 01:09 Ralim

@Ralim Might implementing this possibly be part of the solution for the space issues we are facing on the TS80P?

discip avatar Mar 31 '22 20:03 discip