(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 final part of this article series I explain the”Blinky” example application included in the code accompanying thisarticle. I also give some tips for manual testing of various interruptpreemption scenarios.
The Blinky Example Application
The example project is called “Blinky” because it blinks the 4 userLEDs of the AT91SAM7S-EK evaluation board (Figure 1 below ). Blinky is just aprimitive foreground/background (main+ISRs) application, but has beencarefully design to demonstrate all the features and techniquesdiscussed in this multi-part article.
|Figure1 Atmel AT91SAM7S-EK evaluation board executing the Blinky application.|
Specifically, Blinky relies on proper initialization of the .dataand .bss sections, as well as .rodata, .text, and .stack sections. Theapplication performs the low-level initialization in C to setup the PLLto generate the 48MHz clock for the AT91SAM MCU and to perform thememory remap operation (See Part2 ofthis article series).
I've fine-tuned the application by placing hot-spot functions in RAMfor fast execution (See Part 5 ),whereas I used both ways of locating functions in RAM: the explicitsection assignment with the __attribute__ ((section(“.text.fastcode”))), and the direct placement of functions in thelinker script blinky.ld. I've also compiled selected files in theapplication to ARM and others to Thumb to demonstrate ARM-Thumbinterworking (See the Makefile ).
The C++ version of Blinky (locatedin the cpp_blinky directory ) relies on static constructorinvocation and uses virtual functions with late binding and earlybinding demonstrated in the code. As you can check by comparing the mapfiles, the overhead of the C++ version with respect to the C version isonly 500 bytes, but please keep in mind that the C++ version doessignificantly more than the C version because it supports polymorphism.
Blinky uses interrupts extensively at a very high rate (about 47kHzon average, or every 1000 CPU clock cycles). The interrupt level of theapplication (foreground) consists of three interrupts configured asfollows:
1. Programmable IntervalTimer of the AT91SAM7 (PIT) firing 100 times per second, configured aslow-priority IRQ;
2. Timer0 RC-compareoccurring every 1000 MCK/2 clocks (about 24kHz rate), configured ashigh-priority IRQ, and
3. Timer1 RC-compareoccurring every 999 MCK/2 clocks (about 24kHz rate) configured as FIQby means of the “fast forcing” feature of the AT91SAM7S MCU .
I've specifically configured the clocks of Timer0 and Timer1 to themaximum available frequency (MCK/2) and have chosen their periods veryclose to each other so that the relative phasing of these interruptsshifts slowly by just 2 CPU clock cycles over the interrupt period.
This causes the interrupts to overlap for a long time, so thatvirtually every machine instruction of the IRQ handler as well as theFIQ handler gets “hit” by the interrupt. Timer0 IRQ and Timer1 FIQoverlap at the beat frequency of MCK/2/(1000*999), which is about 27times per second.
The ISRs communicate with the background loop (and potentially amongthemselves) by means of flags grouped into the shared bitmask. All ISRssignal events to the background loop by means of the functioneventFlagSet(), which internally uses the critical section described inPart 7 of this article seriesto protect the shared bitmask.
Listing 1 below shows thestructure of the background loop. The loop checks the shared eventflags for occurrences of events by calling the functioneventFlagCheck().
This function tests a given flag inside a critical section and alsoclears the flag if it's been set. For each event flag that has beenset, the background loop calls the dispatch() method on behalf of thecorresponding object of class Blinky that encapsulates one LED of theAT91SAM7S-EK board.
|Listing1 The background loop of the Blink application (C++ version).|
From the user's perspective, the main job of each Blinky object isto decimate the high rate of the dispatched events so that the LEDblinks at a lower rate observable by the human eye.
Figure 2 below shows thelifecycle of the Blinky class, which is a simple counting state machinealternating between ON and OFF states. In each state the state machinedown-counts the number of events (number of calls to theBlinky::dispatch() method) from the pre-configured delay value for thisstate. The state machine transitions to the opposite state when thedown-counter reaches zero.
For example, the pre-configured delays in Figure 2 are three ticksin the OFF state and two ticks in the ON state, which results in theLED blink rate of 1/5 of the original event rate with the duty cycle of2/3.
(NOTE: The C version of the Blinkyapplication emulates the class concept in C. This simple techniquemight be interesting in itself. You can also quite easily implementsingle inheritance and even polymorphism in C [see www.quantum-leaps.com/devzone/cookbook.htm#OOP for more information] ).
|Figure2 – Lifecycle of a Blinky object: state machine (a), and sequencediagram (b).|
Manual Testing of InterruptPreemptions Scenario
The Blinky application performs quite extensive testing of theinterrupt handling implementation discussed in Parts 6 , 7,8 and 9 of this multi-part series.
The high interrupt rate and the constantly changing relative phasingof 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 formanual testing of various interrupt scenarios, so that you can easilytrigger an interrupt at any machine instruction and observe thepreemptions it causes.
The Blinky example application includes special instrumentation formanual testing of interrupts. When you uncomment the definition of themacro MANUAL_TEST at the top of the bsp.c file, you'll configure theTimer0 and Timer1 interrupts for manual triggering. As shown in Listing 2 below, the timers areconfigured with just one count in the RC-compare register, and thesoftware triggers are NOT applied.
|Listing2 Configuring Blinky for manual testing|
With this setup, you can manually trigger the timers from thedebugger by writing 0x05 (SWTRG + CLKEN) to the timer's ControlRegister (TCx_CR). The TC0_CR is located at the addresses 0xFFFA0000,and the TC1_CR is located at 0xFFFA0040  . Because of just one count inthe RC-comapre register, the timer expires immediately and triggers thedesired interrupt before the next machine instruction gets executed.
The general testing strategy is to break into the application at aninteresting point for preemption, set breakpoints to verify which paththrough the code is taken, and trigger the desired timer.
Next, you need to free-run the code (don't use single stepping) sothat the interrupt controller can perform prioritization. You observethe order in which the breakpoints are hit. This procedure will becomeclearer after some examples.
The first interesting test is verifying the prioritization of IRQinterrupts by the AIC. To test this scenario, you place a breakpointinside ISR_pit() (configured aslow-priority IRQ ).
When the breakpoint is hit, you move the original breakpoint to thevery next machine instruction. You also set another breakpoint on thefirst instruction of the ARM_irq low-level interrupt handler. Next youtrigger 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 hitis the one inside the ARM_irq handler, which means that Timer0 IRQpreempted the PIT IRQ. NOTE: After this breakpoint is hit, you need todisable Timer0 from expiring again, by writing 0x2 (CLKDIS) to theaddress 0xFFFA0000.
2. The second breakpointhit is the one inside ISR_pit(), which means that the low-priority IRQcontinues after the Timer0 IRQ completes. You need to remove allbreakpoints 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 breakpointinside any critical section, say in the eventCheckFlag() function. Yourun the application.
After the breakpoint is hit, you trigger both Timer0 and Timer1, bywriting 0x5 to 0xFFFA0000 and 0xFFFA0040. Before you free-run again,you move the original breakpoint to the next machine instruction andset another breakpoint one machine instruction after the exit from thecritical section (at the BX r14 instruction). You also set breakpointson the first instruction of ARM_irq and ARM_fiq.
The pass criteria of this test are as follows:
1. The first breakpoint hitis the one inside the eventCheckFlag() function, which means thatcritical section cannot be preempted by any pending interrupts.
2. The second breakpoint hitis the one in ARM_fiq interrupt handler, which means that FIQ wins thehead-to-head run against the IRQ. NOTE: At this point you need todisable the Timer1 clock from expiring again, by writing 0x2 (CLKDIS)to the address 0xFFFA0040.
3. The third breakpoint isthe 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 needto disable the Timer0 clock from expiring again, by writing 0x2(CLKDIS) to the address 0xFFFA0000.
4. Finally, the breakpointat the end of eventCheckFlag() function is hit, which means that thetask continues only after all interrupts complete.
I'm sure that this technique can help you in performing many moreinteresting tests to study various preemption scenarios.
In this 10-part article series I've explained most of the code andtechniques that you will need to build-bare metal ARM systems with theGNU toolchain. I hope that you will find this article a significanttime-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'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 read Part 9 go to C-LevelISRs and Other ARM Exceptions
MiroSamek, Ph.D., is president of QuantumLeaps, LLC. He can becontacted at firstname.lastname@example.org.