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.

TMS320F280039C: SVPWM Issues and Questions

Part Number: TMS320F280039C
Other Parts Discussed in Thread: INA296A

Hello,

 

I am migrating an already working system with another MCU to the C2000 MCU with TI. This system relies on AC FOC motor control. I have already working regulators, and Park, Clarke, Inverse park transforms that I am sharing between the two microcontrollers via a library. Therefore, I know they are not the issue here. The only thing different in the FOC control is the SVPWM module. I do not have my own SVPWM module and am using the MCU vendors’ SVPWM module. I am on vacation this week, and the TI e2e site was down for maintenance so I am getting a colleague to post this for me and I am hoping to see some next steps by the time I get back from vacation! Thanks in advance!

 

Overall problem: I command 100 RPM, I get 100 RPM but enough motor voltage that I could have 400 RPM (40.14V when I should have 21.74V). I command 500 RPM, I get 500RPM, but I get enough motor voltage to have 1500 RPM (145.17V when I should have 55.757V).

Suspected cause of problem: I have a ton of D current – the D current reference is 0, so I’m applying a ton of D voltage to try and hold the D current to 0. I think the D current is caused by something being wrong with the SVPWM module from TI. Either I don’t understand the input / output or the SVPWM module does not work correctly. I suspect the SVPWM module works correctly and the issue is my understanding.

 

Note that Park, Clarke, Inverse park transforms and all regulators used below are identical between WORKING SYSTEM / TI so this is not a regulator issue. The code is identical between WORKING SYSTEM / TI in that aspect since all of that is in the library we share between the MCUs. The only difference here is the SVPWM. That is why I suspect an issue with SVPWM.

 

TI SVPWM Module

Input: Alpha / Beta volts divided by DcLinkVoltage to give us Alpha / Beta Duty cycles

Output: A, B, C PWMs scaled from -0.5 to 0.5

TI SVPWM –

 

There are 3 different SVPWM modules. I’ve tried 2 of the 3. My next step is to try the third one once I get back from vacation but I’m hoping someone here might suggest something they see with the SVPWM modules I am using and something I am misunderstanding.

 

Current SVPWM module I am using is actually an old one they have. They suggest not using it and using the new one. I am using it because it calculates SVM sectors whereas the other 2 do not and we need the sectors for current phase reconstruction.

 

 

This looks to output a PWM from -0.5 to 0.5 and then I set it to the motor PWM counter compare value.

 

Issues with this – I compared data at 100 RPM and 500 RPM between WORKING SYSTEM and TI to see how they compare. My takeaways from this on the key differences between WORKING SYSTEM and TI are this –

  1. Q current is the same between the 2 which makes sense since that is where torque comes from
  2. Tons of D current with TI – causes tons of D voltage which in turn causes tons of motor voltage
  3. Duty cycle is obviously different at 500 RPM 60Nm on the scope trace – makes me think SVPWM is wrong – either I don’t understand the output of it or the input of it or the algorithm is wrong.
  4. Why do I need to divide period which is already divided by 2 by 2 again meaning I divide it by 4? The examples do this as well. This confuses me.
  5. Why does example do (period * duty) + period? Duty is -0.5 to 0.5. This confuses me. The newer SVPWM allows us to scale duty from 0.0 to 1.0 and then just multiply it by the period which makes more sense to me.

 

Read below the data because I show TI’s newer SVPWM options there.

 

100 RPM 60Nm

 

WORKING SYSTEM

 

Scope traces

 

 

Trends with 10ms sampling – just good for average comparisons

 

Capture buffers with 250us sampling – good for seeing what is really happening

 

 

Duty cycles : -32767 (-1) to 32768 (1)

 

 

TI

 

Trends –

 

 

 

 

 

Capture buffers

 

 

 

 

 

500 RPM 60Nm

 

WORKING SYSTEM

 

Scope captures

 

 

Trends:

 

 

 

Capture buffers

 

 

 

 

TI

 

 

Trends

 

 

 

Capture buffers

 

 

 

 

TI new SVPWM option 1 –

 

SVGEN_run

 

Input – Alpha / Beta (the function will divide them by DcLink voltage to get duty cycles)

Output – A, B, C duty cycles from -0.5 to 0.5

 

Here I am not sure of 2 things –

  1. How can they do SVPWM successfully without calculating the sector? How do they tell which IGBTs to turn on?
  2. Which SVM mode are we? I tried SVM_COM_C and that resulted in the same as the older SVGEN that calculates sectors I am trying now.

 

TI new SVPWM option 2 –

 

There is a second option that combines the SVGEN_run with SVGEN_current which then does the current reconstruction for us which seems like they must be calculating sectors to be able to do that but not really calling them sectors and saving the sectors.

 

To use it you run, SVGEN_RunRegenCurrent to calculate which phase to reconstruct, then run SVGEN_run above and then call SVGENCURRENT_compPWMData() below it. I have not tried doing this – it might do what we want but it looks like it accounts for phase reconstruction. Here you do not need to divide PWM period already divided by 2 by 2 again and you just multiply the period by duty cycle like I do the other EPWMs which makes sense to me. I like that.

 

SVGEN_RunRegenCurrent looks a lot like our current reconstruction using SVM sectors to reconstruct the current but it allows for reconstructing 2 of 3 phases.

 

 

Then after calculating PWMs, you decide which shunt to ignore which seems a lot like deciding which SVM sector you are in:

 

 

Therefore, my next step is to try to use this method and see how it performs.

 

