This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TMS320F28335: 2 Level Alternating Zero Space Vector Modulation

Part Number: TMS320F28335


Hello,

I am trying to implement alternating zero space vector modulation on an f28335.  It is working mostly, but I notice glitching at the sector transitions. I looked at the space vector modulation setup example in the Control Suite motor control example, but that is symmetrical space vector modulation and doesn't have the quirks of setup that I have.

Basically, each cycle I need to update the CMPA register for two of my three phase legs, and the other needs to stay in an off or on state. What I do right now is used AQCTLA to setup an event at the PWM counter (using an up/down counter) to force pins high or low depending on which zero vector I need. Then I have the PWM pins in toggle model at the crossing with CMPA, and this gives me the desired pattern. For the third pin that needs to be held off or on, I use AQCSFRC to force it high or low consistent with the other pins, and then it should stay that way.  

What I am afraid is happening is that application (or removal, or both) of the software force is happening immediately and not with a shadow buffer at PWM counter = 0. This is kind of the unusual part about what I am doing, and therefore I'm not comfortable it is working correctly.  During initialization, I setup AQSFRC.bit.RLDCSF = 0, which I thought would make my use of AQCSFRC happen with a shadow buffer and at PWM counter = 0, consistent with when I load from CMPA.

Does this way of trying to do what I am doing even make sense? And is it even possible?  I have posted my initialization code below, and also my update case statement.  Any guidance would be most appreciated.  Note I have an inversion in my PWM hardware outside of the DSP so I do things that make seem backwards.

Thank you,

Logan

/************************* INITIALIZATION ****************************************/

// EPWM Module 1 config
EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0x0; //No clock division for maximum speed
EPwm1Regs.TBPRD = PWM_PERIOD; // Period = 4800 TBCLK counts
EPwm1Regs.TBPHS.half.TBPHS = 0; // Set Phase register to zero
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN;// Symmetrical mode
EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE; // Master module
EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW; //Shadow buffer mode
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO; // Sync down-stream module
EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm1Regs.AQCTLA.bit.CAU = AQ_TOGGLE; // set actions for EPWM1A
EPwm1Regs.AQCTLA.bit.CAD = AQ_TOGGLE;
EPwm1Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm1Regs.AQSFRC.bit.RLDCSF = 0x0; //Load software force events at CTR = ZERO consistent with CMP loads
EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; // enable Dead-band module
EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_LOC; // Active low complementary due to need to implement dead time with a hardware inversion
EPwm1Regs.DBFED = DEAD_TIME_AFE; // FED = 300 TBCLKs, or 2us
EPwm1Regs.DBRED = DEAD_TIME_AFE; // RED = 300 TBCLKs, or 2us
EPwm1Regs.ETSEL.bit.SOCAEN = 1; //Enable ADC SOC trigger 1
EPwm1Regs.ETSEL.bit.SOCBEN = 1; //Enable ADC SOC trigger 2
EPwm1Regs.ETSEL.bit.SOCASEL = ET_CTR_ZERO; //Set trigger 1 to occur at PWM counter 0
EPwm1Regs.ETSEL.bit.SOCBSEL = ET_CTR_PRD; //Set trigger 2 to occur at PWM counter peak
EPwm1Regs.ETPS.bit.SOCAPRD = ET_1ST; // Generate trigger 1 pulse on 1st event
EPwm1Regs.ETPS.bit.SOCBPRD = ET_1ST; // Generate trigger 2 pulse on 1st event

// EPWM Module 2 config
EPwm2Regs.TBCTL.bit.HSPCLKDIV = 0x0; //No clock division for maximum speed
EPwm2Regs.TBPRD = PWM_PERIOD; // Period = 4800 TBCLK counts
EPwm2Regs.TBPHS.half.TBPHS = 0; // Set Phase register to zero
EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN;// Symmetrical mode
EPwm2Regs.TBCTL.bit.PHSDIR = TB_UP; // Count UP on sync (=0 deg)
EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE; // Slave module
EPwm2Regs.TBCTL.bit.PRDLD = TB_SHADOW; //Shadow buffer mode
EPwm2Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; // sync flow-through
EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm2Regs.AQCTLA.bit.CAU = AQ_TOGGLE; // set actions for EPWM2A
EPwm2Regs.AQCTLA.bit.CAD = AQ_TOGGLE;
EPwm2Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm2Regs.AQSFRC.bit.RLDCSF = 0x0; //Load software force events at CTR = ZERO consistent with CMP loads
EPwm2Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; // enable Dead-band module
EPwm2Regs.DBCTL.bit.POLSEL = DB_ACTV_LOC; // Active low complementary due to need to implement dead time with a hardware inversion
EPwm2Regs.DBFED = DEAD_TIME_AFE; // FED = 300 TBCLKs, or 2us
EPwm2Regs.DBRED = DEAD_TIME_AFE; // RED = 300 TBCLKs, or 2us

