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.

Understanding C2000 Interrupt Latency

Hi

There has been a bit written on this, but I am having a hard job understanding why I am seeing a 350nsec delay from when the PWM output goes low to before I can execute something in the isr generated by the epwm1 module. I have read that there are 14 cycles needed for an internal interrupt i.e., 175nsec at  a 80MHz clock.

Here is my code which makes use of Biricha Digital Power API's. All it is doing is producing PWM and toggling GPIO18 high in the isr. It is running on a 28069 control stick. The bold text of the isr disassembly is the GPIO toggle that produces a 25nsec wide high pulse that I use for calculating the latency, but what is all the rest and is it avoidable to reduce the interrupt latency? All timing was made using a scope and I was using the CCS4 debugger to load the code to RAM ( I am not absolutely sure - could Flash wait states really double the delay?).

Thanks to those who might be able to shed some light.

#include "csl.h"

uint32_t freq;

uint16_t ticks;

interrupt void isr_pwm1( void )

{

      GpioDataRegs.GPADAT.all = 0x00040000;

      GpioDataRegs.GPADAT.all = 0x00000000;

      PWM_ackInt(PWM_MOD_1); // clears the ePWM and PIE group flags

}

void main( void )

{    

    /* Initialize the MCU */

    SYS_init();    

    freq = 116000;

    ticks = PWM_freqToTicks(freq);

/* Setup PWM */

    PWM_config( PWM_MOD_1, ticks, PWM_COUNT_DOWN );

    PWM_setDeadBandHalfBridge( PWM_MOD_1, 10, HALF_BRIDGE_PN );

    PWM_setDutyA(PWM_MOD_1, ticks/2);

    PWM_setDutyB(PWM_MOD_1, ticks/2);

    PWM_setCallback( PWM_MOD_1, isr_pwm1,PWM_INT_ZERO, PWM_INT_PRD_1 );   

    INT_enableGlobal(true);    

    GPIO_config( GPIO_18, GPIO_DIR_OUT, false);  

    while(1)

    {

    }

}

if I disassemble the isr it gives:

 isr_pwm1:

0x3D8546:   761B        ASP

0x3D8547:   FFF0        PUSH         RB

0x3D8548:   A8BD        MOVL         *SP++, XAR4

0x3D8549:   E20000BD    MOV32        *SP++, STF

0x3D854B:   E6300600    SETFLG       RNDF32=1,RNDF64=1

0x3D854D:   2942        CLRC         OVM|PAGE0

0x3D854E:   5616        CLRC         AMODE

0x3D854F:   761F01BF    MOVW         DP, #0x1bf

0x3D8551:   8F040000    MOVL         XAR4, #0x040000

0x3D8553:   A800        MOVL         @0x0, XAR4

0x3D8554:   0200        MOVB         ACC, #0

0x3D8555:   1E00        MOVL         @0x0, ACC

0x3D8556:   761F01A0    MOVW         DP, #0x1a0

0x3D8558:   56BF011C    MOVB         @0x1c, #0x01, UNC

0x3D855A:   761F0033    MOVW         DP, #0x33

0x3D855C:   56BF0421    MOVB         @0x21, #0x04, UNC

0x3D855E:   E28000BE    MOV32        STF, *--SP

0x3D8560:   8ABE        MOVL         XAR4, *--SP

0x3D8561:   FFF1        POP          RB

0x3D8562:   7617        NASP

