New & Reviewed

New Hardware - XIAO RP2040

Liam Davies

Issue 66, January 2023

This article includes additional downloadable resources.
Please log in to access.

Log in

We check out the latest thumb-sized development board from Seeed, the XIAO RP2040.

Every month, new development boards are popping up in electronics stores with more features, smaller sizes and an increasingly high standard of build quality. The XIAO RP2040 is everything we were looking for in a tiny development board, so our friends at Pakronics sent out a bunch to us to get our hands on!

First Impressions

Seeed wasn’t kidding when they said it was thumb-sized! It’s impressively small, so much so that the USB-C connector takes up a good third of the board’s space. We’ve well and truly entered the age of the USB-C connector - Micro-USB is just far too finicky and breakable to be recommended anymore.

The real brains of the XIAO RP2040 sit underneath a metal shielding on the top side of the board, which makes hacking the processor pins directly difficult, if you’re into that type of thing. Not that there’s much room to do that anyway! The components are very snug together on the board, with some barely visible resistors and capacitors sitting around the USB port.

One may expect a lack of processing power to go along with the thumb-sized footprint, and yet we’re seeing the same hardware as the popular Raspberry Pi Pico. We’re excited to see the speedy RP2040 processor coming into other applications outside of the Raspberry Pi ecosystem. The RP2040 clocks in at a healthy 133MHz, nearly 10 times faster than the processor at the heart of the Arduino Uno. Along with it comes an array of interfaces and features, which we’ll dive into shortly.

Processing power isn’t everything, though. The XIAO RP2040 also has some other goodies onboard that may interest makers both new and experienced. Aside from the regular power, status and user LEDs, there is also a NeoPixel-compatible RGB LED onboard that is bright and easy to work with. This can be accessed simply by using Pin 12 as a NeoPixel strip on the XIAO RP2040, although you’ll also need to turn Pin 11 on to actually power the NeoPixel strip.

Available Pins

Virtually every interface that one could want is available on the XIAO RP2040, although they are limited in number solely because of the pin count itself. Every GPIO pin on the board doubles as either an I2C, SPI, UART or analog channel, which makes interfacing with most forms of maker modules and sensors the same process as any other standard Arduino board.

However, it’s worth noting that the RP2040 microcontroller itself has internally twice as many interfaces than is ‘broken out’ to the pins on board. It includes 2x I2C, SPI and UART channels, of which one each are simply not available on the XIAO RP2040’s pins - of course, this is mostly because of the limited size to place pins on the board. Regardless, it would have been nice to see these interfaces (and additional GPIO pins) exposed on the bottom side of the board as solder pads so advanced users can access them if necessary.

This is already the case with the XIAO RP2040’s SWD (Serial Wire Debug) pins, which have four golden pads on the underside of the board.

Programmable IO

If you’re an advanced user, you’ll find Programmable IO (PIO) an awesome feature. If you’ve tried to implement some form of protocol on a regular Arduino Uno, you’ll be well aware of the hardware’s limitations. You often have to mess around with the internal timers and ensure that nothing else will slow down the chip to adhere to timing requirements. Alternatively, many makers will ‘bit-bang’ their own implementations of protocols through software. While it’s a valid solution, it often comes with a slew of other issues - namely, the chip can’t do much else while the protocol is being emulated, especially if the protocol is high-frequency.

The programmable state machine of the RP2040 chip

The RP2040 has the ability to program functionality on any of its GPIO pins, which is executed with an internal state machine rather than using all of the processor’s cycles. There isn’t any pre-programmed functionality embedded into these state machines - it really is a DIY option that lets users make extremely versatile and fast solutions to interfaces. While we won’t be showing off its functionality in this review, we may use it in future projects to accomplish some tricky IO tasks.

Languages

The XIAO RP2040 is compatible with a variety of languages for easy programming, and any can be used depending on your confidence level with the platform. It supports CircuitPython and MicroPython, as well as C for the diehard nerds - we’ll first check out some basic ‘Getting Started’ programs and then delve into some more interesting features of the XIAO RP2040 in C.

