Building Bare-Metal ARM Systems with GNU: Part 9

(Editor's note: In this series of ten articles Miro Samek ofQuantum Leaps details developing apps on the ARM processor using QNU,complete with source code in C and C++.)

In this part I wrap up the interrupt handling for ARM by describing theinterrupt service routines (ISRs) in C, as well as the initializationof the ARM vector table and the interrupt controller.

The examples I present here pertain to the Atmel's AdvancedInterrupt Controller (AIC) [1], but should be easy to modify for other interrupt controllers (e.g.,NXP's VIC). I also discuss a rudimentary policy of handling other ARMExceptions, such as Undefined Instruction or Data Abort.

The BSP_irq Handler Function
As described in Part 6 , thelow-level interrupt “wrapper” function ARM_irq() calls the C-functionBSP_irq(), which encapsulates the particular interrupt controller ofyour ARM-based MCU. The BSP_irq() indirection layer is only necessaryto separate the generic ARM_irq() implementation from thevendor-specific interrupt controller interface.

If only the industry couldagree on the standardized interface tothe interrupt controller , the low-level IRQ handler ARM_irqcouldperform the vectoring in a standard way and thus eliminate the need forthe BSP_irq() indirection.

However, the variousARM-silicon vendors use different registerlocations for their interrupt controllers, so it's impossible toperform vectoring generically . (Ofcourse, at the cost of losinggenerality you can eliminate the BSP_irq() function overhead byin-lining it directly inside ARM_irq() ).

(NOTE:ARM Limited hasstandardized the interrupt controller interface in the new ARM v7-Marchitecture, which comes with the standard Nested Interrupt Controller(NVIC) [2] .)

Listing1. The BSP_irq() function defined in the file isr.c.

Listing 1 above shows theimplementation of the BSP_irq() function for the Atmel's AIC. Thehighlights of the code are as follows:

(1) The function BSP_irq()is assigned to the section .text.fastcode, which the linker scriptlocates in RAM for faster execution (see Part 2).

(2) The BSP_irq() functionis a regular C-function (not an IRQ-function!). It is entered with IRQdisabled and FIQ enabled.

(3) This typedef defines thepointer-to-function type for storing the address of the ISR obtainedfrom the interrupt controller.

(4) The current interruptvector is loaded from the AIC_IVR register into a temporary variablevect. Please note that BSP_irq() takes full advantage of the vectoringcapability of the AIC, even though this is not the traditionalauto-vectoring.

For vectoring to work, the appropriate Source Vector Registers inthe AIC must be initializedwith the addresses of the correspondinginterrupt service routines (ISRs).