0x3D8563:   7602        IRET

  • Andrew,

    You seem to have 14 cycles unaccounted for.  Your ISR is written in C, and is:

    interrupt void isr_pwm1( void )
    {
        GpioDataRegs.GPADAT.all = 0x00040000;
        GpioDataRegs.GPADAT.all = 0x00000000;
        PWM_ackInt(PWM_MOD_1); // clears the ePWM and PIE group flag
    }

    Have you examined the generated assembly code to see what instructions are occurring prior to actually pulsing the GPIO pin?  There will be some addressing setup, and also any extra context saving the compiler decides to do.  There is also a stack align instruction I recall.

    Regards,

    David

     

  • Agree with David - the below instructions take time.  It also looks like the ISR is executing from Flash.   If you execute the ISR from RAM the cycles should reduce some.

    Andrew Green1 said:

    0x3D8546:   761B        ASP

    0x3D8547:   FFF0        PUSH         RB

    0x3D8548:   A8BD        MOVL         *SP++, XAR4

    0x3D8549:   E20000BD    MOV32        *SP++, STF

    0x3D854B:   E6300600    SETFLG       RNDF32=1,RNDF64=1

    0x3D854D:   2942        CLRC         OVM|PAGE0

    0x3D854E:   5616        CLRC         AMODE

    This While loop has the potential to add time depending on when the interrupt is recieved.  If the CPU happens to be executing the associated branch, for example, it must complete the branch before the interrupt will be taken.   You can instead put the CPU in idle asm("    IDLE"); so that instructions won't hold off interrupts when they come in.

    Andrew Green1 said:

        while(1)

        {

        }

  • Hi David and Lori

    Thanks for taking the time to answer. I took out the while loop and replaced it with an IDLE command as you suggested - no observable difference.

    I recompiled using a linker command file called csl_RAM_LNK.cmd provided by BIRICHA, so I assume it was running in RAM, also no observable difference. I just do not know why the compiler is putting in those extra op codes in the isr. I will ask Biricha, perhaps somewhere in the routines they provide there are actions being performed that causes the compiler to have to initiate a few extras commands on receiving an interrupt.

    It would not have anything to do with debugger or compiler settings would it?  I can probably work with the delay as it stands, I just want to be sure I am doing everything possible to reduce it to the minimum.

    Kind Regards

    Andrew

  • Andrew,

    Andrew Green1 said:

    I just do not know why the compiler is putting in those extra op codes in the isr. I will ask Biricha, perhaps somewhere in the routines they provide there are actions being performed that causes the compiler to have to initiate a few extras commands on receiving an interrupt.

     
    This has nothing to do with Bircha.  A processor cannot just stop on a dime and process the C-code you have in an ISR.  It needs to take care of housekeeping first.

    Andrew Green1 said:

    0x3D8546:   761B        ASP

    0x3D8547:   FFF0        PUSH         RB

    0x3D8548:   A8BD        MOVL         *SP++, XAR4

    0x3D8549:   E20000BD    MOV32        *SP++, STF

    0x3D854B:   E6300600    SETFLG       RNDF32=1,RNDF64=1

    0x3D854D:   2942        CLRC         OVM|PAGE0

    0x3D854E:   5616        CLRC         AMODE

    The ASP is a stack alignment instruction.  It has to be there for any ISR.  Next is a PUSH RB.  Seems to be saving the repeat block counter.  I think this is standard procedure for floating point devices.  You've then got a few MOV instructions doing some context saving of registers that are not automatically saved.  You've then got a SETFLG which is getting the FPU into the expected known state for the ISR (because since this is an interrupt, the compiler doesn't know what state the FPU is in).  Similar for the CLRC instructions - CPU stuff is being setup.

    If you compile a completely empty ISR, say 'interupt void foo(void)', you get this assembly code:

    _foo:
            ASP       ; [CPU_]
            PUSH      RB                    ; [CPU_]
            MOV32     *SP++,STF             ; [CPU_]
            SETFLG    RNDF32=1, RNDF64=1    ; [CPU_]
            CLRC      PAGE0,OVM             ; [CPU_]
            CLRC      AMODE                 ; [CPU_]

            MOV32     STF,*--SP             ; [CPU_]
            POP       RB                    ; [CPU_]
            NASP      ; [CPU_]
            IRET      ; [CPU_]

    Same stuff you have except for not having the MOVL *SP++, XAR4.  This context save of XAR4 would only be done if XAR4 register was used in the ISR.  In your case, it must be being used.

    You are showing 7 instructions.  Each of these is single-cycle (in RAM).  So, 7 additional cycles.  You've then got 3 more instructions before the GPIO status is changed:

    0x3D854F:   761F01BF    MOVW         DP, #0x1bf

    0x3D8551:   8F040000    MOVL         XAR4, #0x040000

    0x3D8553:   A800        MOVL         @0x0, XAR4

    Each of these is also single cycle.  So, 3 more cycles.  Total so far is 10.  I believe there were 14 cycles unaccounted for according to your measurements.  The other 4 cycles are someplace.  Perhaps a cycle in the scope reading, a few cycles for your while(1) or IDLE loop, etc.  They're there.  It is normal.

     

    Regards,

    David 

  • Thanks for clearing that up for me David.

    Kind Regards

    Andrew Green

  • Andrew,

    Lori has pointed out to me that you can get slightly improved results if you use the INTERRUPT compiler pragma to declare your ISR and use the high-priority interrutp specifier.  This is instead of the interrupt keyword that you are using now.  The FPU has shadow registers that can be used for faster context save/restore.  But you cannot nest interrupts in the ISR if you use them.  High-priority interrupt tells the compiler that you are not nesting interrupts in that ISR.  You can look up the INTERRUPT pragma in the compiler guide, SPRU514E, p.106.

    #pragma INTERRUPT (foo, HPI);    // High priority interrupt
    void foo(void)
    {
    }

    Generates this code on C2000 compiler v6.2.0B1:
    _foo:
            ASP       ; [CPU_]
            SAVE      RNDF32=1, RNDF64=1    ; [CPU_]
            CLRC      PAGE0,OVM             ; [CPU_]
            CLRC      AMODE                 ; [CPU_]

            RESTORE   ; [CPU_]
            NASP      ; [CPU_]
            IRET      ; [CPU_]

    --------------------------

    #pragma INTERRUPT (foo, LPI);   // Low priority interrupt
    void foo(void)
    {
    }

    Generates this code on C2000 compiler v6.2.0B1:

    _foo:
            ASP       ; [CPU_]
            PUSH      RB                    ; [CPU_]
            MOV32     *SP++,STF             ; [CPU_]
            SETFLG    RNDF32=1, RNDF64=1    ; [CPU_]
            CLRC      PAGE0,OVM             ; [CPU_]
            CLRC      AMODE                 ; [CPU_]

            MOV32     STF,*--SP             ; [CPU_]
            POP       RB                    ; [CPU_]
            NASP      ; [CPU_]
            IRET      ; [CPU_]

     

     Per the compiler guide, the interrupt keyword is the same as using the pragma with LPI.

     

    - David