Galactic Unicorns and Custom Pimoroni Pico Firmware

Originally posted on Medium

I bought an excellent Galactic Unicorn from Pimoroni, with the aim of connecting a Time-of-Flight sensor (VL53L5CX) to use as input for the board, connecting it over the QW/ST using I2C.

Sounds simple right? No.

There are some examples for the VL53L5CX working with the Raspberry Pi Pico, so I assumed this would work OK with the Galactic Unicorn, which uses a Pico W (A Pico with WiFi, great for IoT!).

Was I right? Also no.

The example I needed for the VL53L5CX used ulab numpy, a module created to speed up mathematical operations on arrays, and it turns out this isn’t included in the Galactic Unicorn firmware. It’s quite a large module, so has been removed to save space in a number of the Pimoroni uf2’s.

With some advice from Jimmo on the Micropython Discord, and some pointers from Gadgetoid on Discord and Mastodon, I was able to remove a bunch of modules from the Galactic Unicorn firmware, and add in ulab numpy.

How to make custom Pimoroni firmware with ulab numpy

If you want to make your own custom firmware, turns out it’s pretty easy. It requires a bit of github knowledge. I’ll outline the steps:

  1. Go to and fork it.
  2. In your new repo, create a branch
  3. Go to micropython/modules/micropython-picow_galactic_unicorn.cmake
  4. Under #Sensors & Breakouts, comment out everything you don’t need. This saves some space, it might not be necessary, but as I didn’t need support for anything except the VL53L5CX, I removed them all
  5. After #include(micropython-common) add:
target_compile_definitions(usermod_ulab INTERFACE
        # Support for complex ndarrays
# Determines, whether scipy is defined in ulab. The sub-modules and functions
        # of scipy have to be defined separately
# The maximum number of dimensions the firmware should be able to support
        # Possible values lie between 1, and 4, inclusive
# By setting this constant to 1, iteration over array dimensions will be implemented
        # as a function (ndarray_rewind_array), instead of writing out the loops in macros
        # This reduces firmware size at the expense of speed
# If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function
        # This option saves approx. 250 bytes of flash space
# Slicing can be switched off by setting this variable to 0
# The default threshold for pretty printing. These variables can be overwritten
        # at run-time via the set_printoptions() function
# determines, whether the dtype is an object, or simply a character
        # the object implementation is numpythonic, but requires more space
# the ndarray binary operators
# Firmware size can be reduced at the expense of speed by using function
        # pointers in iterations. For each operator, the function pointer saves around
        # 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case.
  • Now create a release, and commit & push your changes.
  • Github Actions will now compile everything. You need to wait a short while, and you’ll find your UF2 in Actions > Micropython (second one down) > select your latest Workflow Run at the top, then scroll down, and you’ll see your firmware with ulab included! Eg: pimoroni-picow_galactic_unicorn-[blah]-micropython.uf2

You can in theory do the same for the other boards, take a look in /micropython/modules, and view all the various cmake files.

Hope that quick overview is helpful to someone!

Leave a Reply

Your email address will not be published. Required fields are marked *