Setting GPIOs on the micro:bit edge connector
Last updated: Dec 8, 2023
Figuring out how to set the micro:bit edge connector pads high or low
The problem
How do we know which bits to twiddle in which register on the micro:bit’s microcontroller to set a pad on the edge connector high or low? I’m using the micro:bit v2. The v1 has a different microcontroller and pinout, but the principles are the same.
I wrote this after TA’ing on the SCC369 Embedded Systems course at Lancaster University. A lot of us got confused with all the fiddly details of getting from a pin on the breakout board we’re using with the micro:bit to which bit to twiddle in a register on the microcontroller. I’ve aimed the content at a wider audience than the final year students on the course and assume little prior knowledge of microcontrollers or programming.
Summary
We trace two micro:bit edge connector pads to the microcontroller using the micro:bit’s schematic. From this, we read the GPIO channel number associated with each pad. We find out which registers need adjusting using the data sheet (called the Product Specification). We figure out which bit to adjust to set the pad voltage high or low.
I created a table showing how all of the micro:bit edge connector pads map to their GPIO channels. This is at the end of this post.
Nomenclature
Best not to get confused between ports and channels. A port is a collection of channels. The Product Specification states:
“The general purpose input/output pins (GPIOs) are grouped as one or more ports, with each port having up to 32 GPIOs.”
For this example, we are using port 0. I refer to the individual GPIOs in the port as ‘channels’.
I call the little gold pads on the edge connector of the micro:bit ‘pads’. These are the areas that we can set to have a high or a low voltage. For whatever reason, the schematic calls the edge connector an ’expansion connector’. It’s the connector on the edge of the board.
The pads are connected by ’traces’ to the internal shenaningans of the micro:bit, often directly to the microcontroller. But not always! Be careful which pads you use as they may not be directly available to be driven or read by the microcontroller. For example, they may be connected to the status LEDs instead.
Resources
Kitronik edge connector datasheet
Get the latest data sheet (called Product Specification) for the nRF52833. Note that this is valid only for the micro:bit v2. The micro:bit v1 uses a different microcontroller). The datasheet can be downloaded from:
What is the GPIO channel number for a given pad
Lets consider two of the pads towards the left hand edge of the board. I put a blue box around the pad called P0 and a red box around the smaller thin pad P5. If we can figure out how to deal with these two pads, we can figure out how to deal with the rest of the pads. Looking at the diagram of the edge connector below:
The blue box is around a pad labelled P0 on the on the schematic of the edge connector. On the actual microbit, this is the large pad labelled ‘0’ with a 4mm hole in it on the left hand side of the edge connector as you look at the top of the board. The 4mm hole is to allow ‘banana’ plugs to be easily attached to the board. This pad has a line numbered ‘2’ connected to a funky shaped yellow box with a label ‘RING0’.
Some of the pads on the edge connector are shown connected to these yellow boxes with labels in them and some of them just have a name, such as COLR1. The pads that connect to the yellow boxes, such as ‘RING0’ connect directly to the microcontroller. These are the pads that we can control directly from the microcontroller by configuring the registers on the micro:bit correctly.
The red box is around a pad labelled ‘P5’ on the schematic of the edge connector. This is connected to a box labelled ‘BTN_A’.
If we look at the schematic showing the microcontroller, we can see which pins on the microcontroller each of these pads is connected to:
RING0 is connected to P0.02. There is some other information for that pin on the schematic. Each pin can have several different functions in addition to being a GPIO. AIN1 means that this pin can be also configured as an analog input, as channel 1 of the analog input port.
A12 is the name of the little pad on the base of the microcontroller that the track is physically connected to. The microcontroller is in what is called a BGA package - ball grid array. Instead of little legs on the edge of the package, there are small solder balls on the base of the package. This makes for hours of fun when designing the circuit board.
For the time being, all we are interested in is ‘P0.02’. This tells us that this pin can be used channel 2 on GPIO port 0. I say ‘can be used as’ - the pin may not do what we want it to until we set the configuration registers on the microcontroller correctly.
Similarly, we can see that BTN_A is connected to P0.14, so is channel 14 on GPIO port 0. Physically, this track is connected to ball AC9 on the base of the microcontroller.
Kitronik edge connector
A common way of prototyping with the micro:bit is to use a Kitronik edge connector.
Where do P0 (RING0) and P5 (BTN_A) connect on the Kitronik edge connector? I’ve indicated which pins connect with these pads on the diagram below:
Both of each pair of pins is connected to the same pad.
The 0V pins on the right of the connector are connected to the pad labelled GND on the micro:bit. The same pad gets called different things: ground, 0V or negative. They all mean the same when using the micro:bit.
Setting up registers
DIR register
To allow us to use a channel on a GPIO port to control e.g. an LED, we need to configure the channel to be an output. Inputs and outputs are configured in different ways inside of the microcontroller. An input needs to see a high impedance and an output needs to present a low impedance. This is achieved using different transistor configurations to drive the output or to be driven by an input. You don’t need to understand the details to use the GPIO pins though. You need to understand that the pin needs to be configured as an output, to well, work as an output.
To make a channel on the GPIO port be an output, we need to set the relevant bit in the DIR register at address offset 0x5000 0514 to be high. See the screen grab below.
The screen grab shows that if we want to configure a channel as an output, then we need to set the relevant bit to be 1. This is the opposite configuration from using PIC microcontrollers, which shows us we need to read the datasheet whenever we use a new microcontroller. There’s no standardisation across different microcontroller families!
For RING0, which is P0 on the edge connector and pins 0 on the Kitronik edge connector, we need to set bit number 2 high in the DIR register. This is the third bit in the register, as we start counting at 0.
How do I know this is channel 2? Because the schematic labels is P0.02, which is channel 02 of GPIO port 0. On the screen grab above, we can see that channel 2 is the 3rd bit in the GPIO port, as we start at 0.
This is 0b0100 (binary )= 0x04 (hexadecimal ) = 0d4 (decimal). Working in binary or hex is easier than decimal when we’re working with memory addresses.
BTN_A connects with P5 on the edge connector, which is labelled ‘5’ on the Kitronik edge connector. This pad is connected to P0.14 on the microcontroller, which is channel 14 on GPIO port 0. Therefore we need to set bit 14 high, which is 0b 0100 0000 0000 0000 = 0x4000 = I don’t care what it is in decimal.
We can set both bits high at the same time by setting DIR to be:
0b0100 0000 000 0100
While we’re here, look at the Reset values in the register. They are all ‘0’s, which means on a reset, the microcontroller defaults to having the GPIO channels to be inputs. If we need to use a pin as an input, we don’t have to explicitly set the port to be an input. I tend to explicitly set the channel anyway, even if it defaults to the value I’m going to use on reset. Why? So that whoever comes after me to use the code I write can clearly see what it is I’m trying to do.
OUT register
Now we need to set the port to be high of low. This is done with the register called OUT at 0x5000 0504:
We can see that the bit locations are the same for the GPIO channels for the OUT register as for the DIR register. So we need to set the same bits high to set the output values high.
But, but, but. If you hook something up to BTN_A, it’ll show high before we configure the port. Why is this? Look at the schematic fragment below:
The trace is held high as it is connected by a 10K resistor to VREG. VREG is the voltage output from the onboard regulator, which will be 3V if the board is powered from the USB port. If the micro:bit is battery powered, then VREG will be whatever the batteries can supply, which is usually less than 3V.
If we don’t configure the DIR and OUT registers for BTN_A, this channel will be high. Once we configure the DIR register as an output, we can set the channel to be high or low by setting or clearing bit 14 in the OUT register.
Looking at the schematic for the button above, we can see why it is a bad idea to use this trace as a GPIO on a battery powered device. When we pull the GPIO low, we are connecting power to ground through a 10K Ohm resistor. This creates a ’leakage current’ of 3V/10K Ohm = 0.3mA when the GPIO is set low. Not much, but something to consider if you’re setting up a battery powered device to be left on and unattended for a while, like a remote sensor.
example code
So far, this discussion has been language agnostic. I’ll give a short piece of example code in C for the Lancaster University codal framework.
The following code is for the configures channels 2 and 14 as outputs, then sets channel 2 as high and channel 14 as low. Channel 14 is high by default, as explained above, so I’m setting it low so I can see a change and prove the code is doing something.
#include "MicroBit.h"
int main()
{
// 0x50000514 is the DIR register, 1 is output, 0 is input
volatile uint32_t *d = (uint32_t *) 0x50000514;
// 0x50000504 is the OUT register, 1 is high, 0 is low
volatile uint32_t *p = (uint32_t *) 0x50000504;
// 0x50000700 + (n.0x4) is the PIN_CNF register
volatile uint32_t *cnf_14 = (uint32_t *) 0x5000738;
// Set channels 2 and 14 as output
*d = 0b0100000000000100;
// Set channel 2 high, all other channels low
*p = 0b0000000000000100;
}
One thing that we can improve to avoid confusion is how we set bits. Putting in long binary values, as in the code example above, will lead to error. In my post how to flip bits painlessly I show you how to make this clearer and more reliable.
How the edge connector pads map to GPIO channels
Here is a table explaining the connections between all of the pads on the micro:bit edge connector, the pins that these are connected to on the Kitronik edge connector and the GPIO channels that the pads connect with on the nRF52833 microcontroller.
For example, if you want to operate P14 on the micro:bit edge connector, which is connected to the pin labelled ‘14’ on the Kitronik expansion board, you need to configure channel 1 on port 0.
micro:bit edge connector | Kitronik pin | schematic track name | nRF52833 channel |
---|---|---|---|
GND | 0V | GND | n/a |
GND | 0V | GND | n/a |
GND | 0V | GND | n/a |
P20 | 20 | I2C_EXT_ SDA | P1.00 |
P19 | 19 | I2C_EXT_ SCL | P0.26 |
VREG | 3V | 3V | n/a |
VREG | 3V | 3V | n/a |
VREG | 3V | 3V | n/a |
P16 | 16 | GPIO3 | P1.02 |
P15 | 15 | SPI_EXT_ MOSI | P0.13 |
P14 | 14 | SPI_EXT_ MISO | P0.01 |
P13 | 13 | SPI_EXT_ SCK | P0.17 |
P2 | 2 | RING2 | P0.04 |
P12 | 12 | GPIO4 | P0.12 |
P11 | 11 | BTN_B | P0.23 |
P10 | 10 | COLR5 | not connected |
P9 | 9 | GPIO2 | P0.09 |
P8 | 8 | GPIO1 | P0.10 |
P1 | 1 | RING1 | P0.03 |
P7 | 7 | COLR2 | not connected |
P6 | 6 | COLR4 | not connected |
P5 | 5 | BTN_A | P0.14 |
P4 | 4 | COLR1 | not connected |
P0 | 0 | RING0 | P0.02 |
P3 | 3 | COLR3 | not connected |
discussion
Figuring out which channel needs its bits flipped about needs careful reading of the datasheet. It’s confusing at first. It’s also confusing the second, third and pretty much every other time you try to figure it out. There are a lot of steps in the process.