Before you attend this weekβs lab, make sure:
- you understandΒ control flowβwhat factors influence the order in which instructions get executed in your program (we have been talking about this since week 2!)
- you have attended (or watched) theΒ lecturesΒ on the basics of interrupts
- youβre able to browse around and understand new assembly code (e.g. provided in a library) with the help of theΒ assembler documentation
In this weekβs lab you will:
- configure a timer interrupt to periodically βhijackβ the control flow of your program
- configure the GPIO pins connected to the joystick on your discoboard (the little blue diamond thingy) so that pressing down on the joystick triggers an interrupt
- write an interrupt handler function toΒ do something usefulΒ when you press the joystick button
- use interrupt priorities to control what happens when different interrupts come in at the same time
Introduction
Discuss with your neighbourβwhat does it mean for your program to have a βmain loopβ? On your discoboard, does your main loop have toΒ doΒ anything for the program to be useful?
So far, following the control flow through your program has been easy. In most cases, the execution (which you can track through theΒ pcΒ register) just flows from one assembly instruction (i.e. a line of assembly code) to the next. Sometimes you jump around with branch instructions (e.g.Β bΒ andΒ bl), and in certain cases you even makeΒ conditionalΒ branches using the condition flags in the status register (e.g.Β beq,Β bgtΒ orΒ bmi).
In todayβs lab, this all changes. Youβre going to configure aΒ timer interruptΒ which will periodically βinterruptβ the flow of your program, execute a special interrupt handler function, and then return back to where your βmainβ program was executing. Then youβll go further by showing how the discoboard can handleΒ multipleΒ interrupts, each with their own handler function, and how each interrupt has aΒ priorityΒ so thatΒ interrupts can interrupt one another. ItΒ soundsΒ confusingβ¦ but itβs not, really. Youβll get the hang of it π
Plug in your discoboard, fork & clone theΒ lab 9 templateΒ and letβs get started.
Exercise 1: enabling the SysTick timer
A timer is a hardware component which holds a value (like a register) which counts down (or up) over time. Timers come in various shapes and sizes; some are simple and donβt have much potential for configuration, while others areΒ extremelyΒ configurable, e.g. counting down to zero vs counting up from zero, counting at different rates, etc. Any given microcontroller can include many different timers, all with different names and configuration options, and multiple timers can be used simultaneously.
Your discoboard has a timer called theΒ SysTickΒ timer, described in theΒ ARM reference manualΒ inΒ Section B3.3. As with all things on your discoboard, you configure the SysTick timer by reading and writing to special hardware registers. To configure and use the SysTick timer your program needs to:
- enable the timer using theΒ SysTick Control and Status RegisterΒ (
SYST_CSR), (also set theΒCLKSOURCEΒ bit to use the processor clock); - set theΒ SysTick Reload Value RegisterΒ (
SYST_RVR)βthis is the value which gets loaded into the register when it is βreloadedβ, i.e. after it runs down to zero; - read the current value of the timer register using theΒ SysTick Current Value RegisterΒ (
SYST_CVR).
For example, if the SysTick timer is enabled (inΒ SYST_CSR) and the value ofΒ SYST_RVRΒ isΒ 0x4000Β then the timer will take 16384 cycles to count down to zero. How long this takes in wall-clock time depends on the CPU frequency (cycles per second) of the board.
To configure the SysTick timer youβll need to use theΒ load-twiddle-storeΒ pattern from lab 5 all over again. This time, the relevant information (addresses, offsets, bits) starts atΒ Section B3.3.2Β on page 677 of theΒ ARMv7 reference manualΒ and includes the next couple of sections as well.
Weβre in week 9 now, so you now have the tools to read the manual and figure it out for yourself (although donβt be afraid to ask your tutor for help). Here are a few things to be mindful of:
- remember that these are memory-mapped registers, so e.g. to read the current value into a general-purpose CPU register (e.g.Β
r0) you need to use anΒldrΒ instruction with the appropriate memory address - you can find the memory-mapped addresses for both of these registers in the table inΒ Section B3.3.2
- to enable the timer, youβll need to set theΒ enableΒ bit inΒ
SYST_CSRΒ and also set the clock source to use the processor clock - even though the timer will count down automatically (once tick per clock cycle) your program still needs to be running, so make sure youβve got an infinite βrunβ loop in your program
- the initial clock speed of your discoboard when you first turn it on is 4MHz so keep that in mind when youβre setting theΒ
SYST_RVRΒ reload value
For exercise 1, all you need to do is enable the SysTick timer, start it running, and watch the values from theΒ SYST_CVR.
Write an assembly program which configures the SysTick timer to count down fromΒ 4000000, and goes into aΒ finishedΒ infinite loop when the timer reaches zero. Commit and push your program to GitLab.
Exercise 2: configuing the interrupt
You may have noticed that thereβs another bit in theΒ SYST_CSRΒ configuration register which you didnβt set in the last exercise, but which looks interesting: theΒ TICKINTΒ bit. TheΒ ARMv7 refernce manualΒ says that this particular bit:
indicates whether counting to 0 causes the status of the SysTick exception to change to pending
So what does this mean, exactly? Well, as discussed in lectures, an interrupt/exception is βa signal to the processor emitted by hardware or software indicating an event that needs immediate attentionβ (fromΒ Wikipedia). If theΒ TICKINTΒ bit is set inΒ SYST_CSR, then the SysTick timer triggers an interrupt every time it counts down to zero. Your CPU handles this interrupt by branching to anΒ interrupt handlerΒ which will (hopefully) branch back when itβs finished. In words, when an interrupt comes in then the CPU stops what itβs doing and branches somewhere else.
The ARM CPU in your discoboard recognises many different types of interrupts. Some are triggered by timers, some are triggered by external peripherals (like the joystick), some are triggered by other chips or wires connected to the discoboard.
All interrupts on your discoboard have:
- an index (which is just a number for identifying the source of the interrupt)
- a priority
- an entry in theΒ vector table, which is a region of the discoboardβs memory where the addresses (i.e. the place to branch to) of theΒ handlerΒ routine for each interrupt
You might be wonderingβwhereΒ does my code branch to when the interrupt comes in? Well, thatβs what theΒ vector tableΒ is for. Itβs a special part of theΒ memory address spaceΒ (starting atΒ 0x0) where the addresses of the different interrupt handler functions are stored. Think of it like a bunch of βjump-off pointsββtheΒ codeΒ for handling the interrupt will be stored somewhere else, the vector table just has the address of the starting point for that code.
You can see your programβs vector table in theΒ lib/startup.SΒ file starting at around line 60
.section .rodata.vtable
.word _stack_end
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
@
@ more entries follow...
@
What does it mean if thereβs aΒ 0Β in a particular βslotβ in the vector table?
Try and find the vector table for yourself in the startup file. Look for theΒ .section .rodata.vtableΒ directiveβcan you see how it mirrors the table fromΒ Section B1.5.2? You can see that thereβs already aΒ SysTick_HandlerΒ label in there in the 16th slot in the vector table, but my βhot tipβ to you is that theΒ SysTick_HandlerΒ function isnβt very interesting at the moment, itβs just defined to be equal to theΒ Default_HandlerΒ (which is just an infinite loop) down at the bottom of the file.
Your job in Exercise 2 is to build on the counter program you wrote inΒ Exercise 1Β and add a couple of things:
- when you configure the timer, set theΒ TICKINTΒ bit as well
- somewhere in your program, write aΒ functionΒ (i.e. something which you canΒ
blΒ to and which does aΒbx lrΒ at the end) calledΒSysTick_Handler
If you set it up correctly, yourΒ Systick_HandlerΒ function will get called every time the counter gets to zero.
Again, here are a couple of things to be careful of:
- youβll need to declareΒ
SysTick_HandlerΒ as a label withΒ.globalΒ visibility so that the address ofΒ yourΒSysTick_HandlerΒ function will get used in the vector table inΒsrc/startup.S, not the boring default one down the bottom of that file) - similarly, make sureΒ
SysTick_HandlerΒ is declared as a function with the usualΒ.type SysTick_Handler, %function1 - remember that the interrupt handler (in this caseΒ
SysTick_Handler) needs to be a function, and also to play nice and obey theΒ AAPCSΒ (otherwise it might mess with other parts of your program)
Using theΒ led.SΒ library provided, write a program which uses theΒ SysTick_HandlerΒ interrupt to toggle the red LED on and off with a frequency of 1Hz (two toggles per second). Commit and push your program to GitLab.
Exercise 3: GPIO interrupts
Ok, so theΒ SysTick_HandlerΒ looks after the SysTick timer interrupt, but what about the other peripherals on your discoboard? Is there aΒ Joystick_HandlerΒ for handling presses on the joystick? If not, whereΒ canΒ you put your code to be executed when the joystick is pressed?
The discoboard includes a Nested Vectored Interrupt Controller (NVIC), a special bit of hardware which is responsible for watching the various bits of hardware (and software) which can trigger interrupts in your discoboard.
A brief recap: remember that interrupts are a method of triggering anΒ interruptionΒ to the sequence of assembly instructions being executed by the discoboard. Configuring interrupts requires (at a minimum) enabling the interrupt and creating anΒ interrupt handlerβthe function which gets called when the interrupt is triggered.

