Before you attend this weeks lab, make sure:
- you understand control flowwhat 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
- youre able to browse around and understand new assembly code (e.g. provided in a library) with the help of the assembler documentation
In this weeks 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 neighbourwhat 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 todays lab, this all changes. Youre 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 youll 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 its not, really. Youll get the hang of it 🙂
Plug in your discoboard, fork & clone the lab 9 template and lets 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 dont 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 theCLKSOURCE
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 youll 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.
Were in week 9 now, so you now have the tools to read the manual and figure it out for yourself (although dont 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 anldr
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, youll 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 youve 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 youre 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 theres another bit in the SYST_CSR
configuration register which you didnt 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 its finished. In words, when an interrupt comes in then the CPU stops what its 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 discoboards memory where the addresses (i.e. the place to branch to) of the handler routine for each interrupt
You might be wonderingwhere does my code branch to when the interrupt comes in? Well, thats what the vector table is for. Its 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 pointsthe 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 programs 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 theres 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
directivecan you see how it mirrors the table from Section B1.5.2? You can see that theres 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 isnt very interesting at the moment, its 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 abx lr
at the end) calledSysTick_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:
- youll need to declare
SysTick_Handler
as a label with.global
visibility so that the address of yourSysTick_Handler
function will get used in the vector table insrc/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, %function
1 - 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 handlerthe function which gets called when the interrupt is triggered.
In this exercise were going to configure an interrupt based on the GPIO pins. In this lab, were going to be using our GPIO pins as input devices to register a click on the discoboards blue diamond-shaped joystick (pictured below). Finally, you can give (physical) input to your discoboard!
Compared to the SysTick interrupt, theres 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 interruptsits not just something which ST decided to put in when they designed your discoboard, its part of the ARM standard. The GPIO pins, on the other hand, arent part of a standardeach 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 hardwares perspective):
- the EXTI notices the hardware event (e.g. an edge trigger on a GPIO line, or a timer event from one of the discoboards 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, youll get another chance to work through it (in more detail) in next weeks lab.
There are often more things to configure (i.e. GPIO pins) than there are bits in a 32-bit registercan you guess how the designers of the discoboard get around this limitation?
Since youre hopefully well acquainted with the load-twiddle-store process for writing the configuration registers like this, to start this lab weve provided some files with the initialisation code to save you some time. Its worth having a look through, thoughif you dont understand what theyre doing (and you cant figure it out from the assembler manual) then you need to ask for helpits 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 doingif you dont, youll 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 wellhave a look at the functions marked
.global
at the top ofled.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
youll need to writedont forget to declare the handler as.type EXTI0_IRQHandler, %function
- unlike the SysTick interrupt, the EXTI interrupts are not enabled by defaultthey 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 address0x40010400
) - the EXTI controller can use either a rising edge (when the signal goes from
0
to1
) or falling edge (when it goes from1
to0
) or both as a trigger for the interruptits currently set to rising edge in thejoystick_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 its 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 its symbol to the vector table. Commit & push your program to GitLab.
In this exercise youve turned on the centre button on the joystick (PA0), but the directions (up/down/left/right) dont worktheyre connected to pins 1 to 5 of the same GPIO port (GPIOA). If youre 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, %functionSysTick_Handler: bl red_led_onSysTick_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 (its an interrupt, not the right place to do computationally intensive work). But its 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. Heres a diagram to show what it might look like:
This isnt the full story, thoughthe discoboard doesnt 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 its 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 doesnt 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) youll 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 address0xE000ED20
) 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 valueswhat happens if theyre the same?
If youre wondering how to figure out exactly which bits to set to control the priorities, then heres an article which might help you out. Its 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. Its a simple game:
- you blink the red LED on your discoboard for a short time every 5 seconds
- the players 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 its harder to get the timing right for the next round).
When youre clicking your joystick, try and be a bit gentle on your discoboard. Make sure youre on a flat surface (and that none of the pins are likely to get bent). Dont get too carried away and smash your fist down on the joystickits 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 registerNVIC_ICER0
(address:0xE000E180
)
- enable the joystick interrupt by setting the bit in the interrupt set enable register
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
5/5 – (1 vote)
Theres 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 youre 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 phasewhich (almost certainly) isnt what you want!
Again, heres an example: say there are 3 interrupts currently enabled, then a load from the corresponding register would have 1
s in those three positions, and 0
s 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 youre interested in, and a 0
in all the other bits.
Next weeks lab goes into more detail about how the interrupts are routed in your discoboard, so if youre still confused/curious then jump ahead and see how it works in more detail.
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 discoboards 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 workingits not too difficult.
Reviews
There are no reviews yet.