Programming ARM registers
Like the AVR Version 4.1 of uLisp released earlier this month, the ARM version now adds a register function to allow you to read the values of 32-bit peripheral registers, or write values to the registers. It allows you to control the peripherals in the processor from a Lisp program, or interactively experiment with the peripherals by giving commands at the uLisp prompt.
Note that because peripherals are mapped to memory addresses on ARM processors you can also use the register command to read and write memory, although this is not recommended as you may interfere with running programs.
This article first appeared on the uLisp Forum.
Example
To read the register at address #x42004808 on the ATSAMD21 give the command:
> (register #x42004808) 0
To write a value to the same register give the value as the third parameter:
> (register #x42004808 65432) 65432
You can now confirm the contents of the register with:
> (register #x42004808) 65432
Note that for this example I've chosen the DAC DATA register which is a 16-bit register.
Examples
Examples for specific boards are given in the following sections:
ATSAMD21 and ATSAMD51 boards
uLisp provides predefined keywords for the most useful ATSAMD21 and ATSAMD51 port registers:
Register | Keywords | Description |
DIR | :pa-dir, :pb-dir | Define port data direction bits |
DIRCLR | :pa-dirclr, :pb-dirclr | Clear bits in data direction register |
DIRSET | :pa-dirset, :pb-dirset | Set bits in data direction register |
DIRTGL | :pa-dirtgl, :pb-dirtgl | Toggle bits in data direction register |
OUT | :pa-out, :pb-out | Write to port outputs |
OUTCLR | :pa-outclr, :pb-outclr | Clear bits in output port |
OUTSET | :pa-outset, :pb-outset | Set bits in output port |
OUTTGL | :pa-outtgl, :pb-outtgl | Toggle bits in output port |
IN | :pa-in, :pb-in | Read port inputs |
For any other registers you want to access you will need to specify their address, or define a variable to refer to them. To find the addresses for each register refer to the processor's datasheet.
For example, for the ATSAMD21 the register base addresses are defined in the Peripherals Configuration Summary table, on page 66 of the SAMD21 Family Data Sheet, and the specific registers provided for the PORT peripheral are defined in the PORT Register Summary on page 371 of the SAMD21 Family Data Sheet. For example, from this table we can see that the PORT base address for PA is #x41004400. One other thing we need to know is that the base address for successive ports PB, etc is got by adding #x80 to the PORT base address.
Blinking an LED
For the first example we will blink the built-in LED on the Arduino Zero or MKRZERO board by directly accessing the port it's connected to, without using the usual pinmode and digitalwrite functions.
On the MKRZERO the LED is connected to the pin PB08, which corresponds to bit 8 in port B. We can define a variable for a bit pattern with bit 8 set:
(defvar led (ash 1 8))
First we need to define the I/O pin as an output by writing this bit pattern to the data-direction register DIRSET:
(register :pb-dirset led)
To light the LED we write the bit pattern to the register OUTSET:
(register :pb-outset led)
Blink for Arduino Zero and MKRZERO
Finally here's the whole blink routine to flash the built-in LED, using the register OUTTGL as a shortcut to toggling the appropriate bit in the port:
(defun blink () (let ((led (ash 1 8))) (register :pb-dirset led) (loop (register :pb-outtgl led) (delay 1000))))
Blink for other ATSAMD21 and ATSAMD51 boards
On other M0 and M4 boards the built-in LED is on different pins on PORT A or PORT B, as shown in the following table:
Board | Pin |
Adafruit Gemma M0 | PA23 |
Adafruit Feather M0 | PA17 |
Adafruit ItsyBitsy M0 | PA17 |
Adafruit Metro M0 | PA17 |
Adafruit Feather M4 | PA23 |
Adafruit ItsyBitsy M4 | PA22 |
Adafruit Metro M4 | PA16 |
Adafruit Grand Central M4 | PB01 |
Adafruit QT-Py (M0) | n/a |
Seeduino XIAO (M0) | PA17 |
Adafruit PyBadge (M4) | PA23 |
Adafruit PyGamer (M4) | PA23 |
The boards shown n/a don't have a built-in LED. Some of them have a Neopixel or DotStar serial RGB LED instead which won't work with this program.
To find the correct pin for boards not listed above, search for the board's schematic in Google, and then identify the pin connected to the built-in LED. Alternatively use the port scanner below.
Run the routine blinka or blinkb as appropriate, giving the pin number in the port as a parameter:
(defun blinka (p) (let ((led (ash 1 p))) (register :pa-dirset led) (loop (register :pa-outtgl led) (delay 1000))))
(defun blinkb (p) (let ((led (ash 1 p))) (register :pb-dirset led) (loop (register :pb-outtgl led) (delay 1000))))
For example, on the ItsyBitsy M4:
(blinka 22)
To exit from the program enter a ~.
Port scanner for ATSAMD21 boards
The following program scans the digital input pins on port A of the ATSAMD21 every second, and displays their state as 0 or 1:
(defun scan () (let ((wrconfig #x41004428)) (register :pa-dir #x00000000) ; 0 = input (register :pa-out #xFFFFFFFF) ; 1 = pullup (register wrconfig #x4006FFFC) ; Set pullups and (register wrconfig #xC00618FF) ; enable input buffers ; Scan ports (format t "...22...22.2111111111100000000..~%") (format t "...87...32.0987654321098765432..~%") (loop (format t "~32,'0b~%" (register :pa-in)) (delay 1000))))
To configure the pullups and enable the input buffers it uses the PORT WRCONFIG register at #x41004428. This allows you to configure the port 16 bits at a time.
The scan program prints two heading lines, showing the first and second digit of the pin number corresponding to each column. The pins marked '.' are not available as I/O pins, because they are assigned to other functions such as the clock crystal, and they may have indeterminate values. All the other pins are defined as inputs with a pullup, and so will initially read as a '1'. The subsequent lines show the states of the pins every second.
By connecting a pin to GND you can identify which PA pin it corresponds to. For example, connecting the pin labelled D12 to GND on the Adafruit Feather M0 board after two seconds gives the following display:
> (scan) ...22...22.2111111111100000000.. ...87...32.0987654321098765432.. 01011010110111111111111111111100 01011010110111111111111111111100 01011010110101111111111111111100 01011010110101111111111111111100 01011010110101111111111111111100
The printout shows that the pin in the column 19 has changed from 1 to 0, showing that the D12 pin on the board is connected to the I/O line PA19.
The program could be extended to test the pins on port PB too, or to work with other processors.
nRF51822 boards
The nRF51822 used in the BBC Micro:bit v1 has one port of 32 I/O lines. The port is referred to as P0, rather than PA as on the ATSAMD processors.
uLisp provides predefined keywords for the most useful registers on this port:
Register | Keywords | Description |
OUT | :p0-out | Write to port outputs |
OUTSET | :p0-outset | Set bits in output port |
OUTCLR | :p0-outclr | Clear bits in output port |
IN | :p0-in | Read port inputs |
DIR | :p0-dir | Define port data direction bits |
DIRSET | :p0-dirset | Set bits in data direction register |
DIRCLR | :p0-dirclr | Clear bits in data direction register |
There are no toggle options, but you can achieve the same effect by a read-modify-write as in the blink example below.
For any other registers you want to access you will need to specify their address, or define a variable to refer to them. To find the addresses for each register refer to the processor's datasheet.
Blink for the BBC Micro:bit v1
The BBC Micro:bit doesn't have an LED on a single I/O line, but a matrix of 25 LEDs are connected with their cathodes to nine columns and their anodes to three rows, so to light one LED it's necessary to take its column line low and its row line high. Here's a routine to flash the top left LED, which is on row 1, P0.13, and column 1, P0.04:
(defun blink () (let ((row (ash 1 13)) (col (ash 1 4))) (register :p0-dirset row) (register :p0-dirset col) (register :p0-outclr col) (loop (register :p0-out (logxor (register :p0-out) row)) (delay 1000))))
nRF52833 and nRF52840 boards
The nRF52833 used in the BBC Micro:bit v2, and the nRF52840 used in the Adafruit ItsyBitsy nRF52840, each provide two I/O ports, P0 and P1. uLisp provides predefined keywords for the most useful port registers:
Register | Keywords | Description |
OUT | :p0-out, :p1-out | Write to port outputs |
OUTSET | :p0-outset, :p1-outset | Set bits in output port |
OUTCLR | :p0-outclr, :p1-outclr | Clear bits in output port |
DIRTGL | :p0-in, :p1-in | Read port inputs |
OUT | :p0-dir, :p1-dir | Define port data direction bits |
OUTCLR | :p0-dirset, :p1-dirset | Set bits in data direction register |
OUTSET | :p0-dirclr, :p1-dirclr | Clear bits in data direction register |
There are no toggle options, but you can achieve the same effect by a read-modify-write as in the blink examples below.
For any other registers you want to access you will need to specify their address, or define a variable to refer to them. To find the addresses for each register refer to the processor's datasheet.
Blink for Adafruit ItsyBitsy nRF52840
On the Adafruit ItsyBitsy nRF52840 there's a built-in LED on P0.06. Here's a routine to flash this LED:
(defun blink () (let ((led (ash 1 6))) (register :p0-dirset led) (loop (register :p0-out (logxor (register :p0-out) led)) (delay 1000))))
Blink for the BBC Micro:bit v2
The BBC Micro:bit v2 doesn't have an LED on a single I/O line, but a matrix of 25 LEDs are connected with their cathodes to five columns and their anodes to five rows, so to light one LED it's necessary to take its column line low and its row line high. Here's a routine to flash the centre LED, which is on row 3, P0.15, and column 3, P0.31:
(defun blink () (let ((row (ash 1 15)) (col (ash 1 31))) (register :p0-dirset row) (register :p0-dirset col) (register :p0-outclr col) (loop (register :p0-out (logxor (register :p0-out) row)) (delay 1000))))
RP2040 boards
The RP2040 used in the Raspberry Pi Pico and other RP2040 boards provides 29 general purpose digital I/O pins. uLisp provides predefined keywords for the most useful port registers:
Register | Keywords | Description |
GPIO_IN | :gpio-in | Read port inputs |
GPIO_OUT | :gpio-out | Write to port outputs |
GPIO_OUT_SET | :gpio-out-set | Set bits in output port |
GPIO_OUT_CLR | :gpio-out-clr | Clear bits in output port |
GPIO_OUT_XOR | :gpio-out-xor | Toggle bits in output port |
GPIO_OE | :gpio-oe | Define port data direction bits |
GPIO_OE_SET | :gpio-oe-set | Set bits in data direction register |
GPIO_OE_CLR | :gpio-oe-clr | Clear bits in data direction register |
GPIO_OE_XOR | :gpio-oe-xor | Toggle bits in data direction register |
You can read the input from any pin using :gpio-in. However, before using any pin for output you first need to define its function as SIO (software I/O) by setting the appropriate GPIOx_CTRL register to 5.
The address of each GPIOx_CTRL register is:
IO_BANK_BASE + pin * 8 + 4 where IO_BANK_BASE is #x40014000.
Here's the blink function:
(defun blink (pin) (let ((io-bank0-base #x40014000) (led (ash 1 pin)) (sio 5)) (register (+ io-bank0-base (* pin 8) 4) sio) (register :gpio-oe-set led) (loop (register :gpio-out-xor led) (delay 1000))))
For example, to blink the green LED on pin 25 of the Raspberry Pi Pico:
(blink 25)
and on the Adafruit Feather RP2040 do:
(blink 13)