// EPWM Module 3 config
EPwm3Regs.TBCTL.bit.HSPCLKDIV = 0x0; //No clock division for maximum speed
EPwm3Regs.TBPRD = PWM_PERIOD; // Period = 4800 TBCLK counts
EPwm3Regs.TBPHS.half.TBPHS = 0; // Set Phase register to zero
EPwm3Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN;// Symmetrical mode
EPwm3Regs.TBCTL.bit.PHSDIR = TB_UP; // Count UP on sync (=0 deg)
EPwm3Regs.TBCTL.bit.PHSEN = TB_ENABLE; // Slave module
EPwm3Regs.TBCTL.bit.PRDLD = TB_SHADOW; //Shadow buffer mode
EPwm3Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; // sync flow-through
EPwm3Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm3Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW; //CMP load double buffering enabled
EPwm3Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm3Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; //Load CMP at CTR = ZERO
EPwm3Regs.AQCTLA.bit.CAU = AQ_TOGGLE; // set actions for EPWM3A
EPwm3Regs.AQCTLA.bit.CAD = AQ_TOGGLE;
EPwm3Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm3Regs.AQSFRC.bit.RLDCSF = 0x0; //Load software force events at CTR = ZERO consistent with CMP loads
EPwm3Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; // enable Dead-band module
EPwm3Regs.DBCTL.bit.POLSEL = DB_ACTV_LOC; // Active low complementary due to need to implement dead time with a hardware inversion
EPwm3Regs.DBFED = DEAD_TIME_AFE; // FED = 300 TBCLKs, or 2us
EPwm3Regs.DBRED = DEAD_TIME_AFE; // RED = 300 TBCLKs, or 2us

/************************* Update Code ****************************************************/

//State machine for determining PWM configuration for a given sector
switch (control_block->sector)
{
case 1:
{
// Configure PWM module for sector 1 actions
// Phase A is forced high (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases B and C are setup to go high at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases B and C is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x1;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm3Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
sector_tracker = control_block->sector;
}

EPwm2Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase B
EPwm3Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase C
break;
}
case 2:
{
// Configure PWM module for sector 2 actions
// Phase C is forced low (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases A and B are setup to go low at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases A and B is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x2;
EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET;
EPwm2Regs.AQCTLA.bit.ZRO = AQ_SET;
sector_tracker = control_block->sector;
}

EPwm1Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase A
EPwm2Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase B
break;
}
case 3:
{
// Configure PWM module for sector 3 actions
// Phase B is forced high (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases A and C are setup to go high at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases A and C is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x1;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm1Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm3Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
sector_tracker = control_block->sector;
}

EPwm1Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase A
EPwm3Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase C
break;
}
case 4:
{
// Configure PWM module for sector 4 actions
// Phase A is forced low (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases B and C are setup to go low at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases B and C is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x2;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCTLA.bit.ZRO = AQ_SET;
EPwm3Regs.AQCTLA.bit.ZRO = AQ_SET;
sector_tracker = control_block->sector;
}

EPwm2Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase B
EPwm3Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase C
break;
}
case 5:
{
// Configure PWM module for sector 5 actions
// Phase C is forced high (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases A and B are setup to go high at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases A and B is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x1;
EPwm1Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
EPwm2Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
sector_tracker = control_block->sector;
}

