(Editor's note: In this series of ten articles Miro Samek of
Quantum Leaps details developing apps on the ARM processor using QNU,
complete with source code in C and C++.)
In this final part of this article series I explain the
"Blinky" example application included in the code accompanying this
article. I also give some tips for manual testing of various interrupt
preemption scenarios.
The Blinky Example Application
The example project is called "Blinky" because it blinks the 4 user
LEDs of the AT91SAM7S-EK evaluation board (Figure 1 below). Blinky is just a
primitive foreground/background (main+ISRs) application, but has been
carefully design to demonstrate all the features and techniques
discussed in this multi-part article.
 |
| Figure
1 Atmel AT91SAM7S-EK evaluation board executing the Blinky application. |
Specifically, Blinky relies on proper initialization of the .data
and .bss sections, as well as .rodata, .text, and .stack sections. The
application performs the low-level initialization in C to setup the PLL
to generate the 48MHz clock for the AT91SAM MCU and to perform the
memory remap operation (See Part
2 of
this article series).
I've fine-tuned the application by placing hot-spot functions in RAM
for fast execution (See Part 5),
whereas I used both ways of locating functions in RAM: the explicit
section assignment with the __attribute__ ((section
(".text.fastcode"))), and the direct placement of functions in the
linker script blinky.ld. I've also compiled selected files in the
application to ARM and others to Thumb to demonstrate ARM-Thumb
interworking (See the Makefile).
The C++ version of Blinky (located
in the cpp_blinky directory) relies on static constructor
invocation and uses virtual functions with late binding and early
binding demonstrated in the code. As you can check by comparing the map
files, the overhead of the C++ version with respect to the C version is
only 500 bytes, but please keep in mind that the C++ version does
significantly more than the C version because it supports polymorphism.
Blinky uses interrupts extensively at a very high rate (about 47kHz
on average, or every 1000 CPU clock cycles). The interrupt level of the
application (foreground) consists of three interrupts configured as
follows:
1. Programmable Interval
Timer of the AT91SAM7 (PIT) firing 100 times per second, configured as
low-priority IRQ;
2. Timer0 RC-compare
occurring every 1000 MCK/2 clocks (about 24kHz rate), configured as
high-priority IRQ, and
3. Timer1 RC-compare
occurring every 999 MCK/2 clocks (about 24kHz rate) configured as FIQ
by means of the "fast forcing" feature of the AT91SAM7S MCU [1].
I've specifically configured the clocks of Timer0 and Timer1 to the
maximum available frequency (MCK/2) and have chosen their periods very
close to each other so that the relative phasing of these interrupts
shifts slowly by just 2 CPU clock cycles over the interrupt period.
This causes the interrupts to overlap for a long time, so that
virtually every machine instruction of the IRQ handler as well as the
FIQ handler gets "hit" by the interrupt. Timer0 IRQ and Timer1 FIQ
overlap at the beat frequency of MCK/2/(1000*999), which is about 27
times per second.
The ISRs communicate with the background loop (and potentially among
themselves) by means of flags grouped into the shared bitmask. All ISRs
signal events to the background loop by means of the function
eventFlagSet(), which internally uses the critical section described in
Part 7 of this article series
to protect the shared bitmask.
Listing 1 below shows the
structure of the background loop. The loop checks the shared event
flags for occurrences of events by calling the function
eventFlagCheck().
This function tests a given flag inside a critical section and also
clears the flag if it's been set. For each event flag that has been
set, the background loop calls the dispatch() method on behalf of the
corresponding object of class Blinky that encapsulates one LED of the
AT91SAM7S-EK board.
 |
| Listing
1 The background loop of the Blink application (C++ version). |
From the user's perspective, the main job of each Blinky object is
to decimate the high rate of the dispatched events so that the LED
blinks at a lower rate observable by the human eye.
Figure 2 below shows the
lifecycle of the Blinky class, which is a simple counting state machine
alternating between ON and OFF states. In each state the state machine
down-counts the number of events (number of calls to the
Blinky::dispatch() method) from the pre-configured delay value for this
state. The state machine transitions to the opposite state when the
down-counter reaches zero.
For example, the pre-configured delays in Figure 2 are three ticks
in the OFF state and two ticks in the ON state, which results in the
LED blink rate of 1/5 of the original event rate with the duty cycle of
2/3.
(NOTE: The C version of the Blinky
application emulates the class concept in C. This simple technique
might be interesting in itself. You can also quite easily implement
single inheritance and even polymorphism in C [see www.quantum-leaps.com/devzone/cookbook.htm#OOP
[2] for more information]).
 |
