8

Can someone please show me an example of how to setup an ARM9 interrupt vector table using C or inline assembly in a bare-metal environment with no RTOS or Linux OS?

Specifically how can I use inline assembly or C to set branches to my IRQ interrupt handler ISR coded in C?

    /// timer1 64-bit mode interrupt handler connected to TINT2 interrupt=#34
    /// \todo I think I need to ACK it once I get working
    interrupt void interruptHandlerTimer1(void) {
        printf("\n [* ISR *] \n");
        // ACK TINT2 interrupt #34
        AINTC ->IRQ1 = 1 << (34 - 32);
    }
    void main(void) {
    
        TIMER1 ->TCR = 0x00000000;
        // TGCR: TIMMODE=0 64-bit GP, TIM34RS=TIM12RS=1
        TIM0ER1 ->TGCR = 0x00000003;
        TIMER1 ->TIM34 = 0x00000000;
        TIMER1 ->TIM12 = 0x00000000;
        TIMER1 ->PRD34 = 0x00000000;
        TIMER1 ->PRD12 = 0x0000ffff;
        // TCR: inc until period match, then reset
        TIMER1 ->TCR = (2 << 6);
    
        // This is wrong.
        // I think I need to insert opcode or assembly to branch to interruptHandlerTimer1 ?
        // AINTC ->EABASE located @ 0x00000000
        uint32_t** ptrEabase = (uint32_t**) (AINTC ->EABASE);
        ptrEabase[34] = (uint32_t*) (interruptHandlerTimer1);
    
        // Set INT34 TINT2 to IRQ priority 2
        AINTC ->INTPRI4 = 0x00000200;
        // Enable INT34
        AINTC ->EINT1 = (1 << (34 - 32));
    
        // Enable IRQ in CPSR
        // "TMS32DM644x ARM Subsystem", 3.3 Processor Status registers
        asm("    ;Enable IRQ in CPSR");
        asm("    mrs     r0, cpsr");
        asm("    bic     r0, r0, #0x80");
        asm("    msr     cpsr_c, r0");
    
        // I expected to see " [* ISR *] " print
        // when TIMER1->TIM12 reaches 0x0000ffff
        while (1) {
            printf("%08x %08x\r\n", TIMER1 ->TIM34, TIMER1 ->TIM12);
        }
    }

Interrupt Entry Table

Thanks in advance for any tips or direction.

Bare-metal development examples for ARM9 are very hard to find.

Ed

  • TI TMS320DM6466
  • Code Composer Studio v5.5
Diracx
  • 61
  • 10
