GD32F427, GD32F4XX, timer 0 complementary output PWM

Let’s share the GD32F427, based on the GD official library, which controls the PWM channel channel0, channel0N and channel2 of TIMER0 to output PWM, where channel0 and channel0N are complementary output PWM.

In this project, SSysClock is 120M. Initialize the clock first. After initialization, get the clock to see if it is correct and whether the APB clock meets the datasheet requirements.

SysClk = rcu_clock_freq_get(CK_SYS); //120M
HClk = rcu_clock_freq_get(CK_AHB); //120M
PClk1 = rcu_clock_freq_get(CK_APB1); //30M
PClk2 = rcu_clock_freq_get(CK_APB2); //60M

IO initialization: The IOs involved are PE8, PE9, and PE12, which are TIMER0_C H0_ON, TIMER0_C H0, and TIMER0_C H2_ON respectively.

The datasheet is as follows:

 rcu_periph_clock_enable(RCU_GPIOE);
    
    gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE,GPIO_PIN_8);
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_8);
    gpio_af_set(GPIOE, GPIO_AF_1,GPIO_PIN_8);
    
    gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE,GPIO_PIN_9);
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9);
    gpio_af_set(GPIOE, GPIO_AF_1,GPIO_PIN_9);
   
    gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12);
    gpio_af_set(GPIOE, GPIO_AF_1,GPIO_PIN_12); 

Share the PWM parameters below. In this example, the output PWM frequency is 40K, the duty cycle of the complementary output has dead zone control, and the frequency division psc and period are calculated according to the formula:

PWM period T = (1 + psc)*(1 + period)/(SysClock*CLK_DIV);

SysClock is the timer clock, the frequency multiplier is 120M, CLK_DIV is the frequency division coefficient, and TIMER_CKDIV_DIV1 is selected by default.

Then there is the output configuration of Channel0 and Channel0N, just pay attention to the timer_ocintpara structure. Set whether the channel outputs, output polarity, idle state polarity and other parameters.

Then set the output duty cycle. The duty cycle is a ratio. The duty cycle can be obtained by dividing the parameter by period. In actual applications, the numerator can be deduced based on the required duty cycle.

Then set the PWM output mode and shadow settings.

Then there is the dead zone setting and dead zone time calculation: the code is as follows:

uint8_t deadtime(uint8_t pwmfre_KHz,uint8_t myCLK_MHz, uint8_t mydutyc)
{
    float T_TDS;
    T_TDS = (float)1000 / myCLK_MHz; // 1000/60 = 16.6666 1000/48 = 20.8333

    float DT;//dead time
    DT = (50 - mydutyc) *10000 / pwmfre_KHz; // (50-20)*10000/40 = 7500

    uint8_t dt;
    if (DT >= 0 & amp; & DT <= T_TDS * 127) //16.666 *127 = 2116
    {
        dt = (uint8_t)(DT / T_TDS);

    }
    else if ( DT >= T_TDS * 128 & DT <= T_TDS * 254) //16.666 *128 = 2133.338 16.666 *254=4233
    {
        dt = (uint8_t)(DT / (2 * T_TDS) + 64 );

    }
    else if ( DT >= T_TDS * 256 & DT <= T_TDS * 504) //16.666 *256= 4266 16.666 *504 = 8400 ,20.8333333*256=5,333.3333248 , 20.8333333*504=1049 9
    {
        dt = (uint8_t)(DT / (8 * T_TDS) + 160 ); // 216, 205
 
    }
    else
    {
        dt = (uint8_t)(DT / (16 * T_TDS) + 192 );

    }
    return dt;
}

According to the output of 40Khz PWM pulse, 120M system clock, 20% duty cycle, the dead zone calculation time function is:

deadtime = deadtime(40,120,20);

The timer initialization and PWM parameter initialization code is as follows, the above code:

void Drv_PWM_TimerConfig(void)
{
    timer_oc_parameter_struct timer_ocintpara;
    timer_parameter_struct timer_initpara;
    timer_break_parameter_struct timer_breakpara;

    rcu_periph_clock_enable(RCU_TIMER0);
    rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);

    timer_deinit(TIMER0);

    /* TIMER0 configuration */
    timer_initpara.prescaler = 3-1;
    timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.period = 1000-1;
    timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER0, & timer_initpara);

    /* CH0/CH0N configuration in PWM mode0 */
    timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    timer_ocintpara.outputnstate = TIMER_CCXN_ENABLE;
    timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    timer_channel_output_config(TIMER0,TIMER_CH_0, & timer_ocintpara);
    
    timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 500);
    timer_channel_output_mode_config(TIMER0,TIMER_CH_0,TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
 
    /* automatic output enable, break, dead time and lock configuration*/
    timer_breakpara.runoffstate = TIMER_ROS_STATE_ENABLE;//TIMER_ROS_STATE_DISABLE
    timer_breakpara.ideloffstate = TIMER_IOS_STATE_ENABLE;//TIMER_IOS_STATE_DISABLE
    timer_breakpara.deadtime = 216; //216
    timer_breakpara.breakpolarity = TIMER_BREAK_POLARITY_HIGH;
    timer_breakpara.outputautostate = TIMER_OUTAUTO_ENABLE;
    timer_breakpara.protectmode = TIMER_CCHP_PROT_0;
    timer_breakpara.breakstate = TIMER_BREAK_DISABLE; //TIMER_BREAK_ENABLE
    timer_break_config(TIMER0, & timer_breakpara);

        /*channel 2 pwm output*/
    timer_ocintpara.outputstate = TIMER_CCX_DISABLE; // TIMER_CCX_ENABLE
    timer_ocintpara.outputnstate = TIMER_CCXN_ENABLE; // TIMER_CCXN_DISABLE
    timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; //TIMER_OC_IDLE_STATE_HIGH
    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; //TIMER_OCN_IDLE_STATE_LOW
    timer_channel_output_config(TIMER0,TIMER_CH_2, & timer_ocintpara);
    timer_channel_output_mode_config(TIMER0,TIMER_CH_2,TIMER_OC_MODE_PWM0);
 
    timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_2, 500);
    timer_channel_output_mode_config(TIMER0,TIMER_CH_2,TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);
    /* TIMER0 primary output function enable */
    timer_primary_output_config(TIMER0,ENABLE);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER0);

    /* TIMER0 counter enable */
    timer_enable(TIMER0);
    
}

Pay special attention to the fact that some of the routines in the GD official library, such as complementary output or dead zone control, have incorrect parameters, such as

timer_breakpara.runoffstate = TIMER_ROS_STATE_ENABLE;//TIMER_ROS_STATE_DISABLE
timer_breakpara.ideloffstate = TIMER_IOS_STATE_ENABLE;//TIMER_IOS_STATE_DISABLE
timer_breakpara.deadtime = 216; //216
timer_breakpara.breakpolarity = TIMER_BREAK_POLARITY_HIGH;
timer_breakpara.outputautostate = TIMER_OUTAUTO_ENABLE;
timer_breakpara.protectmode = TIMER_CCHP_PROT_0;
timer_breakpara.breakstate = TIMER_BREAK_DISABLE; //TIMER_BREAK_ENABLE
timer_break_config(TIMER0, & timer_breakpara);

The runoffstate, ideloffstate, breakstate, etc. in the official demo are my annotated parameters, which cannot be output normally. As a result, I spent nearly two days troubleshooting the problem. Then I compared the functions and parameters of the ST library and corrected it. Normal output. Okay, here’s the waveform.

Channel0 is the output of PE8

Channel1 is the output of PE9

Channel2 is the output of PE12