1
\$\begingroup\$

I have some simple firmware written for an ATmega328 (not Arduino) and I'm converting to STM32.

I have more than one STM32 development board, so for now I'm searching a generic config and generic code.

The original firmware generates PWM (duty cycle 50%) and every 8 cycles inverts the signal with register TCCR1A and set bits COM1A1 and COM1A0.

Here is the source code:

uint8_t var = 0;

ISR(TIMER2_COMPA_vect)
{
    if (var)
        TCCR1A = _BV(COM1A1) | _BV(WGM11);
    else
        TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);

    var != var;
    PORTC = var & 1;
}

int main(void)
{
    //Set OC1A as output
    DDRB = (1 << PB1);  
    
    //Set PC0 as output
    DDRC = (1 << PC0);
    
    //Halt timers
    GTCCR = (1 << TSM)|(1 << PSRASY)|(1 << PSRSYNC);
    
    //Set timer one to Fast PWM (ICR1 = TOP)
    ICR1 = 23;
    OCR1A = 11;
    TCCR1A = (1 << COM1A1) | (1 << WGM11);
    TCCR1B = (1 << CS10) | (1 << WGM12) | (1 << WGM13);
    
    //Set timer two to CTC mode (OCR2A = TOP)
    OCR2A = (23*8)+11;
    TIMSK2 = (1 << OCIE2A);
    TCCR2A = (1 << WGM21);
    TCCR2B = (1 << CS20);
    
    //Reset all timer
    TCNT0 = 0;
    TCNT1 = 0;
    TCNT2 = 0;
    
    //Restart all timers
    GTCCR = 0;
    
    //Enable interrupts   
    sei();
    
    while (1);
}

Timer1 is used for generating PWM, while TIMER2 is used for inverting the signal.

The output on oscilloscope:

Oscilloscope

In my configuration of the STM32 I have used two synchronized timers, master and slave. The master generates the PWM (channel 1) TRGO update event, and the slave is configured as external clock 1, trigger timer master, output compare no output channel 1.

I tested many values for the counter period and pulse, but none is working very well. For inverting the initial state of PWM I used ch polarity, but is not very good as a solution.

When there is an interrupt for the timer with output compare, I execute this code (TIM2 in my case generates PWM):

void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t tx = 0;
    while(TIM2->CNT);
    if (tx == 0) {
        TIM2->CCER = 1;
    } else {
        TIM2->CCER = 3;
    }
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, tx == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET);
    tx = !tx;
}

This is the output of oscilloscope for the STM32:

oscilloscope stm32

There are wrong glitches. This is execution in release mode; when executed with debug, the glitches are less "pronounced".

Is there a better way for generating this signal? The STM32 has many configurations, but I can't find a way.

\$\endgroup\$
8
  • \$\begingroup\$ One of possible problem could be that HAL functions are too slow for your case and your code change polarity too late. Even in best case the code (both original and new one) seem as unreliable hack to do this. I would use 1 timer with logic in interrupt, much more reliable and readable. \$\endgroup\$
    – Rokta
    Feb 4, 2023 at 16:10
  • \$\begingroup\$ @Rokta I don't use HAL for timer, only for SET and CLEAR GPIO. The GPIO is on second channel of oscilloscope but is useless because it used only for debug. I don't know if it's slow because if I remove the "while(TIM2->CNT);" instruction before the polarity change the gap of glitch is wider. For the solution 1 timer you would do without pwm only toggle, set, clear GPIO? The frequency is about 700 KHz so an interrupt every 1uS. I'll try if I can't find a solution. \$\endgroup\$
    – Giovanni
    Feb 5, 2023 at 7:21
  • \$\begingroup\$ You use HAL function for timer interrupt. Your image show that you have 10 cycles between inverts, is that intentional? For one timer solution I would create whole sequence (32 states) and then use DMA triggered by timer to write it to correct BSRR register. \$\endgroup\$
    – Rokta
    Feb 5, 2023 at 9:48
  • \$\begingroup\$ @Rokta 10 cycles is wrong, should be 8 but I can change ARR of TIM1. For the solution with DMA have you example? \$\endgroup\$
    – Giovanni
    Feb 5, 2023 at 10:04
  • \$\begingroup\$ I think I can use github.com/mnemocron/STM32_PatternDriver for example \$\endgroup\$
    – Giovanni
    Feb 5, 2023 at 10:17

1 Answer 1

1
\$\begingroup\$

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):

  1. 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.

  1. 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.