Thanks for your help and sorry for the long post! Hopefully the data and my sharing this helps get to the root cause of the problem and a good next step to try!

 

    • How can they do SVPWM successfully without calculating the sector? How do they tell which IGBTs to turn on?
    • Which SVM mode are we? I tried SVM_COM_C and that resulted in the same as the older SVGEN that calculates sectors I am trying now.

    Are your questions as above two? The min-max method doesn't need to calculate the sector for SVPWM, it still calculate the PWM duty to turn on/off the PWM for IGBT.

    You can use the SVM_COM_C which is as the SVGEN you mentioned above.

    All the functions in SVGEN_current.c/h are only for current reconstruction in OVM mode.

  • Hey Yanming, 

    Chris posted this question for me while I was on vacation. Thanks Chris for posting this! I hope you're ok with working with me even though Chris posted the question. 

    I switched from using the older runSVGenDQ to using SVGEN_run() in combination with SvgenCurrent(). The input into the SVGEN_run() is Alpha / Beta voltage which is also equal to motor line RMS voltage. I'm not sure if TI expects this to be a SQRT_3 higher or not? With the other microcontroller vendor, they expected Alpha / Beta to be equal to motor line RMS voltage. 

    For the PWMs I am setting them like this: 

      // Get PWM period
        float periodA = (float)(EPWM_getTimeBasePeriod(A_PWM_HS_BASE));
        float periodB = (float)(EPWM_getTimeBasePeriod(B_PWM_HS_BASE));
        float periodC = (float)(EPWM_getTimeBasePeriod(C_PWM_HS_BASE));
    
        // Limit PWMs to be safe
        float dutyALim = Limit(dutyA, 0.5, -0.5);
        float dutyBLim = Limit(dutyB, 0.5, -0.5);
        float dutyCLim = Limit(dutyC, 0.5, -0.5);
    
        // Scale duty cycles from 0 to 1
        float dutyAScaled = dutyALim + 0.5; // 0~1.0
        float dutyBScaled = dutyBLim + 0.5; // 0~1.0
        float dutyCScaled = dutyCLim + 0.5; // 0~1.0
    
        // Compute Compare value
        int16_t dutyACmpVal = (int16_t) Limit(dutyAScaled * periodA, periodA, pwmData.minCMPValue);
        int16_t dutyBCmpVal = (int16_t) Limit(dutyBScaled * periodB, periodB, pwmData.minCMPValue);
        int16_t dutyCCmpVal = (int16_t) Limit(dutyCScaled * periodC, periodC, pwmData.minCMPValue);
    
        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
        EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
        EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
        EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);

    Below is all of the functions using the TI SVGEN / SVGEN_Current modules. 

    uint16_t SVM_TI(GE_Primary_Container_t *pContainer)
    {
        // setup the space vector generator (SVGEN) module
        float OneDivDcBus = 1 / pContainer->mpStatusBuffer->mPage1.mDcLinkVoltage;
    
        // Set Alpha / Beta from core libraries
        Vab_out_V.value[0] = pContainer->mpDrive->mAcPMSMController.mAlphaBetaVoltsReq.Alpha;
        Vab_out_V.value[1] = pContainer->mpDrive->mAcPMSMController.mAlphaBetaVoltsReq.Beta;
    
        SVGEN_setup(svgenHandle, OneDivDcBus);
    
        // run the space vector generator (SVGEN) module
        SVGEN_run(svgenHandle, &Vab_out_V, &(Vabc_pu));
    
        // Determine Alpha / Beta duty cycles
        pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Alpha = pContainer->mpDrive->mAcPMSMController.mAlphaBetaVolts.Alpha * OneDivDcBus;
        pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Beta = pContainer->mpDrive->mAcPMSMController.mAlphaBetaVolts.Beta * OneDivDcBus;
    //    svgen.Ualpha = pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Alpha;
    //    svgen.Ubeta  = pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Beta;
    
        // Run SVPWM
        //runSVGenDQ(&svgen);
    
        // Store duty cycles
        pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhA = Vabc_pu.value[0];
        pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhB = Vabc_pu.value[1];
        pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhC = Vabc_pu.value[2];
        dutyALocal = pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhA;
        dutyBLocal = pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhB;
        dutyCLocal = pContainer->mpDrive->mAcPMSMController.mABCDutyFlt.PhC;
    
        GE_PriConvStatus_SetDebug1(pContainer, pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Alpha);
        GE_PriConvStatus_SetDebug2(pContainer, pContainer->mpDrive->mAcPMSMController.mAlphaBetaCompensatedVoltsFlt.Beta);
        pContainer->mpStatusBuffer->mPage4.mDebug3 = Vab_out_V.value[0];
    
        // run the PWM compensation and current ignore algorithm
        SVGENCURRENT_compPWMData(svgencurrentHandle, &pwmData.Vabc_pu, &pwmDataPrev);
    
    
        // Return SVM sector
        //return svgen.VecSector;
        return svgenHandle->sector;
    }
    
    void Motor_PWMSetTrigger()
    {
        int16_t pwmNum = midVolShunt;
        int16_t pwmCMPA = EPWM_getCounterCompareValue(pwmHandle[pwmNum],
                                                       EPWM_COUNTER_COMPARE_A);
    
        int16_t pwmSOCCMP = 5;
    
        if(svgencurrent.ignoreShunt == SVGENCURRENT_USE_ALL)
        {
            // Set up event source for ADC trigger
            EPWM_setADCTriggerSource(A_PWM_HS_BASE,
                                     EPWM_SOC_A,
                                     EPWM_SOC_TBCTR_D_CMPC);
        }
        else
        {
            pwmSOCCMP = pwmCMPA - pwmData.deadband - pwmData.noiseWindow;
    
            if(pwmSOCCMP <= 0)
            {
                pwmSOCCMP = 5;
    
                // Set up event source for ADC trigger
                EPWM_setADCTriggerSource(A_PWM_HS_BASE,
                                         EPWM_SOC_A,
                                         EPWM_SOC_TBCTR_U_CMPC);
            }
            else
            {
                pwmSOCCMP = 5;
    
                // Set up event source for ADC trigger
                EPWM_setADCTriggerSource(A_PWM_HS_BASE,
                                         EPWM_SOC_A,
                                         EPWM_SOC_TBCTR_D_CMPC);
            }
    
        }
    
        //
        pwmData.socCMP = pwmSOCCMP;
    
        // write the PWM data value  for ADC trigger
        EPWM_setCounterCompareValue(A_PWM_HS_BASE,
                                    EPWM_COUNTER_COMPARE_C,
                                    pwmSOCCMP);
    }
    
    // Set 3Phs PWMs
    bool Motor_PWMSet()
    {
        // Invert PWMs to match NXP
        float dutyA = dutyALocal * -1;
        float dutyB = dutyBLocal * -1;
        float dutyC = dutyCLocal * -1;
    
        // Get PWM period
        float periodA = (float)(EPWM_getTimeBasePeriod(A_PWM_HS_BASE));
        float periodB = (float)(EPWM_getTimeBasePeriod(B_PWM_HS_BASE));
        float periodC = (float)(EPWM_getTimeBasePeriod(C_PWM_HS_BASE));
    
        // Limit PWMs to be safe
        float dutyALim = Limit(dutyA, 0.5, -0.5);
        float dutyBLim = Limit(dutyB, 0.5, -0.5);
        float dutyCLim = Limit(dutyC, 0.5, -0.5);
    
        // Scale duty cycles from 0 to 1
        float dutyAScaled = dutyALim + 0.5; // 0~1.0
        float dutyBScaled = dutyBLim + 0.5; // 0~1.0
        float dutyCScaled = dutyCLim + 0.5; // 0~1.0
    
        // Compute Compare value
        int16_t dutyACmpVal = (int16_t) Limit(dutyAScaled * periodA, periodA, pwmData.minCMPValue);
        int16_t dutyBCmpVal = (int16_t) Limit(dutyBScaled * periodB, periodB, pwmData.minCMPValue);
        int16_t dutyCCmpVal = (int16_t) Limit(dutyCScaled * periodC, periodC, pwmData.minCMPValue);
    
        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
        EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
        EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
        EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);
    
        ignoreShuntNextCycle = SVGENCURRENT_getIgnoreShunt(svgencurrentHandle);
        midVolShunt = SVGENCURRENT_getVmid(svgencurrentHandle);
    
        // Set trigger point in the middle of the low side pulse
        Motor_PWMSetTrigger();
    
        return 0;
    }
    
    


    Doing this fixes my issue where D current was very high. However, the motor is very loud when using this and my IGBTs trip on a desaturation fault around 500 RPM. Therefore something is still off compared to SVM with another microcontroller vendor. 

    This is what things look like now at 500 RPM and 30Nm of torque. Like I said, I have all the motor control algorithms working with a different MCU vendor and am sharing the same functions between both MCUs. The only difference in the motor control is the SVPWM module. I do not have my own SVPWM module so I am relying on using the module from TI. 

  • You may have to use svgen() and HAL_writePWMData() together, and the active level of the PWM is high.

    I'm not sure if TI expects this to be a SQRT_3 higher or not? With the other microcontroller vendor, they expected Alpha / Beta to be equal to motor line RMS voltage. 

    The input of the SVGEN is the Alpha/Beta voltage with SI format (V).

  • Hey Yanming, 

    I am using svgen and HAL_writePWMData together(). 

    This is the function HAL_writePWMData. 

    static inline void
    HAL_writePWMData(HAL_MTR_Handle handle, HAL_PWMData_t *pPWMData)
    {
        HAL_MTR_Obj *obj = (HAL_MTR_Obj *)handle;
    
        float32_t period = (float32_t)(EPWM_getTimeBasePeriod(obj->pwmHandle[0]));
    
        uint16_t pwmCnt;
    
        for(pwmCnt=0; pwmCnt<3; pwmCnt++)
        {
          // compute the value
            float32_t V_pu = -pPWMData->Vabc_pu.value[pwmCnt];      // Negative
            float32_t V_sat_pu = __fsat(V_pu, 0.5, -0.5);           // -0.5~0.5
            float32_t V_sat_dc_pu = V_sat_pu + 0.5;                 // 0~1.0
            pPWMData->cmpValue[pwmCnt]  = (int16_t)(V_sat_dc_pu * period);  //
    
            if(pPWMData->cmpValue[pwmCnt] < pPWMData->minCMPValue)
            {
                pPWMData->cmpValue[pwmCnt] = pPWMData->minCMPValue;
            }
    
            // write the PWM data value
            EPWM_setCounterCompareValue(obj->pwmHandle[pwmCnt],
                                        EPWM_COUNTER_COMPARE_A,
                                        pPWMData->cmpValue[pwmCnt]);
    
            EPWM_setCounterCompareValue(obj->pwmHandle[pwmCnt],
                                        EPWM_COUNTER_COMPARE_B,
                                        pPWMData->cmpValue[pwmCnt]);
        }
    
        return;
    } // end of HAL_writePWMData() function

    I created my own function that more or less does the same thing as this function. I see no difference besides the inversion of duty cycles and I added in the svgen_current functions into this function - do you see any big difference here?

    // Set 3Phs PWMs
    bool Motor_PWMSet()
    {
        // Invert PWMs to match NXP
        float dutyA = dutyALocal * -1;
        float dutyB = dutyBLocal * -1;
        float dutyC = dutyCLocal * -1;
    
        // Get PWM period
        float periodA = (float)(EPWM_getTimeBasePeriod(A_PWM_HS_BASE));
        float periodB = (float)(EPWM_getTimeBasePeriod(B_PWM_HS_BASE));
        float periodC = (float)(EPWM_getTimeBasePeriod(C_PWM_HS_BASE));
    
        // Limit PWMs to be safe
        float dutyALim = Limit(dutyA, 0.5, -0.5);
        float dutyBLim = Limit(dutyB, 0.5, -0.5);
        float dutyCLim = Limit(dutyC, 0.5, -0.5);
    
        // Scale duty cycles from 0 to 1
        float dutyAScaled = dutyALim + 0.5; // 0~1.0
        float dutyBScaled = dutyBLim + 0.5; // 0~1.0
        float dutyCScaled = dutyCLim + 0.5; // 0~1.0
    
        // Compute Compare value
        int16_t dutyACmpVal = (int16_t) Limit(dutyAScaled * periodA, periodA, pwmData.minCMPValue);
        int16_t dutyBCmpVal = (int16_t) Limit(dutyBScaled * periodB, periodB, pwmData.minCMPValue);
        int16_t dutyCCmpVal = (int16_t) Limit(dutyCScaled * periodC, periodC, pwmData.minCMPValue);
    
        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
        EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
        EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
        EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);
    
        ignoreShuntNextCycle = SVGENCURRENT_getIgnoreShunt(svgencurrentHandle);
        midVolShunt = SVGENCURRENT_getVmid(svgencurrentHandle);
    
        // Set trigger point in the middle of the low side pulse
        Motor_PWMSetTrigger();
    
        return 0;
    }

    I believe the input to the svgen is correct. It is in the format of V. 

  • I created my own function that more or less does the same thing as this function. I see no difference besides the inversion of duty cycles and I added in the svgen_current functions into this function - do you see any big difference here?

    Why don't you use these two functions in the TI example lab directly? Or use  all of your own functions and just set the PWM duty and CMPA according to the active level of the PWM output?

  • "Or use  all of your own functions and just set the PWM duty and CMPA according to the active level of the PWM output?" 

    Is this not what I am doing? The only function I have that I need from TI is the space vector PWM. That is the only function I am using from TI for motor control. The reason is that I want the functions between different MCUs to be as close as possible. 

  • I am just checking the inputs into everything - the Alpha / Beta voltage looks correct based on the D / Q voltage. My motor requires 89V / 1000 RPM. This is taken running at 500 RPM. Looks like I am requesting a total of 44.33 motor volts on the left cursor which would be correct. 

    Do the duty cycles look correct for the alpha / beta voltages provided by the left and right cursors? 

    I take each of these duty cycles, multiply them by -1, limit them from 0.5 to -0.5, and then add 0.5 to them to get a duty cycle scaled from 0 to 1, then multiply period by the duty cycle scaled 0 to 1. This looks like also what HAL_writePWMData does. Am I wrong? 

  • Hi Derek,

    How are you determining the rotation angle to update the period at the correct time? The FAST estimator blocks also has PIE speed and torque controller to limit current saturation. The newer enhanced sliding mode observer (ESMO) or older (LSMO) gets the motor started in open loop using FOC sensorless mode then switches to closed loop >1Hz speed.

    Why not try the motor control SDK labs to get familiar with the embedded motor control blocks that surely differ from NXP model. The motor ID method determines the inductance, resistance and few other parameters to enter into user.h file with your motor current settings and many other system variables. I notice the x39c has 4MSPS ADC which is nice feature. SDK has CMPSS fault tripping for ePWM outputs to detect over current conditions you can set the current thresholds trip points to protect the DC inverter.

  • Hi, 

    I have a resolver that I read the angle of every single fast thread which is triggered by the ADC conversion using a 4kHz PWM like in the example project. 

    Our project does all of the same things as example project.

    Every 250us fast interrupt we do this:

    1) Read electrical angle from resolver / read 3 phase current from ADC

    2) We use the electrical angle and 3 phase current to calculate alpha / beta current using Clark transform

    3) Use Alpha / Beta voltage requested and electrical angle to get estimated D / Q voltage using Park transform

    4) We have a current regulator that takes in the D/Q current requested and then outputs D / Q voltage required using Park transform

    5) We take D/Q volts requested and then do inverse Park transform to get Alpha / Beta volts requested

    6) We input Alpha / Beta volts requested into SVGEN and then apply duty cycles to EPWM compare value(this is the only thing new in the process)

    Every 1ms slow interrupt we do this: 

    1) We take in a speed target, calculate the Q current required to drive the speed using a trajectory

    It looks like this is the exact same process as the block diagram you shared. We have all our own regulators, trajectory, clark and park transforms that we've used for years and are well proven out. The only thing we require to use from the MCU vendor is the SVPWM module. I'm not sure how anything else in the process could be wrong besides the duty cycles? If the electrical angle was wrong the motor would not spin at all and our software would trip on overcurrent. I don't need to detect resistance, inductance, inertia and such because my current regulator takes account for those things. 

  • Is there any way we could all get online together and look at this? I'm not sure where Yanming is located - if you are in China, I am ok to work in my night and your morning if you can get on a call and help me. I also have Chinese colleagues that I can work with to work with you. 

  • The only thing we require to use from the MCU vendor is the SVPWM module.

    Yet that was developed using the FAST estimator in ROM. I notice an oddity with your working system scope current capture, there is dead time in the zero crossing events. True sinewave current does not have dead time in zero crossing events.

    Every 1ms slow interrupt we do this: 

    That could be an ePIE interrupt issue but typically the x39c ePWM module triggers preconfigured time for an ADC to have SOCA event interrupt, where SVGEN_setup/run() are called and HAL_writePWMData() (near end of ISR) for CMPA reloads.

    The HAL_writePWMData() reloads the ADC trigger timer count for SOCA via CMPC or CMPD but don't see that in your code. Note CMPB was not being used in the SVM calls or dead band generators for SDK labs. CMPB is likely only present in examples to show the period can be reloaded too if chosen to do so though it complicates HAL_writePWMData().

    Below part of lab7 module was also used for over modulation mode current reconstruction (SDK labs 8/13) 100% duty cycle. Notice I defined decals at top for compiler optimization (global) speed 5 seemed to reduce THD total harmonic distortion. The module produces discontinuous SVM modulation where only a few inverter switches are on state at any given time. 

  • Hi Derek,

    Actually, the SVGEN/SVPWM is still an algorithm module that is not dependent on the device. You can still use your existing SVGEN/SVPWM if its outputs are the PWM duty of the three phases, just need to convert the PWM duty to the PWM Compare value and set the PWM compare value to the related registers according to the device you used.

    If you try to use the example SVGEN/SVPWM function from TI Universal Lab in step 6 you mentioned above, please note that the input Alpha / Beta voltages to SVGEN are SI value with "volt", and the "oneOverDcBus_invV" used in SVGEN is 1.0f/Vdcbus.

    What current sensor are you using for motor control? You may remove the over modulation related codes first, and try to run the code for motor with open-loop to verify the SVPWM and PWMDRV.

    ignoreShuntNextCycle = SVGENCURRENT_getIgnoreShunt(svgencurrentHandle);
    midVolShunt = SVGENCURRENT_getVmid(svgencurrentHandle);

    // run the PWM compensation and current ignore algorithm
    SVGENCURRENT_compPWMData(svgencurrentHandle, &pwmData.Vabc_pu, &pwmDataPrev);

  • Since the source code for svgen() is provided by TI, I did copy it over to my other MCU vendor project that is using their SVPWM module (which I do not have source code for - otherwise I'd try to do the same thing with the TI MCU (see if it works better there)) to see if I have the same problem or not and compare the duty cycles from TI's svgen to the SVPWM module they provide with the other MCU to see if I can use that to figure out what is going on. There seems to be a piece missing somewhere. svgen seems to not calculate symmetrical looking duty cycles. To use svgen you do not need anything extra that I see from the demo project. The function takes in Alpha / Beta volts and then calculates duty cycles. However, I have the same issue with this function in my non TI-MCU project that I have with my TI MCU project. The motor sounds terrible and the duty cycles are not symmetrical. It makes me thing there is a piece missing. I did this so we can have more clarity that something around this function and the way I am using it is causing my issue. 

    I took the svgen source code and created this function: 

    lib_uint16_t SVM(GE_Primary_Container_t *pContainer, Drive_AlphaBeta_Flt_t *AlphaBeta, Drive_3PhABC_Int_t *DutyABC, float DcLinkVoltage)
    {
    	// Get 1 div DcLink
    	float oneDivDcBus = 1 / DcLinkVoltage;
    
    	// Get Alpha / Beta duty cycles
        float AlphaDuty = AlphaBeta->Alpha * oneDivDcBus;
        float BetaDuty = AlphaBeta->Beta * oneDivDcBus;
    
        // Calculate Valpha / Vbeta
        float VaTmp = 0.5 * AlphaDuty;
        float VbTmp = SQRT_3_OVER_2 * BetaDuty;
    
        // Calculate Va, Vb, Vc
        float Va = AlphaDuty;
        float Vb = -VaTmp + VbTmp;
        float Vc = -VaTmp - VbTmp;
    
        // Get Vmax and Vmin
        float Vmax = 0.0;
        float Vmin = 0.0;
        if(Va > Vb)
        {
        	Vmax = Va;
        	Vmin = Vb;
        }
        else
        {
        	Vmax = Vb;
        	Vmin = Va;
        }
    
        if(Vc > Vmax)
        {
        	Vmax = Vc;
        }
        else if(Vc < Vmin)
        {
        	Vmin = Vc;
        }
    
    	// Compute Vcom
        float Vcom = 0.5 * (Vmax + Vmin);
    
        // Subtract common mode term to achieve SV modulation
        Drive_3PhABC_Flt_t FltDtyCycles;
        FltDtyCycles.PhA = Va - Vcom;
        FltDtyCycles.PhB = Vb - Vcom;
        FltDtyCycles.PhC = Vc - Vcom;
    
        // Convert float duty cycles to 16 bit integer
        DutyABC->PhA = FRAC16(FltDtyCycles.PhA);
        DutyABC->PhB = FRAC16(FltDtyCycles.PhB);
        DutyABC->PhC = FRAC16(FltDtyCycles.PhC);
    
    	return 0;
    }

    This matches what the svgen function from TI is doing. Shown below: 

    static inline void
    SVGEN_run(SVGEN_Handle handle, const MATH_Vec2 *pVab_V, MATH_Vec3 *pVabc_pu)
    {
        float32_t Vmax_pu = 0,Vmin_pu = 0,Vcom_pu;
        float32_t oneOverDcBus_invV = SVGEN_getOneOverDcBus_invV(handle);
        SVM_Mode_e svmMode = SVGEN_getMode(handle);
    
        float32_t Valpha_pu = pVab_V->value[0] * oneOverDcBus_invV;
        float32_t Vbeta_pu = pVab_V->value[1] * oneOverDcBus_invV;
    
        float32_t Va_tmp = (float32_t)(0.5f) * Valpha_pu;
        float32_t Vb_tmp = MATH_SQRTTHREE_OVER_TWO * Vbeta_pu;
    
        float32_t Va_pu = Valpha_pu;
    
        //
        // -0.5*Valpha + sqrt(3)/2 * Vbeta
        //
        float32_t Vb_pu = -Va_tmp + Vb_tmp;
    
        //
        // -0.5*Valpha - sqrt(3)/2 * Vbeta
        float32_t Vc_pu = -Va_tmp - Vb_tmp;
    
        //
        // Find Vmax and Vmin
        //
        if(Va_pu > Vb_pu)
        {
            Vmax_pu = Va_pu;
            Vmin_pu = Vb_pu;
        }
        else
        {
            Vmax_pu = Vb_pu;
            Vmin_pu = Va_pu;
        }
    
        if(Vc_pu > Vmax_pu)
        {
            Vmax_pu = Vc_pu;
        }
        else if(Vc_pu < Vmin_pu)
        {
            Vmin_pu = Vc_pu;
        }
    
        // Compute Vcom = 0.5*(Vmax+Vmin)
        Vcom_pu = 0.5f * (Vmax_pu + Vmin_pu);
    
        if(svmMode == SVM_COM_C)
        {
            // Subtract common-mode term to achieve SV modulation
            pVabc_pu->value[0] = (Va_pu - Vcom_pu);
            pVabc_pu->value[1] = (Vb_pu - Vcom_pu);
            pVabc_pu->value[2] = (Vc_pu - Vcom_pu);
        }
        else if(svmMode == SVM_MIN_C)
        {
            pVabc_pu->value[0] = (Va_pu - Vmin_pu) - 0.5f;
            pVabc_pu->value[1] = (Vb_pu - Vmin_pu) - 0.5f;
            pVabc_pu->value[2] = (Vc_pu - Vmin_pu) - 0.5f;
        }
        else if(svmMode == SVM_MAX_C)
        {
            pVabc_pu->value[0] = (Va_pu - Vmax_pu) + 0.5f;
            pVabc_pu->value[1] = (Vb_pu - Vmax_pu) + 0.5f;
            pVabc_pu->value[2] = (Vc_pu - Vmax_pu) + 0.5f;
        }
    
        return;
    } // end of SVGEN_run() function

    I have the exact same issue in a project that has been working perfectly for years only replacing this single function. This is what my duty cycles look like at 500 RPM with TI's svgen function - as can be seen the duty cycles are not symmetrical. This causes Alpha / Beta voltages to not be a perfect sine wave since we are adjusting for D current being produced. 

    Whereas I expect them to look like this at 500 RPM - this is what the SVPWM function from my other MCU vendor produces:

    Please note that the alpha / beta voltages not being sinusoidal with the svgen function are due to compensation for the bad duty cycles. Between the 2 pictures I only changed 1 line of code - just which line I commented / uncommented below here: 

        // Uncomment to test TI's svgen
        //pAcPMSMController->mSVMSector = SVM(pContainer, &pAcPMSMController->mAlphaBetaVoltsReq, &pAcPMSMController->mABCDuty, pContainer->mpStatusBuffer->mPage1.mDcLinkVoltage);
        
        // Use MCU vendor SVPWM - comment to test TI's svgen
        pAcPMSMController->mSVMSector = hal.mpSVM(pContainer);

  • Yanming, 

    Your comment here "SVGEN/SVPWM is still an algorithm module that is not dependent on the device. You can still use your existing SVGEN/SVPWM if its outputs are the PWM duty of the three phases, just need to convert the PWM duty to the PWM Compare value and set the PWM compare value to the related registers according to the device you used." is exactly why I did what I just did. 

    My other MCU vendor project has been working for years and I should be able to take svgen and use it there since it is just an algorithm. It is not vendor, MCU or motor specific. Therefore, I had the idea of just debugging the algorithm and what is going on with it to cause my issues. I feel like since I just switched to the C2000 MCU it introduces too many variables here and just debugging why the algorithm is introducing this problem is the best step to take. 

    Current sensing: We have low side IGBT current sensing that we read on a shunt. The full scale is 0 to 4095 counts from -400A to 400A. We normally used SVM sector to do our own current reconstruction which is why I was confused when svgen did not calculate sector. However, you have your own functions for current reconstruction. 

    "please note that the input Alpha / Beta voltages to SVGEN are SI value with "volt", and the "oneOverDcBus_invV" used in SVGEN is 1.0f/Vdcbus." this is correct as well - I am putting the voltages you see in my pictures with SI value "volt" directly into svgen. 

  • Hey Yanming, 

    I fixed a scaling issue when porting svgen to my other project. That project wants a 16 bit integer scaled -1 to 1 centered around 16383. Now the duty cycles look much cleaner produced by svgen. 

    Now just comparing svgen with my other MCU SVPWM it looks like it causes double Alpha / Beta volts with the same / similar duty cycles. 

    Other MCU:
    A Max = 18422
    A Min = 14318
    B Max = 18488
    B Min = 14318
    C Max = 18434
    C Min = 14311
    Alpha Peak = 42.860
    Alpa Min = -42.640
    Beta Peak = 41.797
    Beta Min = -41.455

    TI:
    A Max = 18417
    A Min = 14514
    B Max = 18423
    B Min = 14360
    C Max = 18413
    C Min = 14340
    Alpha Peak = 82.860
    Alpa Min = -84.262
    Beta Peak = 83.884
    Beta Min = -82.062

  • Ok, I am now convinced that svgen is not the issue. svgen works perfectly with my other project to replace it's SVPWM function now. The final thing I had to do to get svgen to work with my other project was multiply the calculated duty cycle by 2 since the output of svgen is -0.5 to 0.5 and my other MCU project expected 1 to -1. 

    This means that svgen is not causing my issues. Maybe it is something with reading the electrical angle or ePWM setup. Maybe a thread priority issue as GI mentioned. I will comment out svgen current and such right now and just use svgen to see if I can figure out what is going wrong with the TI project. 

  • You can use the example HAL_writePWMData() in Universal Lab or the code you posted above. Please also refer to the PWM configuration if you need to use the PWM SOC to trigger the ADC and interrupt.

    You may run a motor with open-loop to check if the PWM outputs are correct, the open-loop doesn’t need the current/feedback signals. You can also check if the current/voltage feedback sensing signals are correct to verify the PWM and Interrupt configuration for ADC.

  • Hey Yanming, 

    This is a good suggestion. Next I will just request a constant voltage and see what the PWM shown is. I switched back to the svgen function from TI, and removed everything but it and then using the below function to set the PWMs. 

    Function to set PWMs: 

    // Set 3Phs PWMs
    bool Motor_PWMSet()
    {
        // Invert PWMs to match NXP
        float dutyA = dutyALocal * -1;
        float dutyB = dutyBLocal * -1;
        float dutyC = dutyCLocal * -1;
    
        // Get PWM period
        float periodA = (float)(EPWM_getTimeBasePeriod(A_PWM_HS_BASE));
        float periodB = (float)(EPWM_getTimeBasePeriod(B_PWM_HS_BASE));
        float periodC = (float)(EPWM_getTimeBasePeriod(C_PWM_HS_BASE));
    
        // Limit PWMs to be safe
        float dutyALim = Limit(dutyA, 0.5, -0.5);
        float dutyBLim = Limit(dutyB, 0.5, -0.5);
        float dutyCLim = Limit(dutyC, 0.5, -0.5);
    
        // Scale duty cycles from 0 to 1
        float dutyAScaled = dutyALim + 0.5; // 0~1.0
        float dutyBScaled = dutyBLim + 0.5; // 0~1.0
        float dutyCScaled = dutyCLim + 0.5; // 0~1.0
    
        // Compute Compare value
        uint16_t dutyACmpVal = (uint16_t) Limit(dutyAScaled * periodA, periodA, pwmData.minCMPValue);
        uint16_t dutyBCmpVal = (uint16_t) Limit(dutyBScaled * periodB, periodB, pwmData.minCMPValue);
        uint16_t dutyCCmpVal = (uint16_t) Limit(dutyCScaled * periodC, periodC, pwmData.minCMPValue);
    
        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
        EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
        EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
        EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);
    
    //    ignoreShuntNextCycle = SVGENCURRENT_getIgnoreShunt(svgencurrentHandle);
    //    midVolShunt = SVGENCURRENT_getVmid(svgencurrentHandle);
    
        // Set trigger point in the middle of the low side pulse
        //Motor_PWMSetTrigger();
    
        return 0;
    }

    As you can see the PWMs and alpha / beta volts are very wobbly. I did not see this wobble when running the code with the other MCU vendor. I am not sure if this is due to something getting set wrong in PWM or electrical angle not reading the same every time? 

  • Hey Yanming, 

    Open loop was a great suggestion! I think this has eliminated anything with current sensing, svgen, or the electrical angle being my problem. I think we've narrowed it down to something with the PWM. 

    If I do open loop and just command 10V, I get beautiful alpha / beta volts and SVM duty cycles: 

    I need to check the actual values being output on the PWM. It looks like I should have a max of +/- 0.015 with 10V requested. 

    I also checked 100V - here I get a desaturation on my IGBTs which I am happy about. This will be easier to debug in open loop mode. With 100V, I have beautiful SVM and Alpha / Beta volts. I'll look at the duty cycles on the scope to see if I can determine what is going wrong causing the desaturation. 

    With 100V I get +/-0.15 on the duty cycle so it looks like this is all correct. In this case, the electrical angle is just a rotating 60Hz sine wave from -1 to 1 so I think this also eliminates the electrical angle as a variable and we've narrowed this down to something with the PWM

    I'll let you know what I see on the PWM! Last time I tried to read the PWM in system I blew up the converter. Our DC link is 565V with a ground reference of -565V so it is tricky measuring the actual PWM. I'll do this tomorrow once my hardware team gets back to me tonight on best way to measure PWM in the system. 

  • As you can see the PWMs and alpha / beta volts are very wobbly.

    The wobble may be due to 250µs SOC interrupt being nested with 1ms interrupt times. With Instaspin we use decimation times (GPIO marker pulses) to check main_ISR() loop is never preempted by another IRQ (GPIO marker pulse).

    Seemingly had a similar x49c unresolved issue with SCIB 30ms IRQ inflicting collateral damage on SOC ISR 150µs. Perhaps check the decimation time before doing the destructive test last mentioned. Typical practice to isolate scope AC ground pin via 2 prong plug adapter.

  • Hey GI and Yanming, 

    GI - I will look into timing of interrupts and be sure that the interrupt reading the electrical angle is never preempted by another IRQ. 

    Yanming - 

    I captured what my PWMs look like when I get a desaturation on my IGBTs when commanding 100V open loop.  

    Measured duty cycles on scope are below - 

    AHS - On for 129.47us (ALS is inverse of AHS)

    BHS - On for 167.227us (BLS is inverse of BHS)  

    CHS - On for 81.003us (CLS is inverse of CHS)

    This would mean in this case the duty cycles commanded by svgen are below

    Phase A is at 129.47 / 250 = 0.51788 - 0.5 = 0.01788

    Phase B is at 167.227 / 250 = 0.6689 - 0.5 = 0.1689

    Phase C is at 81.003 / 250 = 0.3240 - 0.5 = -0.17598

    Looks like I caught the svgen around here: 

    Looking at the duty cycles - do you see anything wrong with them? Nothing really jumps out to me. 

  • I think I am getting closer! I compared the duty cycles between my other MCU and the C2000 from TI and they are comparable. Therefore, I believe the PWM is correct. However, it looks like the deadtime is missing with my TI project. I think I have it set up but it is not working. Debugging the deadtime will be the first thing I do Monday morning. 

    In the last scope trace I shared you can see that both AHS and ALS switch at the same time. I need 3us of deadtime between switching IGBTs. 

    Other MCU duty cycles for 100V open loop: 

    PWMs are comparable but you notice deadtime here: 

    A HS on for 123.917us = 123.917 / 250 = 0.495668 – 0.5 = -0.004332

    B HS on for 159.611us = 159.611 / 250 = 0.638444 – 0.5 = 0.13844

    C HS on for 83.474us = 83.474 / 250 = 0.3339 – 0.5 = -0.166104

    3us deadtime present in this project that is missing from my TI project: 

    How do I initialize and get the deadtime providing 3us between IGBT switching? 

    I tried doing 2 things looking at the universal motor control lab. 

    Set min compare value for my EPWM compare value to the deadtime - is this even necessary or will the PWM driver insert deadtime on it's own if I initialize it correctly? I'm not sure I need to do this, it seems the driver would handle deadtime on it's own.

        // Set up deadband and noise window
        pwmData.deadband = ABC_DEADTIME;
        pwmData.noiseWindow = NOISE_WINDOW;
        pwmData.minCMPValue = pwmData.deadband + pwmData.noiseWindow + 33; // 33 is copied from TI demo project
        
        bool Motor_PWMSet()
    {
        // Invert PWMs to match NXP
        float dutyA = dutyALocal * -1;
        float dutyB = dutyBLocal * -1;
        float dutyC = dutyCLocal * -1;
    
        // Get PWM period
        float periodA = (float)(EPWM_getTimeBasePeriod(A_PWM_HS_BASE));
        float periodB = (float)(EPWM_getTimeBasePeriod(B_PWM_HS_BASE));
        float periodC = (float)(EPWM_getTimeBasePeriod(C_PWM_HS_BASE));
    
        // Limit PWMs to be safe
        float dutyALim = Limit(dutyA, 0.5, -0.5);
        float dutyBLim = Limit(dutyB, 0.5, -0.5);
        float dutyCLim = Limit(dutyC, 0.5, -0.5);
    
        // Scale duty cycles from 0 to 1
        float dutyAScaled = dutyALim + 0.5; // 0~1.0
        float dutyBScaled = dutyBLim + 0.5; // 0~1.0
        float dutyCScaled = dutyCLim + 0.5; // 0~1.0
    
        // Compute Compare value
        uint16_t dutyACmpVal = (uint16_t) Limit(dutyAScaled * periodA, periodA, pwmData.minCMPValue);
        uint16_t dutyBCmpVal = (uint16_t) Limit(dutyBScaled * periodB, periodB, pwmData.minCMPValue);
        uint16_t dutyCCmpVal = (uint16_t) Limit(dutyCScaled * periodC, periodC, pwmData.minCMPValue);
    
        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
        EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
        EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
        EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);
    
    //    ignoreShuntNextCycle = SVGENCURRENT_getIgnoreShunt(svgencurrentHandle);
    //    midVolShunt = SVGENCURRENT_getVmid(svgencurrentHandle);
    
        // Set trigger point in the middle of the low side pulse
        //Motor_PWMSetTrigger();
    
        return 0;
    }
    

    When I initialize the PWMs I tried to initialize with 3us deadtime - am I missing something here? 

        // setup the Dead-Band Generator Control Register (DBCTL)
        EPWM_setDeadBandDelayMode(A_PWM_HS_BASE, EPWM_DB_RED, true);
        EPWM_setDeadBandDelayMode(A_PWM_HS_BASE, EPWM_DB_FED, true);
        EPWM_setDeadBandDelayMode(B_PWM_HS_BASE, EPWM_DB_RED, true);
        EPWM_setDeadBandDelayMode(B_PWM_HS_BASE, EPWM_DB_FED, true);
        EPWM_setDeadBandDelayMode(C_PWM_HS_BASE, EPWM_DB_RED, true);
        EPWM_setDeadBandDelayMode(C_PWM_HS_BASE, EPWM_DB_FED, true);
    
        // select EPWMA as the input to the dead band generator
        EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);
        EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);
        EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);
    
        // configure the right polarity for active high complementary config.
        EPWM_setDeadBandDelayPolarity(A_PWM_HS_BASE, EPWM_DB_RED, EPWM_DB_POLARITY_ACTIVE_HIGH);
        EPWM_setDeadBandDelayPolarity(A_PWM_HS_BASE, EPWM_DB_FED, EPWM_DB_POLARITY_ACTIVE_LOW);
        EPWM_setDeadBandDelayPolarity(B_PWM_HS_BASE, EPWM_DB_RED, EPWM_DB_POLARITY_ACTIVE_HIGH);
        EPWM_setDeadBandDelayPolarity(B_PWM_HS_BASE, EPWM_DB_FED, EPWM_DB_POLARITY_ACTIVE_LOW);
        EPWM_setDeadBandDelayPolarity(C_PWM_HS_BASE, EPWM_DB_RED, EPWM_DB_POLARITY_ACTIVE_HIGH);
        EPWM_setDeadBandDelayPolarity(C_PWM_HS_BASE, EPWM_DB_FED, EPWM_DB_POLARITY_ACTIVE_LOW);
    
        // setup the Dead-Band Rising Edge Delay Register (DBRED)
        EPWM_setRisingEdgeDelayCount(A_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
        EPWM_setRisingEdgeDelayCount(B_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
        EPWM_setRisingEdgeDelayCount(C_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
    
        // setup the Dead-Band Falling Edge Delay Register (DBFED)
        EPWM_setFallingEdgeDelayCount(A_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
        EPWM_setFallingEdgeDelayCount(B_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
        EPWM_setFallingEdgeDelayCount(C_PWM_HS_BASE, ABC_DEADTIME_PWM_MIN);
    

    My deadtime constants are - I am unsure where I got ABC_DEADTIME = 25 from for a 4kHz PWM with a 120MHz clock? 

    #define ABC_DEADTIME_PWM_MIN 25  // 3us - measured with testing PWM driver
    #define ABC_DEADTIME 360  // 3us - 3 * 120 MHz
    #define NOISE_WINDOW 180  // 3us - Deadtime / 2 * 120 MHz

    Thanks for the help! Hope you have or had a good weekend!

  • I fixed the deadtime so it is now 3us - I did this by changing ABC_DEADTIME_PWM_MIN above from 25 to 360. I'm not sure where I got 25 from. This allows me to get +/- 3000 RPM however, I still hear a loud sound across the speed range and I still get a desaturation on the IGBTs so there is still some work to do. Increasing the deadtime further does not fix this issue so there is something else as well. I think the PWMs look better now though. I'll see what they look like when I get the desat again now.  

  • Actually, I'm not sure the deadtime is quite right. With my other MCU vendor I have 3us before HS changes on the LS and then 3us of deadtime once HS goes false before LS changes. 

    With TI, It looks like I have 3us of deadtime before HS rises, but LS is allowed to rise before HS drops. This is also exactly when the desat occurs. 

  • Did you check if the PWM frequency is correct as you want?

    You may try to set the input to the band generator as below.

    // select EPWMA as the input to the dead band generator
    EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);

  • This is a little weird. My deadtime is right most of the time. However, when I get the desat it seems the deadtime is wrong. What could cause this - it seems to only happen during higher duty cycles that the falling edge deadtime is missed and both HS and LS go on at the same time. 

    Deadtime is correct during high duty cycle - 300V output: 

    Deadtime is missing on falling edge - desat occurs - I think this may also be the source of my terrible noise: 




    // select EPWMA as the input to the dead band generator
    EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_LS_BASE, EPWM_DB_INPUT_EPWMA);

    Why only EPWMA? Why not B if A is high side IGBT and B is low side IGBT? 

  • I guess you should be using the complementary mode for PWM, so the EPWMB output is dependent on the EWPMA output.

  • I am doing that - whenever I set a duty cycle on A, the inverse of that duty cycle is output on B. 

  • You may refer to the Universal lab to use the build level 1 to check if the clocks of the EPWM module for three phase are synchronized.

    You may try to call the code below before configure the PWMs 

      // disable the ePWM module time base clock sync signal
    // to synchronize all of the PWMs
    SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);

    And then call the code below after configured the PWMs.

    // enable the ePWM module time base clock sync signal
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);

  • Hey Yanming, 

    I seem to have fixed the desat / dead time issue I believe. 

    Here is what I needed to do: 

    1) Since PWM is in complement mode don't set compare value for EPWMB: 

        // write the PWM data value
        EPWM_setCounterCompareValue(A_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyACmpVal);
    //    EPWM_setCounterCompareValue(A_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyACmpVal);
        EPWM_setCounterCompareValue(B_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyBCmpVal);
    //    EPWM_setCounterCompareValue(B_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyBCmpVal);
        EPWM_setCounterCompareValue(C_PWM_HS_BASE, EPWM_COUNTER_COMPARE_A, dutyCCmpVal);
    //    EPWM_setCounterCompareValue(C_PWM_LS_BASE, EPWM_COUNTER_COMPARE_B, dutyCCmpVal);

    2) Remove EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB) and add EPWM_setFallingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA).  

        // select EPWMA as the input to the dead band generator
        EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setFallingEdgeDeadBandDelayInput(A_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    //    EPWM_setRisingEdgeDeadBandDelayInput(A_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);
        EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setFallingEdgeDeadBandDelayInput(B_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    //    EPWM_setRisingEdgeDeadBandDelayInput(B_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);
        EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
        EPWM_setFallingEdgeDeadBandDelayInput(C_PWM_HS_BASE, EPWM_DB_INPUT_EPWMA);
    //    EPWM_setRisingEdgeDeadBandDelayInput(C_PWM_LS_BASE, EPWM_DB_INPUT_EPWMB);

    I still have 2 issues - 

    1) The motor is much louder than it should be. I think this is because of issue #2 (whatever might be causing it). 

    2) I have about 1000 RPM of noise on the speed - I am targeting 2540 RPM and am getting it as the average but the accuracy is bad. 

  • I think this issue is caused by the electrical angle I am using to calculate the speed and make adjustments with my regulators. I am leading my angle 66 counts per 1000 RPM and if I remove this the issues get better. I also see this when I print out my cpu cycle counter that shows how long different processes take in my system. I think I am not reporting the electrical angle to my "slow" interrupt at the same time every time. Sometimes, I report it after 4 fast interrupts, sometimes I report it after 5 fast interrupts. 

  • You may try to run the angle generator and SVPWM in the same ISR.

  • Hi Derek,

    Dead band of 25*8.3ns=208ns with 120Mhz PWMCLK and depending on your DSAT timing and safe for some slower NFETS. See you decided to stick with CMPA for now seems a wise choice perhaps later open the throttle when the least code works well.

    The inversion syntax used above does not seem proper to multiply (value * -1). Some examples of integer inversion use (^=) symbols quite effectively.

    Sometimes, I report it after 4 fast interrupts, sometimes I report it after 5 fast interrupts. 

    Seemingly why the FAST estimator angle is determined from embedded ROM code during main_ISR() in SDK example labs. Oddly my scope was seeing 300ms of phase blanking repeating after many 150µs periods, watched via two GPIO port decimation intervals CH1& CH2. The ePIE was often overlapping main_ISR() peripheral core priority order 150µs periods, not waiting for the 300ms blanking intervals when the peripheral bus was free. How could it since the firmware was not checking any register to determine the end of a phase drive string event thus signaling the CPU bus was idle for 300ms. Recall my slow interrupt was only 30ms long event but overlapped into several main_ISR() 150µs to even 375µs decimation periods.

  • Hi GI, 

    What do you mean by this? Can you show me where this is done? Is it done this way in the universal_motor_control_lab? 

    "Seemingly why the FAST estimator angle is determined from embedded ROM code during main_ISR() in SDK example labs. "

    Hi Yanming, 

    I do have both the angle generator and SVPWM in the same ISR. Increasing the optimization to 5 fixes the issues I was having with terrible noise, and reduces the RPM noise I am seeing but it is still not perfect. I'm going to see if I can figure out what is overrunning in the ISR. 

  • The ROM call to change or update FAST estimator angle is made inline main_ISR() in the SDK, it is not a separate interrupt. That comment was regards to earlier post closed loop driving 1ms interrupt. Anyway, a 1ms call inside same ISR will likely over run 150µs decimation times. Perhaps unless rewrite current control to take less CPU time than required for calculating and outputting PWM period. Seemingly why FAST estimator does a rapid ROM call to update the angle. I'd lay odds that even UVMC SDK uses the same ROM call to FAST estimator.

    MCSDK gets the ROM angle sets sin/cos phasors

  • . I'm going to see if I can figure out what is overrunning in the ISR. 

    You may check if the PLL of the clock is configured correctly and the PWM frequency is correct. What's the PWM and control frequency in your project?

    Is there still noise if you just run the motor with open-loop and without load? Is it possible to capture the motor phase current at this test condition?

  • Hey Yanming and GI, 

    I have made the noise much better - my 1ms task is broken into 4 100us sections so that no 250us ISR overruns and one of the muxes were overrunning by over 100us. I have that almost fixed. The overrun is only a few us now and I have another post here asking for help with that issue: https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1163233/tms320f280039c-spi-dma-setup-function-taking-too-many-cpu-cycles/4375942#4375942

    I also have another strange issue with bad SPI data from my resolver causing some of the noise which I created another post about here: https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1163699/tms320f280039c-bad-spi-data

    Once I fix these 2 issues I will move onto current reconstruction with max duty cycles using SVGENCURRENT_compPWMData();

    The PWM frequency is correct at 4kHz - see my period is 250us:

    I am using this XTAL for the PLL: 

    I think if the PLL of the clock is configured incorrectly PWM period would be wrong? 

  • The PWM frequency is correct at 4kHz - see my period is 250us:

    That seems bit slow for TBPRD clock more often set for 20 - 40 KHz period, 120Mhz PWMCLK. Typical period 50µs to 25µs for fast NFET, GAN or IGBT devices depending on motor speed and switch SOA curve. The ISR time can exceed the PWM period by as much as x6, not much more.

    I think if the PLL of the clock is configured incorrectly PWM period would be wrong? 

    That is true and seems possible if the period 250µs. Many device SOA tables show that area is only good for very low DS, EC currents.

  • Hi GI, 

    Our motor must run at a 4kHz frequency - this would be a period of 250us. You can see I have the PWM set up for this frequency and that is the period I am measuring: 

        // since the PWM is configured as an up/down counter, the period register is
        // set to one-half of the desired PWM period
        EPWM_setTimeBasePeriod(A_PWM_HS_BASE, ABC_PWM_FREQ);
        EPWM_setTimeBasePeriod(B_PWM_HS_BASE, ABC_PWM_FREQ);
        EPWM_setTimeBasePeriod(C_PWM_HS_BASE, ABC_PWM_FREQ);

  • I think if the PLL of the clock is configured incorrectly PWM period would be wrong? 

    You are right.

    I have made the noise much better - my 1ms task is broken into 4 100us sections so that no 250us ISR overruns and one of the muxes were overrunning by over 100us

    You may enable nesting interrupt for not missing any ISR for motor control as the links below.

    https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1026118/tms320f280049-nesting-interrupt

    https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1033085/tms320f280025-nesting-interrupts-with-priority-levels

  • The PWM period is typically set 25 or 50 microseconds producing 20 or 40 KHz frequency. Perhaps TB module prescale clock divisor is incorrectly set, SYSCLK confirmed 120MHz?

  • I also have another strange issue with bad SPI data from my resolver causing some of the noise which I created another post about

     I don't think it would be best to set TBPRD up/down count 4KHz period simply to produce 250µs ISR. More common to use the EPWM module SOCA trigger source setting to achieve main_ISR() decimation time, or 5x CMP-C/D static count retriggers 250µs ISR. That way the frequency driving the inverter switches 20KHz/50µs remains in the SOA range and seemingly more synchronous with other peripherals using SYSCLK such as SPI.

  • GI,

    As Derek mentioned above, they have to use low PWM frequency according to their power inverter which maybe can't support high switching frequency. Please create a new thread to discuss this topic about PWM and control frequency for motor drive applications if you want. Thanks!

  • Hey Yanming and GI, 

    At the moment I am very happy with the motor control and how it sounds! I fixed the 2 issues that were keeping me from moving forward that I listed yesterday: 

    1) Slow CPU cycles on DMA transfer to SPI https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1163233/tms320f280039c-spi-dma-setup-function-taking-too-many-cpu-cycles/4375942#4375942

    2) Missed resolver angles due to bad SPI packets (this one I worked around and am still working on a resolution with Manoj to fix the root cause). https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1163699/tms320f280039c-bad-spi-data

    Monday I will start working on current reconstruction / max duty cycles and will post another question if I have issues with that! We've resolved my SVM / PWM issues! 

    Thank you both for all the help! 

  • Yanming If you check my comment was more about how main_ISR() was creating IRQ for SVM drive using the period to do so. Still what kind of IGBT prefers 250us on time period, speaking of SOA current frequency. TI engineers since 2008 have never designed an IGBT inverter that touted low frequency modulation that I'm aware of, hence the warning flags. Albeit Derek's DC inverter is driving AC motor with mechanical slip in the electrical angle.

    On the other hand, TIDA kits have always pushed high voltage IGBT switching frequency 40KHz or above to achieve efficiency. They don't reduce PWM frequency to handle greater inductive current, always and forever increased the frequency to reduce the strain on IGBT modules and heat sink. Driving ADC interrupt via the PWM period seems counter intuitive, seemingly Derek was unaware the ADC ISR can be synchronously triggered via SOCA feature. Derek can choose to use that trigger feature or not but it has been suggested.

  • GI,

    The ADC trigger frequency is also dependent on the current sensor and sampling mode. You have to use a sampling frequency as PWM frequency if low-side shunt current sensing mode is used.

  • You have to use a sampling frequency as PWM frequency if low-side shunt current sensing mode is used.

    This might be taken out of context but for FAST (FOC) the SOCA triggering ADC is 5x the PWM frequency of TBPRD, 20Khz decimation was roughly 250µs.

    I reduce CMPC/D to 3x period after the first call to HAL_writePWMdata() and thereafter. Good to read your recent post reply FOC via FAST takes roughly 21µs 100MHz and 12µs 200MHz MCU's. So, the rest of main_ISR() is for ADC reads, updates to PWM period, trajectory speed or torque, PID current and other rotor position tracking tasks. Perhaps an outline of flow added to SDK could remove some of the mystery in that ISR summation.

    Checking today x39c clock tree I noticed SPI has reduced clock rate PERx.LSPCLK and divider in the SYSCLK domain Fig.6-9 x39c datasheet. Seemingly resolver updates may need delay time added to ADC preset trigger source to account for any positional latency in the SPI data stream. Personally, If I was using an external resolver to determine rotor position it would be high speed EQEP input in the same clock domain as the ePWM and ADC peripherals. At first use FAST estimator to reduce up time then test positional accuracy via an EQEP position sensor. If latter plan B failed can then fall back on plan A regroup my ducks' try again later.  

  • Yes, you can decimate a higher PWM frequency to use a low control frequency, but it's meaningless to use a higher control frequency than the sampling frequency when low-side shunt sensing method is used.

    You may take a look at the related application notes about using low-side current sensing method and the trigger time point needed for ADC by this method.

  • but it's meaningless to use a higher control frequency than the sampling frequency when low-side shunt sensing method is used.

    Your speaking of the current control using low side shunts? Above I was referring to phase (alpha beta) versus using high speed EQEP for rotor position (alpha beta) into FAST estimator. Since SPI peripheral clock is in different domain it might complicate ADC sample timing. But phases don't need to provide any FAST feedback with EQEP position control module,  right?

  • You may take a look at the related application notes about using low-side current sensing method and the trigger time point needed for ADC by this method.

    BTW: There is nothing in the application notes (SPRUHJ1I – JANUARY 2013 – REVISED OCTOBER 2021) about PWM trigger events or sub module triggering EOC for rotor position control, only the TRM diagram below but PRD can produce 250µs INT at 4KHz modulation contrary to PWM filter pole text. However, it states RC filter should be changed (20 - 40) KHz PWM modulation for PMSM and nothing about AC motor filter pole. This thread late mentions AC motor (induction?) but many SPM such as (4 pole Anaheim) run on AC sinewave phase currents equally well. 

    The Filter Pole text suggesting only low frequency high speed motors generating voltages in the order of a few kilohertz should change the RC filter. Perhaps x39c TRM (technical resource manual) has more detail, this thread SVM module is being used seemingly for ACIM motor drive.

    I assume EQEP inputs (A phase, B phase, index) via SW position module translates, (bypassing Clarke) passing (alpha, beta) and speed (rads/sec) into FAST estimator digital voltage inputs and trajectory control. The position control module example does not have a visual diagram of that interconnection method.

    Perhaps that is what you were thinking since low side shunts are only used for current control block via FAST estimator, if latest MCSDK FAST library is used x39c. That new FAST library should make it somewhat less trouble to transition or migrate code to different MCU classes as to maintain backward compatibility.