For a university project, our project teams wants to write a bare-metal operating system for the Lego Mindstorm EV3 plattform. We encountered a problem regarding the interrupt handling which we were unable to solve, although we did a lot of research and testing.
This are details of the plattform:
CPU: ARM926EJ-S (ARMv5 architecture)
SoC: TexasInstruments Sitara AM1808
SoC-Short Documentation: http://www.ti.com/lit/ds/symlink/am1808.pdf
SoC-Technical Reference Manual: http://www.ti.com/lit/ug/spruh82a/spruh82a.pdf
Here is what we try to do and how we try to do it:
We want to initialize a timer interrupt which should be triggered every millisecond in order to get a system tick. This tick is required by various other components of our operating system (e.g. Software-I2C-Management, etc.). Since our team is quite new to the bare-metal programming, we followed an example by TexasInstruments to intialize the timer interrupt and interrupt controller of the SoC. This example is part of StarterWare and designed to run on an AM1808 evaluation board. Since the Lego Mindstorm EV3 uses the same SoC, it should work for us as well. See the following source code:
startup.S: Entry-Point of our program (based on TI provided source code)
#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: our application and initialization code provided by 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: here we initialize the timer interrupt and the interrupt 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();
}
All other source code files required (e.g. for controlling the interrupt controller) are unmodified versions of TI's StarterWare code (it provides drivers to control the various hardware components of the SoC like the interrupt controller, timers, etc.). I could add them here, but I am not sure if they are of any use. I looked them through and for me it looks like there is nothing strange happening in this code.
We use the Uboot bootloader of the EV3 the run our program. This bootloader is already part of the default firmware. It loads the programm which is located on an SD card to 0xC1000000 (an adress which maps to the RAM of the EV3) and jumps the the program's entry point ("Entry:" in assembler code). No problem so far, the code compiles and is executed as expected.
And here is the problem:
The interrupt is triggered as expected as well. This happens excatly after 455 cycles of the loop in c_entry() every time the programm is executed. But as soon as the interrupt is triggered, the program stops without any error or something. Neither the interupt service routine (systick_isr_C in systick.c) nor the interrupt handler (IRQHandler in assembler code or if we set our own handler irqHandler in test.c) is called. Our guess is that this problems is caused by the fact that the interrupt vector table might be at the wrong location, although 0xFFFF0000 is the address described in the manual (this address maps to the ARM CPU local RAM).
If we deactivate the interrupt handling on the CPU (with the last line of code in systick.c --> systick_init()) and check the register indicating that an interrupt was triggered manually, everything works perfectly fine. In this case we trigger the irqHandler-function directly. We do this using the following code:
unsigned int* isr_pointer = *(unsigned int *)*((unsigned int*)0xFFFEF604);
if (isr_pointer != 0) {
irqHandler();
}
We also tried setting the irqHandler as default handler for all possible interrupts (just to make sure no other interrupt was triggered). This did not lead to any other result.
Unfortunatly we have no JTAG adapter at the moment, which would allow us to check the registers when the interrupt is triggered (e.g. program counter). We expect to get on soon, but for now we have to work without one.
Has anyone any idea what might be the problem or why our interrupt handler is not called at all?
I tried to describe the problem as detailed as possible, but if I can provide any further information that might be useful I am happy to do so.
Thanks for any hint in advance.