In this exercise weβre going to configure an interrupt based on the GPIO pins. In this lab, weβre going to be using our GPIO pins asΒ inputΒ devices to register a βclickβ on the discoboardβs blue diamond-shaped joystick (pictured below). Finally, you can give (physical) input to your discoboard!

Compared to the SysTick interrupt, thereβs a slightly different process in configuring GPIO pins as sources of interrupts. This is because SysTick interrupt is one of the 16 βbuilt-inβ ARM Cortex interruptsβitβs not just something which ST decided to put in when they designed your discoboard, itβs part of the ARM standard. The GPIO pins, on the other hand, arenβt part of a standardβeach microcontroller manufacturer is free to include (or not) any number of GPIO pins on their board, and the way that they are wired into the CPU is up to them (although there are some conventions, so most of them do things in pretty much the same way).
On your discoboard, the GPIO pins are managed through the Extended Interrupts and Events Controller (EXTI), which is described in detail inΒ Section 12Β of theΒ discoboard reference manual. From that section:
The extended interrupts and events controller (EXTI) manages the external and internal asynchronous events/interrupts and generates the event request to the CPU/Interrupt Controller (the NVIC) and a wake-up request to the Power Controller.
This means that raising a GPIO-triggered interrupt is really a two-stage process (at least from the hardwareβs perspective):
- the EXTI notices the hardware event (e.g. an edge trigger on a GPIO line, or a timer event from one of the discoboardβs many timers) and raises an interrupt line into the NVIC
- the NVIC deals with that interrupt,Β potentiallyΒ saving the current register context to the stack and switching to the handler function (depending on whether the interrupt is currently enabled, whether any higher priority interrupts are already running, etc.)
So, to configure your discoboard so that when you press the central joystick button an interrupt is triggered (which you can then write a handler for) you need to enable & configure the interrupt in both the EXTI and the NVIC. As for most things on your discoboard, this is done by reading & writing the right bits in the right places to the various EXTI & NVIC configuration registers.
If this is still a bit confusing, youβll get another chance to work through it (in more detail) inΒ next weekβs lab.
There are often more things to configure (i.e. GPIO pins) than there are bits in a 32-bit registerβcan you guess how the designers of the discoboard get around this limitation?
Since youβre hopefully well acquainted with the load-twiddle-store process for writing the configuration registers like this, to start this lab weβve provided some files with the initialisation code to save you some time. Itβs worth having a look through, thoughβif you donβt understand what theyβre doing (and you canβt figure it out from theΒ assembler manual) then you need to ask for helpβitβs not too late!
The template provides some starter code for using the joystick inΒ joystick.S. Make sure you read and understand what the functions in that library are doingβif you donβt, youβll have trouble later on. Here are a few things worth noticing:
- the LED library (which builds on the LED code from the last couple of labs) now supports the green LED as wellβhave a look at the functions markedΒ
.globalΒ at the top ofΒled.sΒ to get an idea of the things you can do with this library - the joystick init code sets the GPIO pins toΒ input modeΒ using theΒ
GPIOA_MODERΒ register (this is different to theΒ blinky lab, where we only used the pins in output mode) - the central joystick button is connected toΒ PA0Β which (at least as configured in the setup code) will trigger theΒ
EXTI0_IRQHandlerΒ youβll need to writeβdonβt forget to declare the handler asΒ.type EXTI0_IRQHandler, %function - unlike the SysTick interrupt, the EXTI interrupts are not enabled by defaultβthey must be enabled by setting the relevant bit in the interrupt mask registerΒ
EXTI_IMR1Β (described inΒ Section 12.5.1Β of theΒ disco-boardΒ reference manual, mapped to addressΒ0x40010400) - the EXTI controller can use either aΒ rising edgeΒ (when the signal goes fromΒ
0Β toΒ1) orΒ falling edgeΒ (when it goes fromΒ1Β toΒ0) orΒ bothΒ as a trigger for the interruptβitβs currently set to rising edge in theΒjoystick_initΒ function (think: what will the difference be if you set a falling edge trigger instead/as well?) - once the interrupt handler function has done whatever it needs to do, it needs to tell the EXTI controller that itβs finished handling interruptΒ nΒ by writing theΒ nth bit in theΒ
EXTI_PR1Β βpendingβ register (0x40010414)
Write a program where pressing the central joystick button toggles the green LED on and off. Hint: the interrupt configuration & enabling is already done inΒ joystick_initβfor this Exercise you only need to write the interrupt handler function and add itβs symbol to the vector table. Commit & push your program to GitLab.
In this exercise youβve turned on the βcentreβ button on the joystick (PA0), but the βdirectionsβ (up/down/left/right) donβt workβtheyβre connected to pins 1 to 5 of the same GPIO port (GPIOA). If youβre keen, you can turn on the other ones (same process as before except some of the register addresses & bit indexes will be different).
Exercise 4: interrupt priorities
What happens when you are busy handling interrupt and another interrupt happens? In this exercise you will construct such a scenario and see how interrupt priorities work.
Use the following code as theΒ SysTick_HandlerΒ function:
.type SysTick_Handler, %function
SysTick_Handler:
bl red_led_on
SysTick_Handler_infloop:
nop
b SysTick_Handler_infloop
.size SysTick_Handler, .-SysTick_Handler
This handler just turns on the red LED and then goes into an infinite loop. The effect is that when the first SysTick interrupt happens, control flow will get stuck in this handler code.
This is actually aΒ badΒ idea for writing an interrupt handler. Usually you want the interrupt handling to be quick (itβs an interrupt, not the right place to do computationally intensive work). But itβs useful to do it this way to see how interrupt priorities work.
Now if you press the joystick button when the red LED is on, what happens?
Do you remember that theΒ NΒ inΒ NVIC stands forΒ nested? This means that the interrupts can happen inside of one another. Hereβs a diagram to show what it might look like:

