如何在C语言或内联汇编中设置ARM中断向量表分支?

8

能否有人给我展示一个在没有实时操作系统或Linux操作系统的裸机环境下如何使用C或内联汇编来设置ARM9中断向量表的例子呢?

具体来说,我该如何使用内联汇编或C将分支设置到我的用C编写的IRQ中断处理程序ISR上呢?

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

提前感谢任何提示或指导。

很难找到针对ARM9的裸机开发示例。

Ed

  • TI TMS320DM6466
  • Code Composer Studio v5.5

请参见使用GCC的预处理器作为汇编器。基本上,您可以在向量表(32位数组)中编码一些ldr pc,[pc,#offset]。您有像VEC_IRQ等的数组索引。填充表并将值设置为[pc,#offset]到您实际的'C'例程。您必须使用函数属性,例如__attribute__(("interrupt"))。请参见:FIQ vs IRQ - artless noise
你仍然需要使用链接器来定位事物。虽然在技术上是可能的,但为什么不直接使用汇编器呢?__attribute(("interrupt"))可以在gcc中给出正确的返回代码,例如subs pc,lr,#4;你的工具需要类似的东西,否则你需要保存寄存器并返回到一些执行适当返回的汇编程序。 - artless noise
我会研究Cortex-A系列程序员指南中相关的部分(http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0013d/index.html)(虽然它不是ARM9,但它涵盖了你问题的一些部分)。树莓派是ARM11,应该有很多关于裸机编程的例子。我还有一些关于BeagleBone Black的资料https://github.com/auselen/down-to-the-bone/tree/master/baremetal_runtime。最终,关键在于找到一个起点,研究与您的设备相关的文档并进行试验。 - auselen
谢谢大家。我已经成功安装了ISR处理程序。然后我发现需要设置软件中断(SWI)才能调用代码以设置CPSR中的IRQ使能位。当我调用SWI#1时,它跳转到预期的0x08向量,其中包含对swiHandler符号的分支。但是,下一个单步操作会进入高内存?像堆栈或某些未配置或损坏的东西或错误的链接器命令一样?与任何微控制器相比,ARM更具挑战性。 - Ed of the Mountain
1
当您启动时,您处于SVC模式,可以自由地启用或禁用中断。如果您想在SYS模式下进行引导,则同样适用。因此,实际上没有必要SWI来启用中断。只需设置好一切,并在最后切换到USR模式时启用中断即可。至于堆栈未配置:SWI将带您(返回)到SVC模式。如果您没有在那里设置堆栈并立即切换到USR模式,则确实不会有堆栈。不要这样做。 - Goswin von Brederlow
1个回答

3

请注意,此答案仅适用于Cortex系列ARM处理器

我看到您一段时间前已经找到了解决方案,希望这对未来遇到类似问题的人有所帮助。

有许多设置中断向量的方法,它们在硬件之间差异很大,某些平台可能需要额外的步骤。下面的解决方案通常适用于ARM Cortex微控制器,并且与编译器无关。

    #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()
    {
        
    }
    
    ...

如果您只需要向表中添加单个条目,则可以使用以下方法:
    void set_vector_table_entry(int index, isr_vector vector)
    {
        *(HARDWARE_VECTOR_TABLE_ADDRESS + (sizeof(isr_vector) * index)) = vector;
    }

说到底,设置表格只是一件容易的事情,你所做的就是将函数指针表加载到特定位置,难点在于正确地设置寄存器中的位以启用中断并在适当时清除它们。

此外,请注意,中断函数通常需要使用编译器特定的关键字或pragma进行声明,以确保编译器生成正确的代码。这是因为调用中断向量与调用普通函数时的函数调用和返回处理通常不同。

请注意,ARMv7架构支持重新映射向量表,这非常有用,但这种方法会对表的对齐方式产生约束,从而需要编译器/链接器特定的指令。


1
中断表需要在ARM(非Cortex-M)上使用代码而不是函数指针。也就是说,通常需要一种“b pc+xxx”类型的指令。此外,您的'C'代码需要能够重定位。您可以形成一个类似于“ldr pc,[pc,#offset]”的表来创建您提出的表,但该表不会位于主要中断位置。此外,您没有详细说明如何从中断返回以及处理将处于不同模式的堆栈。 - artless noise
似乎我的大脑皮层已经被宠坏了!你能解释一下为什么C代码必须是可重定位的吗?我不想涉及中断返回和处理堆栈的具体细节,因为它们通常是使用编译器特定的pragma或中断关键字实现的,看起来@Ed已经理解了这一点。我猜对于从谷歌到达此页面的任何人都不会如此。我将在周末尝试扩展此内容,欢迎任何编辑或更多建议! - Michael Shaw
1
你能解释一下为什么C代码必须是可重定位的吗?每当你memcpy代码时,它需要是PC相关的。编写可重定位的'C'代码通常不像看起来那么简单。实际上,无论从哪个编译器到另一个编译器,都不能真正保证它是可重定位的,因此最简单的方法通常是编写一些小的汇编代码。在这种情况下,这个关于预处理器作为汇编器的问题可能会有所帮助。(https://dev59.com/Ym_Xa4cB1Zd3GeqP48Y8) - artless noise
啊,谢谢,我现在明白了:使用 ldr pc,[pc, #offset] 样式中断向量的芯片需要根据内存中向量表的位置使用不同的偏移量。我想,我正确地认为这在Cortex系列上不是问题,因为它们使用函数指针,对吗?我将重新确认我的答案,指定它仅适用于Cortex部件,并添加一个部分,强调应该使用适当编译器的中断关键字或预处理指令。 - Michael Shaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接