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.

Interrupt Handlers and C Code

I have a question about the compilation of Tiva C interrupt handlers.


(1) My understanding is that when an IRQ (e.g. peripheral) interrupt occurs the hardware saves the following registers on the stack from high addresses to low addresses: xPSR, PC, LR, R12, R3, R2, R1, R0. The SP points at R0. Other general purpose registers are not saved on the stack.


(2) An interrupt handler and the subroutines that it calls should not use registers that are not saved. How does one inform the compiler not to use these unsaved registers? One could, of course, explicitly save the other registers, but I've never seen example C-code that does this.


(3) I have used the C construct: #pragma INTERRUPT( foo ) to indicate that subroutine foo is interrupt code. I don't know if this works. However, many examples show the interrupt handler to call other functions  in DriverLib or SensorLib. How do these other functions know not to use the unsaved registers?

How does one write safe interrupt handlers, use library subroutines, and insure the unsaved CPU state is not damaged?


GJM

  • Hi,

    First, it is wise to look for the following documents on TI site:

    a) spnu118k.pdf - ARM Assembler Language Tools User Guide, v5.0

    b) spnu151i.pdf = ARM Optimizing C/C++ Compiler v5.1 User Guide

    Here you will find out how the compiler manages the source code - your (1) and (2) are best covered by these documents. (for (2) - if registers R4-R11 are used and then needed, they are pushed separately on stack by the compiler).

    As for (3) - for Cortex-Mx processors all interrupts are declared as "weak", explicitly or not (CCS does not, but takes care about) and if you declare an interrupt vector in startup file and the same name is later used for an interrupt function, then the linker uses that name in vector table as your interrupt routine. No need for #pragma constructs (it is known/allowed to not emit any warning if not implemented).

    Petrei

  • Petrei:


    Thanks for the quick response and your time.


    I did look at the suggest manuals. In the ARM Optimizing... manual section 5.5.2 describes the "interrupt" keyword. I'm using the TI v5.1.6 compiler.

    I am trying to work with the SensorHub I2CMaster.

    Here is my source code:

    //
    // The interrupt handler for the I2C module.
    //
    extern void I2C7_IntServiceRoutine( void );

    interrupt void I2C7_IntServiceRoutine( void ) {

        ui32_NbrI2C7Interrupts++;

        //
        // Call the I2C master driver interrupt handler.
        //
        I2CMIntHandler( &sI2C7_Instance );
        }

    I2C7_IntServiceRoutine is in the interrupt vector table at interrupt 119.


    Here is the assembly code the compiler produces (sorry for the formatting):

    $C$DW$55    .dwtag  DW_TAG_subprogram, DW_AT_name("I2C7_IntServiceRoutine")
        .dwattr $C$DW$55, DW_AT_low_pc(I2C7_IntServiceRoutine)
        .dwattr $C$DW$55, DW_AT_high_pc(0x00)
        .dwattr $C$DW$55, DW_AT_TI_symbol_name("I2C7_IntServiceRoutine")
        .dwattr $C$DW$55, DW_AT_external
        .dwattr $C$DW$55, DW_AT_TI_begin_file("../Task_I2C7_Handler.c")
        .dwattr $C$DW$55, DW_AT_TI_begin_line(0x4d)
        .dwattr $C$DW$55, DW_AT_TI_begin_column(0x10)
        .dwattr $C$DW$55, DW_AT_decl_file("../Task_I2C7_Handler.c")
        .dwattr $C$DW$55, DW_AT_decl_line(0x4d)
        .dwattr $C$DW$55, DW_AT_decl_column(0x10)
        .dwattr $C$DW$55, DW_AT_TI_interrupt
        .dwattr $C$DW$55, DW_AT_TI_max_frame_size(0x08)
        .dwpsn    file "../Task_I2C7_Handler.c",line 77,column 47,is_stmt,address I2C7_IntServiceRoutine,isa 1

        .dwfde $C$DW$CIE, I2C7_IntServiceRoutine

    ;*****************************************************************************
    ;* FUNCTION NAME: I2C7_IntServiceRoutine                                     *
    ;*                                                                           *
    ;*   Regs Modified     : A1,A2,A3,A4,V9,SP,LR,SR,D0,D0_hi,D1,D1_hi,D2,D2_hi, *
    ;*                           D3,D3_hi,D4,D4_hi,D5,D5_hi,D6,D6_hi,D7,D7_hi,   *
    ;*                           FPEXC,FPSCR                                     *
    ;*   Regs Used         : A1,A2,A3,A4,V9,SP,LR,SR,D0,D0_hi,D1,D1_hi,D2,D2_hi, *
    ;*                           D3,D3_hi,D4,D4_hi,D5,D5_hi,D6,D6_hi,D7,D7_hi,   *
    ;*                           FPEXC,FPSCR                                     *
    ;*   Local Frame Size  : 0 Args + 0 Auto + 4 Save = 4 byte                   *
    ;*****************************************************************************
    I2C7_IntServiceRoutine:
    ;* --------------------------------------------------------------------------*
        .dwcfi    cfa_offset, 0
            PUSH      {LR}                  ; [DPU_3_PIPE]
        .dwcfi    cfa_offset, 4
        .dwcfi    save_reg_to_mem, 14, -4
            SUB       SP, SP, #4            ; [DPU_3_PIPE]
        .dwcfi    cfa_offset, 8
        .dwpsn    file "../Task_I2C7_Handler.c",line 79,column 2,is_stmt,isa 1
            LDR       A2, $C$CON1           ; [DPU_3_PIPE] |79|
            LDR       A1, [A2, #0]          ; [DPU_3_PIPE] |79|
            ADDS      A1, A1, #1            ; [DPU_3_PIPE] |79|
            STR       A1, [A2, #0]          ; [DPU_3_PIPE] |79|
        .dwpsn    file "../Task_I2C7_Handler.c",line 84,column 2,is_stmt,isa 1
            LDR       A1, $C$CON2           ; [DPU_3_PIPE] |84|
    $C$DW$56    .dwtag  DW_TAG_TI_branch
        .dwattr $C$DW$56, DW_AT_low_pc(0x00)
        .dwattr $C$DW$56, DW_AT_name("I2CMIntHandler")
        .dwattr $C$DW$56, DW_AT_TI_call
            BL        I2CMIntHandler        ; [DPU_3_PIPE] |84|
            ; CALL OCCURS {I2CMIntHandler }  ; [] |84|
        .dwpsn    file "../Task_I2C7_Handler.c",line 85,column 2,is_stmt,isa 1
            ADD       SP, SP, #4            ; [DPU_3_PIPE]
        .dwcfi    cfa_offset, 4
    $C$DW$57    .dwtag  DW_TAG_TI_branch
        .dwattr $C$DW$57, DW_AT_low_pc(0x00)
        .dwattr $C$DW$57, DW_AT_TI_return
            POP       {PC}                  ; [DPU_3_PIPE]
        .dwcfi    cfa_offset, 0
            ; BRANCH OCCURS                  ; []
        .dwattr $C$DW$55, DW_AT_TI_end_file("../Task_I2C7_Handler.c")
        .dwattr $C$DW$55, DW_AT_TI_end_line(0x55)
        .dwattr $C$DW$55, DW_AT_TI_end_column(0x02)
        .dwendentry
        .dwendtag $C$DW$55

    My take is when the ISR handler is invoked, the LR is saved on the stack. I'm not sure why an additional 4 bytes are allocated on the stack. The variable is incremented, then the I2CMIntHandler is called with the specific I2C instance. The stack is restored and a normal, e.g. restore the saved LR to the PC (not interrupt) return is taken.

    Using the #pragma construct gives the same results.


    I'd like to understand the interaction between hardware interrupts and C-code for three reasons: (1) I'm curious, (2) I want to understand the link between hardware and software, and (3) I need to teach this stuff.


    How do I indicate to the C-compiler that the following subroutine is an interrupt service routine and (a) tread lightly on the CPU state or (b) save and restore all the CPU state upon entry and exit?

    Thanks for your help.


    GJM

  • Hi,

    I will start from end: to inform the compiler about an interrupt, you must:

    a) in startup_ccs.c declare extern void my_i2cIntHandler(void);

    b) place this name in interrupt vector table, instead IntDefaultHandler; 

    c) in your file declare the same as in startup file;

    d) insert the definition of the interrupt;

    void my_i2cIntHandler(void){

    // place the code here

    }

    About the code and listing: your code seems to call a function inside - for that the compiler reserve some space for the registers to be preserved (as I said in my first post). The listing is a little bit difficult to read, a lot of conventions, but is explicit in registers used and the space used. An easier to read listing is that one generated by GCC, but without additional info.

    Your code seems to use a wrapper to another function -usual this is not needed, I understand your construct only to increment a variable - may be inserted in the other function. The goal is to keep interrupts short.

    Usually just write what you need in interrupts, the rest is the compiler job and must guarantee you the correct registers management.

    Petrei

  • Gary Minden said:
    ...many examples show the interrupt handler to call other functions  in DriverLib or SensorLib.

    And - as poster Petrei has often opined - such may not prove, "best/brightest" code design!  Long ago - hallowed halls of UCLA - we were taught that (almost all) interrupt service should be, "Short - sweet!" 

    One notes hundreds of posts here - (shipwrecks really) perhaps due to SensorLib's following the practice your quote notes.  May be "unwise" to base your course design upon examples on/around, "SensorLib..."  (my belief)

    Far safer - methinks to set key control/steering bits w/in the handlers - yet, "read/react" to them from the (relative) safety found w/in main().

  • Petrei:

    With respect.

    Hi,

    I will start from end: to inform the compiler about an interrupt, you must:

    a) in startup_ccs.c declare extern void my_i2cIntHandler(void);

    b) place this name in interrupt vector table, instead IntDefaultHandler; 

    c) in your file declare the same as in startup file;

    d) insert the definition of the interrupt;

    >>>>I understand these steps. I have been programming interrupts since the Digital Equipment Corporation PDP-15 and PDP-11, the IBM 7094, the Intel 8008, and the TMS 9900. I have other interrupt routines running on the TM4C1294 LaunchPad and have programmed their entries into the startup_ccs.c file.

    About the code and listing: your code seems to call a function inside - for that the compiler reserve some space for the registers to be preserved (as I said in my first post). The listing is a little bit difficult to read, a lot of conventions, but is explicit in registers used and the space used. An easier to read listing is that one generated by GCC, but without additional info.

    Your code seems to use a wrapper to another function -usual this is not needed, I understand your construct only to increment a variable - may be inserted in the other function. The goal is to keep interrupts short.

    >>>> I am following the example of using the I2CMaster from the SensorLib document. In that document it shows a user level interrupt routine that calls the I2CMIntHandler with the specific I2C peripheral instance. The reason for incrementing the local variable is a simple debug approach to know that the ISR was entered.


    >>>>Yes, interrupts should be short. However, the implementation of the I2CMIntHandler subroutine in SensorLib makes them rather long.

    Usually just write what you need in interrupts, the rest is the compiler job and must guarantee you the correct registers management.

    >>>>This statement is scary. Never, ever, trust software (compilers). You must know how your compilers translates your program into executable code. Otherwise, people may die.


    >>>>My question remains, how do I inform the TI C-compiler that I am compiling an interrupt routine and that it should save (protect) CPU state that the hardware does not?

    Thanks,

    GJM

  • Hi,

    Well, you should see/take into account also the evolutionary part of the compilers -every one is looking for progress. A big step forward was in Cortex-Mx tools, where you do not need to declare anything about interrupt routines, except  their names, and that is due to the "weak" attribute, I told you about - in CCS is not specifically expressed, the compiler is smart enough to avoid that. But with other tools (Keil, GCC) you will see these declarations. Also the classic ARM7TDMI still uses the attribute " interrupt " to declare a function as interrupt routine. Not anymore the case here for Cortex, so my statement is not scary.(of coarse if is the case, you may inspect the listing and decide if it correct or not).

    About the CPU state protection - first this microcontroller save on stack automatically some working registers, according with ARM standards. If needed more, as in your case, it just adjust the stack. This is not the first microcontroller which save on stack all registers (some from Motorola/Freescale does the same), easing the developer's job. 

    Another new fact and treatment here is the interrupt management - while classical mode of disabling interrupts globally can be used, here you may disable just a specific interrupt since this is possible, each interrupt has its own enable/disable bit. But the news here is you can never disable interrupts globally or specific ones - uses some other implementation. This is good for RTOSes.

    Petrei

  • Petrei:

    My answer is in the calling conventions of the TI ARM environment. According to Section 6.3 of the ARM Optimizing... manual, the caller is responsible for saving the state of registers R0-R3 and R12 (hardware notation, A1-A4, and V9 compiler notation). The PC must also be saved to make a satisfactory return from a subroutine. Normally, the caller's PC is saved in the LR. If the subroutine makes a call to another subroutine, the LR is first saved on the stack before the call is made and then restored to the PC to cause a return from subroutine.

    The called subroutine (aka Child) is responsible for preserving R4-R11 (hardware notation, or V1-V8 compiler notation) if used.

    In the case of an interrupt, the hardware saves R0-R3, R12, LR, PC, and PSR on the stack. This is similar to what a caller would do to preserve the state of these registers. On an interrupt, LR is set to a special code (See Section 2.5.7.2 of the TM4C1294 datasheet). When this special code is moved to the PC, the hardware recognizes this code and restores the state of the CPU from the saved registers that are on the stack.

    Hence, the calling conventions of the TI ARM runtime environment insure that if registers other than those saved on the stack during an interrupt are used, the called subroutine (ISR) will save them upon entry and restore them upon exit.

    I believe this answers my question.

    Thanks,

    GJM

  • Hi,

    I know these, but did not wanted to send you directly to that, since to me seems you have been more concerned about practical aspects of writing/declaring interrupts in (old style) used by other micro controllers.

    Petrei