This isnβt the full story, thoughβthe discoboard doesnβt always βkick outβ the currently running interrupt for the new one, it depends on the priority. On the discoboard (as in life) some things are more important than others, and each interrupt has a priority associated with it. On your discoboard, this priority is represented by a 4-bit number, with 0 being the highest priority and 15 being the lowest. When an interrupt handler is running and a new interrupt is triggered, it will only preempt (i.e. interrupt) the currently running interrupt handler if the priority is lower. If itβs the same or higher, that interrupt handler will be run once the currently running one finishes (i.e. returns withΒ bx lr).
If your green LED doesnβt turn on when you press the joystick and the red LED is on, this means that either the SysTick interrupt has the same or higher priority (i.e. a smaller number as the priority value) than the EXTI0 interrupt. To change the interrupt priority so that you can click the green LED on even when the red one is blinking (i.e. when the SysTick interrupt handler is running) youβll need to lower the priority (give a higher number) to the SysTick interrupt.
Because the two interrupts (the SysTick timer interrupt and the EXTI0 joystick interrupt) have some differences as mentioned earlier (one is part of the core ARM Cortex standard, one is a discoboard-specific thing) you need to set their interrupt priorities in slightly different places:
- for the SysTick interrupt, you can set the interrupt priority by writing bits 28-31 of the System Handler Priority Register 3 (
SHPR3, base addressΒ0xE000ED20) described in B3.2.12 of theΒ ARM architecture reference manual - for the PA0 interrupt, you can set the interrupt priority by writing bits 20-23 of the NVIC interrupt priority register (
NVIC_IPR1, base addressΒ=0xE000E404)
Modify the priority of your SysTick interrupt handler so that itΒ doesΒ get preempted by the EXTI0 handler and the green light comes on when the red LED is on. Commit and push your program to GitLab. Experiment with different priority valuesβwhat happens if theyβre the same?
If youβre wondering how to figure out exactly which bits to set to control the priorities, thenΒ hereβs an article which might help you out. Itβs for a different ARM Cortex-M board (i.e. not your discoboard) the main principle is the same.
Exercise 5: QuickClick
In the final exercise, your job is to take your new knowledge of interrupts and make a game calledΒ QuickClick. Itβs a simple game:
- you blink the red LED on your discoboard for a short time every 5 seconds
- the playerβs goal is then to press the joystick button when the red LED is on
- if you get the timing right (i.e. the red LEDΒ isΒ on when the button is pressed) the green LED comes on
- each time you get it right, the red blink duration gets shorter (so that itβs harder to get the timing right for the next round).
When youβre clicking your joystick, try and be aΒ bitΒ gentle on your discoboard. Make sure youβre on a flat surface (and that none of the pins are likely to get bent). Donβt get too carried away and smash your fist down on the joystickβitβs not built to handle that π
For this exercise, you can take advantage of your ability to enable & disable different interrupts in software to make it easy to implement the βis the red light on? if so, then clicking the button will turn on the greenβ logic:
- perform all the configurations steps necessary (including defining the handler function) to use the joystick as an input device
- in yourΒ
SysTick_Handler:- enable the joystick interrupt by setting the bit in the interruptΒ setΒ enable registerΒ
NVIC_ISER0Β (address:Β0xE000E100) The bit you are looking to set can be found in the position column of the interrupt vector table in section 11.3 of theΒ discoboard reference manual - You may also want to clearΒ bothΒ EXTI and NVIC interrupt pending bitsΒ beforeΒ enabling the EXTI interrupt. Otherwise the pending interrupt will trigger when you enable it, causing the green LED to turn on as soon as the red is on.
- blink the red LED in aΒ blockingΒ fashion (i.e. use a delay, so that the red LED goes on and then off again before theΒ
SysTick_HandlerΒ exits) - beforeΒ
SysTick_HandlerΒ exits, disable the joystick interrupt by setting the bit in the interruptΒ clearΒ enable registerΒNVIC_ICER0Β (address:Β0xE000E180)
- enable the joystick interrupt by setting the bit in the interruptΒ setΒ enable registerΒ
Thereβs one more βgotchaβ to be aware of when dealing with theΒ clearΒ enableΒ andΒ clearΒ pendingΒ NVIC control registers (e.g.Β NVIC_ICER0Β orΒ NVIC_ICPR0). As described above, to disable an interrupt (ICER) or clear a pending interrupt (ICPR) you write aΒ 1Β to the corresponding bit (e.g. to disable the interrupt in position 6 of the NVIC you write aΒ 1Β to the 7th bit from the right inΒ NVIC_ICER0).
However, you might have noticed something if you were reading Sections B3.4.5 (p684) and B3.4.7 (p685) theΒ ARM reference manualΒ really closely. In the description for those registers it says:
1: On reads, interrupt enabled
which means that if an interrupt is enabled, then aΒ readΒ from that (memory-mapped) register will show the corresponding bit asΒ 1.
This is a problem for theΒ load-twiddle-storeΒ pattern, because the point of the load twiddle store is to leave all the bits unchanged except for the one youβre interested in. However, this means that all of the currently enabled interrupts (whose bits will read asΒ 1Β in theΒ loadΒ phase) will be disabled when you write the bits back in theΒ storeΒ phaseβwhich (almost certainly) isnβt what you want!
Again, hereβs an example: say there are 3 interrupts currently enabled, then a load from the corresponding register would haveΒ 1s in those three positions, andΒ 0s elsewhere. If you load/twiddle/store the value, then all three of those interrupts would beΒ clearedΒ by the store operation.
This means that for theΒ clearΒ enable/pendingΒ registers you should just write aΒ 1Β for the particular interrupt youβre interested in, and aΒ 0Β in all the other bits.
Next weekβs lab goes into more detail about how the interrupts are routed in your discoboard, so if youβre still confused/curious thenΒ jump aheadΒ and see how it works in more detail.
Can you see how you can use this technique to temporarily enable the joystick interrupt in the SysTick interrupt handler so that the joystick will only work when the red LED is on?
Implement the QuickClick game following the steps above (you can use as much of the startup code provided earlier as you like). Commit and push your program to GitLab.
Extension ideas
There areΒ heapsΒ of things you can do to stretch yourself further:
- can you modify QuickClick to make it more fun? (e.g. using the direction buttons on the joystick as well?)
- can you re-implement the QuickClick game without interrupts?
- can you turn on the discoboardβs random number generator (RNG) and use it so that the red LED blinks on randomly, rather than at regular intervals? Hint:Β Section 24Β of theΒ discoboard reference manualΒ is the place to find the configuration steps required to get the RNG workingβitβs not too difficult.



