Lego Mindstorm EV3(TexasInstruments Sitara AM1808 SoC)的裸金属中断处理

时间:2015-10-29 17:12:16

标签: interrupt interrupt-handling bare-metal mindstorms

对于大学项目,我们的项目团队希望为Lego Mindstorm EV3平台编写一个裸机操作系统。虽然我们进行了大量的研究和测试,但是我们遇到了一个我们无法解决的中断处理问题。

这是平台的细节:
CPU:ARM926EJ-S(ARMv5架构)
SoC:TexasInstruments Sitara AM1808

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

以下是我们尝试做的以及我们如何尝试: 我们想要初始化一个定时器中断,该中断应该每毫秒触发一次以获得系统滴答。我们的操作系统的各种其他组件(例如,软件I2C管理等)都需要此标记。由于我们的团队对裸机编程很陌生,我们遵循TexasInstruments的一个例子来初始化SoC的定时器中断和中断控制器。此示例是StarterWare的一部分,旨在在AM1808评估板上运行。由于乐高头脑风暴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)都不会被调用。我们的猜测是这个问题是由于中断向量表可能位于错误的位置引起的,尽管0xFFFF0000是手册中描述的地址(该地址映射到ARM CPU本地RAM)。

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

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

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

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

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

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

感谢您提前提示。

1 个答案:

答案 0 :(得分:0)

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

问题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];
    }
}

问题2:
用汇编程序编写的IRQ-Handler无法确定相应ISR的正确地址。但是可以调用我们想要的任何C函数。所以我们决定将调用正确ISR的任务委托给我们用C编写的IRQ-Handler(参见test.c - &gt; 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

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