Arduino ESP32 S3 MCPWM complimentary PWM for Half or H-Bridge and ability to disable. Synchronous Buck

HELP! after many days of going insane banging my head on a wall and reaching the limits of my competence, I’m seeking some help with MCPWM!

Ultimately I’m aiming to make an ‘interleaved’ high current synchronous Buck converter primarily for the charging of LiFePO4 (or other chemistry) batteries using DC input from solar or high power PC/Server power supplies. (typ. 36 to 80V IN, 24-28V out @ 45Amax approx).

I already have a working 2 phase interleaved buck prototype version BUT this uses MCPWM to generate PWM output signals, one for each buck phase (180 degree phase shifted) plus an Enable signal going to a half bridge gate driver IC such as IR2104. Such driver ICs have a single PWM input and internally create the complimentary high and low half bridge gate drive signals, add dead time (400ns) and shoot-through protection such that both FETS can’t be conducting at the same time. Gate current drive is poor, switching frequency is limited & so switching losses are poor.

For higher frequency switching, higher gate current, smaller dead time and thus less switching losses using Half bridge gate drivers such as a UCC27211 is highly desirable but these require two separate complimentary PWM drive signals with dead time to independently control the high and low side switches of a Half or H-Bridge. They have no enable input, to disable both PWM inputs must be set low.

https://www.researchgate.net/figure/Synchronous-buck-converter-a-block-diagram-and-b-PWM-signals-with-dead-time_fig4_290495912

It seems Arduino examples of a decent Half bridge / H bridge driver routine using an ESP32 MCPWM are elusive but would be really useful in synchronous Buck / Boost circuits, bench BUCK PSU designs like Riden PSUs, induction heating drivers, Solar MPPT chargers etc,

Below I used the MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE to generate two perfect PWM signals to drive a Half Bridge with adjustable dead band but struggle to over ride outputs to LOW.

MY fundamental problem is that to disable the Half bridge requires BOTH of the complimentary drive signals to be forced low. Ie, if I set say Zero or say <5% PWM then I need BOTH PWM lines to instantly fly LOW, the PWM stops, both bridge MOSFETS are turned OFF. At no time should both PWM signals be simultaneously High (Shoot-through = Bang…Dead MOSFETS!) , and when disabling, no PWM signal should remain in a delayed or extended high state.

Others appear to have faced the same issue [MCPWM: both low level at complementary pwm's with dead time. · Issue #12237 · espressif/esp-idf · GitHub]

Questions:-
What is the best way to rapidly force both the complimentary outputs Low? How to implement in Arduino?
Has anyone got examples of ESP32 MCPWM driving a Half or H-Bridge in Arduino ?
Is this something for the Fault handler? – how to do in code & Arduino? Can it be achieved in registers? How?
Am I missing a different timer / Generator /dead time arrangement ?

//This configures the MCPWM peripheral to produce complimentary A & B PWM signals to drive upper & Lower
//MOSFETS in a Half bridge. dead time is required 
//Two PWM channels, 180 degrees shifted are required for an interleaved synchronous buck charger project (48A battery charging from solar or 3 x server supplies
//in series). 
//Four outputs (A&B for Phase 1, plus A&B for phase 2) are needed with 180 degree phase shift between them. 
//!!!MY PROBLEM - HOW TO SET BOTH A&B Low to DISABLE both Half bridge FETs??
//Being able to set both low is needed for bridge drivers like UCC27211
//Is it a job for Fault module?  How!  Register operation?? Different Timer / generator / deadtime strategy OR add external logic and enable line
//(but this is a sophisticated MCPWM! - must be possible?!?


#include "arduino.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"

#define GPIO_PWM0A_OUT 35   //High side FET PHASE1
#define GPIO_PWM0B_OUT 36   //Low Side FET PHASE1
#define GPIO_2PWM0A_OUT 39  //High side FET PHASE2
#define GPIO_2PWM0B_OUT 40  //Low Side FET PHASE2

#define GPIO_SCOPE_Trigger_Sig 38   //For testing - trigger scope to monitor signals at start up, PWM changes and disabling

int frequency = 60000;
float PWM = 15;

//float Rise_delay = 200-9f;

bool pwm0 = false;
bool pwm1 = false;
bool buckEnable= false;


//************************************************************
void MCPWM_SetUP(){

//we set up the MCPWM to create complimentary PWM with a set dead time on rising edges...

mcpwm_group_set_resolution(MCPWM_UNIT_0, 160000000);
mcpwm_timer_set_resolution(MCPWM_UNIT_0, MCPWM_TIMER_0, 160000000);
mcpwm_timer_set_resolution(MCPWM_UNIT_0, MCPWM_TIMER_1, 160000000);  //Found necessary to apply this to all timers

//Config structure for timers....
 mcpwm_config_t pwm_config;
    pwm_config.frequency = frequency;    //frequency = 1000Hz
    pwm_config.cmpr_a = 0;
    pwm_config.cmpr_b = 0;
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
   
   mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);  
   mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config); 

   delay(20); 
   //To sync times and apply a phase shift, Enabling sync output of another timer by invoking mcpwm_set_timer_sync_output()
   //and selecting desired event to generate sync output from:-
   mcpwm_set_timer_sync_output(MCPWM_UNIT_0, MCPWM_TIMER_0,MCPWM_SWSYNC_SOURCE_TEZ);   //'MCPWM_SWSYNC_SOURCE_TEZ' = the sync signal is generated when Timer0 counts to zero
   mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_0,MCPWM_SELECT_TIMER0_SYNC, 0);   //Dont think this is needed?... sync source is from TIMER_0
   mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_1,MCPWM_SELECT_TIMER0_SYNC, 500); //timer 2 has 180 degree shift
   
   mcpwm_deadtime_enable(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,50,50);
   mcpwm_deadtime_enable(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,50,50);

   //PWM starts operating using the set PWM Duty value...
   mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,PWM);
   mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_GEN_A,PWM);

 delay (20);
 //mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);  
 //vTaskDelay(pdMS_TO_TICKS(10));
 delay(20);
// mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);   //gives a clean cycle start to PWM generator, no part duty waveforms
 
 delay (17);
 mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM0A,GPIO_PWM0A_OUT);   // PWM generation commences following init commands
 mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM0B,GPIO_PWM0B_OUT);   // Phase 1 GPIOs

 mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM1A,GPIO_2PWM0A_OUT);   // PWM generation commences following init commands
 mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM1B,GPIO_2PWM0B_OUT);   // Phase 2 GPIOs

  // digitalWrite(GPIO_SCOPE_Trigger_Sig, HIGH);     //Toggle a pin..for scope diagnostic purposes
  buckEnable=1; 
}

//***************************************************************
void buck_Disable(void){

  //This is my problem!How to force BOTH A and B generator outputs Low when using MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE
  //Tried changing the dead time mode such that A and B can be independently controlled...
  //Tried just setting them low but it can extent high time of A or B - doesnt feel correct!
  //Is it a job for Fault module ?  Setting registers
 
 //pinMode(GPIO_PWM0A_OUT, OUTPUT);    // sets the digital pin 13 as output
 //pinMode(GPIO_PWM0B_OUT, OUTPUT);    // sets the digital pin 13 as output
 
 digitalWrite(GPIO_SCOPE_Trigger_Sig, HIGH); // sets the status monitor pin high 
 digitalWrite(GPIO_PWM0A_OUT, LOW); // sets the status monitor pin low
 digitalWrite(GPIO_PWM0B_OUT, LOW); // sets the status monitor pin low

 
 digitalWrite(GPIO_SCOPE_Trigger_Sig, LOW); // sets the status monitor pin high 

 mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_0); 
  
/* mcpwm_deadtime_enable(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_BYPASS_RED,0,0);   
  mcpwm_set_signal_low(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A);
  mcpwm_set_signal_low(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B);
  //mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B,0);
  //mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,0);
  buckEnable=0;  
  */   
}

//****************************************************************
void Set_PWM(float duty){
    if(duty>4){      
        mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_B,duty);
        mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,duty);
    }
    else{
      buck_Disable();
    }
}


//**********************************************************************

void setup() {
 pinMode(GPIO_SCOPE_Trigger_Sig, OUTPUT);    // sets the digital pin 13 as output
 digitalWrite(GPIO_SCOPE_Trigger_Sig, LOW); // sets the status monitor pin low
  
 }

