This chapter describes how to write interrupt handlers in both Forth and assembler. It details how to set up and control interrupt handlers. The first part of the chapter describes how to set up and use the interrupts, and the second part provides more technical detail.
Different ARM implementations use different interrupt controllers, although many are derived from, or similar to the ARM PL190 controller. The code for the ones we have used is collated into the file ARM\IntARM3.fth. The syntax setting for interrupt handlers varies slightly between the controllers.
When an interrupt occurs and is accepted, an ARM processor branches through a location (a `vector') in low memory. The vector table starts at address 0, the vector used being dependent on the source of the interrupt. The code at the address stored in the vector table is then executed. For more information on interrupts for the ARM, refer to the processor's user guide. In this chapter we are mostly interested in the IRQ and FIQ interrupts, although the principles apply equally well to the other interrupts.
A Forth interrupt service routine (ISR) is just like any other Forth word. It can therefore be tested and debugged like a normal Forth word. Only when the word is fully tested need it be assigned to an interrupt.
The interrupt handler allows you to chain several interrupt handlers onto the primary IRQ and FIQ handlers. This makes complex code much easier to manage. The action of of each handler should be:
' A/D-READY ADD-IRQ
which adds the word A/D-READY
to the IRQ chain.
There are a few common problems that might cause an interrupt not to work correctly:
An interrupt service routine can use the stack while it is executing, but must clear up the stack before returning from the interrupt. The normal symptom of a stack fault is that the interrupt handler runs but then the target board crashes, either immediately or after a length of time.
Once an interrupt handler is triggered by an interrupt, the source of the interrupt must be told that the interrupt is being serviced. If this is not done, the source of the interrupt will carry on generating interrupts. Normally this appears as the interrupt handler executing once and then the target board `locking'. Interrupts are not enabled
Interrupts need to be enabled with EI
before any interrupts will
be serviced. The vectors must be set up or the interrupt handler
assigned to the deferred word before the interrupts are enabled.
If you use the standard IRQ and FIQ chains, just write your
handler as a CODE ... END-CODE
definition and add it to
the chain in the usual way.
If you want to replace one of the MPE interrupt handlers, please
use the code in ARM\INTARM3.FTH as an example if you
have not written ARM interrupt handlers before.
Interrupts can be in one of two states, enabled or disabled.
To disable interrupts use DI. Once DI has been executed, all
interrupts are disabled. Some MPE code uses DI
to
disable the IRQ interrupt, and DFI
to disable the
FIQ line.
The following example patches a high-level interrupt service routine (ISR) onto a timer interrupt.
The example ISR increments a variable that can be fetched in the foreground to detect that the timer is working. The ISR also needs to acknowledge that the interrupt has happened.
VARIABLE TICKS
: TIMER-SERVICE \ --
_TCSR C@ DROP \ must *read* the register
_TCSR_VALUE _TCSR W! \ clear interrupt
1 TICKS +! \ increment counter
;
Before the timer ISR will run it requires initialising:
_TCSR_/32 \ select required clock rate
_TCSR_TME OR \ enable timer and
_TCSR_PASSWORD OR \ use "password"
EQU _TCSR_VALUE \ to build the *word* that
\ we will send to tscr.
: TIMER-INIT \ --
_TCSR_VALUE _TCSR W! \ enable timer, select rate
EI \ enable interrupts
;
The ISR needs to be patched onto the timer interrupt. This
interrupt has been setup so you need only to assign the timer code
to the DEFER
ed word TOV-ISR
.
ASSIGN TIMER-SERVICE TO-DO TOV-ISR
The timer needs to be initialised with TIMER-INIT
and
then the timer can be tested by checking the variable TICKS
.
TIMER-INIT
TICKS ?
TICKS ?
This displays the current value of TICKS
.
When an interrupt occurs and is accepted, an ARM CPU branches through a location (a `vector') in low memory. A conventional interrupt handler written in assembler simply saves any registers and fixed locations it will use, then calls or executes the required routine, and then restores the previous state of registers and locations, and returns. The Forth interrupt handlers work in the same way, with a few additions.
Interrupt handlers written entirely in assembler are written in
the usual way. When a Forth VARIABLE
is referred to by name
inside a code section, its data address in RAM is returned. Thus
variables can be shared between high level routines and
subroutines or assembler interrupt handlers.
The code for the interrupt handler can be found in the file
ARM\INTARM3.FTH
. This source code can be modified and
updated as required. The interrupt handler code can
be adapted for single-chip use. This file supports a number of
different interrupt controllers. The functions supported are
documented within the file itself and in the ARM specific target
manual. The files INTARM.FTH and INTARM2.FTH are
provided for compatibility with earlier releases, but are no
longer supported by MPE. We strongly encourage you to convert
code using the earlier files to use INTARM3.FTH.
A separate user area is reserved for the interrupt handler, and is shared by all the high-level interrupt handlers. Consequently high-level Forth interrupts cannot be nested if user variables are changed, although an interrupt that does not use the Forth interrupt handler can be nested. The layout of the interrupt RAM page is identical to that for a task page (see later).
When an interrupt that has a high-level handler occurs, all registers are pushed onto the stack. These are then loaded with the required values for interrupt processing.
The interrupt code causes the processor to jump to an area of memory that contains calls to two words. The first is a call to a word to run the interrupt; this can be reassigned. The second is a call to the return from interrupt word.
To set the action of an interrupt, simply assign that word to do
the appropriate deferred word. The return from interrupt is
provided by the second word of the table entry. This means that
the action word need contain no return action, and consequently it
may be tested from the keyboard. In the earlier example, the
variable TICKS
can be inspected to see that the timer is
running. Changes to this structure can be made by rebuilding
the Forth kernel with the cross compiler.
In particular, some interrupts may need to be serviced by an assembler routine, and the overhead of the Forth dispatcher is unwanted. In this instance edit the interrupt entry code to call a different routine.
Since ARM interrupts (exceptions) are vectored, the final requirement is to set the vector itself.
If you wish to add vectors for processing other exceptions, you should use the code provided in INTARM3.FTH as a prototype, and follow a similar structure.
The words ASSIGN
and TO-DO
can be used to assign
a Forth word to be executed by the interrupt handler in the
following manner.
ASSIGN action TO-DO xxxx-ISR
where xxxx
is the name of the interrupt, as given in
the table below, and action
is the Forth word that the
interrupt handler must execute. Thus to set up a complete
interrupt system the vector must be assigned, and the word
connected to the interrupt structure.
ASSIGN action TO-DO xxxx-ISR
<xxxx-INT0> <vector-name> !
where <vector-name>
is the vector name. Note that interrupt
handlers written in assembler do not need to use these structures
at all. They must still, however, set the CPU vectors in low
memory.
Source | Vector name | Entry point | Deferred word |
---|---|---|---|
SWI | SWIV | SWI_exception | SWI_handler |
Undef | UndefV | Undef_exception | Undef_handler |
Prefetch abort | PabortV | PAbort_exception | PAbort_handler |
Data abort | DabortV | DAbort_exception | DAbort_handler |
Unused | UnusedV | Unused_exception | Unused_handler |
IRQ | IRQV | IRQ_exception | Chained by ADD-IRQ |
FIQ | FIQV | FIQ_exception | Chained by ADD-FIQ |
The word [I
saves the current interrupt status on the
return stack, and disables interrupts, which can then be
restored by I]
which restores the interrupt mask to
the state saved by [I
. If simple enabling and
disabling of interrupts is required, the words EI
and
DI
respectively perform these functions. [I
and
I]
are particularly necessary for critical sections
of code, and for implementing semaphores. In general the use
of [I ... I]
requires less code than the use of
SAVE-INT ... RESTORE-INT
below, which are now deprecated.
The word SAVE-INT
saves the current interrupt status,
and disables interrupts, which can then be restored by
RESTORE-INT
which restores the interrupt mask to the
state saved by SAVE-INT
.
On the ARM compiler these words are implemented by code
generators, and so it is not necessary to supply target
definitions for them. If they are required interactively, example
definitions can be found in LIBARM.FTH, and will be included
automatically if required, providing that you use the relevant
LIBRARIES ... END-LIBS
sequence.
Certain interrupts on the ARM cores require that the programmer explicitly clear the interrupt source before returning from the interrupt. This action must be performed by the high-level interrupt routine. In many cases, resetting the appropriate bit in a status register clears the interrupt source. Glossary
This glossary contains details of the major words in the interrupt system. Other words exist, but are only used as fractions of the words below. The source code for all these words may be found in ARM\IntARM3.fth.
code DI \ --
Disables interrupts.
code EI \ --
Enables interrupts.
code [I \ R: -- x
Save the current interrupt status on the return stack and disable
interrupts. This word can only be used inside a colon definition
and [I
and I]
must be used in matching pairs.
code I] \ R: x --
Restore the interrupt status from the return stack. This word can
only be used inside a colon definition and [I
and I]
must be used in matching pairs.
code RESTORE-INT \ x --
Restore the interrupt enable state previously saved by
SAVE-INT
. OBSOLETE, use I]
instead.
code SAVE-INT \ -- x
Saves the current state of the interrupt enable on the stack, and
disables interrupts. See RESTORE-INT
.
OBSOLETE, use [I
instead.