You don't need the complexity of chained timers to achieve this on STM32. You can either use TIM_RCR for certain timers to have a Nth UEV interrupt change polarity, or you can simply implement a software counter in an UEV interrupt handler to emulate RCR.
But the most important thing is to use preloaded OC register control for correct glitch-free operation. There are two modes of preloaded control that you can use (TIM1 as an example):
- Output in PWM mode with preloaded OC control:
TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1;
In this mode, TIM_CCR1 is preloaded, i.e. any changes to it will be applied after UEV (update event). In other words, every write operation changes the next duty cycle, not the current.
This mode is mandatory for glitch-free PWM mode operation.
- Preloaded commutation control:
TIM1_CR2 = TIM_CR2_CCPC;
In this mode, changes to certain bits in the TIM1_CCMRx and TIM1_CCER registers are preloaded. The values programmed are applied upon COM (commutation event) which can arrive either as a trigger or via TIM1_EGR = TIM1_EGR_COMG.
This mode is required for glitch-free commutation control.
In your code, you change polarity via TIM2_CCER without any preloaded control enabled, hence glitches are unavoidable. It's also worth noting that your code has excessive latency due to weird statements like while(TIM2->CNT);. In correctly written code, changing polarity even without preloaded control will still give good results albeit not ideal.
The initial requirement is: The original firmware generates PWM (duty cycle 50%) and every 8 cycles inverts the signal
It's hard to come with a good solution without further details. It's unknown, for example, whether duty cycle is always fixed at 50% or should change. If it's fixed, we can come up for instance with another approach - use timer PWM mode with preloaded OC control at twice the frequency and have duty cycle alternate between 0% and 100% in a preloaded fashion. At the 8th iteration, alternation changes.
A very simplified interrupt-based example:
void tim1_up_isr(void) {
static int n;
if (!(++n & 7)) return; // Change polarity every 8th iteration, i.e. do not alternate
TIM1_CCR1 ~= TIM1_CCR1; // Alternate duty cycle between 0% and 100%
}
...
TIM1_BDTR = TIM_BDTR_MOE; // Enable main output
TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1; // PWM1 mode, buffered CCR1
TIM1_CCER = TIM_CCER_CC1E; // Enable output
TIM1_DIER = TIM_DIER_UIE; // Enable UEV interrupt
The above is just an example of the preloaded PWM control concept and in no way is the most efficient code.
In the ultimate case, when duty cycle can change and on-the-fly polarity/mode change is required, the only correct way is to use both preloaded commutation control and PWM mode with preloaded OC control.
Answer from neoxic on Stack ExchangeThe question is old but I was wondering the same and started digging into the topic.
In an STM32 micro, not all timers implement every function. My post is based on the STM32F030 Timer 1, which is I believe the most feature packed 4 channel implementation (or at least it was a few years ago).
The timer basically acts as a counter, with various options to where the counter clock comes from, what resets it, what the period is and the counter direction. This is the base for all extra features it implements, the HAL driver refers to it as Time Base. By itself, the counter function is not related to the timer channels.
There are 1-6 channels implemented in STM32 timers. These channels can be configured either independently, or in some case, in pairs (for functions like quadrature encoder mode). A channel can be configured as either input capture, or output compare. The input capture "listens to" some event and saves the counter of the time base in the CCRx registers. The output compare compares the counter register to a set value, given in the CCRx registers.
All the simple IC/OC modes and the extra functions, like PWM input, output, Hall effect sensor interfacing, are built on top of these two modes, their respective option bits act on the various input/output multiplexers and, regarding to OC mode basically tell the hardware what action to take based on the comparator outputs (CNT = CCRx, CNT > CCRx). In this case, PWM mode lets the output mode controller return to the CNT <= CCRx state when the counter register resets (more specifically, the update event), where as in the other OC modes the output mode controller ignores this signal and can be reset by hand, or by an external signal. The output signal is the OCxREF signal which then passes through some more hardware before it gets to the output pins. If it gets output at all, because you are allowed to not connect the timer to the output pins.
The STM32 timers are complicated. They have many logical blocks and a ton of configuration registers/bits so I may have missed something or completely misread something. Please feel free to correct me.
PWM modes: Output changes continually based on comparison of counter value and compare register.
-PWM mode 1: (110) if TIMx_CNT < TIMx_CCR1 output is LOW else HIGH
-PWM mode 2: (111) if TIMx_CNT < TIMx_CCR1 output is HIGH else LOW
Output pin state changes two times for one update event, first when CNT == CCR and second when ARR overflows (update event) and is set to back zero (in upcounting). Setting the ARR controls period and setting CCR controls the duty cycle.
OC modes: Output is only changed when counter is equal to compare register
-Toggle on match: (011)
-Set active (high) on match: (001)
-Set inactive (low) on match: (010)
Output pin state changes only when CNT == CCR. The overflow event does not change the pin state.
You can technically generate PWM by using OC mode, but it very impractical. OC mode is more suitable for generating various shape pulses other than PWM.
There is only one "output" block of the timer which can be configured to both modes. There is a lot of other settings (polarity, deadtime, output disconnect) you can find in Reference Manual.
Hint: Let Cube MX and HAL do the dirty work of configuring the timer clock tree and pin assignments (function like MX_TIM8_Init()). Than control the timer and do what you want by directly setting the bits in the timer control registers. It is easier and faster to understand how to work with timer by reading few reference manual pages than trying to understand multiple layers of HAL functions and Cube MX settings. Moreover, you may need to change setting of the timer during the program run, like reconfigure from PWM to OC mode, or generate only one pulse some times.