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.

TMS320F28379D: ePWM ADC sync discrepancy

Part Number: TMS320F28379D


Tool/software:

Development board: TI LAUNCHXL F28379D 

I am working on  ePWM SOC triggered ADC conversion. 

Following are the system settings in my code:

SYSCLK=100 MHz (10 ns)

ADC Resolution: 12Bit

ADCCLK: 100/4 = 25 MHz (40 ns)

ACQPS = 7 (for 8 SYSCLK cycles --> 80ns) 

Conversion time for 12bit ADC (from reference manual) = 10.5 ADCCLK cycles = (10.5* 40nS = 420ns)

Expected time for single ADC conversion = ACQPS + conversion time 

= 80ns + 420ns = 500ns

GPIO67 is toggled in ADC ISR (done this with a expectation that GPIO toggle represents completion of  ADC conversion)

GPIO0 is used configured for EPWM1A output. 

I viewed EPWM1A and GPIO67 on logic analyzer. First waveform is EPWM1A output while second one is of GPIO67 toggled in the adca1_isr

The difference between the rising edges of the two signals was 875ns

My expectation from the code was, the ADC should complete converting the analog value in about 500ns (error of 10ns to 50ns is tolerable) as Soon as EPWM1A output is generated.

Below is the code for EPWM ADC sync

//#############################################################################
//
// FILE:   empty_bitfield_driverlib_main.c
//
// TITLE:  Empty Example
//
// Empty Bit-Field & Driverlib Example
//
// This example is an empty project setup for Bit-Field and Driverlib 
// development.
//
//#############################################################################
//
// 
// $Copyright:
// Copyright (C) 2013-2024 Texas Instruments Incorporated - http://www.ti.com/
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions 
// are met:
// 
//   Redistributions of source code must retain the above copyright 
//   notice, this list of conditions and the following disclaimer.
// 
//   Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the 
//   documentation and/or other materials provided with the   
//   distribution.
// 
//   Neither the name of Texas Instruments Incorporated nor the names of
//   its contributors may be used to endorse or promote products derived
//   from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// $
//#############################################################################

//
// Included Files
//
#include "F28x_Project.h"
#include "driverlib.h"
#include "device.h"

//
// Function Prototypes
//
void ConfigureADC(void);
void ConfigureEPWM(void);
void SetupADCEpwm(Uint16 channel);
interrupt void adca1_isr(void);


//
// Defines
//
#define RESULTS_BUFFER_SIZE 256


//
// Globals
//
Uint16 AdcaResults[RESULTS_BUFFER_SIZE];
Uint16 resultsIndex;
volatile Uint16 bufferFull;


void main(void)
{
//
// Step 1. Initialize System Control:
// PLL, WatchDog, enable Peripheral Clocks
// This example function is found in the F2837xD_SysCtrl.c file.
//
    InitSysCtrl();


//
// Step 2. Initialize GPIO:
// This example function is found in the F2837xD_Gpio.c file and
// illustrates how to set the GPIO to it's default state.
//
    InitGpio(); // Skipped for this example


    EALLOW;
    //  GPIO67 configuration for checking timer0 interrupts every 1msec
    GpioCtrlRegs.GPCLOCK.bit.GPIO67 = 0; //Unlock GPIO configuration registers for PORT C
    GpioCtrlRegs.GPCGMUX1.bit.GPIO67 = 0 ; // GPIOC GMUX for making pin 67 to function as GPIO
    GpioCtrlRegs.GPCMUX1.bit.GPIO67 = 0 ; // GPIOC MUX for making pin 67 to function as GPIO
    GpioCtrlRegs.GPCCSEL1.bit.GPIO67 = 0 ;// Select CPU1 as MASTER CORE
    GpioCtrlRegs.GPCDIR.bit.GPIO67 = 1 ; // Set GPIO 67 Direction AS OUTPUT
   
     //  GPIO0 configuration for  Output ePWM1A
    GpioCtrlRegs.GPALOCK.bit.GPIO0 = 0; //Unlock GPIO configuration registers for PORT C
    GpioCtrlRegs.GPAGMUX1.bit.GPIO0 = 0 ; // GPIOC MUX for making pin 0 to function as EPWM1A (O)
    GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1 ; // GPIOC MUX for making pin 0 to function as EPWM1A (O)
    GpioCtrlRegs.GPACSEL1.bit.GPIO0 = 0 ;// Select CPU1 as MASTER CORE
    GpioCtrlRegs.GPADIR.bit.GPIO0 = 1 ; // Set GPIO 0 Direction AS OUTPUT


    EDIS;


//
// Step 3. Clear all interrupts and initialize PIE vector table:
// Disable CPU interrupts
//
    DINT;


//
// Initialize the PIE control registers to their default state.
// The default state is all PIE interrupts disabled and flags
// are cleared.
// This function is found in the F2837xD_PieCtrl.c file.
//
    InitPieCtrl();


//
// Disable CPU interrupts and clear all CPU interrupt flags:
//
    IER = 0x0000;
    IFR = 0x0000;


//
// Initialize the PIE vector table with pointers to the shell Interrupt
// Service Routines (ISR).
// This will populate the entire table, even if the interrupt
// is not used in this example.  This is useful for debug purposes.
// The shell ISR routines are found in F2837xD_DefaultIsr.c.
// This function is found in F2837xD_PieVect.c.
//
    InitPieVectTable();


//
// Map ISR functions
//
    EALLOW;
    PieVectTable.ADCA1_INT = &adca1_isr; //function for ADCA interrupt 1
    EDIS;


//
// Configure the ADC and power it up
//
    ConfigureADC();


//
// Configure the ePWM
//
    ConfigureEPWM();


//
// Setup the ADC for ePWM triggered conversions on channel 0
//
    SetupADCEpwm(0);


//
// Enable global Interrupts and higher priority real-time debug events:
//
    IER |= M_INT1; //Enable group 1 interrupts
    EINT;  // Enable Global interrupt INTM
    ERTM;  // Enable Global realtime interrupt DBGM


//
// Initialize results buffer
//
    for(resultsIndex = 0; resultsIndex < RESULTS_BUFFER_SIZE; resultsIndex++)
    {
        AdcaResults[resultsIndex] = 0;
    }
    resultsIndex = 0;
    bufferFull = 0;


//
// enable PIE interrupt
//
    PieCtrlRegs.PIEIER1.bit.INTx1 = 1;


//
// sync ePWM
//
    EALLOW;
    CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = 1;


//
//take conversions indefinitely in loop
//
    do
    {
        //
        //start ePWM
        //
        EPwm1Regs.ETSEL.bit.SOCAEN = 1;  //enable SOCA
        EPwm1Regs.TBCTL.bit.CTRMODE = 2; //unfreeze, and enter up count mode


        //
        //wait while ePWM causes ADC conversions, which then cause interrupts,
        //which fill the results buffer, eventually setting the bufferFull
        //flag
        //
        while(!bufferFull);
        bufferFull = 0; //clear the buffer full flag


        //
        //stop ePWM
        //
        // EPwm1Regs.ETSEL.bit.SOCAEN = 0;  //disable SOCA
        // EPwm1Regs.TBCTL.bit.CTRMODE = 3; //freeze counter


        //
        //at this point, AdcaResults[] contains a sequence of conversions
        //from the selected channel
        //


        //
        //software breakpoint, hit run again to get updated conversions
        //
        //asm("   ESTOP0");
    }while(1);
}


