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.
Unlike ARM7 and ARM9 CPUs, Cortex devices all use the same interrupt controller, the Nested Vectored Interrupt Controller, or NVIC for short. The target code file Cortex/IntCortex.fth contains the code.
Cortex devices differentiate between interrupts and exceptions. Exceptions may be fault handlers, or system interrupts such as the system ticker or the SVC call to a service. Fault handlers provided by MPE use a slightly different entry sequence for fault handlers. See the file Cortex/FaultCortex.fth for the details. Conditional compilation handles the differences between Cortex-M variants.
When an interrupt occurs and is accepted, a Cortex processor branches through a location (a 'vector'), which is usually in low memory. Each exception or interrupt has its own location. 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. The first 16 vectors are for reset and system exceptions, the rest are defined by the chip designer.
The Forth code refers to all exceptions by their index in the exception vector table. Thus exception 1 is the reset entry and exception 16 is the first chip-specific interrupt. This is unlike the ARM CMSIS habit, which uses negative numbers for the system exceptions and positive for chip-specific interrupts. Not all of the 16 system vectors are defined, and some manufacturers use one or more of the undefined locations for chip-specific data. Index zero is special in that it contains the value of the R13 stack pointer used at start up (SP_main in ARM documentation). The MPE code uses SP_main for interrupt and exception handlers, and the main application code uses SP_process. Normally Forth code runs in "privileged Thread mode" in ARM parlance.
For more information on Cortex interrupts, refer to the device's user guide and the Cortex-M3 Technical Reference Manual from www.arm.com. In this chapter we are mostly interested in the interrupts, although the principles apply equally well to the other exceptions.
The files Cortex\IntCortex.fth and Cortex\FaultCortex.fth contain conditional compilation to handle the differences between Cortex-M0/M1 and Cortex-M3 upwards.
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.
' A/D-READY 23 Exc: adc-isr
The name adc-isr
is a label you choose. It returns
the address of the exceptions's entry point.
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.
Fault handlers must be enabled with EFI
.
Just write your handler as a CODE ... END-CODE
definition and add it to the vector table in the usual way.
If your handler only needs to use registers R0..R3 and R12,
these are saved and restored by the core itself. In this
case you will not need the MPE wrapper for a high level
interrupt handler. The following template comes from a
SysTick handler.
0 value ticks
Proc SysTickISR \ --
mvl32 r0, # ' ticks >body
ldr r1, [ r0, # 0 ]
add .s r1, r1, # tick-ms
str r1, [ r0, # 0 ]
bx lr
end-code
SysTickISR SysTickvec# setExcVec
If you want to replace one of the MPE interrupt handlers, please
use the code in Cortex/IntCortex.fth as an example if you
have not written Cortex interrupt handlers before.
Interrupts can be in one of two states, enabled or disabled.
To enable interrupts use EI
. Once EI
has been
executed, all interrupts are enabled. MPE code uses EI
to enable interrupts, and EFI
to enable the fault
exceptions.
To disable interrupts use DI. Once DI has been
used, all interrupts are disabled. MPE code uses DI
to
disable interrupts, and DFI
to disable fault exceptions.
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 into the vector table.
' TIMER-SERVICE <vec#> Exc: 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
Cortex/IntCortex.fth. This source code can be modified and
updated as required. The interrupt handler code can
be adapted for single-chip use.
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 the Cortex 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 Cortex/LibCortex.fth, and
will be included automatically if required, providing that
you use the relevant LIBRARIES ... END-LIBS
sequence.
Most interrupts on Cortex 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.
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 Cortex/CodeCortex.fth.
code DI \ --
Disables interrupts.
code EI \ --
Enables interrupts.
code DFI \ --
Disables fault exceptions. Not available for Cortex-M0/M1.
code EFI \ --
Enables fault exceptions. Not available for Cortex-M0/M1.
code [I \ R: -- x1 x2
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: x1 x2 --
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.