While it’s compatible with the Arduino platform, the actual process of programming the XIAO RP2040 is quite a different beast than your standard Arduino. When plugged into your computer, the XIAO RP2040 acts as a USB drive and your firmware file can be dragged into it manually, or with software like Mu, Thonny or Visual Studio Code.

To start programming our XIAO RP2040, we downloaded the Thonny editor to use MicroPython. We first needed to program MicroPython onto the XIAO RP2040 so it can run code in real-time. To do this, we opened the Thonny editor, went to the Interpreter options in the settings menu, and clicked on the “Install or update MicroPython” button.

From here, we can select that we’re using a Raspberry Pi Pico board (even though we’re not, the pinout and functionality are identical), and upload the firmware. It should only take a few seconds!

If “MicroPython” appears in the shell window, we’re all good to go! We can now program the XIAO RP2040. To start off, we’re showing off the shiny NeoPixel LED.

Project 1: Mini Rainbow LED

from machine import Pin
import neopixel
import utime
np = neopixel.NeoPixel(machine.Pin(12), 1)
p0 = Pin(11, Pin.OUT)
p0.on()
while True:
    np[0] = (255,0,0)
    np.write()
    utime.sleep(1)
    np[0] = (0,255,0)
    np.write()
    utime.sleep(1)
    np[0] = (0,0,255)
    np.write()
    utime.sleep(1)

If you’re new to MicroPython, the above code may be a little intense, but in reality, it’s even simpler than Arduino code! MicroPython includes the Neopixel library by default, so all we have to do is type ‘import neopixel’ to import the code we need. We can then start a NeoPixel object on Pin 12, and turn on Pin 11 to power the NeoPixel LED.

The ‘while True’ part of the loop will repeat forever, so any instructions inside will run indefinitely. To set the LED in the NeoPixel strip, we can use np[0] = (R, G, B), where R, G and B represent different colour values we’re writing to the NeoPixel. Afterwards, we run np.write() to actually write the value to the strip. Finally, we run utime.sleep(1) to wait one second, before repeating the process for the other colours.

This is a great starter project that can be accomplished with little else other than the XIAO RP2040, and can be adjusted to show your own colours and sequences!

Project 2: USB Joystick

Parts RequiredJaycar
1x XIAO RP2040-
1x Arcade Joystick with MicroswitchesSM1052
8x Female Spade Connectors^PT4722

# Slightly different than the part we used. Does not support diagonal switching. ^ Crimping tools may be required. Alternatively, solder directly to the microswitches.
Additional breadboarding and jumper wires required.

We built a mini joystick with our XIAO RP2040 that can be used as a regular USB keyboard to control the arrow keys! The joystick we’re using is often seen in Arcade machines, and can be used as an eight-way switch for pressing the four cardinal direction buttons, or two simultaneously to form a diagonal direction. Each direction has a simple NO (normally open) limit switch, which clicks closed when moved in that direction.

Since we needed to connect wires to our XIAO RP2040, we ended up soldering some basic pin headers onto it. Since the holes are castellated, pin headers can be easily soldered directly through the holes, or your wires of choice can be attached to the side of the board for some seriously compact applications.

Instead of soldering wires directly to the joystick, we crimped a bunch of wires with spade connectors. This lets us slide the connectors on easily and reconfigure the cables as necessary. The blue wires are the ground wires, and are connected across every button. The white wires are the signal wires, and one is used for each button channel. The other end of each of the wires are terminated with a 6-wide male header for easy insertion into our breadboard with the XIAO RP2040 in it.

Great, let’s get to writing our code! Before we can do that, though, we’ll need to upload CircuitPython instead of the MicroPython display as we used in the previous project. Download the CircuitPython file from Adafruit’s website for the RP2040, and upload it to the root directory of the XIAO RP2040.

Once it’s done copying, it’s time to write some code for our project! The code itself is a bit more extensive than our previous projects, however it’s nothing too complicated. The first thing we need to do is import all of the required libraries as below:

import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode

This is why CircuitPython was required on the XIAO RP2040 - Adafruit includes custom HID firmware that allows our board to act as a Keyboard device when plugged into the computer. We can send keystrokes as if it’s any regular USB peripheral. Because keycodes also differ based on the keyboard layout you use, you should also import the corresponding region code. I.e. KeyboardLayoutUS.

Next, we can actually set up our keyboard device:

time.sleep(1)  # Sleep for a bit to avoid a race condition on some systems
keyboard = Keyboard(usb_hid.devices)
keyboard_layout = KeyboardLayoutUS(keyboard)

This code was adapted from Adafruit’s example code for a HID keyboard, although we’ve added our own touches to suit our own joystick setup. You can see below, we’re setting up our GPIO pins to act as inputs. We’re also using the XIAO RP2040’s internal pullup resistors.

If you’ve never heard of this term before, it essentially refers to the resistors on each pin that can help keep the buttons in a known state when they aren’t pressed. When a button is in it’s open state (i.e. not pressed), the wire connecting it to the RP2040 will be floating, meaning that there is no predictable voltage on the pin, causing unpredictable behaviour. The pullup resistors we’re using will pull the voltage to 3.3V when the buttons aren’t pressed. When pressed, each button is connected to ground and will pull that pin to 0V.

# Initialise Joystick GPIO pins
down = digitalio.DigitalInOut(board.GP1)
down.switch_to_input(pull=digitalio.Pull.UP)
right = digitalio.DigitalInOut(board.GP2)
right.switch_to_input(pull=digitalio.Pull.UP)
left = digitalio.DigitalInOut(board.GP3)
left.switch_to_input(pull=digitalio.Pull.UP)
up = digitalio.DigitalInOut(board.GP4)
up.switch_to_input(pull=digitalio.Pull.UP)

We can now do our continuous loop to check the states of each button pin. The ‘while True:’ loop continues forever, and on each loop we check each individual pin. If the button is pressed, we press the corresponding keyboard button, and if it’s not, we release that corresponding keyboard button. Note that releasing each key is very important, otherwise the button will act as though it’s stuck on!

Keen code readers will notice we’re using the if statements with ‘not’ in each IF statement - this is because our buttons are active-low, meaning they actually read a value of 0 (or false) when pressed, and 1 (or true) when not pressed.

#Loop infinitely.
while True:
    # Check UP Arrow
    if not up.value:
        keyboard.press(Keycode.UP_ARROW)
    else:
        keyboard.release(Keycode.UP_ARROW)
    # Check DOWN Arrow
    if not down.value:
        keyboard.press(Keycode.DOWN_ARROW)
    else:
        keyboard.release(Keycode.DOWN_ARROW)
    # Check LEFT Arrow 
    if not left.value:
        keyboard.press(Keycode.LEFT_ARROW)
    else:
        keyboard.release(Keycode.LEFT_ARROW)
    # Check RIGHT Arrow
    if not right.value:
        keyboard.press(Keycode.RIGHT_ARROW)
    else:
        keyboard.release(Keycode.RIGHT_ARROW)

And that’s it! Pretty simple, right? Just press the play button in the code editor to run it on your XIAO RP2040 and test it out by moving the joystick around. If the program you’re using has some functionality with arrow keys, it should behave as if it was any other keyboard. It’s truly transparent and acts as a generic HID device, so it’s compatible with every software compatible with USB keyboards.

But why stop there? You could really go above and beyond to create your own macro keyboard with the XIAO RP2040 by creating a pad of buttons, switches and joysticks. You can map each one to a specific shortcut of keycodes, a sequence of keystrokes or even control the mouse! For example, why not create a quick-shortcut panel that can Alt-Tab between windows, or instantly minimise or maximise windows at the press of a button.

Project 3: Temperature Display

Parts RequiredJaycar
1x XIAO RP2040-
1x 8 Digit 7 Segment Display ModuleXC3714

Additional breadboarding and jumper wires required.

The XIAO RP2040 has its own inbuilt temperature sensor, so let’s hook it up to a cool LED display to graphically display the current ambient temperature!