//
// ConfigureADC - Write ADC configurations and power up the ADC for both
//                ADC A and ADC B
//
void ConfigureADC(void)
{
    EALLOW;


    //
    //write configurations
    //
    AdcaRegs.ADCCTL2.bit.PRESCALE = 6; //set ADCCLK divider to /4
    AdcSetMode(ADC_ADCA, ADC_RESOLUTION_12BIT, ADC_SIGNALMODE_SINGLE);


    //
    //Set pulse positions to late
    //
    AdcaRegs.ADCCTL1.bit.INTPULSEPOS = 1;


    //
    //power up the ADC
    //
    AdcaRegs.ADCCTL1.bit.ADCPWDNZ = 1;


    //
    //delay for 1ms to allow ADC time to power up
    //
    DELAY_US(1000);


    EDIS;
}


//
// ConfigureEPWM - Configure EPWM SOC and compare values
//
void ConfigureEPWM(void)
{
    EALLOW;
    // newly added
    CpuSysRegs.PCLKCR2.bit.EPWM1 = 1;
    // Assumes ePWM clock is already enabled
    EPwm1Regs.ETSEL.bit.SOCAEN    = 1;    // Disable SOC on A group
    EPwm1Regs.ETSEL.bit.SOCASEL    = 4;   // Select SOC on up-count
    EPwm1Regs.ETPS.bit.SOCAPRD = 1;       // Generate pulse on 1st event
    EPwm1Regs.CMPA.bit.CMPA = 0x0800;     // Set compare A value to 2048 counts
    EPwm1Regs.TBPRD = 0x1000;             // Set period to 4096 counts
    //EPwm1Regs.TBCTL.bit.CTRMODE = 3;      // freeze counter
   


    EPwm1Regs.TBCTL.bit.CTRMODE = 2;  // Up-Down count mode for center-aligned PWM
   // EPwm1Regs.TBPRD = PWM_PERIOD;     // Set period
    EPwm1Regs.TBCTL.bit.PHSEN = 0;    // Disable phase loading
    EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0; // Divider value for ePWM clock divides by .. /1
    EPwm1Regs.TBCTL.bit.CLKDIV = 0; // Divider value for ePWM clock divides by .. /1


    //EPwm1Regs.CMPA.bit.CMPA = PWM_PERIOD / 2; // Initial duty cycle 50%
   
    //EPWM1A
    EPwm1Regs.AQCTLA.bit.CAU = 2;  // Set PWM1A on counter up
    EPwm1Regs.AQCTLA.bit.CAD = 1;  // Clear PWM1A on counter down
    CpuSysRegs.PCLKCR0.bit.TBCLKSYNC = 1;
    
    // // EPWM1B:(complement of A)
    // EPwm1Regs.AQCTLB.bit.CAU = 1;  // Clear
    // EPwm1Regs.AQCTLB.bit.CAD = 2;  // Set
    EDIS;
}


