Configuring GPIO as input on the micro:bit
Last updated: Oct 19, 2023
How to setup a micro:bit GPIO as an input
The problem
We want to configure the registers on the micro:bit so that a GPIO pin works as an input. This allows us to read the digital values input to that pin from the IN directory.
The register names and details are the same for the micro:bit v1 and v2. What pin is connected to which edge connector pad can vary between micro:bit v1 and v2. For instance, pad 0 is connected to pin 2 on the micro:bit v2 but is connected to pin 3 on the micro:bit v1. Guess how I found this out?
Summary
The PIN_CNF register for the GPIO pin needs to be configured correctly so that the input buffer is connected. The value that is input to the GPIO pin is then read from the IN directory.
How to figure this out
It’s all in the Product Specification. Looking at v1.5 of the Product Specification for the nRF52833, which is the microcontroller for the micro:bit v2:
Section 6.8 GPIO states that (highlighting is mine):
The GPIO port peripheral implements up to 32 pins, PIN0 through PIN31. Each of these pins can be individually configured in the PIN_CNF[n] registers (n=0..31). The following parameters can be configured through these registers:
- Direction
- Drive strength
- Enabling of pull-up and pull-down resistors
- Pin sensing
- Input buffer disconnect
So, what is an input buffer and why is it disconnected?
Further down in section 6.8.1, we read “A GPIO pin input buffer can be disconnected from the pin to enable power savings when the pin is not used as an input”. Looking closely at the diagram in section 6.8.1 and who hasn’t admired this diagram? I’ve indicated the critical bit:
The input buffer is probably something like a simple operational amplifier, though I stand to be corrected on this. These components require some power. It make sense to disconnect these if they are not needed.
We can see that there is an internal switch connecting or disconnecting the GPIO pin (in this case labelled as PIN0) from the PIN[0].IN register. We aren’t going to be able to ready any change of value in the IN register unless this switch is closed.
How do we close the switch and connect the input buffer? We need to look at the pin configuration register called PIN_CNF.
PIN_CNF register
Looking at section 6.8.2.10 of v1.5 of the Product Specification for the nRF52833 (I put a box around the interesting bit)
By default, the input buffer is disconnected.
So we need to set bit 1 of the PIN_CNF register for the GPIO pin to be 0 to enable the input to work. This is in addition to setting the DIR register bit to the correct value to enable the pin to work as an input.
What is the address of the PIN_CNF register for my GPIO pin
Looking at 6.8.2.10 again, the address offset is 0x700 + (n x 0x4), where n is the pin number, starting at 0.
So the PIN_CNF address for pin 0 of port 0 has an address:
0x50000700
Pin 1 of port 0 has an address:
0x50000704
We need to clear bit 1 of the PIN_CNF for the pin to enable the input to work. But about all the other values in PIN_CNF?
I got the pin to work as an input and to read the IN register for that pin by setting the entire register to 0.
There are a several other parameters controlling the GPIO pin behaviour that can be adjusted in this register. For instance ID C enables a pullup or pulldown on the pin. This is a useful feature as instead of using external resistors on the breadboard, we can use the internal resistors of the microcontroller if we are setting up e.g. a button.
ID D refers to ‘high drive’ and ‘standard drive’. In high drive mode, the pin can source or sink more current than in standard drive. The details are shown in section 6.8.3.1.
For the purpose of setting up a simple input GPIO, set bit 1 to zero, leave all other values as zero and see how you get on.
Example code
I tested this using Forth on the micro:bit v1 as there is not yet a Forth compiler for the micro:bit v2. I may have a go at porting the compiler to the micro:bit v2. Pad 1 is GPIO pin 2 on the micro:bit v1.
$50000504 constant gpio_out \ Write GPIO port
$50000510 constant gpio_in \ Read GPIO port
$50000514 constant gpio_dir \ Direction of GPIO pins
$50000700 constant gpio_cnf_0 \ Configuration of pin 0, add 4*n for other pins.
$50000708 constant gpio_cnf_2 \ Configuration of pin 2
\ configure pad1 as an output
: pad1_output %1 2 lshift gpio_dir bis! ;
\ configure pad1 as an input
: pad1_input %0 2 lshift gpio_dir bis! ;
\ check if pad1 has a high bit in the IN register.
: pad1_high_check %1 2 lshift gpio_in cbit@ . ;
compiletoram
pad1_input
$000 gpio_cnf_2 ! \ need to clear bit 1 to connect the input buffer
%1 1 lshift gpio_cnf_2 cbit@ . \ double check bit 1 is not set
\ check on the value that pad1 is reading in the IN register
pad1_high_check
I connected the pad connected to GPIO pin 2 to GND and 3V. The command ‘pad1_high_check’ returned the correct values.
I’m using Forth, but the registers and bit flipping are all the same as using C. Just far more aesthetic.
‘In my day’ Lancaster university ran Summer Schools. One of the courses I took as a teenager was Forth. Sadly, the Summer Schools were closed as they didn’t make enough profit.