EPwm1Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase A
EPwm2Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase B
break;
}
case 6:
{
// Configure PWM module for sector 6 actions
// Phase B is forced low (recall there is an inversion) using the software force, which is setup to
// have a synchronous load at counter = 0. Phases A and C are setup to go low at zero, and will also load
// the comparator values at 0. They will toggle to generate the expected switching state pattern. Any forcing
// on Phases A and C is removed.

if (control_block->sector != sector_tracker)
{
EPwm1Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm2Regs.AQCSFRC.bit.CSFA = 0x2;
EPwm3Regs.AQCSFRC.bit.CSFA = 0x0;
EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET;
EPwm3Regs.AQCTLA.bit.ZRO = AQ_SET;
sector_tracker = control_block->sector;
}

EPwm1Regs.CMPA.half.CMPA = control_block->Ta*PWM_MAX; //Set Phase A
EPwm3Regs.CMPA.half.CMPA = control_block->Tb*PWM_MAX; //Set Phase C
break;
}
}

  • What I am afraid is happening is that application (or removal, or both) of the software force is happening immediately and not with a shadow buffer at PWM counter = 0.

    Why do you think this is happening? Do you have any scope captures show this behavior?

    If yes:

    Since this "sector tracker" code is relatively complex and is required to be synchronized to the PWMs, I would start by testing that. Add a GPIO toggle to each section of code and ensure that the GPIO toggles at the correct time.

    Another option instead of running this code in the main loop, is potentially using the PWM ISRs to toggle the state of the AQCSFRC registers. But, yes, it does appear like your solution could be used to do what you have indicated.

    Regards,
    Cody 

  • Cody,

    Thanks for your reply. To add a little more context, this code is running in an ISR driven by an ADC EOC.  I have the PWM generator a trigger to the ADC at counter = 0 and counter = PRD so that I can double sample the signals.  Only one of these triggers generates an ADC EOC interrupt however.  The update PWM code above is the last thing that runs in the ISR loop.

    The waveforms that it generates look fairly like ideal sinusoidal when run in "open loop" with an internally generated sinusoidal command - but one can notice small dips at the sector boundaries.  When I close the control loop, these dips cause chatter across the sectors and result in current transients (see image below).  When I simulate the algorithmic parts of my code in a circuit simulator with C simulating functionality, I get the expected result, which lead me to believe that it is how I have configured the ePWM module, which I cannot simulate.  I am fairly confident from my initialization code above that I am setup to have the CMPA registers load in shadow mode at counter = 0.  What I have never tried before (and therefore why I am questioning it) is using the software trigger to force one of the PWM channels as a part of normal functionality - I have used it asynchronously to enable/disable PWM, but not to control precisely timed switching.  I was hoping that if it had a shadow buffer functionality, then when I applied a force (or unapplied it) it would happen at a precise time.  Otherwise, I worry it would cause a change of PWM behavior in the middle of the cycle and therefore create discontinuities.

    From the last thing you said, it seems like my desired functionality should be possible? I am worried that what I am asking for isn't possible with this DSP and that I'll need to fall back to sine-triangle modulation.

    Is there any other way for me to cause one of the channels not to switch for a time (and to have the command to do so happen at a precise time)?  My first thought was to just command the CMPA register for a value larger than the PWM counter period so that nothing would happen, but according to the manual, being in up/down mode will cause the action to occur regardless when counter = PRD.  I found this to be true in testing.

    Thanks,

    Logan

  • Logan,

    Its typical to use SET and CLEAR actions for CAU and CAD. You seem to have used toggle, is this required for how you have designed your code? I don't particularly like "Toggle" because if an event is missed for some reason now your signal has just inverted.

    If you set CMPA = PRD then I would expect you to have no action from CAU, but the CAD action would still take place, Figure 3-21 "Up-Down-Count Mode Symmetrical Waveform" from the datasheet explains this well. If you have configured CAD=SET then this will force your PWM low while CMPA=PRD. 

    I do believe that the continuous SW force should operate reliably, I have never personally used it synchronized to TBCTR=0 through shadow loading, however this feature should work.

    To debug what is going on I would focus on finding the PWM abnormalities that are leading to the dips, the dips are a symptom of an improper PWM input to your system which will make it harder to diagnose the root cause if you only look at the symptom. Once we figure out where in the PWM cycle/ your code that the PWM abnormality is occurring we can look into what could cause said issue. Toggling GPIOs as specified places in code can help you to align what you're seeing on an o-scope to where you are at in your SW, so start with that.

    Regards,
    Cody