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:
- 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:

- Legend: Red Waveform = ledISR | Orange Waverform = Led Config Signal, EPWM Output
- Analysis:
- Notice how the first cycle is not correct (1.548 us instead of 300ns).
- 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.
- Any idea why this could be happening?
- 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