Lego Mindstorm EV3(Texas Instruments Sitara AM1808 SoC)上的裸机中断处理

Bare-Metal Interrupt Handling on Lego Mindstorm EV3 (TexasInstruments Sitara AM1808 SoC)

对于一个大学项目,我们的项目团队想要为 Lego Mindstorm EV3 平台编写一个裸机操作系统。尽管我们做了很多研究和测试,但我们遇到了一个我们无法解决的中断处理问题。

这是平台的详细信息:
CPU:ARM926EJ-S(ARMv5 架构)
SoC:德州仪器 Sitara AM1808

SoC-简短文档:http://www.ti.com/lit/ds/symlink/am1808.pdf
SoC-技术参考手册:http://www.ti.com/lit/ug/spruh82a/spruh82a.pdf

这是我们尝试做的事情以及我们如何尝试做的:
我们想要初始化一个定时器中断,它应该每毫秒触发一次以获得系统节拍。我们操作系统的各种其他组件(例如软件-I2C-管理等)都需要此标记。由于我们的团队对裸机编程还很陌生,所以我们按照 TexasInstruments 的示例来初始化 SoC 的定时器中断和中断控制器。此示例是 StarterWare 的一部分,旨在 运行 在 AM1808 评估板上使用。由于 Lego Mindstorm EV3 使用相同的 SoC,它应该也适用于我们。请参阅以下源代码:

startup.S:我们程序的入口点(基于 TI 提供的源代码)

#include "hw_aintc.h"
#include "soc_AM1808.h"

    .global Entry
    .global _stack                  
    .global _bss_start
    .global _bss_end
    .global start_boot
    .global _Reset
    .global IRQHandler
    .global FIQHandler
    .global AbortHandler
    .global SWIHandler
    .global UndefInstHandler
    .global CPUAbortHandler

@************************ Internal Definitions ********************************
@
@ Define the stack sizes for different modes. The user/system mode will use
@ the rest of the total stack size
@
    .set  UND_STACK_SIZE, 0x8
    .set  ABT_STACK_SIZE, 0x8
    .set  FIQ_STACK_SIZE, 0x8
    .set  IRQ_STACK_SIZE, 0x500
    .set  SVC_STACK_SIZE, 0x8

@
@ to set the mode bits in CPSR for different modes
@        
    .set  MODE_USR, 0x10            
    .set  MODE_FIQ, 0x11
    .set  MODE_IRQ, 0x12
    .set  MODE_SVC, 0x13
    .set  MODE_ABT, 0x17
    .set  MODE_UND, 0x1B
    .set  MODE_SYS, 0x1F  

    .equ  I_F_BIT, 0xC0 
    .equ ADDR_HIPVR1, SOC_AINTC_0_REGS + AINTC_HIPVR(0)
    .equ ADDR_HIPVR2, SOC_AINTC_0_REGS + AINTC_HIPVR(1)
    .equ MASK_SWI_NUM, 0xFF000000

@**************************** Code Seection ***********************************
    .text

@
@ This code is assembled for ARM instructions
@
    .code 32
@******************************************************************************
@
@******************************************************************************
@
@ The reset handler sets up the stack pointers for all the modes. The FIQ and
@ IRQ shall be disabled during this. Then, clearthe BSS sections, switch to the
@ main() function. 
@
Entry:
@
@ Set up the Stack for Undefined mode
@
     LDR   r0, =_stack                     @ Read the stack address
     MSR   cpsr_c, #MODE_UND|I_F_BIT       @ switch to undef  mode
     MOV   sp,r0                           @ write the stack pointer
     SUB   r0, r0, #UND_STACK_SIZE         @ give stack space
@
@ Set up the Stack for abort mode
@        
     MSR   cpsr_c, #MODE_ABT|I_F_BIT       @ Change to abort mode
     MOV   sp, r0                          @ write the stack pointer
     SUB   r0,r0, #ABT_STACK_SIZE          @ give stack space
@
@ Set up the Stack for FIQ mode
@       
     MSR   cpsr_c, #MODE_FIQ|I_F_BIT       @ change to FIQ mode
     MOV   sp,r0                           @ write the stack pointer
     SUB   r0,r0, #FIQ_STACK_SIZE          @ give stack space
