Before you attend this weeks lab, make sure:
- you have completed lab 9 and understand how signals (i.e. voltage changes) on your GPIO pins can trigger interrupts
In this weeks lab you will:
- configure the GPIO pins on your board for both input and output
- connect the GPIO pins to one another with physical wires
- configure and write interrupt handlers to do things when stuff happens on these wires
- simulate a multiboard setup where you connect the two sides of your discoboard to each other with wires and turn the LEDs on from either side
Introduction
This week youll take a deeper dive into the GPIO & interrupt capability on your discoboard. The GP in GPIO stands for General Purpose, which means that each pin (the pointy little gold-coloured bits of metal sticking up in rows along the sides of your discoboard) can be used for either digital input or output (or even other things). The mode (input mode, output mode, alternate mode) of a given pin is configured (you guessed it!) by writing certain bits to special GPIO configuration registers.
In this weeks lab youll learn more about the stuff you did in Week 8 when you set the joystick pins to input mode, and youll learn how to control the signals (i.e. high and low voltages) coming out of the pins in software.
Exercise 1 lays the groundwork (and is pretty wordy) but then things pick up in Exercise 2, and by the end of the lab youll get to be really creative. This lab might seem a bit similar to last week, but it goes into a bit more depth about exactly what it means for you to configure, read & write to the GPIO pins, which is going to be super-helpful for assignment 3.
Today youll be working on sending signals to and from GPIO pins by connecting these pins using jumper cables. To start off, think about: in the context of GPIO pins, what is a signal? Is there a difference between a signal which comes internally through the discoboard (e.g. the joystick input you used in the Week 9 lab and the signal which comes in through an external wire?
Exercise 1: click-to-blink recap
This first exercise will seem like a re-hash of stuff youve done beforeclicking the joystick and turning on an LED.
However, this time when you fork & clone the lab 10 template and have a look inside theres some good news and some bad news:
- the bad news is that you dont have the sweet
joystick.S
andled.S
helper libraries from last time1 - the good news is that youll get to write it yourself and see exactly how it works (and there are still some helpful utility functions & macros in the templateyou dont have to start completely from scratch)
As you re-do the LED & joystick configuration using a generic GPIO config process youll get a better picture of how the GPIO infrastructure and NVIC/EXTI interrupt controllers work together on your discoboard.
GPIO output
To configure a pin in output mode, src/libcomp2300/macros.S
has a handy GPIO_configure_output_pin
macro. For example, to declare pin PH0
as an output pin, you could call the macro like so:
GPIO_configure_output_pin H, 0
Most of the macros in the macros.S
are just simple wrappers around function calls, using the macro to set up correct parameters (remember, macros are just kids with scissors!). You still need to be aware (and careful) of which registers the macros might touch. Before you use any macros in this lab, make sure you read the macro definition and understand what it is doing.
To use the GPIO pins, you need to turn them on by making sure the corresponding GPIO port receives a clock signal. Remember from lab 5 that the discoboards red and green LEDs are connected to ports B and E respectively:
GPIOx_clock_enable BGPIOx_clock_enable E
Make sure you enable port A
as well for joystick.
To send data out (i.e. to change the voltage) on the GPIO pin, you write a 0
or 1
to the GPIO ports Output Data Register (ODR). Remember that the red LED is connected on GPIO PB2
and the green LED is on PE8
. So you can turn on the LEDs by writing a 1
to their output data registers. Most of these helper macros have set
, clear
and toggle
versions, which do what youd expect.
GPIO_configure_output_pin B, 2 @ (red LED)GPIO_configure_output_pin E, 8 @ (green LED)GPIOx_ODR_set B, 2GPIOx_ODR_set E, 8
Once you write a signal (a 0
or 1
) to the GPIO line, how long does it stay there for? How could you figure this out?
GPIO input
All good so farremember that this is just a recap. Now, lets take the same approach to the joystick, which is similar to the LEDs in that its wired to specific GPIO pins (PA0
: centre, PA1
: left, PA5
: down, PA2
: right, PA3
: up) but this time its an input device. There are some macros for this, too:
GPIO_configure_input_pin A, 0 @ (central joystick button)
Here, youve declared the pin PA0
as an input pin. The GPIO_configure_input_pin
macro does a couple of things:
- sets the input bit pattern into the appropriate mode register (its
0b00
for input, just like it was0b01
for output) - configures the input pin to use a pull-down resistorthis is so that it will reliably read a
0
even if its not connected to anything (if you dont do this, then you might get weird results when your input isnt connected to anything)
To read data in from a GPIO pin, you can read the current value (high 1
or low 0
) on any GPIO pin at any time by reading the appropriate bit from the GPIO ports Input Data Register (IDR). Theres a helper macro for this as wellGPIOx_IDR_read
, and it sets flags based on the result (so the zero flag will be set if the GPIO line is low, and it wont be set if the GPIO line is high).
You can do this as often as you likereading data from the pin with GPIOx_IDR_read
will always leave the current value (0
or 1
) in r0
and also set the flags appropriately, and it doesnt change the signal on the pin. You can use this to poll a given pin in a loop:
poll_gpio: @ read PA0, set flags based on result GPIOx_IDR_read A, 0 @ do something based on the flags in here b poll_gpio
Write a program which enables the central joystick button as an input, and polls (as in the loop above) to turn the green LED on when the button is pressed. Commit & push your program to GitLab.
Exercise 2: lets do it again, this time using interrupts
As we talked about in the week 8 lectures, polling the current value on the pin in a loop isnt the best way to do things, because it makes it hard to do other stuff in the meantime. Theres a better way: configure the GPIO line to fire an interrupt when the value changes.
Before you can enable and configure the interrupts, you need to enable the System Configuration Controller (SYSCFG
) clock so that you can modify the system configuration (see Chaper 8 and 8.2.3 in the discoboard reference manual).
@ enable SYSCFG clockRCC_APB2ENR_set 0
This code is included in the template, you can see the relevant configuration register in Section 6.4.21 on p233 of the discoboard reference manual.
Theres one other macro in macros.S
which is helpful when setting up GPIO pins as input: GPIO_configure_input_pin_it
note the _it
suffix. This macro does all the configuration of GPIO_configure_input_pin
and additionally registers the pin as a source for interrupts. There are two parts of the discoboard which are working together to do this:
- the Extended Interrupts and Events Controller (EXTI) is the part of your discoboard which allows signals (either a rising or falling edge) on the GPIO pins to trigger an interrupt
- the Nested Vectored Interrupt Controller2 (NVIC) is the hardware which receives the interrupt, and (depending on the priority, what other interrupts are running, and a few other things) will interrupt the CPU and transfer control to the appropriate handler function in the vector table
The GPIO_configure_input_pin_it
sets the appropriate bits to enable the the pin as a source of interrupts. The next step in the EXTI configuration is to determine whether the interrupt will be triggered on a rising edge (0
to 1
transition) or a falling edge (1
to 0
transition). Again, there are helper macros for this:
GPIO_configure_input_pin_it A, 0EXTI_set_rising_edge_trigger 0EXTI_set_falling_edge_trigger 0
There are a couple of quirks here: firstly, the EXTI controller can only listen to one port for a given pin number, so for example you cant have both PA0
and PB0
triggering an interrupt. This is because the pins are multiplexed in the EXTI controller (see the diagram below). This is to keep things simpleyou probably dont need separate interrupt triggers on all the pins. Heres an example of what this looks like for EXTI0pin zero from all ports goes in there, and the EXTI controller can only listen to one at a time.
Secondly (and more confusingly) the EXTI controller only has 7 GPIO interrupt lines into the NVIC, and since there are more than 7 pins in each GPIO port (there are 16, in fact) this means that some of the pins have to share an interrupt. The first 5 (EXTI0
to EXTI4
) get their own interrupts, but 59 have to share the EXTI9_5
interrupt, and 1015 have to share the EXTI15_10
interrupt. Heres a picture to make things clearer (the extra number in the NVIC column is the position of the interrupt in the NVIC vector table):
This is all shown (along with the names, positions & priorities) of all the other interrupts in your discoboard in Table 42, Section 11.3 on p321 of the discoboard reference manual. Heres a simplified version of that table which only contains the rows relevant to the EXTI controller:
position | interrupt |
---|---|
6 | EXTI0 |
7 | EXTI1 |
8 | EXTI2 |
9 | EXTI3 |
10 | EXTI4 |
23 | EXTI9_5 |
40 | EXTI15_10 |
For this reason, the GPIO_configure_input_pin_it
macro doesnt enable the NVIC interruptyou need to enable it yourself using the NVIC_set
macro like so:
NVIC_set ISER 8
Which interrupt (in the NVIC) does the above line of assembly code enable?
ISER stands for Interrupt Set Enable Register; you can also use ISPR (set pending) or other register banks there, see the macros file for details.
Heres an example of where this gets tricky: if you want to have interrupts on (say) pins PE13
and PE14
they will both trigger the same interrupt handler function EXTI15_10_IRQHandler
, since theyre both in the 1015 range. To deal with this, the handler function will have to check another register (the EXTI interrupt pending register) to see which pin number triggered the interrupt.
So, the full journey of a GPIO interrupt through your system is:
- an edge (rising or falling) is detected on your input GPIO pin
- the EXTI controller detects this (assuming the interrupt is enabled and its watching the right port) and raises one of the
EXTIn
interrupt lines to the NVIC - if the
EXTIn
interrupt is enabled in the NVIC, your program is interrupted and the handler function (determined by the address in the vector table) is called
If you have trouble, here are a few questions to ask yourself:
- have you tried pressing the reset button before debugging?
- have you enabled the SYSCFG clock?
- have you clocked the GPIO pins?
- have you configured the GPIO pins as input pins?
- have you configured the GPIO pins to trigger an interrupt?
- have you configured the trigger for the interrupt (i.e. rising or falling edge)?
- have you enabled the appropriate EXTI interrupt in the NVIC?
- have you written the interrupt handler function, and is it globally visible?
- does your interrup handler function clear its pending register before it exits? (the
EXTI_PR_clear_pending
macro will probably help you out here)
Write a program where pressing the central joystick button blinks the red LED, but pressing one of the direction buttons blinks the green LED (dont forget to enable the correct interrupts in the NVIC, and to disable the interrupt pending flag using the EXTI_PR_clear_pending
macro before the handler function exits). Commit & push your program to GitLab.
Exercise 3: click-over-the-wire
So far this lab has been a bit of an information dump, and all you did was turn on the LEDs with the joystick (which youve known how to do for ages). In this exercise, youll take your knowledge of general GPIO input and output and re-implement the click-to-blink program again, but this time sending the click signal over the wire.
Grab one of your jumper leads and connect it to your board from pin PB7
to PE13
. Youll use one end as the receiver and one end as the senderit doesnt matter which. What you need to do in this exercise is:
- configure your output pin as a GPIO output
- configure your input pin as an interrupt-enabled GPIO input
- write your joystick
EXTI0
interrupt handler so that instead of toggling the LED directly, it toggles the value on the sender data pin (using the ODR helper macro) - write another interrupt handler function (have a careful think about what should it be called?) and enable it in the NVIC and make that handler function toggle the LED
The info in the first two exercises will help you outthere are a few gotchas, so read it carefully and ask for help if you get stuck. Once youre done with that, add another wire between PD0
and PE14
repeat the process so that pressing one of the joystick direction buttons toggles the other LED over the wire.
If you use PD0
youll have to be a bit carefulsince EXTIO
is used by the center joystick button (wired to PA0
), its NOT possible to set EXTIO
as an interrupt for PD0
. Why cant we use the same handler for both A0 and D0 interrupts?
Write a program where you can toggle the red & green LEDs using the joystick with the signals travelling over the wires (as described above). Commit & push your program to GitLab.
What happens if you experiment with different triggering schemesrising+falling edge vs rising edge only? Do the triggering schemes have to be the same on both ends of the wire? How many different ways can you configure the wires & interrupts in your click-over-the-wire program?
Exercise 4: multi-player QuickClick
Due to current circumstances and #socialdistancing the original version of this exercise wont really work. So here is an altered version that only involves a single discoboard.
Remember the QuickClick game you made last week? In Exercise 4 you will wire up both sides of your discoboard to build a multi-player3 variation of QuickClick.
In this instance we are going to pretend that each side of the board is actually two separate boards, this is a bit different to actually having to separate boards but the concept is similar, if you did actually have 2 boards, then how would this be possible?
As you (hopefully) just figured out, the multiplayer part of this is super-easy because you already did all the hard work in Exercise 3. Once youre sending a signal over a wire, it doesnt matter whether both ends of the wire are connected to the same discoboard or different discoboards.
There is one caveat here: voltages, such as the low and high voltages youve been setting and reading from your GPIO pins, are actually relative measurements. Think of it like the concepts of shortness/tallness: someone might say youre either tall or short depending on who youre standing next to. Even though you might say that a person is tall (or short) what you really mean is that that person is taller (or shorter) than the average of the heights of all the other people you know. Well, voltage is similarwhat you care about is the voltage difference between two places. When your wire is connected to your board only, theres no problemboth ends have the same common reference or ground. But when two boards are connected to each other they need some way of agreeing on what the 0
voltage level is. This is achieved by connecting their GND
ground reference pins together with a separate cable. Your discoboard has several GND
pins because needing to connect boards to a common ground is so common, so you need to have plenty of pins handy to do it.
To get back to the multiplayer QuickClick exercise, what youll need to do is:
- Reconfigure youre interrupts and handlers so that each side of the boards have a button on the joystick.
- The left joystick button will be the left sides control (wired to
PA1
) - The right joystick button will be the right sides control (wired to
PA2
)
- The left joystick button will be the left sides control (wired to
- configure sender and receiver pins and interrupts for both sides of the board
- left:
- Receiver:
PB7
- Sender:
PD0
- Receiver:
- right:
- Receiver:
PE14
- Sender:
PE13
- Receiver:
- left:
- connect one of the
GND
pins on the left of your discoboard to one of theGND
pins on the right of your discoboard4 - take two wires and connect the sender on the left (
PD0
) to the receiver on the right (PE14
), then repeat this process from the sender on the right (PE13
) to the receiver on the left (PB7
). - figure out how the game is going to work:
- an option here would be to assign an LED to each side and play tennislets say the red led means the ball is in the left players court and a green LED means it is in the right players court. Now, when you click one of the players buttons it should pass the led to the other player.
- eg: if the green LED is currently on, then pressing the right button will turn the green LED off and send a signal over the wire to the left side that the LED is now in their court and they should turn the red LED on
- an option here would be to assign an LED to each side and play tennislets say the red led means the ball is in the left players court and a green LED means it is in the right players court. Now, when you click one of the players buttons it should pass the led to the other player.
It may not seem like it, but this is actually similar to what you will do in assignment 3, so do stop and analyse your current program before moving on.
Rate this product
The fact that the GPIO interrupts go though both the EXTI and the NVIC is complicated, and it also means there are several different ways of enabling/disabling/triggering these interrupts. What youve done in this exercise is to enable it in both places: the EXTI enabling happens in the GPIO_configure_input_pin_it
macro, and the NVIC enabling in the NVIC_set
macro. If you want to disable it in the EXTI, you disable the interrupt for pin n by clearing (setting to 0
) the nth bit in the EXTI_IMR1
register (base address 0x40010400
).
To disable it in the NVIC, you set (to 1
) the correct bit (see mapping diagram above) in the NVIC_ICERn
register (base addresses starting at 0xE000E180
for NVIC_ICER0
, but theres lots of theme.g. EXTI15_10
spills into NVIC_ICER1
register because its in slot 40). As before, you can use the NVIC_set
macro for this, just use ICER
where you used ISER
in Exercise 2. Be careful not to use the normal load-twiddle-store approach for this, thoughas discussed in the Week 8 lab.
Just to recap: to disable an interrupt in the EXTI you clear a bit, and to disable it in the NVIC you set a bit, and its a different bit in each case. Dont ask me why things are so inconsistent, blame the people who designed the discoboard. The macros.S
file should handle some of this stuff for you, but thats the full story if youre getting confused trying to disable/re-enable interrupts in your program.
there are a lot of moving parts here, but some things to think about are:
- how do you keep track of whos turn it is?
- who starts with the ball (LED) in their court?
- is their implicit communication going on in your implementation?
- or could you directly take it and use 2 boards and it would work the same?
After having thought about and answered the points above, here are some things to evaluate the correctness of your solution:
- Do you turn the LEDs off and on instead of toggling them?
- Where do you control the LEDs? the left side should only be changing the red LED and the right side should only be changing the green LED
- What happens if you try to pass the LED when it isnt on your side?
- How do you know if the LED is on your side?
- Can you make it so that each side alters their output if and only if the LED is on their side?
- Pull out a connection and play the game, does it behave as you expect it to?
- Do the left and right sides share any memory locations? If they do then consider the case where they are on two separate boards, would it still function the same?
Reviews
There are no reviews yet.