How to compile your own firmware for the Raspberry Pi Pico

I'm using a ST7789 LCD display with a Raspberry Pi Pico. I use the great st7789_mpy library to drive it.

I wanted to benefit from the latest Pico firmware updates.

So here is the procedure to create a custom Pi Pico firmware that includes the st7789_mpy library.

This can easily be adapted to include any other libraries or packages you wish to include (as frozen code) into the Pico firmware.

Install a Linux VM :

If you are running macOS or Windows, first install a Linux VM with multipass.

Here is the procedure under macOS :

Install multipass :

brew install multipass

Start a VM :

multipass launch lts -n primary -c 4 -m 4G -d 30G

Log in :

multipass shell

From now on, we will work in this shell.

For info, your macOS home directory is mounted under /home/ubuntu/Home in the multipass VM.

Install required packages :

sudo apt-get -y install build-essential libffi-dev git pkg-config cmake virtualenv python3-pip python3-virtualenv
sudo apt-get -y install libstdc++-arm-none-eabi-newlib

Get the source code :

mkdir mpy && cd mpy

Clone the MicroPython repo :

git clone https://github.com/micropython/micropython.git

Clone the st7789_mpy library repo :

git clone https://github.com/russhughes/st7789_mpy.git

From now on work from the micropython directory :

cd micropython/

Update the git submodules :

git submodule update --init --recursive

note: the recursive flag is probably not needed

You can add any python package to be frozen simply by copying them into ports/rp2/modules/.

We add some fonts :

cp ../st7789_mpy/fonts/truetype/*.py ports/rp2/modules/

Compile :

Compile the MicroPython cross-compiler :

make -C mpy-cross/ -j 16

Compile the firmware :

make -C ports/rp2 BOARD=RPI_PICO_W clean
make -C ports/rp2 BOARD=RPI_PICO_W submodules

Compile the library :

# use an absolute path for the user modules
make -C ports/rp2 BOARD=RPI_PICO_W -j 16 USER_C_MODULES=/home/ubuntu/mpy/st7789_mpy/st7789/micropython.cmake

The result should be something like :

...
[ 97%] Linking CXX executable firmware.elf
   text	   data	    bss	    dec	    hex	filename
 860220	      0	  42548	 902768	  dc670	/home/ubuntu/mpy/micropython/ports/rp2/build-RPI_PICO_W/firmware.elf
make[3]: Leaving directory '/home/ubuntu/mpy/micropython/ports/rp2/build-RPI_PICO_W'
[100%] Built target firmware
make[2]: Leaving directory '/home/ubuntu/mpy/micropython/ports/rp2/build-RPI_PICO_W'
make[1]: Leaving directory '/home/ubuntu/mpy/micropython/ports/rp2/build-RPI_PICO_W'
make: Leaving directory '/home/ubuntu/mpy/micropython/ports/rp2'

Finally, your custom firmware is ready and available at :

./ports/rp2/build-RPI_PICO_W/firmware.uf2

Check the new firmware :

Connect to your Pico with, for example, Thonny and issue the following command :

help('modules')

The result should be something like :

MicroPython v1.24.0-preview.98.g4d16a9cce on 2024-07-06; Raspberry Pi Pico W with RP2040

Type "help()" for more information.
>>> help('modules')
NotoSansMono_32   aioble/server     gc                rp2
NotoSans_32       array             hashlib           select
NotoSerif_32      asyncio/__init__  heapq             socket
__main__          asyncio/core      io                ssl
_asyncio          asyncio/event     json              st7789
_boot             asyncio/funcs     lwip              struct
_boot_fat         asyncio/lock      machine           sys
_onewire          asyncio/stream    math              time
_rp2              binascii          micropython       tls
_thread           bluetooth         mip/__init__      uasyncio
_webrepl          builtins          neopixel          uctypes
aioble/__init__   cmath             network           urequests
aioble/central    collections       ntptime           vfs
aioble/client     cryptolib         onewire           webrepl
aioble/core       deflate           os                webrepl_setup
aioble/device     dht               platform          websocket
aioble/l2cap      ds18x20           random
aioble/peripheral errno             re
aioble/security   framebuf          requests/__init__
Plus any modules on the filesystem

We can see that this new firmware includes the st7789 library and the TrueType fonts we added as frozen code.

Notes

No need to create a manifest.py file because the default micropython manifest already includes the port dir :

https://github.com/micropython/micropython/blob/4d16a9cced42ad0a298f1be8b993357abe7398ab/ports/rp2/boards/manifest.py :

freeze("$(PORT_DIR)/modules")

That is why any modules copied into $(PORT_DIR)/modules will be frozen.

Resources :