@
@ Set up the Stack for IRQ mode
@       
     MSR   cpsr_c, #MODE_IRQ|I_F_BIT       @ change to IRQ mode
     MOV   sp,r0                           @ write the stack pointer
     SUB   r0,r0, #IRQ_STACK_SIZE          @ give stack space
@
@ Set up the Stack for SVC mode
@        
     MSR   cpsr_c, #MODE_SVC|I_F_BIT       @ change to SVC mode
     MOV   sp,r0                           @ write the stack pointer
     SUB   r0,r0, #SVC_STACK_SIZE          @ give stack space
@
@ Set up the Stack for USer/System mode
@      
     MSR   cpsr_c, #MODE_SYS|I_F_BIT       @ change to system mode
     MOV   sp,r0                           @ write the stack pointer

@
@ Clear the BSS section here
@
Clear_Bss_Section:

     LDR   r0, =_bss_start                 @ Start address of BSS
     LDR   r1, =(_bss_end - 0x04)          @ End address of BSS
     MOV   r2, #0  
Loop: 
     STR   r2, [r0], #4                    @ Clear one word in BSS
     CMP   r0, r1
     BLE   Loop                            @ Clear till BSS end


@
@ Enter the start_boot function. The execution still happens in system mode
@
Enter_main:
     LDR   r10,=start_boot                 @ Get the address of start_boot
     MOV   lr,pc                           @ Dummy return 
     BX    r10                             @ Branch to start_boot

