interrupt - Lego Mindstorm EV3 上的裸机中断处理(TexasInstruments Sitara AM1808 SoC)

标签 interrupt interrupt-handling bare-metal mindstorms

对于一个大学项目,我们的项目团队想要为 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 的定时器中断和中断 Controller 。此示例是 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:这里我们初始化定时器中断和中断 Controller
/*
 *  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();
}

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

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

这就是问题所在:
中断也按预期触发。每次执行程序时,在 c_entry() 中循环 455 个循环后,这种情况就会发生。但是一旦触发中断,程序就会停止而没有任何错误或其他东西。中断服务例程(systick.c 中的 systick_isr_C)和中断处理程序(汇编代码中的 IRQHandler 或者如果我们在 test.c 中设置我们自己的处理程序 irqHandler)都不会被调用。我们的猜测是,这个问题是由于中断向量表可能在错误的位置引起的,尽管 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 适配器,这将允许我们在触发中断时检查寄存器(例如程序计数器)。我们预计很快就会开始,但现在我们必须在没有人的情况下工作。

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

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

感谢您提前提供任何提示。

最佳答案

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

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

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

在启动.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 的任务委托(delegate)给我们自己用 C 编写的 IRQ-Handler(参见 test.c --> irqHandler)。

在启动.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

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

关于interrupt - Lego Mindstorm EV3 上的裸机中断处理(TexasInstruments Sitara AM1808 SoC),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33420591/

相关文章:

java - 为什么Java线程收不到中断标志?

windows-ce - ARM 目标上 WinCE 6.0 的典型中断延迟是多少?

javascript - WebAssembly 游戏和低延迟键盘鼠标

assembly - Intel 30386 中的中断错误被推送到堆栈描述

virtual-machine - 如何在裸机上安装 Vagrant Box?

c - 如何使用 arm gdb 忽略中断

c - PIC18F4550外部中断(按钮不工作)

c - 如何加载IDT?

gcc - Newlib:如何在链接器脚本中指定堆大小

c - Windows操作系统的C语言软件中断服务例程