EmbeddedRelated.com
Forums

interrupt causes abort

Started by shwouchk March 26, 2008
Hi,

I'm very frustrated - I've been trying for days to make interrupts
work correctly on my lpc2138 board, but to no avail.

My startup and linker files are the ones which come with newlib-lpc
(which I don't use besides for those files) with somem minor
modifications for my board, and larger stack sizes.

No matter what I do, when the time for the ISR to fire up comes, the
program jumps to the ABORT mode... I've tried using the naked
attribute with assembler macros. I've tried using a wrapper function
with assembler macros. I've tried using the IRQ attribute in hopes
that it would work.

Nothing did. I'm musing gcc-4.2.3 if that matters and can provide the
code, but it's pretty standard stuff I've seen posted countless times
so I'm not pasting it this time (I will if it helps though).

Help?

Thanks in advance,
Shwouchk.

An Engineer's Guide to the LPC2100 Series

> - interrupt keyword does not generate the correct code in some
versions.
> I always use an asm wrapper

Robert

that is exactly the point.
Using (method 1)
ldr pc, [pc, #_irq_handler - . - 8]

means that there is one wrapper that saves context and gets the
interrupt handler's address and calls it.

Using (method 2)
LDR PC, [PC, #-0x0FF0] ; Vector from VicVectAddr

calls the interrupt handler directly and the compiler has to add the
wrapper (contect save only) as part of the routine.

I can use both methods with IAR compiler (using the keyword __irq
__arm together with method 2 or no keyword together with method 1)

With GCC I can use method 1 since the _irq_handler wrapper does the
context save and the interrupt routine is a 'normal' function.
When using the interrupt keyword method 2 didn't work together with
GCC - I have also found various references to this being a bug so that
is why I have stuck to method 1.

When you say "It certainly does work for me" are you referring to
method 1 or method 2? If I correctly interpret "I don't trust
interrupt keywords, they are among the most fragile parts of
a compiler in my experience. I always use an asm wrapper" then it
means that you in fact use method 1 (same as me in this case).

Or do you write an asm wrapper for each individual interrupt? Are you
confirming that the interrupt keyword is to be avoided when working
with GCC?

Regards

Mark

--- In l..., "Mark Butcher" wrote:
>
> --- In l..., "shwouchk" wrote:
> >
> > Hi,
> >
> > I'm very frustrated - I've been trying for days to make interrupts
> > work correctly on my lpc2138 board, but to no avail.
>
> > I'm musing gcc-4.2.3
> Hi Which method are you using?
> 1.
> org 0x18
> LDR PC, [PC, #-0x0FF0] ; Vector from VicVectAddr
>
> This calls the isr directly from the VicVectAddr. This doesn't seem to
> work with GNU because it doesn't allow the isr to be contructed to
> correctly handle this (possibly there is a fix in newer GCC versions
> but I use this only with IAR - for example)
>
> 2. (or similar - this is GCC compatible)
> ldr pc, [pc, #_irq_handler - . - 8]
> /* irq */
>
> _irq_handler:
> .word irq_handler
>
> .code 32
> .global irq_handler
>
> irq_handler:
> stmfd sp!, {r0}
> mrs r0, spsr
> tst r0, #0x80
> /* check if I bit is set in SPSR to detect spurious interrupts */
> ldmfd sp!, {r0}
> subnes pc, lr, #4
> stmfd sp!, {r0-r4, r12, lr}
> /* save contect registers */
> ldr r4, =0xFFFFFF00
> ldr r3, [r4]
> /* load the value from the interrupt vector register */
> mov lr, pc
> bx r3
> /* jump to the interrupt handler */
> mov r3, #0
> str r3, [r4]
> /* acknowledge the interrupt */
> ldmfd sp!, {r0-r4, r12, lr}
> /* return interrupted context registers */
> subs pc, lr, #4
> /* return */
> This works with GNU since it calls the interrupt handler as a normal
> routine.
>
> To debug, do the following:
> - Set a break point at the address 0x18
> - When an IRQ arrives it will excecute code from this location (if it
> aborts before there is a different problem)
> - Step carefully (in assembler mode) to see what happens and which
> instruction, jump etc. is causing the abort.
> - The reason should then be visible
>
> Regards
>
> Mark
>
> http://www.uTasker.com
>

Wow, that confused the hell out of me...
If I set the bp to the address you provided, I get an abort, and oddly
the abort line is sitting at that address...

Is there a problem with my startup script? (directly from the
newlib-lpc package with maybe a couple mods):

/**************************** crt0.s ************************************/
/* Copyright 2003/12/28 Aeolus Development */
/* */
/* Freely modifiable and redistributable. Modify to suit your own needs*/
/* Please remove Aeolus Development copyright for any significant */
/* modifications or add explanatory notes to explain the mods and */
/* list authour(s). */
/* */
/* THIS SOFTWARE IS PROVIDED BY THE AEOULUS DEVELOPMENT "AS IS" AND ANY */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AEOLUS DEVELOPMENT BE */
/* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR */
/* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF */
/* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */
/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,*/
/* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE */
/* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/* */
/* Startup for LPC210X. Use with associated linker script and newlib. */
/* Developed from source and hints from multiple startup modules */
/* developed by others for various ARM systems. */
/************************************************************************/
/*
* TLIB revision history:
* 1 crt0.s 30-Dec-2003,10:32:32,`RADSETT' First archival version.
* 2 crt0.s 03-Mar-2004,15:56:50,`RADSETT' Add stack setup for each
CPU mode to
* allow support of interrupts.
* TLIB revision history ends.
*/

.extern main /* Usual C startup. */

/* .text is used instead of .section .text so it works with
arm-aout too. */

.text
.code 32

.align 0

/* Defined in link script so startup knows where everything */
/* is. */
.extern __bss_beg__
.extern __bss_end__
.extern __stack_end__
.extern __data_beg__
.extern __data_end__
.extern __data_beg_src__

.global start
.global endless_loop

.set INIT_FIQ_MODE, 0x11
.set INIT_IRQ_MODE, 0x12
.set INIT_SUPERVISOR_MODE, 0x13
.set INIT_ABORT_MODE, 0x17
.set INIT_UNDEFINED_MODE, 0x1B
.set INIT_SYSTEM_MODE, 0x1F

/********************* start ********************************************/
/* start (AKA _start, _mainCRTStartup) -- Gains control on reset and */
/* set up environment before running the operating C program. */
start:
_start:
_mainCRTStartup:

/* Start by setting up a stack */
/* Set up the stack pointer to end of bss */
msr cpsr_c, #INIT_FIQ_MODE
ldr sp, =__stack_end_fiq__
msr cpsr_c, #INIT_IRQ_MODE
ldr sp, =__stack_end_irq__
msr cpsr_c, #INIT_SUPERVISOR_MODE
ldr sp, =__stack_end_supervisor__
msr cpsr_c, #INIT_ABORT_MODE
ldr sp, =__stack_end_abort__
msr cpsr_c, #INIT_UNDEFINED_MODE
ldr sp, =__stack_end_undefined__
msr cpsr_c, #INIT_SYSTEM_MODE
ldr sp, =__stack_end__

sub sl, sp, #512 /* Assumes 512 bytes below sp */

mov a2, #0 /* Fill value */
mov fp, a2 /* Null frame pointer */
mov r7, a2 /* Null frame pointer for Thumb */

ldr r1, .LC1 /* __bss_beg__ set in link script to */
/* point at beginning of uninitialized */
/* ram. */
ldr r3, .LC2 /* __bss_beg__ set in link script to */
/* point at end of uninitialized ram. */
subs r3, r3, r1 /* Subtract two to find length of */
/* uninitialized ram. */
beq .end_clear_loop /* If no uninitialzed ram skip init. */

mov r2, #0 /* Value used to init ram. */

.clear_loop:
strb r2, [r1], #1 /* Clear byte at r1, advance to next */
subs r3, r3, #1 /* One less to do */
bgt .clear_loop /* If not done go the next. */

.end_clear_loop:

ldr r1, .LC3 /* __data_beg__ set in link script to */
/* point at beginning of initialized ram*/
ldr r2, .LC4 /* __data_beg_src__ set in link script */
/* to point to beginning of flash copy */
/* of the initial values of initialized */
/* variables. */
ldr r3, .LC5 /* __data_end__ set in link script to */
/* point at end of initialized ram */
subs r3, r3, r1 /* Calculate length of area in ram */
/* holding initialzed variables. */
beq .end_set_loop /* If no initialized vars skip init. */

.set_loop:
ldrb r4, [r2], #1 /* Read byte from flash (increment ptr),*/
strb r4, [r1], #1 /* store it in ram (increment ptr) and, */
subs r3, r3, #1 /* reduce bytes to copy by 1. */
bgt .set_loop /* Continue until all copied. */

.end_set_loop:

/* Set up arguments to main and call. */

mov r0, #0 /* no arguments */
mov r1, #0 /* no argv either */

bl main

/* Returning from main in this environment is really an error. */
/* Go into a dead loop. */
endless_loop:
b endless_loop

/* For Thumb, constants must be after the code since only
positive offsets are supported for PC relative addresses. */

.align 0
.LC1:
.word __bss_beg__
.LC2:
.word __bss_end__
.LC3:
.word __data_beg__
.LC4:
.word __data_beg_src__
.LC5:
.word __data_end__

/**** Exception/Interrupt table ****/

/* defaults are defined in the link script. The reserved */
/* exception should be overridden by the download program */
/* (see LPC210X documentation). */
/*
.section .startup,"ax"
.code 32
.align 0

b start
b undefined_instruction_exception
b software_interrupt_exception
b prefetch_abort_exception
b data_abort_exception
b reserved_exception
// b interrupt_exception
ldr pc,[pc,#-0xFF0] // Vector via VIC
b fast_interrupt_exception
*/

--- In l..., "Mark Butcher" wrote:
> > That's it. Mostly as a matter of habit from previous architectures..
>
> OK Thanks Robert.
>
> All is clear now. I'll stick with the present method since this allows
> interrupt routines to be declared as normal routines.
>
> Regards
>
> Mark
>
> http://www.uTasker.com
>

I have been following your conversation for a while now, not answering
what was addressed to me because I don't have a good answer yet. One
thing I can definitely say is that this is exactly the reason I have
been procrastinating interrupts for the last 6 months...

Mark, I actually considered using uTasker, but since the product
intends to be commemrcial on the one hand, and uTasker (as is freertos
or any rtos I know of) seems to be too complex and with a learning
curve, I decided against it for now. (I also have a substantial amount
of code which currently doesn't use an rtos, I really don't want to
start a redesign now). I also know that if I try your library and
decide not to use it, I will still be tempted to use

What I can definitely say is that I will have to learn arm assembler
now. In the mean time,
"> All is clear now. I'll stick with the present method since this allows
> interrupt routines to be declared as normal routines."

Can you please (although I'm pretty sure you did outline the method,
but I didn't completely understand yet) elaborate a bit more about
your "method"?

Thanks,
Shwouchk.
--- In l..., "shwouchk" wrote:
>
> Hi,
>
> I'm very frustrated - I've been trying for days to make interrupts
> work correctly on my lpc2138 board, but to no avail.

> I'm musing gcc-4.2.3
Hi Which method are you using?
1.
org 0x18
LDR PC, [PC, #-0x0FF0] ; Vector from VicVectAddr

This calls the isr directly from the VicVectAddr. This doesn't seem to
work with GNU because it doesn't allow the isr to be contructed to
correctly handle this (possibly there is a fix in newer GCC versions
but I use this only with IAR - for example)

2. (or similar - this is GCC compatible)
ldr pc, [pc, #_irq_handler - . - 8]
/* irq */

_irq_handler:
.word irq_handler

.code 32
.global irq_handler

irq_handler:
stmfd sp!, {r0}
mrs r0, spsr
tst r0, #0x80
/* check if I bit is set in SPSR to detect spurious interrupts */
ldmfd sp!, {r0}
subnes pc, lr, #4
stmfd sp!, {r0-r4, r12, lr}
/* save contect registers */
ldr r4, =0xFFFFFF00
ldr r3, [r4]
/* load the value from the interrupt vector register */
mov lr, pc
bx r3
/* jump to the interrupt handler */
mov r3, #0
str r3, [r4]
/* acknowledge the interrupt */
ldmfd sp!, {r0-r4, r12, lr}
/* return interrupted context registers */
subs pc, lr, #4
/* return */
This works with GNU since it calls the interrupt handler as a normal
routine.

To debug, do the following:
- Set a break point at the address 0x18
- When an IRQ arrives it will excecute code from this location (if it
aborts before there is a different problem)
- Step carefully (in assembler mode) to see what happens and which
instruction, jump etc. is causing the abort.
- The reason should then be visible

Regards

Mark

http://www.uTasker.com

Hi

>>too complex
The idea of an OS is to make application programming more adstract and
thus easier. Generally you should be able to greatly simplify
developments and reduce project development times, so I am not sure
that this argument is generally true.

>>I also have a substantial amount of code which currently doesn't use
an rtos
This shouldn't really make much difference. All such code can be put
into a simple task and so run as before (it can live alongside
additional services provided by the OS) - by making more use of OS
services in the existing code, additional performance enhancements
should be possible (such as removing unnecessary polling of flags) but
not absolutely necessary.

>>elaborate a bit more
I think that you should have the code allowing interrupts to work, but
I think that you have changed something which has made it inoperable
(eg. it looks as though you have commented out critical interrupt
vector code).
Below is the assembler code responsible for interrupts and it is GCC
compatible.

{1}
.section .vectors, "ax"
.code 32
.align 0
.global _vectors

/* reset vector followed by exception vectors */
_vectors:
ldr pc, [pc, #_reset_handler - . - 8] /* reset vector */
ldr pc, [pc, #_undef_handler - . - 8] /* undefined instruction */
ldr pc, [pc, #_swi_handler - . - 8] /* swi handler */
ldr pc, [pc, #_pabort_handler - . - 8] /* abort prefetch */
ldr pc, [pc, #_dabort_handler - . - 8] /* abort data */
nop /* reserved */
ldr pc, [pc, #_irq_handler - . - 8] /* irq */
ldr pc, [pc, #_fiq_handler - . - 8] /* fiq */

_irq_handler:
.word irq_handler

This is the interrupt vector (including reset). The irq address is
simply jumping to the code at irq_handler.

{2}
.code 32
.global irq_handler
irq_handler:
stmfd sp!, {r0}
mrs r0, spsr
tst r0, #0x80 /* check I in SPSR to detect spurious interrupts */
ldmfd sp!, {r0}
subnes pc, lr, #4
stmfd sp!, {r0-r4, r12, lr} /* save contect registers */
ldr r4, =0xFFFFFF00
ldr r3, [r4]/* load the value from the interrupt vector register */
mov lr, pc
bx r3 /* jump to the interrupt handler */
mov r3, #0
str r3, [r4] /* acknowledge the interrupt */
ldmfd sp!, {r0-r4, r12, lr} /* return interrupted context */
subs pc, lr, #4 /* return */

The is irq_handler. It is saving registers to the stack and collecting
the address of the specific interrupt routine from the vector address
register (address 0xffffff00). It calls this address (this corresponds
to calling a normal sub-routine) and when the code returns it reloads
the internal registers again and returns to the original excecution
position as the interrupt was started.
There is a small loop in this code which corrects for a bug in the
original ARM core where it is possible that interrupts are taken just
after the interrupt mask has been set - I am not sure that it is
needed with the newer ARM cores, but it doesn't hurt.
This code calls the interrupt routine with interrupts masked out -
some implementations first unmask interrupts before calling to allow
nested interrupts to take place.

Regards

Mark

http://www.uTasker.com