@ Interrupt Handler from exceptionhandler.S (provided by TI) - Note: the following code 
@ will not be reached since start_boot contains an endless loop at it's end
@******************************************************************************
@*                  Function Definition of SWI Handler
@******************************************************************************    
@
@ The SWI Handler switches to system mode if the SWI number is 458752. If the
@ SWI number is different, no mode switching will be done. No other SWI are 
@ handled here
@
SWIHandler:
    STMFD    r13!, {r0-r1, r14}       @ Save context in SVC stack
    LDR      r0, [r14, #-4]           @ R0 points to SWI instruction
    BIC      r0, r0, #MASK_SWI_NUM    @ Get the SWI number
    CMP      r0, #458752
    MRSEQ    r1, spsr                 @ Copy SPSR  
    ORREQ    r1, r1, #0x1F            @ Change the mode to System
    MSREQ    spsr_cf, r1              @ Restore SPSR
    LDMFD    r13!, {r0-r1, pc}^       @ Restore registers from IRQ stack

@******************************************************************************
@*                  Function Definition of IRQ Handler
@******************************************************************************    
@
@ The IRQ handler jumps to the ISR of highest priority pending IRQ. The address
@ is taken from the HIPVR2 register, which contains the ISR address of highest
@ pending IRQ. This handler doesnot support nesting.
@
IRQHandler:
    STMFD    r13!, {r0-r3, r12, r14}  @ Save context in IRQ stack
    LDR      r0, =ADDR_HIPVR2         @ R0 points to address of HIPVR2
    LDR      r1, [r0]                 @ R1 contains address of ISR
    ADD      r14, pc, #0              @ Save return address in LR 
    LDR      pc, [r1]                 @ Go to ISR (still in IRQ mode)
    LDMFD    r13!, {r0-r3, r12, r14}  @ Restore registers from IRQ stack
    SUBS     pc, r14, #0x4            @ Return to program before IRQ

@******************************************************************************
@*                  Function Definition of FIQ Handler
@******************************************************************************    
@
@ The FIQ Handler jumps to the ISR of the highest priority pending FIQ. The
@ address is taken from HIPVR1, which contains the ISR address of the highest
@ pending FIQ. This handler doesnot support nesting
@
FIQHandler:
@
@ Save the required context in FIQ stack. 
@
    STMFD    r13!, {r0-r3, r12, r14}  @ Save context in FIQ stack
    LDR      r0, =ADDR_HIPVR1         @ R0 points to address of HIPVR1
    LDR      r1, [r0]                 @ R1 contains address of ISR
    ADD      r14, pc, #0              @ Save return address in LR 
    LDR      pc, [r1]                 @ Go to ISR (still in FIQ mode)
    LDMFD    r13!, {r0-r3, r12, r14}  @ Restore registers from FIQ stack
    SUBS     pc, r14, #0x4            @ Return to program state before FIQ 

@******************************************************************************
@*             Function Definition of Abort/Undef Handler
@******************************************************************************    
@
@ The Abort handler goes to the C handler of abort mode. Note that the undefined
@ instruction is not handled separately.
@ if nothing is done in the abort mode, the execution enters infinite loop.
@
AbortHandler:
UndefInstHandler:
@
@ Disable all the interrupts
@
    MRS     r0, cpsr                  @ Read from CPSR
    ORR     r0, r0, #0xC0             @ Clear the IRQ and FIQ bits    
    MSR     cpsr, r0                  @ Write to CPSR
    ADD     r14, pc, #0               @ Store the return address
    LDR     pc, =CPUAbortHandler      @ Go to C handler

test.c:TI

提供的我们的应用和初始化代码
#include <stdio.h>
#include <stdlib.h>
#include <systick.h>
#include <mytypes.h>
#include "cpu.h"

extern void Entry(void);
extern void UndefInstHandler(void);
extern void SWIHandler(void);
extern void AbortHandler(void);
extern void IRQHandler(void);
extern void FIQHandler(void);

// This is our own IRQ-Handler - it is not called either, we just wanted to check if the assembler code was the source of the problem
void irqHandler(void) {
    unsigned int* isr_pointer = *(unsigned int *)*((unsigned int*)0xFFFEF604);
    printf("ISR Handler called: %#10x\n", isr_pointer);
    typedef void func(void);
    func* f = (func*) isr_pointer;
    f();
}

void c_entry() {
    unsigned int counter = 1;
    systick_init();  
    U32 localSystick = systick_get_ms();
    // printf will write debug output to a UART port connected to a PC
    printf("Current tick: %u (%u)\n", localSystick, counter);
    do {
        ++counter;
        localSystick = systick_get_ms();
        printf("Current tick: %u (%u)\n", localSystick, counter);
    } while (1);
}


// Code from Startup.c (TI example)
#include "hw_syscfg0_AM1808.h"
#include "hw_syscfg1_AM1808.h"
#include "hw_pllc_AM1808.h"
#include "hw_ddr2_mddr.h"
#include "soc_AM1808.h"
#include "evmAM1808.h"
#include "hw_types.h"
#include "psc.h"

#define E_PASS    0
#define E_FAIL    -1

static void CopyVectorTable(void);
void BootAbort(void);

static unsigned int const vecTbl[16]=
{
    // Primary vector Table entries
    (unsigned int)Entry,
    (unsigned int)UndefInstHandler,
    (unsigned int)SWIHandler,
    (unsigned int)AbortHandler,
    (unsigned int)AbortHandler,
    0xE59FF010,
    (unsigned int)IRQHandler,
    (unsigned int)FIQHandler,
    // Secondary Vector Table entires
    (unsigned int)Entry,
    (unsigned int)UndefInstHandler,
    (unsigned int)SWIHandler,
    (unsigned int)AbortHandler,
    (unsigned int)AbortHandler,
    0xE59FF010,
    (unsigned int)IRQHandler,
    (unsigned int)FIQHandler
};


/**
 * \brief   Boot strap function which enables the PLL(s) and PSC(s) for basic
 *          module(s)
 *
 * \param   none
 *
 * \return  None.
 * 
 * This function is the first function that needs to be called in a system.
 * This should be set as the entry point in the linker script if loading the
 * elf binary via a debugger, on the target. This function never returns, but
 * gives control to the application entry point
 **/
unsigned int start_boot(void) 
{
    printf("start_boot called\n");
    SysCfgRegistersLock();

    /* Disable write-protection for registers of SYSCFG module. */
    SysCfgRegistersUnlock();

    /* Initialize the vector table with opcodes */
    CopyVectorTable();

    c_entry();

    while(1);
}


static void CopyVectorTable(void)
{
    printf("CopyVectorTable called\n");
    // According to the AM1808 Technical Reference Manual (Page 88), the vector table has to be located at 0xFFFF0000
    unsigned int *dest = (unsigned int *)0xFFFF0000;
    unsigned int *src =  (unsigned int *)vecTbl;
    unsigned int count;

    for(count = 0; count < sizeof(vecTbl)/sizeof(vecTbl[0]); count++)
    {  
        dest[count] = src[count];
    }
}


void BootAbort(void)
{
    printf("BootAbort called");
    while (1);
}

systick.c:这里我们初始化定时器中断和中断控制器

/*
 *  This provides a 1000Hz tick for the system.
 *
 *  We're using the AMT1808 Hardware Timer 2
 * 
 *  See also: timerCounter.c in TI/examples/evmAM1808/timer
 */

#include "soc_AM1808.h"
#include "hw_syscfg0_AM1808.h"
#include "interrupt.h"
#include "timer.h"
#include "evmAM1808.h"
#include "cpu.h"

#include "systick.h"

#include <stdio.h>

#define TMR_PERIOD_LSB32               (0x07FFFFFF)
#define TMR_PERIOD_MSB32               (0x0)

static volatile U32 systick_ms;

/* ISR, called 1000 times per second */
void
systick_isr_C(void)
{
    /* Disable the timer interrupt */
    TimerIntDisable(SOC_TMR_2_REGS, TMR_INT_TMR12_NON_CAPT_MODE);

    printf("ISR called");

    /* Clear the interrupt statusIntChannelSet(SYS_INT_TIMR2_ALL, 0); in AINTC */
    IntSystemStatusClear(SYS_INT_TIMR2_ALL);
    TimerIntStatusClear(SOC_TMR_2_REGS, TMR_INT_TMR12_NON_CAPT_MODE);

    ++systick_ms;

    /* Enable the timer interrupt */
    TimerIntEnable(SOC_TMR_2_REGS, TMR_INT_TMR12_NON_CAPT_MODE);
}


U32
systick_get_ms(void)
{
    return systick_ms;
}


void
systick_init(void)
{
    /* Setup timer for 64 bit mode */
    /* Configuration of Timer */
    TimerConfigure(SOC_TMR_2_REGS, TMR_CFG_64BIT_CLK_INT);

    /* Set the 64 bit timer period */
    TimerPeriodSet(SOC_TMR_2_REGS, TMR_TIMER12, TMR_PERIOD_LSB32);
    TimerPeriodSet(SOC_TMR_2_REGS, TMR_TIMER34, TMR_PERIOD_MSB32);

    /* Set up the ARM Interrupt Controller for generating timer interrupt */
    /* Initialize AINTC and register timer interrupt */
    IntAINTCInit();

    /* Register the Timer ISR */
    IntRegister(SYS_INT_TIMR2_ALL, systick_isr_C);

    /* Set the channel number for Timer interrupt, it will map to IRQ */
    IntChannelSet(SYS_INT_TIMR2_ALL, 2);

    /* Enable IRQ for ARM (in CPSR)*/
    IntMasterIRQEnable();

    /* Enable AINTC interrupts in GER */
    IntGlobalEnable();

    /* Enable IRQ in AINTC */
    IntIRQEnable();

    /* Enable timer interrupts in AINTC */
    IntSystemEnable(SYS_INT_TIMR2_ALL);

    /* Enable the timer interrupt */
    TimerIntEnable(SOC_TMR_2_REGS, TMR_INT_TMR12_NON_CAPT_MODE);

    /* Start the timer */
    TimerEnable(SOC_TMR_2_REGS, TMR_TIMER12, TMR_ENABLE_CONT);

    // Deactivate interrupts on CPU - for test purposes
    //IntMasterIRQDisable();
}

所需的所有其他源代码文件(例如用于控制中断控制器)是 TI StarterWare 代码的未修改版本(它提供驱动程序来控制 SoC 的各种硬件组件,如中断控制器、定时器等)。我可以在这里添加它们,但我不确定它们是否有用。我仔细查看了它们,对我来说,这段代码似乎没有发生任何奇怪的事情。

我们使用 EV3 的 Uboot 引导加载程序 运行 我们的程序。此引导加载程序已经是默认固件的一部分。它将位于 SD 卡上的程序加载到 0xC1000000(映射到 EV3 的 RAM 的地址)并跳转到程序的入口点(汇编代码中的 "Entry:")。到目前为止没有问题,代码编译并按预期执行。

问题来了:
中断也按预期触发。每次执行程序时,都会在 c_entry() 中循环 455 次后立即发生这种情况。但是一旦触发中断,程序就会停止,不会出现任何错误或其他情况。中断服务例程(systick.c 中的 systick_isr_C)和中断处理程序(汇编代码中的 IRQHandler,或者如果我们在 test.c 中设置我们自己的处理程序 irqHandler)都不会被调用。我们猜测这个问题是由于中断向量 table 可能位于错误的位置引起的,尽管 0xFFFF0000 是手册中描述的地址(此地址映射到 ARM CPU 本地 RAM ).

如果我们停用 CPU 上的中断处理(使用 systick.c 中的最后一行代码 --> systick_init())并检查表明中断的寄存器手动触发,一切正常。在这种情况下,我们直接触发 irqHandler 函数。我们使用以下代码执行此操作:

unsigned int* isr_pointer = *(unsigned int *)*((unsigned int*)0xFFFEF604);
if (isr_pointer != 0) {
    irqHandler();
}

我们还尝试将 irqHandler 设置为所有可能中断的默认处理程序(只是为了确保没有触发其他中断)。这并没有导致任何其他结果。

不幸的是,我们目前没有 JTAG 适配器,这将允许我们在触发中断时检查寄存器(例如程序计数器)。我们希望尽快开始,但现在我们必须在没有人的情况下工作。

有没有人知道可能是什么问题或者为什么我们的中断处理程序根本没有被调用?

我已尝试尽可能详细地描述问题,但如果我能提供任何可能有用的进一步信息,我很乐意提供。

感谢您的提前提示。

经过更多的研究和试验,我们能够解决这个问题。 如果有人对解决方案感兴趣,以下是导致问题的原因:

问题 1:
中断向量未正确初始化。与 TexasInstruments 示例相反,向量不应包含中断处理程序的地址。相反,它应该包含机器代码操作。任何将程序计数器更改为正确地址的东西都有效,例如B ...(分支)或 LDR pc,...(加载程序计数器)。

这是解决这个问题的新代码:

在startup.S中:

ExceptionHandler:
    B Entry
    B ExceptionHandler
    B ExceptionHandler
    B ExceptionHandler
    B ExceptionHandler
    B ExceptionHandler
    B IRQHandler
    B ExceptionHandler

在test.c中:

/* Copy the vector table to it's destination at 0xFFFF0000 - this address is specified in the board's manual. */
static void CopyVectorTable(void)
{
    unsigned int *dest = (unsigned int *)0xFFFF0000;
    /* The address of the assembler exception vector */
    unsigned int *addrExceptionHandler = (unsigned int *)ExceptionHandler;
    int i;

    /* We only set vector 1 to 7 and leave the Reset vector in peace */
    /* Important: don't set the address of the vectors - we need to write the operation (i.e. the machine code) to that location */
    for (i = 1; i < 8; ++i) {
        dest[i] = addrExceptionHandler[i];
    }

    /* This code is required in order for the branch instructions (B ...) to work */
    for (; i < 2048; ++i) {
        dest[i] = addrExceptionHandler[i];
    }
}

问题二:
用汇编程序编写的 IRQ-Handler 无法确定相应 ISR 的正确地址。但是可以调用我们想要的任何 C 函数。所以我们决定将调用正确 ISR 的任务委托给我们自己用 C 编写的 IRQ 处理程序(参见 test.c --> irqHandler)。

在startup.S中:

IRQHandler:
    STMFD    r13!, {r0-r3, r12, r14}  @ Save context in IRQ stack
    ADD      r14, pc, #0              @ Save return address in LR 
    @ For whatever reason, we are unable to get the correct address of the ISR in this assembler code
    @ So we just call our C handler which has a static address in RAM and let it handle the interrupt
    @LDR      pc, =systick_isr_C       @ Works as well, but is not dynamic - we could only handle 1 interrupt
    LDR      pc, =irqHandler
    LDMFD    r13!, {r0-r3, r12, r14}  @ Restore registers from IRQ stack
    SUBS     pc, r14, #0x4            @ Return to program before IRQ

这解决了问题。也许这可以帮助将来的某人,所以我想我会 post 我们的解决方案。