Raspberry RP2040 (Pico board) experiences

This is for a Raspberry Pico board. It's got an RP2040, which is a 2-core ARM Cortex-M0+ 133MHz, 264kB SRAM, magic PIO peripheral, and a builtin bootloader that speaks USB mass-storage. It has an RT6150B-33GQW 3.3V buck-boost regulator and a 2MB W25Q16JVUXIQ SPI flash program memory, and a 12MHz crystal. It has a USB connector, an active-high LED on GPIO25, and a BOOTSEL button to temporarily disable the SPI memory during boot-up.

I want to program it, and in embedded projects I tend to indulge my bias against complication. So, I'm not using the darn Raspberry Pico SDK.

Here's the git tree where I'm doing all of my generic rp2040 development (initialization, peripheral interface, and linking examples; picoprog unprivileged USB programmer):

git clone http://galexander.org/git/rp2040.git

Email me:

March 26, 2023.

To reprogram it, you have to power-cycle it while holding down the BOOTSEL button. I expect I will therefore be handling it a lot while I get started, so my first detour is designing a case for it. Thoughtless case design: picobox.scad.

April 9, 2023.

Raspberry makes their documentation easier to get to than STM does:

I was surprised I didn't immediately stumble onto an rp2040 SDK .deb, so I think I will try to do without it. I found elf2uf2 utility to create the "uf2" format used by the boot rom. I am using binutils-arm-none-eabi and gcc-arm-none-eabi Debian packages. gcc and gas both want -mcpu=cortex-m0plus.

May 6, 2023.

I read a bunch and came at an easy way to use it! I can target SRAM directly in the ldscript, and the UF2 bootloader can read that and it copies it into SRAM and sets some magic WDT register that is able to communicate across a reboot, and then it reboots into my SRAM! So it never writes into flash and after the next reboot, it still doesn't find a valid second stage bootloader in flash so it always boots into the USB bootloader and I never have to press the little hardware button!

The icing on top is that I set up the WDT to reboot after about 9 seconds (or I can drag that out to about 5 minutes by changing the WDT divider, or I can refresh it as long as my program runs). So I can structure my program as an infinite loop, and then it reboots into the bootloader. Iterating is now maximum easy!

Obviously not suitable for really embedding in anything. But my first project aims to use it as a general I/O controller that I want to leave connected to my PC and perhaps re-purpose over time (I already have an STM32 serving this role in a more hacky way on a different PC). It'll also be great for testing my own USB interface.

To boot off of flash, I have to make a "second stage bootloader". It's supposed to enable XIP (eXecute In Place, directly from flash) on the flash and then branch to an entry point. But if I am ever confronted with it, I would be inclined to simply copy flash into SRAM and branch into that SRAM. Flash XIP mode reconfigures the SPI interface to speak a different protocol (implicit command headers I guess), and you have to go through some dance to turn it on or off to, for example, write to flash.

Here's blink-20230506.tgz (a snapshot from my git repository), a blinky written in ASM, targetting SRAM, with no dependencies (it even has its own ldscript). It blinks the LED 4 times (toggling about once per second) and then reboots from the WDT.

As an aside, the .thumb_func directive seems to be history. The idiom to make a "thumb function" (so its address will be odd to indicate thumbness to anything that branches to it) is now:

.global foo
.type start,function

May 7, 2023.

I wanted to be able to write to the Pico without mounting the fake filesystem or doing anything privileged, so that calls for a libusb userspace program. I started out trying to make a libusb mass storage driver, and I got most of the way there but I just felt so stupid decoding a FAT filesystem just to stream a UF2 file.

So I used the PICOBOOT protocol instead. The RP2040 bootrom supports two USB interfaces -- mass storage and PICOBOOT. PICOBOOT is nice and simple and really not that different from mass storage. So it's easy to support. So now I simply plug it in, run picoprog foo.elf, and a second later it reboots into my program...and then when my program finishes (allows WDT to elapse), it is ready to receive another one! It is so convenient!

That happens to mean I don't need UF2 anymore so this is now completely free of any Raspberry SDK components.

Download my picoprog tool: picoprog-20230521.tar.gz. (you will be better off using the one from git!).

May 12, 2023.

And now I've got a C blinky, which also demonstrates a systick ISR to show that I can trap interrupts in an SRAM program. So that shows a broad range of functionality working without any SDK. Seems pretty approachable. Here: cblink-20230513.tar.gz.

May 16, 2023.

Had a hiccup in my progress to track down this gcc bug. It will intermittently translate any NULL pointer dereference (including all of the members of the struct I'm using to reference the bootrom's header) into a ".inst 0xdeff", which is the standardized encoding for the "UDF" undefined instruction. I assume it generates a processor fault of some sort. Since that will terminate execution, gcc doesn't compile the rest of the function.

So, add -fno-delete-null-pointer-checks -fwrapv to your gcc commandline. The "overflow is undefined so obviously programmer intent is impossible to discern" mess was bad enough (that's the -fwrapv), but this is a new low. No one wants NULL pointer dereferences to be silently turned into illegal instructions. At most, someone might enjoy a compile-time diagnostic (error message). They're intentionally antagonizing programmers because the standard says they can. This is evil.

May 21, 2023.

Spent a while discovering with the fact that the RP2040 requires 256 byte alignment for the vector table (VTOR), even though the ARMv6-M manual only says 128 byte (but it also says implementation defined). Sigh.

I worked out all the kinks in my second-stage boot loader. So now it can boot from SRAM (blink_sram.elf), a flash loader that copies .text and .rodata into SRAM and then executes from SRAM (blink_fl.elf), or in XIP mode where it accesses .text and .rodata directly from flash (blink_xip.elf). So the C blinky is now roughly a minimal complete SDK. It now has everything I wish had come in the box with the Pico: cblink-20230521.tar.gz.

In the process, worked out all the details in picoprog (get the 20230521 picoprog above). Really, if you're working with my stuff, you should just clone the git repository and work from that.