Ed of the Mountain
  • 5,219
  • 4
  • 46
  • 54
  • See [Use GCC's pre-processor as an assembler](http://stackoverflow.com/questions/15465958/using-gccs-pre-processor-as-an-assembler). Basically, you can encode some `ldr pc, [pc,#offset]` in the vector table (an array of 32bits). You have array indexes like `VEC_IRQ`, etc. Populated the table and set the value at `[pc,#offset]` to your actual 'C' routine. You must use a function attribute like `__attribute__(("interrupt"))`. See: [FIQ vs IRQ](http://stackoverflow.com/questions/973933/what-is-the-difference-between-fiq-and-irq-interrupt-system/21270225#21270225). – artless noise Jan 29 '14 at 16:30
  • You still have to use a linker to position things. Well, technically possible, why not just use an assembler? The `__attribute(("interrupt"))` gives correct return code such as `subs pc,lr,#4` with *gcc*; your tool needs something similar, otherwise you need to save registers and return to some assembler doing the proper return. – artless noise Jan 29 '14 at 16:32
  • I would study related parts in Cortex-A series programmers guide (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0013d/index.html) (it is not ARM9 but it covers parts about your question). Raspberry PI is ARM11 and there should be many examples about baremetal programming for it. I also do have some stuff about beaglebone black https://github.com/auselen/down-to-the-bone/tree/master/baremetal_runtime At the end it is about finding a point to start, study documents specific to your device and make trials. – auselen Jan 29 '14 at 22:31
  • Thanks guys. I got the ISR handler to install correctly. Then I learned I needed to setup a software interrupt ( SWI ) in order to call the code to set the IRQ enable bit in the CPSR. When I call SWI #1, it jumps to the expected 0x08 vector where a branch to symbol for my swiHandler is located. However, the next single-step ends up in high memory? Like stack or something is not configured or corrupted or bad linker commands? An ARM is much more challenging to do bare-metal than any micro controller. – Ed of the Mountain Feb 01 '14 at 15:06
  • 1
    When you boot you are in SVC mode and can freely enable or disable interrupts. The same goes for SYS mode if you feel like doing your bootstraping there. So there realy is no need to SWI to enable interrupts. Just setup everything and enable interrupts at the latest when you switch to USR mode. As for the stack being not configured: SWI takes you (back) to SVC mode. If you didn't set a stack there and emidiatly switched to USR mode then yes, you won't have a stack. Don't do that. – Goswin von Brederlow Apr 09 '15 at 11:14

1 Answers1

3

Note that this answer is only applicable to Cortex series ARM processors

I see you reached a solution for this a while back, hopefully this will be useful to anyone with a similar problem in the future.

There are many ways to set up interrupt vectors, they vary a lot between hardware and some platforms may require extra steps. The solution below is generally viable on ARM Cortex micro controllers and is compiler agnostic.

    #include <string.h>
        
    //Size of vector table, note yours is probably 16 entries
    #define VECTOR_TABLE_ENTRIES 4
    //Base address in MCU memory of vector table (by default 0x0 for ARM9)
    #define HARDWARE_VECTOR_TABLE_ADDRESS 0x00000000
       
    typedef void(*isr_vector)(void);
    
    void myIsr1();
    void myIsr2();
    void myIsr3();
    void myIsr4();
    
    static isr_vector s_vector_table[VECTOR_TABLE_ENTRIES] =
    {
        myIsr1,
        myIsr2,
        myIsr3,
        myIsr4
    };
        
    /**
     * Load interrupt vector to correct area in system memory, call on startup
     */
    void load_vector_table()
    {
        memcpy(HARDWARE_VECTOR_TABLE_ADDRESS, s_vector_table, sizeof(isr_vector));
    }
        
    void myIsr1()
    {
        
    }
    
    ...

If you just need to add a single entry to the table you could use the following:

    void set_vector_table_entry(int index, isr_vector vector)
    {
        *(HARDWARE_VECTOR_TABLE_ADDRESS + (sizeof(isr_vector) * index)) = vector;
    }

At the end of the day setting up the table is the easy part, all you are doing is loading a table of function pointers to a specific location, the hard part is setting the right bits in the right registers to enable the interrupts and clear up after them properly.

Further be aware that the interrupt functions typically need to be declared with a compiler specific keyword or pragma to make sure that the compiler generates correct code. This is because function call and return handling is often different when calling an interrupt vector to when calling a normal function.

Note that ARMv7 architecture supports remapping the vector table which can be very useful, this approach adds constraints to the alignment of the table though which in turn requires compiler/linker specific directives.

Diracx
  • 61
  • 10
Michael Shaw
  • 294
  • 1
  • 11
  • 1
    The interrupt table needs code and not function pointer on the ARM (non-cortex-M). Ie, you typically need a `b pc+xxx` type instruction. Also, your 'C' code needs to be able to relocate. You could form a table like `ldr pc,[pc, #offset]` to make a table like you propose, but the table would not be at the primary interrupt location. Also, you didn't detail how to return from the interrupt nor handling the stack which will be in a different mode. – artless noise Jul 31 '15 at 13:28
  • It would seem I have been spoilt by the cortexes! Can you explain why the C code must be relocatable? I didn't want to touch on the interrupt returns and specifics of handling the stack as they are usually implemented with a compiler specific pragma, or the interrupt keyword, It looked like @Ed already understood this. I guess that wouldn't be true for anyone reaching this page from google. I'll try to expand on this over the weekend, any edits or more advice are welcome! – Michael Shaw Jul 31 '15 at 16:52
  • 1
    *Can you explain why the C code must be relocatable?* Whenever you `memcpy` code, it needs to be PC relative. Often it is not as simple as it seems to write relocatable 'C' code. There is no real guarantee is will be relocatable from compiler to compiler so it is often easiest just to write some small assembler. This [question on a pre-processor as an assembler](http://stackoverflow.com/questions/15465958/using-gccs-pre-processor-as-an-assembler) might be useful in this context. – artless noise Aug 04 '15 at 14:51
  • Ahh thanks, I think I get you now so: Arms that use the `ldr pc,[pc, #offset]` style interrupt vector will require a different offset depending on the location of the vector table in memory. Am I correct in thinking that this would not be an issue on the cortex series as they are using function pointers? I will re qualify my answer specifying it only applies to cortex parts and add a section highlighting that the interrupt keyword or pragma for the appropriate compiler should be used. – Michael Shaw Aug 04 '15 at 15:58