RTRM: Reading The Reference Manual

We have previously seen the GPIO pins on the nRF52833. On this chip (and on many others) the GPIO pins are grouped into ports. There are two ports, Port 0 and Port 1, abbreviated to P0 and P1 respectively. The pins within each port are named with numbers starting from 0. Port 0 has 32 pins, named P0.00 to P0.31, and Port 1 has 10 pins, named P1.00 to P1.09.

The first thing we have to remember out is which pin is connected to which LED. We previously did this by tracing the schematic. That turns out to be hard mode: the required information is in the MB2 pinmap table.

The table says:

  • ROW1, the top LED row, is connected to the pin P0.21. P0.21 is the short form of: Pin 21 on Port 0.
  • ROW5, the bottom LED row, is connected to the pin P0.19.

Up to this point, we know that we want to change the state of the pins P0.21 and P0.19 to turn the top and bottom rows on and off. These pins are part of Port 0 so we'll use the P0 peripheral to set them up.

Each peripheral has a register block associated with it. A register block is a collection of registers allocated in contiguous memory. The address at which the register block starts is known as its base address. We need to figure out what's the base address of the P0 peripheral. That information is in the following section of the microcontroller Product Specification:

Section 4.2.4 Instantiation - Page 22

The table says that base address of the P0 register block is 0x5000_0000.

Each peripheral also has its own section in the documentation. Each of these sections ends with a table of the registers that the peripheral's register block contains. For the GPIO family of peripheral, that table is in:

Section 6.8.2 Registers - Page 144

OUT is the register which we will be using to set/reset. Its offset value is 0x504 from the base address of the P0. We can look up OUT in the reference manual.

That register is specified right under the GPIO registers table:

Subsection 6.8.2.1 OUT - Page 145

Anyway, 0x5000_0000 + 0x504 = 0x50000504. That looks familiar! Finally!

This is the register we were writing to. The documentation says some interesting things. First, this register can both be written to and read from. Next, the register is a 32-bit piece of memory, and each bit represents the state of the corresponding pin. That means that bit 19 matches pin 19, for instance. Setting the bit to 1 will enable the pin output, and setting it to 0 will reset it. Furthermore, we can see that all pin outputs are disabled by default, as the reset value of all bits is 0.

We'll use GDB's examine command: x. Depending on the configuration of your GDB server, GDB will refuse to read memory that isn't specified. You can disable this behaviour by running:

set mem inaccessible-by-default off

So here we go. Furst turn off the inaccessible-by-default flag, then set a couple of breakpoints, reset the device and halt.

(gdb) set mem inaccessible-by-default off
(gdb) break 16
Breakpoint 1 at 0x172: file src/07-registers/src/main.rs, line 16.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) break 19
Breakpoint 2 at 0x17c: file src/07-registers/src/main.rs, line 19.
(gdb) break 22
Breakpoint 3 at 0x184: file src/07-registers/src/main.rs, line 22.
(gdb) break 25
Breakpoint 4 at 0x18c: file src/07-registers/src/main.rs, line 25.
(gdb) monitor reset halt
Resetting and halting target
Target halted

All right. Let's continue until the first breakpoint, right before line 16, and print the contents of the register at address 0x50000504.

(gdb) c
Continuing.

Breakpoint 1, registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:16
16              *(PORT_P0_OUT as *mut u32) |= 1 << 21;
(gdb) x 0x50000504
0x50000504:     0x00000000

Ok, we see that the register's value is 0x00000000 or 0 at this point. This corresponds with the data in the product specification, which says that 0 is the 'reset value' of this register. That means that once the MCU resets, the register will have 0 as its value.

Let's go on. This line consists of multiple instructions (reading, bitwise ORing and writing), so we need to instruct the debugger to continue execution more than once, until we hit the next breakpoint.

(gdb) c
Continuing.

Program received signal SIGINT, Interrupt.
0x00000174 in registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:16
16              *(PORT_P0_OUT as *mut u32) |= 1 << 21;
(gdb) c
Continuing.

Breakpoint 2, registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:19
19              *(PORT_P0_OUT as *mut u32) |= 1 << 19;

We've stopped right before line 19, meaning that line 16 is fully executed at this point. Let's have a look at the OUT register's contents again:

(gdb) x 0x50000504
0x50000504:     0x00200000

The value of the OUT register is 0x00200000 at this point, which is 2097152 in decimal, or 2^21. That means that bit 21 is set to 1, and the rest of the bits is set to 0. That corresponds to the code on line 16, which writes 1 << 21, or a 1 shifted left 21 positions, bitwise ORed with OUTs current value (which was 0), to the OUT register.

Writing 1 << 21 (OUT[21]= 1) to OUT sets P0.21 high. That turns the top LED row on. Check that the top row is now indeed lit up.

(gdb) c
Continuing.

Yeah, I was gonna say that. Now, hit 'c' another time to continue execution up to the next breakpoint and print its value.

Program received signal SIGINT, Interrupt.
0x0000017e in registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:19
19              *(PORT_P0_OUT as *mut u32) |= 1 << 19;
(gdb) c
Continuing.

Breakpoint 3, registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:22
22              *(PORT_P0_OUT as *mut u32) &= !(1 << 21);
(gdb) x 0x50000504
0x50000504:     0x00280000

On line 19, we've set bit 21 of OUT to 1, keeping bit 19 as is. The result is 0x00280000, which is 2621440 in decimal, or 2^19 + 2^21, meaning that both bit 19 and bit 21 is set to 1.

Writing 1 << 19 (OUT[19]= 1) to OUT sets P0.19 high. That turns the bottom LED row on. As such, the bottom row should now be lit up.

The following lines turn the rows off again. First the top row, then the bottom row. This time, we're doing a bitwise AND operation, combined with a bitwise NOT. We calculate !(1 << 21), which is all bits set to 1, except for bit 21. Next, we bitwise AND that with the current value of OUT, ensuring that only bit 21 is set to 0, keeping the value of the other bits intact.

Continue execution and check that the reported values of the OUT register matches what you expect. You can press CTRL+C to pause execution once the device enters the endless loop at the end of the main function.

(gdb) c
Continuing.

Program received signal SIGINT, Interrupt.
0x00000186 in registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:22
22              *(PORT_P0_OUT as *mut u32) &= !(1 << 21);
(gdb) c
Continuing.

Breakpoint 4, registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:25
25              *(PORT_P0_OUT as *mut u32) &= !(1 << 19);
(gdb) x 0x50000504
0x50000504:     0x00080000
(gdb) c
Continuing.

Program received signal SIGINT, Interrupt.
0x0000018e in registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:25
25              *(PORT_P0_OUT as *mut u32) &= !(1 << 19);
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000196 in registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:28
28          loop {}
(gdb) x 0x50000504
0x50000504:     0x00000000

And at this points all LEDs should be turned off again!