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.

TMS320F280041: Problem with first PWM period using the ePWM Module

Part Number: TMS320F280041
Other Parts Discussed in Thread: C2000WARE

Hello TI Community,

I'm working on an ISR-driven ePWM implementation to drive WS2813-Mini LEDs on a C2000 MCU, the TMS320F280041 (F28004x family, using C2000Ware DriverLib and syscfg enabled Project). 

The goal is to generate a continuous bit stream with precise duty cycles for '0' (300ns high / 500ns low) and '1' (500ns high / 300ns low) at 800ns period (TBPRD=79 @ 100MHz, up-count mode).

So far my strategy to achieve this is to set up a simple ePWM module, which will have an interrupt at end of every period (counter = period), in this ISR we shall fetch the CMP value for the module's comparator A to be used in the next cycle, to achieve the desired LED Encoding.

For this I decided to go with the following ePWM intialization:

void EPWM_init()
{
	EPWM_setEmulationMode(LED_DRIVER_BASE, EPWM_EMULATION_FREE_RUN);
	
    EPWM_setClockPrescaler(LED_DRIVER_BASE, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1);	
    EPWM_setTimeBasePeriod(LED_DRIVER_BASE, 200);	
	
    EPWM_setTimeBaseCounter(LED_DRIVER_BASE, 0);	
    EPWM_setTimeBaseCounterMode(LED_DRIVER_BASE, EPWM_COUNTER_MODE_UP);	
	
    EPWM_disablePhaseShiftLoad(LED_DRIVER_BASE);	
    EPWM_setPhaseShift(LED_DRIVER_BASE, 0);
	
    EPWM_setCounterCompareValue(LED_DRIVER_BASE, EPWM_COUNTER_COMPARE_A, 0);	
    EPWM_setCounterCompareShadowLoadMode(LED_DRIVER_BASE, EPWM_COUNTER_COMPARE_A, EPWM_COMP_LOAD_ON_CNTR_ZERO);	
    EPWM_setCounterCompareValue(LED_DRIVER_BASE, EPWM_COUNTER_COMPARE_B, 0);	
    EPWM_setCounterCompareShadowLoadMode(LED_DRIVER_BASE, EPWM_COUNTER_COMPARE_B, EPWM_COMP_LOAD_ON_CNTR_ZERO);
	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);	
    EPWM_setActionQualifierAction(LED_DRIVER_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);
	
    EPWM_setRisingEdgeDelayCountShadowLoadMode(LED_DRIVER_BASE, EPWM_RED_LOAD_ON_CNTR_ZERO);	
    EPWM_disableRisingEdgeDelayCountShadowLoadMode(LED_DRIVER_BASE);	
    EPWM_setFallingEdgeDelayCountShadowLoadMode(LED_DRIVER_BASE, EPWM_FED_LOAD_ON_CNTR_ZERO);	
    EPWM_disableFallingEdgeDelayCountShadowLoadMode(LED_DRIVER_BASE);	
    EPWM_enableInterrupt(LED_DRIVER_BASE);	
    EPWM_setInterruptSource(LED_DRIVER_BASE, EPWM_INT_TBCTR_PERIOD);	
    EPWM_setInterruptEventCount(LED_DRIVER_BASE, 1);
}

Each time and LED configuration is requested, this function is called: 