| Figure
2 - Lifecycle of a Blinky object: state machine (a), and sequence
diagram (b). |
Manual Testing of Interrupt
Preemptions Scenario
The Blinky application performs quite extensive testing of the
interrupt handling implementation discussed in Parts 6, 7,
8 and 9 of this multi-part series.
The high interrupt rate and the constantly changing relative phasing
of the interrupts offer ample opportunities for preemptions among IRQs,
the FIQ, and the background loop.
Here I would like to share with you a complementary technique for
manual testing of various interrupt scenarios, so that you can easily
trigger an interrupt at any machine instruction and observe the
preemptions it causes.
The Blinky example application includes special instrumentation for
manual testing of interrupts. When you uncomment the definition of the
macro MANUAL_TEST at the top of the bsp.c file, you'll configure the
Timer0 and Timer1 interrupts for manual triggering. As shown in Listing 2 below, the timers are
configured with just one count in the RC-compare register, and the
software triggers are NOT applied.
 |
| Listing
2 Configuring Blinky for manual testing |
With this setup, you can manually trigger the timers from the
debugger by writing 0x05 (SWTRG + CLKEN) to the timer's Control
Register (TCx_CR). The TC0_CR is located at the addresses 0xFFFA0000,
and the TC1_CR is located at 0xFFFA0040 [1]. Because of just one count in
the RC-comapre register, the timer expires immediately and triggers the
desired interrupt before the next machine instruction gets executed.
The general testing strategy is to break into the application at an
interesting point for preemption, set breakpoints to verify which path
through the code is taken, and trigger the desired timer.
Next, you need to free-run the code (don't use single stepping) so
that the interrupt controller can perform prioritization. You observe
the order in which the breakpoints are hit. This procedure will become
clearer after some examples.
The first interesting test is verifying the prioritization of IRQ
interrupts by the AIC. To test this scenario, you place a breakpoint
inside ISR_pit() (configured as
low-priority IRQ).
When the breakpoint is hit, you move the original breakpoint to the
very next machine instruction. You also set another breakpoint on the
first instruction of the ARM_irq low-level interrupt handler. Next you
trigger the Timer0 interrupt by writing 0x5 to the address 0xFFFA0000.
You hit the Run button.
The pass criteria of this test are as follows:
1. The first breakpoint hit
is the one inside the ARM_irq handler, which means that Timer0 IRQ
preempted the PIT IRQ. NOTE: After this breakpoint is hit, you need to
disable Timer0 from expiring again, by writing 0x2 (CLKDIS) to the
address 0xFFFA0000.
2. The second breakpoint
hit is the one inside ISR_pit(), which means that the low-priority IRQ
continues after the Timer0 IRQ completes. You need to remove all
breakpoints before proceeding to the next test.
The next interesting test is verifying that FIQ preempts any IRQ,
but only outside a critical section. You start by setting a breakpoint
inside any critical section, say in the eventCheckFlag() function. You
run the application.
After the breakpoint is hit, you trigger both Timer0 and Timer1, by
writing 0x5 to 0xFFFA0000 and 0xFFFA0040. Before you free-run again,
you move the original breakpoint to the next machine instruction and
set another breakpoint one machine instruction after the exit from the
critical section (at the BX r14 instruction). You also set breakpoints
on the first instruction of ARM_irq and ARM_fiq.
The pass criteria of this test are as follows:
1. The first breakpoint hit
is the one inside the eventCheckFlag() function, which means that
critical section cannot be preempted by any pending interrupts.
2. The second breakpoint hit
is the one in ARM_fiq interrupt handler, which means that FIQ wins the
head-to-head run against the IRQ. NOTE: At this point you need to
disable the Timer1 clock from expiring again, by writing 0x2 (CLKDIS)
to the address 0xFFFA0040.
3. The third breakpoint is
the one in ARM_irq, which means that IRQ follows back-to-back the FIQ,
before returning to the task-level code. NOTE: At this point you need
to disable the Timer0 clock from expiring again, by writing 0x2
(CLKDIS) to the address 0xFFFA0000.
4. Finally, the breakpoint
at the end of eventCheckFlag() function is hit, which means that the
task continues only after all interrupts complete.
I'm sure that this technique can help you in performing many more
interesting tests to study various preemption scenarios.
Conclusion:
In this 10-part article series I've explained most of the code and
techniques that you will need to build-bare metal ARM systems with the
GNU toolchain. I hope that you will find this article a significant
time-saver, which will not only help you to jump-start your project,
but also form the backbone of your final ARM-based product.
To read Part 1, go to What's
need to get started.
To read Part 2, go to
Startup code and the low level initialization
To read Part 3, go to The
Linker Script.
To read Part 4, go to C
and C++ compiler options
To read Part 5, go to Fine-tuning
the application
To read Part 6, go to General
description of interrupt handling
To read Part 7, go to Interrupt
Locking and Unlocking
To read Part 8, go to Low-level
Interrupt Wrapper Functions.
To read Part 9 go to C-Level
ISRs and Other ARM Exceptions
To download Zip files with the C and C++ source code
associated with this
article series, go to Embedded.com's Downloadable Code page,
or to Blinky for C and Blinky for C++.
Miro
Samek, Ph.D., is president of Quantum
Leaps, LLC. He can be
contacted at miro@quantum-leaps.com.
References
[1] Atmel Datasheet "AT91
ARM/Thumb-based Microcontrollers, AT91SAM7S64 " available online..
[2] Quantum Leaps, LLC, Object-oriented
programming in C.