(5) The AIC_IVR is written,which is necessary if the AIC is configured in protected mode (see Atmel's documentation [1] ). The writecycle to the AIC_IVR starts prioritization of this IRQ.

(6) After the interruptcontroller starts prioritizing this IRQ, it's safe to enable interruptsat the ARM core level.

(NOTE: Here the inline assembly isused to clear the I-bit in the CPSR register. The MSR instruction isavailable only in the ARM instruction set, which means that the modulecontaining BSP_irq() must be compiled to ARM .)

(7) The interrupt handler isinvoked via the pointer-to-function (vector address) extractedpreviously from the AIC_IVR.

(8) After the ISR returns,IRQ interrupts are locked at the ARM core level by means of inlineassembly.

(9) The End-Of-Interruptcommand is written to the AIC, which informs the interrupt controllerto end prioritization of this IRQ.

The BSP_fiq Handler Function
The AIC, as most interrupt controllers integrated into ARM-based MCUs,does not protect the FIQ linewith the priority controller [1] .Therefore, even though the AICis still capable of performing vectoring of the FIQ, it doesn't reallyadd much value. The implementation of BSP_fiq() shown in Listing 2 below handles the entirework of the interrupt directly, without any interaction with the AIC.

Listing2 The BSP_fiq() function defined in the file isr.c.

The highlights of Listing 2 above are as follows:

(1) The function BSP_fiq()is assigned to the section .text.fastcode, which the linker scriptlocates in RAM for faster execution (see Part 2).

(2) The BSP_fiq() functionis a regular C-function (not an FIQ-function!). It is entered with bothIRQ and FIQ disabled and must never enableinterrupts.

(3-4) The functionBSP_fiq() performs directly the whole work of the interrupt. In thiscase, the work consists of clearing the interrupt source and setting aflag in a bitmask that is shared with the task-level code and perhapsother interrupts as well.

The function eventFlagSet() is designed to be called from FIQ, IRQand the main loop, and thus provides an example of a universalcommunication mechanism within a foreground/background application.Internally, eventFlagSet() protects the shared bitmask with a criticalsection, which is specifically designed to be safe to use in allcontexts (such as task-level, IRQ, and FIQ).

Please refer to the file isr.c in the code accompanying this articlefor the self-explanatory implementation of this function. You mightalso want to go back to the critical section implementation describedin Part7 of this articleseries.

Interrupt Service Routines
The main job of the BSP_irq() indirection layer is to obtain theaddress of the interrupt service routine (ISR) from the interruptcontroller and to invoke the ISR. The ISRs are regular C-functions (notIRQ-type functions!). You are free to compile the ISRs to ARM or Thumb,as you see fit. Listing 3 below shows two examples of ISRs for the Blinky example applicationaccompanying this article.

Listing3 Examples of ISRs.

The highlights of Listing 3 above are as follows:

(1) The C-level ISR is aregular void (*)(void) C-function. The ISR is called in SYSTEM modewith IRQ/FIQ interrupts unlocked at the ARM core level.

(2) The level-sensitiveinterrupt source is cleared, which in this case is the AT91Programmable Interval Timer (PIT). Please note that even thoughinterrupts are unlocked at the ARM core level, they are stillprioritized in the interrupt controller, so a level-sensitive interruptsource does not cause recursive ISR reentry.

(3) The work of theinterrupt consists in this case of setting a shared flag to inform themain() loop about the time tick. The function eventFlagSet() internallyprotects the shared bitmask with a critical section, which is necessarybecause IRQ interrupts can preempt each other [see Listing 1(#6) ].

Please note that the interrupt controller only allows preemptions byIRQs prioritized higher than the currently serviced interrupt, soISR_tick() cannot preempt itself.

(4) The spurious ISR isempty.
[NOTE: Spurious interrupts arepossible in ARM7/ARM9-based MCUs due to asynchronous interruptprocessing with respect to the system clock. A spurious interrupt isdefined as being the assertion of an interrupt source long enough forthe interrupt controller to assert the IRQ, but no longer present wheninterrupt vector register is read. The Atmel datasheet [1] and NXP Application Note [3] provide more information about spuriousinterrupts in ARM-based MCUs .]

Initialization of the Vector Tableand the Interrupt Controller
The whole interrupt handling strategy hinges on the properinitialization of the ARM vector table and the interrupt controller.The code accompanying this article performs this initialization in thefunction BSP_init() located in the file bsp.c.

Listing4. Initialization of the vector table and the interrupt controller.

The highlights of the BSP initialization in Listing 4 above are as follows:

(1) The function BSP_init()is called from main() to initialize the board. BSP_init() is calledwith IRQ and FIQ interrupts disabled.

(2-8) The secondary jumptable starting at address 0x20 is initialized with the addresses of thelow-level exception handlers in assembly (Part 2 of this article seriesdescribes the ARM vector table and the secondary jump table). All theselow-level handlers are defined in the file arm_exc.s provided in thecode accompanying this article. I will briefly discuss the ARMexceptions later in this article.

(7-8) In particular, thejump table entry for the IRQ at address (0x20+0x18==0x38) isinitialized with the address of the low-level handler ARM_irq() and thefollowing entry at 0x3C is initialized with the address of ARM_fiq().Both ARM_irq() and ARM_fiq() are discussed in detail in Part 8 ofthis article series.

(9) The AIC_SVR (SourceVector Register) for the system time tick (PIT) is initialized with theaddress of the tick ISR (See also Listing 3 ).

(10) The AIC_SPU (SpuriousInterrupt Vector Register) is initialized with the address of thespurious ISR (See also Listing 3 ).

(11) The system time tickIRQ priority is set in the AIC.

(12) After the vector tableand the AIC have been configured, the interrupts must be enabled at theARM core level (See Part 7 of this article ).

Other ARM Exception Handlers
The BSP_init() function in Listing 4above initializes the secondary jump table with the addresses ofARM exception handlers, such as ARM_und (Undefined Instruction),ARM_swi (Software Interrupt), ARM_pAbort (Prefetch Abort), andARM_dAbort (Data Abort).

These low-level exception handlers are defined in the file arm_exc.cincluded in the code accompanying this article. All these handlersimplement a rudimentary exception handling policy that might beadequate for simple bare-metal ARM projects.

Listing5 Rudimentary exception handling policy

As shown in Listing 5 above ,every low-level exception handler (such as ARM_undef or ARM_dAbort)loads r0 with the address of the string explaining the exception andthen branches to the common handler ARM_except.

The common handler loads r1 with the return address from theexception, switches to the SYSTEM mode and calls C-functionBSP_abort(). The board-specific function BSP_abort() should try to logthe exception (the information about the exception is provided in r0and r1, which are the arguments of this function call), put the systemin a fail-safe state, and possibly reset the system.

This function should never return because there is nothing to returnto in a bare-metal system. During development, BSP_abort() is a goodplace to set a permanent breakpoint.

Coming Up Next
In the next and final part of this article I'll describe the exampleapplication that accompanies this article series and provide strategiesfor testing of the various preemption scenarios of interrupt handling.

To read Part 1, go to What'sneed to get started.
To read Part 2, go to Startup code and the low level initialization
To read Part 3, go to  TheLinker Script.
To read Part 4, go to  Cand C++ compiler options
To read Part 5, go to  Fine-tuningthe application
To read Part 6, go to  Generaldescription of interrupt handling
To read Part 7, go to  InterruptLocking and Unlocking
To read Part 8, go to Low-levelInterrupt Wrapper Functions.

To download the C and C++ sourcecode associated with this article series, go to Embedded.com's Downloadable Code page , or go to Blinky for C and Blinky for C++ to download theZipfiles.

MiroSamek, Ph.D., is president of QuantumLeaps, LLC. He can becontacted at miro@quantum-leaps.com.

References:
[1] Atmel Datasheet “AT91ARM/Thumb-based Microcontrollers, AT91SAM7S64 ” available online..
[2] ARM Limited, “ARM v7-MArchitecture Application Level Reference Manual“, available online..
[3] NXP Application NoteAN10414 “Handlingof spurious interrupts in the LPC2000“, available online.

1 thought on “Building Bare-Metal ARM Systems with GNU: Part 9

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.