void ledDriverSendConfig(void)
{
    // Don't start new transmission if one is already in progress
    if (gLedDriver.isTransmitting)
    {
        return;
    }
    
    // Build the CMPA sequence from current configuration
    ledDriverBuildCmpaSequence();
    
    // Initialize transmission state
    gLedDriver.currentIdx = 1U;
    gLedDriver.isTransmitting = true;

    // CRITICAL: Disable motor control ISR during LED transmission
    // This prevents motor ISR from interrupting LED data stream
    Interrupt_disable(INT_ADCC_CONFIG_1); 

    // Make sure counter is frozen to prepare for first PWM period
    EPWM_setTimeBaseCounterMode(LED_DRIVER_BASE, EPWM_COUNTER_MODE_STOP_FREEZE);
    
    // Reset ePWM counter to start fresh
    EPWM_setTimeBaseCounter(LED_DRIVER_BASE, 0U);
    
    // Load first CMPA value
    EPWM_setCounterCompareValue(LED_DRIVER_BASE, 
                                EPWM_COUNTER_COMPARE_A, 
                                gLedDriver.cmpaSequence[0]);
    
    // Clear any pending interrupt flags
    EPWM_clearEventTriggerInterruptFlag(LED_DRIVER_BASE);
    
    // Enable ePWM interrupt to start transmission
    EPWM_enableInterrupt(LED_DRIVER_BASE);
    
    // Start ePWM counter
    EPWM_setTimeBaseCounterMode(LED_DRIVER_BASE, EPWM_COUNTER_MODE_UP);
}

In the ISR routine I run the following:

__interrupt void ledDriverISR(void)
{
    GPIO_writePin(isrLedDriver, 1);
    
    if (gLedDriver.currentIdx < gLedDriver.sequenceSize)
    {
        uint16_t cmpaValue = gLedDriver.cmpaSequence[gLedDriver.currentIdx];
        EPWM_setCounterCompareValue(LED_DRIVER_BASE, EPWM_COUNTER_COMPARE_A, cmpaValue);
        gLedDriver.currentIdx++;
    }
    else
    {
        EPWM_setTimeBaseCounterMode(LED_DRIVER_BASE, EPWM_COUNTER_MODE_STOP_FREEZE);
        EPWM_disableInterrupt(LED_DRIVER_BASE);
        Interrupt_enable(INT_ADCC_CONFIG_1);  // Re-enable motor int
        gLedDriver.isTransmitting = false;
        gLedDriver.currentIdx = 0U;
    }
    
    // Clear ePWM interrupt flag
    EPWM_clearEventTriggerInterruptFlag(LED_DRIVER_BASE);
    
    // Clear PIE interrupt flag for this group
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP3);
    
    // Optional: Toggle GPIO for timing measurement
    GPIO_writePin(isrLedDriver, 0);
}

I've also placed a GPIO toggle at the beginning of the ePWM (counter = period) interrupt.

However, I'm facing some issues:

  1. Problem 1: The first duty cycle seems to get corrupt, only after one PWM period the module's output is the desired one. If my first desired high pulse duration is of 300ns. I get the following waveform output:
    1. image.png
    2. Legend: Red Waveform = ledISR | Orange Waverform = Led Config Signal, EPWM Output
    3. Analysis:
      1. Notice how the first cycle is not correct (1.548 us instead of 300ns).
      2. Notice how the led interrupt seems to fire mid duty cycle, when it was programmed to fire when counter = period, which happens for the subsequent cycles.
    4. Any idea why this could be happening?
  2. Problem 2: Right now I'm operating at a 2us PWM period. The desired one is actually 800ns, I did this because I'm facing some overhead issues related to the __interrupt void ledDriverISR(void) routine. I'm thinking of implementing DMA in order to reduce the next CMP value fetch operation overhead. What are your thoughts? I can't think of any other way to do this through a PWM, without using an interrupt at end of each cycle...
     

Thank you for your attention.

Regards,

Martin Blocher

  • Hi Martin,

    Can you ensure TBCLKSYNC gets enabled after PWM has been initialized? It should look like the pseudo below.

    TBCLKSYNC = 0

    init PWM

    TBCLKYSNC = 1

    Do you still observe the erroneous first period?

    Problem 2: Right now I'm operating at a 2us PWM period. The desired one is actually 800ns, I did this because I'm facing some overhead issues related to the __interrupt void ledDriverISR(void) routine. I'm thinking of implementing DMA in order to reduce the next CMP value fetch operation overhead. What are your thoughts? I can't think of any other way to do this through a PWM, without using an interrupt at end of each cycle...

    DMA will reduce the next CMP value fetch overhead. If you have available DMAs would be great to use in this case.

    Best regards,

    Ryan Ma