Cortex interrupts

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.

Interrupts on the Cortex-M3+

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.

Interrupts on the Cortex-M0/M1

The files Cortex\IntCortex.fth and Cortex\FaultCortex.fth contain conditional compilation to handle the differences between Cortex-M0/M1 and Cortex-M3 upwards.

Writing Forth interrupt handlers

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.

Setting 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.

Some common problems

There are a few common problems that might cause an interrupt not to work correctly:

Stack faults

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.

Source is not cleared

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.

Writing assembler interrupt handlers

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.

Controlling the interrupts

Interrupts can be in one of two states, enabled or disabled.

Enabling interrupts

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.

Disabling interrupts

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.

A simple example

The following example patches a high-level interrupt service routine (ISR) onto a timer interrupt.

The timer ISR

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
;

Initialising the timer

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
;

Setting the vector

The ISR needs to be patched into the vector table.

  ' TIMER-SERVICE <vec#> Exc: TOV-ISR

Testing the timer is running

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.

Interrupt handlers in detail

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.

Interrupt protection

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.

End of interrupt requirements

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.

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 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.