//**********************************************************************

void loop(){

delay(2000);
MCPWM_SetUP();
//digitalWrite(GPIO_SCOPE_Trigger_Sig, HIGH); // sets the status monitor pin low  

delay(2000);
//digitalWrite(GPIO_SCOPE_Trigger_Sig, HIGH); // sets the status monitor pin high  
buck_Disable();

delay(2000);
//digitalWrite(GPIO_SCOPE_Trigger_Sig, LOW); // sets the status monitor pin high 
/*
 
  //digitalWrite(GPIO_SCOPE_Trigger_Sig, HIGH); // sets the status monitor pin low  
  PWM=5;
  buck_Enable();
   
   for (int i = 5; i <= 95; i++) {
     PWM=i;
     Set_PWM(PWM);
     delay(10);
     }
     
 delay(3000);
*/  
}

Alternatively I experimented with this below, I also tried using two timers / generators which can force A & B low by setting PWMs to 0 & 100% BUT I cant seem to synchronise a 180 degree phase between timer PWMs from MCPWM unit 0 and 1 ...I can set 0 and 100% PWM to set both PWM signals LOW, HOWEVER I cant achieve 180 degrees between !
I do worry if asynchronously setting the various PWMs could break the complimentary high and low PWM waveform but so far couldnt see evidence of this....is this a job for shadow registers??

//Alternative MCPWM test code for driving 2x Half bridges in a Two phase 'interleaved' Synchronous Buck charger.
//Complementary PWMs to suit two input drivers such as UCC27211
//Using a single operator for the A B outputs proved troublesome to reliably disable and set both outputs LOW
//(using MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, the B output is a compliment of A, set A Low...B goes High!)
//see https://github.com/espressif/esp-idf/issues/12237
//This code uses one MCPWM operator to generate the A High side drive signal, AND a second to produce the Low side B output.
// Setting first operator to 0% PWM forces A Low, setting second operator to 100% PWM forces B Low
//PROBLEM WITH THIS CODE - IT DOES NOT SEEM POSSIBLE TO SYNCRONISE BETWEEN MCPWM Units 0 and 1????? How to get 180 degrees?
//UNABLE TO MAINTAIN 180 degree shift between outputs for phase 1 and phase 2
//Can all 6 timers of Unit 0 and 1 be syncronised????
//Is it safe to change all these PWMs without shadow registers? - How to do that!?



#include "arduino.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"


#define GPIO_PWM0A_OUT 35  //A High side FET  Buck Phase 1
#define GPIO_PWM1B_OUT 36  //B Low Side FET

#define GPIO_1PWM0A_OUT 39  //A High side FET  Buck Phase2
#define GPIO_1PWM1B_OUT 40  //B Low Side FET


#define GPIO_TOGGLE_OUT 38   //For diagnostics - triggering scope on PWM or enable disable transitions

int frequency = 60000; //PWM Freq
float PWM = 25;        //starting PWM =25%


//************************************************************
void buck_Enable(){

//configures two MCPWM Operators to create complimentary PWM signals with a set dead time on rising edges...

mcpwm_config_t pwm_config;                                       //Timer configuration structure
    pwm_config.frequency = frequency;    //frequency = 1000Hz
    pwm_config.cmpr_a = 0;
    pwm_config.cmpr_b = 0;
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
   
   mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);         //Timers 0 and 1 initialised with same setup
   mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);

   mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_0, &pwm_config);         //Timers 0 and 1 initialised with same setup
   mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_1, &pwm_config);
  
  
   mcpwm_deadtime_enable(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,1,1); //Timer 0 deadtime setup 
   mcpwm_deadtime_enable(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,1,1); //Timer 1 deadtime setup 

   mcpwm_deadtime_enable(MCPWM_UNIT_1,MCPWM_TIMER_0,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,1,1); //Timer 0 deadtime setup 
   mcpwm_deadtime_enable(MCPWM_UNIT_1,MCPWM_TIMER_1,MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE,1,1); //Timer 1 deadtime setup 
    

  //Star using the PWM Duty values such that both driver inputs are set LOW i.e both Half bridge transistos are off
  mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,0);      //set to 0%
  mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_GEN_A,100);    //start up set to 100% PWM

  mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_0,MCPWM_GEN_A,0);      //set to 0%
  mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_1,MCPWM_GEN_A,100);    //start up set to 100% PWM




