9 years, 8 months ago.

How do you generate complementary PWM Outputs?

Hi Everyone,

I would like to generate complementary PWM Outputs with adjustable dead time. According to the STM32F401RE Microcontroller datasheet http://www.st.com/web/catalog/mmc/FM141/SC1169/SS1577/LN1810/PF258797, this is possible with Timer 1 (TIM1).

So far I have attempted to configure the timer myself using information available from the TIM HAL Driver from ST: http://developer.mbed.org/users/dreschpe/code/mbed-F401/docs/4e95b79aa640/stm32f4xx__hal__tim__ex_8c.html and looking through an example of someone using the driver: https://petoknm.wordpress.com/2015/01/05/rotary-encoder-and-stm32/. Obviously, I do not want to use a HAL sensor, but this is the closest example I can get to someone using the advanced features of the timers.

Thanks!

Damien

Edit: Here is the code I went with in the end:

PWM Configuration Function

void ConfigurePWM(float duty_us, float period_us){
    unsigned int value;
    float newVal;
    
    // Ensure power is turned on
    // Grabbed from lines 54-57 of analogin_api.h, modified for PWM
    // This turns on the clock to Ports A, B, and C
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
    // This turns on the clock to the Time 1:
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    
    // Set the GPIO Ports properly:
    // PWM1 is connected to  PA_8
    // PWM1N is connected to PA_7
    
    // Set the PWM outputs to general output pins:
    // This sets the PA_7 and PA_8 pins to Alternate Function Pins
    value = 0x8000 + 0x20000;
    GPIOA->MODER |= value;
    
    // Set the PWM outputs to high speed:
    value = 0xC000 + 0x30000;
    GPIOA->OSPEEDR |= value;
    
    // Set PWM as outputs to the pins:
    value = GPIOA->AFR[1];
    // Reset the lowest four bits:
    value &= 0xFFFFFFF0;
    // Configure PA_8 to AF:
    value |= 0x1;
    GPIOA->AFR[1] = value;
    
    value = GPIOA->AFR[0];
    // Reset the the 4 MSB:
    value &= 0x0FFFFFFF;
    // Configure PA_7 to AF:
    value |= 0x10000000;
    GPIOA->AFR[0] = value;
    
    // Set pull down resistors to PWM outputs:
    value = GPIOA->PUPDR;
    // Clear the bits:
    value &= ~(GPIO_PUPDR_PUPDR7 | GPIO_PUPDR_PUPDR8);
    // Set to pull down:
    value |= GPIO_PUPDR_PUPDR7_1 | GPIO_PUPDR_PUPDR8_1;
    // Set the register:
    GPIOA ->PUPDR = value;
    
    // Set the prescale value to 1:
    TIM1->PSC = 0;
    
    // *** TIM1 control register 1: TIMx_CR1 ***
    value = 0;
    // [9:8] Set CKD bits to zero for clock division of 1
    // [7] TIMx_ARR register is buffered, set the ARPE bit to 1:
    // value |= 0x80;
    // [6:5] Set CMS bits to zero for edge aligned mode
    // [6:5] Set CMS bits to 10 for Center Aligned mode 2, up down mode with flags set when counter reaches the top.
    //value |= TIM_CR1_CMS_1;
    // [4] Set DIR bit to zero for upcounting
    // [3] Set OPM bit to zero so that the counter is not stopped at update event
    // [2] Set URS bit to zero so that anything can create an interrupt
    // [1] Set UDIS bit to zero to generate an update event
    // [0] Set the CEN bit to zero to disable the counter
    // * Set the TIMx_CR1 Register: *
    TIM1->CR1 |= value;
    
    // *** TIM1 control register 2: TIMx_CR2 ***
    value  = 0;
    // [14] Set OIS4 bit to zero, the idle state of OC4 output
    // [13] Set OIS3N bit to zero, the idle state of OC3N output
    // [12] Set OIS3 bit to zero, the idle state of OC3 output
    // [11] Set OIS2N bit to zero, the idle state of OC2N output
    // [10] Set OIS2 bit to zero, the idle state of OC2 output
    // [9] Set OIS1N bit to zero, the idle state of OC1N output
    // [8] Set OIS1 bit to zero, the idle state of OC1 output
    // [7] Set TI1S bit to zero, connecting only CH1 pin to TI1 input
    // [6:4] Set to 111: The OC4REF signal is used as trigger output (TRGO)
    // value |= TIM_CR2_MMS_2 | TIM_CR2_MMS_1 | TIM_CR2_MMS_0;
    //value |= TIM_CR2_MMS_1 | TIM_CR2_MMS_0;
    // [3] Set CCDS bit to zero, request sent when CCx event occurs
    // [2] Set CCUS bit to 1, capture/compare control bits are updated by setting the COMG bit or when a rising edge occurs on TRGI
    //value |= 0x4;
    // [0] Set CCPC bit to 1, CCxE, CCxNE and OCxM are update on a commutation event, or rising edge on TRGI
    //value |= 0x1;
    // * Set the TIMx_CR2 Register: *
    TIM1->CR2 = value;
    
    // *** TIM1 Auto Reload Register: ARR ***
    value = 0;
    // [15:0] Set ARR bits to the frequency to be loaded in:
    newVal = ceil(period_us/PWMSTEP_US);
    value = (unsigned int) newVal;
    // * Set the TIMx_ARR Register:
    TIM1->ARR = value;
    
    // *** TIM1 capture/compare register 1: CCR1 ***
    value = 0;
    // [15:0] Set the capture compare value to the duty cycle:
    newVal = ceil(duty_us/PWMSTEP_US);
    value = (unsigned int) newVal;
    // * Set the TIMx_CCR1 Register:
    TIM1->CCR1 = value;
    
    // *** TIM1 capture/compare register 4: CCR4 ***
    value = 0;
    // [15:0] Set the capture compare value to the duty cycle:
    newVal = ceil(duty_us/2.0f/PWMSTEP_US);
    value = (unsigned int) newVal;
    // * Set the TIMx_CCR1 Register:
    TIM1->CCR4 = TIM1->ARR - CH4SHIFT;
    
    // *** TIM1 capture/compare mode register 2: CCMR2
    value = 0;
    // [15] Set OC4CE bit to 0, OC4Ref is not affected by the ETRF input
    // [14-12] Set the OC4M bits to '110', PWM mode 1, which is what we want I think.
    value |= TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1;
    // [11] Set the OC4PE bit to 1, meaning read/write operations to the preload event require an update event.
    value |= 0x800;
    // [10] Set the OC4FE bit to 0, the output compare fast enable is disabled
    // [9:8] Set the CC4S bits to 0, the channel is configured as an output.
    // * Set the TIMx_CCMR2 Register: *
    TIM1->CCMR2 = value;
    
    // *** TIM1 capture/compare mode register 1: CCMR1
    value = 0;
    // [7] Set OC1CE bit to 0, OC1Ref is not affected by the ETRF input
    // [6-4] Set the OC1M bits to '110', PWM mode 1, which is what we want I think.
    value |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;
    // [3] Set the OC1PE bit to 1, meaning read/write operations to the preload event require an update event.
    value |= 0x8;
    // [2] Set the OC1FE bit to 0, the output compare fast enable is disabled
    // [1:0] Set the CC1S bits to 0, the channel is configured as an output.
    // * Set the TIMx_CCMR1 Register: *
    TIM1->CCMR1 = value;
    
    // *** TIM1 capture/compare enable register: CCER
    value = 0;
    // [15:4] - Don't care:
    // [3] Set CC1NP bit to zero for active high.
    // [2] Set CC1NE bit to 0, to de-activate the OC1N signal
    // value |= 0x4;
    // [1] Set the CC1P bit to zero for active high.
    // [0] Set the CC1E bit to 1, to de-activate the OC1 signal
    // value |= 0x1;
    // * Set the TIM1_CCER Register: *
    TIM1->CCER = value;
    
    // *** TIM1 break and dead-time register: BDTR
    value = 0;
    // [15] Set MOE bit to 1 to enable the OC and OCN outputs
    value |= 0x8000;
    // [11] Set the OSSR bit such that the ouputs are forced to their idle mode when not running
    //value |= TIM_BDTR_OSSR;
    // [10] Set OSSI bit such that the outputs are forced to their idle mode when MOE = 0
    value |= TIM_BDTR_OSSI;
    // * Set the TIM1_BDTR register:
    TIM1->BDTR = value;
    
    // *** TIM1 DMA/Interrupt enable register: DIER
    value = 0;
    // [2] Set the CC1IE bit to 1, to trigger an interrupt when counter 1 has a match - which should be half way through the duty cycle.
    value |= TIM_DIER_CC4IE;
    // Set the TIM1_DIER register:
    TIM1->DIER |= value;
    
    // Set the UG bit in the EGR register to kick things off:
    value = 3;
    TIM1->EGR = value;

    // Configure the interrupt:
    NVIC_SetVector(TIM1_CC_IRQn, (uint32_t)&TIM1_CC_IRQHandler);
    NVIC_EnableIRQ(TIM1_CC_IRQn);
    

    return;

}

Question relating to:

Affordable and flexible platform to ease prototyping using a STM32F401RET6 microcontroller.

1 Answer

9 years, 8 months ago.

Hi,

In the STM32CubeF4 package you will find many examples using Timers. I see that there is one example using timer complementary signals. It is based on STM32F4x9I-EVAL board but I think it is easily portable on the STM32F401RE. It can help.

http://www.st.com/web/catalog/tools/FM147/CL1794/SC961/SS1743/LN1897/PF259243?s_searchtype=partnumber#

Regards

Accepted Answer

Hi bco stm,

Thanks for the tip, it helped a bit. In the end, I ended up setting the registered manually. I've posted the code above for anyone else who might find it useful. Since I'm directly accessing the registers, I can only guarantee that it will work with the STM32F401RET6 chip - the one found on the NUCLEO-F401RE.

Thanks,

Damien

posted by Damien Frost 23 Apr 2015

While they have made it alot better, I wouldn't be surprised if still doing it directly by register access is the best way to have it portable on STM devices: There are more differences in their drivers than in the actual hardware ;).

posted by Erik - 23 Apr 2015