We’re using an 8-digit 7-segment LED module to display the current temperature in real-time.

This is connected to the XIAO RP2040 over SPI, which is a very simple serial interface. To connect it up, we only need five wires (two for power, three for data over SPI), which can be seen in the included Fritzing diagram. We connected ours on a breadboard, but you can connect it however is most convenient for you.

The RP2040 barely lifts a finger when it comes to talking to the LED display, so it’s definitely possible to do other processing while outputting data to it. Unlike the USB Joystick project, this project uses MicroPython, so if you’re following along, use the MicroPython uploader in your software of choice to re-upload the firmware.

The code itself is more advanced than the others, but don’t worry, it’s nothing that would require a software degree!

The imports are fairly straightforward, and let us use the inbuilt SPI interfaces onboard the XIAO RP2040, as well as some extra functions for timing.

from machine import Pin, SPI
import machine
import utime
import time

The onboard temperature sensor requires a bit of manipulation to get working, because technically speaking, it is connected straight to an analog pin onboard the RP2040. We won’t go into the conversion factors here, but essentially there is a fixed set of math constants that let us convert a raw reading from the ADC to a reading in degrees Celsius.

sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535)

The XIAO RP2040 can’t just magically know how letters are shaped on the display - the LED module itself will just take whatever data we send it over SPI and display it on the digits, even if it’s garbage. We’re using a simple library called ‘max7219_8digit’ developed by pdwerryhouse on GitHub - the Python file needs to be downloaded to your computer and saved to your XIAO RP2040 so it can be run.

The SPI setup itself is just mapping whatever pins you’re using to the SPI function. After we’ve created an SPI instance, we can actually initialise the display.

# Import MicoPython MAX7219, 8 digit, 7segment library
import max7219_8digit
spi = SPI(0, baudrate=10000000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3))
ss = Pin(1, Pin.OUT)
# Create display instant
display = max7219_8digit.Display(spi, ss)

Finally, we can get into the nitty-gritty! This is actually where we are taking our temperature value and writing it to the display in an infinite loop. Again, there is some maths here that isn’t necessary to understand, so don’t worry about the conversion factor and the ADC readings - the bottom line is that the temperature in degrees Celsius is stored in the ‘temperature’ variable.

The temperature variable is then rounded and converted to string suitable for display onto the LED display. After writing to the SPI buffer, we then tell it to display and sleep for two seconds before doing it all again!

while True:
    reading = 
sensor_temp.read_u16() * conversion_factor 
    temperature = 27 - (reading - 0.706)/0.001721
    temp_string = str(round(temperature,2))
    temp_string += "C"
    display.write_to_buffer(temp_string)     
    display.display()
    utime.sleep(2)

This project works incredibly well and is extremely fast, although the temperature sensor of the RP2040 also measures the core temperature of the chip, so it’s likely skewed by how hard the chip is working. You may wish to use a more advanced environment sensor like the common BME280 series, which provides more than enough digits of precision for most makers like us.

Final Thoughts

We’re pretty happy with the XIAO RP2040, and may look to use it for some future projects where space is limited but functionality is important. Although it has less GPIO pins than the fully fledged Raspberry Pi Pico, it has exactly the same processor at its heart, so projects made for the Pico have a high chance of working with the XIAO RP2040 too.

In general, we’re very excited about the direction maker electronics is heading when it comes to innovative gadgets and new development boards. We’re seeing less big bulky boards with antiquated connectors and sparse instruction guides online, and more boards that are designed with the end user in mind.

While the good ol’ Arduino Uno and its brothers will probably remain the staple of maker projects for the foreseeable future, alternatives like the XIAO RP2040 are becoming more prevalent than ever. Makers who are looking for the next step up in features and functionality will find the XIAO RP2040 a very competitive option, or if you’re more confident with Python programming rather than C++, MicroPython is a great platform to learn on.

We've got a bunch of XIAO RP2040’s to giveaway this month, so we can’t wait to see what you’ll make with them!

Grab it online from Pakronics