//Sync all timers. Generate a sync output of timer0 by invoking mcpwm_set_timer_sync_output()
//and selecting desired event to generate sync output from:-
   mcpwm_set_timer_sync_output(MCPWM_UNIT_0, MCPWM_TIMER_0,MCPWM_SWSYNC_SOURCE_TEZ);   //'MCPWM_SWSYNC_SOURCE_TEZ' = the sync signal is generated when Timer0 counts to zero
   
//Sync all timers
   mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_0,MCPWM_SELECT_TIMER0_SYNC, 0);   // sync source is from TIMER_0
   mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_1,MCPWM_SELECT_TIMER0_SYNC, 0);    //creates 2nd phase on Pin13, a full cycle is 1000
  // mcpwm_sync_enable(MCPWM_UNIT_1, MCPWM_TIMER_0,MCPWM_SELECT_TIMER0_SYNC, 500);    //creates 3rd phase on Pin14,    
  // mcpwm_sync_enable(MCPWM_UNIT_1, MCPWM_TIMER_1,MCPWM_SELECT_TIMER0_SYNC, 500);    //creates 3rd phase on Pin14,    
   
   mcpwm_set_timer_sync_output(MCPWM_UNIT_1, MCPWM_TIMER_0,MCPWM_SWSYNC_SOURCE_TEZ);
   mcpwm_sync_enable(MCPWM_UNIT_1, MCPWM_TIMER_0,MCPWM_SELECT_TIMER0_SYNC, 0); 
   mcpwm_sync_enable(MCPWM_UNIT_1, MCPWM_TIMER_1,MCPWM_SELECT_TIMER0_SYNC, 0);      
  
   mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);   // PWM generation commences following init commands
   mcpwm_gpio_init(MCPWM_UNIT_0,MCPWM1B,GPIO_PWM1B_OUT);

  mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM0A, GPIO_1PWM0A_OUT);   // PWM generation commences following init commands
  mcpwm_gpio_init(MCPWM_UNIT_1,MCPWM1B,GPIO_1PWM1B_OUT);
  
/*
  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);   //Saw comments that stop-start is recommended but doesnt seem to be needed??
  // vTaskDelay(pdMS_TO_TICKS(10));
  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);  //give a clean cycle start to PWM generator, no part duty waveforms 
 
  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_1);   
  // vTaskDelay(pdMS_TO_TICKS(10));
  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_1);   
 */
 
 // digitalWrite(GPIO_TOGGLE_OUT, HIGH);     //Toggle a pin..for scope diagnostic purposes

 }

//****************************************************************
void Set_PWM(float duty){
    if(duty>4){      
        mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,duty);
        mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_GEN_A,duty);

        mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_0,MCPWM_GEN_A,duty);
        mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_1,MCPWM_GEN_A,duty);
    }
    else{   //Disable the driver inputs by setting both inputs LOW
      mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_GEN_A,0);   //0%PWM = A LOW
      mcpwm_set_duty(MCPWM_UNIT_0,MCPWM_TIMER_1,MCPWM_GEN_A,100); //100% PWM, B compliment = LOW 

      mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_0,MCPWM_GEN_A,0);   //0%PWM = A LOW
      mcpwm_set_duty(MCPWM_UNIT_1,MCPWM_TIMER_1,MCPWM_GEN_A,100); //100% PWM, B compliment = LOW 
    }
}
//*****************************************************************

void setup() {
 pinMode(GPIO_TOGGLE_OUT, OUTPUT);    // sets the digital pin 13 as output
 digitalWrite(GPIO_TOGGLE_OUT, LOW); // sets the status monitor pin low
 buck_Enable();
 delay(2000);
 }

//**********************************************************************
void loop(){
  
Serial.println("loop pass");
PWM=25;
digitalWrite(GPIO_TOGGLE_OUT, HIGH); // sets the status monitor pin low  
Set_PWM(PWM);
delay(3000);

digitalWrite(GPIO_TOGGLE_OUT, LOW); // sets the status monitor pin low
Set_PWM(0);  // Setting PWM to 0 will disable both outputs to LOW
delay(2500);


}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.