//
// SetupADCEpwm - Setup ADC EPWM acquisition window
//
void SetupADCEpwm(Uint16 channel)
{
    Uint16 acqps;


    //
    // Determine minimum acquisition window (in SYSCLKS) based on resolution
    //
    if(ADC_RESOLUTION_12BIT == AdcaRegs.ADCCTL2.bit.RESOLUTION)
    {
        acqps = 7; //80ns
    }
    else //resolution is 16-bit
    {
        acqps = 63; //320ns
    }


    //
    //Select the channels to convert and end of conversion flag
    //
    EALLOW;
    AdcaRegs.ADCSOC0CTL.bit.CHSEL = channel;  //SOC0 will convert pin A0
    AdcaRegs.ADCSOC0CTL.bit.ACQPS = acqps; //sample window is 100 SYSCLK cycles
    AdcaRegs.ADCSOC0CTL.bit.TRIGSEL = 5; //trigger on ePWM1 SOCA/C
    AdcaRegs.ADCINTSEL1N2.bit.INT1SEL = 0; //end of SOC0 will set INT1 flag
    AdcaRegs.ADCINTSEL1N2.bit.INT1E = 1;   //enable INT1 flag
    AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; //make sure INT1 flag is cleared
    EDIS;
}


//
// adca1_isr - Read ADC Buffer in ISR
//
interrupt void adca1_isr(void)
{
    //newly added
     GpioDataRegs.GPCTOGGLE.bit.GPIO67 = 1;


    AdcaResults[resultsIndex++] = AdcaResultRegs.ADCRESULT0;
    if(RESULTS_BUFFER_SIZE <= resultsIndex)
    {
        resultsIndex = 0;
        bufferFull = 1;
    }


    AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; //clear INT1 flag


    //
    // Check if overflow has occurred
    //
    if(1 == AdcaRegs.ADCINTOVF.bit.ADCINT1)
    {
        AdcaRegs.ADCINTOVFCLR.bit.ADCINT1 = 1; //clear INT1 overflow flag
        AdcaRegs.ADCINTFLGCLR.bit.ADCINT1 = 1; //clear INT1 flag
    }


    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}


//
// End of file
//

Questions:

1. am I right in expecting 500ns between EPWM1A toggle and GPIO67 toggle of adc isr ?

2. Why is the ADC isr being triggered 875ns after EPWM1A goes high ? 

Need more guidance from the team on what things are going wrong and how to achieve this difference near to 500ns. 

  • Hello,

    Initially the latency you experience in ISR could be because of context saving that takes 20-30 cycles to execute. After those 20-30 cycles, ISR would actually start executing.

  • Despite having bare minimal code in the main loop, still will it take 20-30 SYSCLK for context switching ?

    Is time for context switching dependent on the number of local variables and operations (stack utilization) ?

    If yes then is my context switching time bound to increase from 20 to 30 SYSCLK ?

  • Hello,

    The context switching time, also known as the interrupt latency, is indeed dependent on the number of local variables and operations (stack utilization) in the interrupted code.

    When an interrupt occurs, the CPU needs to save the current state of the program, including the registers and the program counter, before executing the interrupt service routine (ISR). 

    The time it takes to perform context switching depends on several factors, including:

    - Number of registers to save: The more registers that need to be saved, the longer the context switching time.
    - Stack utilization: If the interrupted code has a large number of local variables or uses a lot of stack space, it can increase the context switching time.
    - Interrupt priority: If the interrupt has a high priority, the CPU may need to perform additional checks or handling, which can increase the context switching time.

    In your case, even with a bare minimal code in the main loop, the context switching time would take some additional clock cycles. On average, it would be 20-30 cycles. To determine exact number of clock cycles it takes for context switching, you could perform GPIO profiling.

    To minimize the context switching time, you can try to:

    - Use a small stack size
    - Minimize local variables 
    - Use registers and optimize interrupt handling 

  • Here is my code for GPIO profiling. In the above code I am only making GPIO to high and low. My observed waveform has

    Rise time:  ~10ns

    Fall time: ~10ns

    High time: ~60ns

    Low time: ~70ns

    Questions:

    1. Am I doing GPIO profiling correctly ? If not, then how can I do it ?

    2. If my procedure is correct, then how can I infer context switching time from this ?

  • Hello,

    Your code as it now sets up a simple loop that toggles GPIO 67 high and low continuously, but does not provide you with needed info. 

    To accurately measure the context switching time, you'll need to create a scenario where the CPU is switching between main() and ISR()  and then measure the time it takes for the GPIO signal to toggle.

    You could set it up something like:

    // Main function
    void main() {
        //Main code.......


          GpioDataRegs.GPCSET.bit.GPIO67 = 1;  
          }
    }

    // ISR
    void ISR1() {
    GpioDataRegs.GPCCLEAR.bit.GPIO67 = 1;

    //.....ISR code
    }

    Then measure how long it takes for GPIO to toggle. Hope this helps.