I decided to take a break from porting Loser Corps to the ESP32, and do something completely different: port Loser Corps to the Raspberry Pi Pico!

Loser Corps rendered on the Raspberry Pi  Pico
Loser Corps rendered on the Raspberry Pi Pico

The Pico is smaller than the ESP32, with about half as much RAM, flash and clock frequency:

ESP32-WROOMFeatureRaspberry Pi Pico
(2x) Tensilica Xtensa LX6Processor(2x) Arm Cortex-M0+
240 MHzClock133 MHz
520 KBRAM264 KB
4 MBFlash2 MB
2.4 GHzWiFinope

The Pico (like the ESP32) can adjust its main clock frequency: 133 MHz is the official maximum, but I'm using it at 125 MHz because that makes VGA signal generation much simpler: the 12.5 MHz pixel clock required for the 320x240 VGA mode is exactly 1/10 of the main clock (well, technically it should be 12.59 MHz, but 12.5 is close enough).

The Pico connected to my homemade DACs and a Wii Nunchuk. Ignore the row of LEDs on the second breadboard, I was using them to debug the output of my PIO program.
The Pico connected to my homemade DACs and a Wii Nunchuk. Ignore the row of LEDs on the second breadboard, I was using them to debug the output of my PIO program.

There are some example programs that output VGA using an official library provided by the Raspberry Pi Foundation, but they don't work with my old CRT monitor. I haven't looked at the library or the examples too closely, but it seems that the pixel clock they use is too far away from the spec for my monitor: 640x480 should be 25.18 MHz, and from what I can tell the examples use 24.0 MHz.

One of the cool things about the Pico is that it has a programmable input/output (PIO) hardware interface, which is basically a tiny specialized processor that can run programs that perform I/O operations independently from the main cores. I used the PIO (fed via DMA) for the VGA output.

After a lot of trial and failure, I went with an extremely simple PIO program: it's just a single instruction that gets data from the DMA and feeds it one byte at a time to 8 output pins. The data has to be prepared exactly the right way so the output is a correctly-timed VGA signal, which is stays right as long as the PIO program runs at a fixed frequency equal to the VGA pixel clock. In practice, this means that the images still need vertical and horizontal sync bits embedded in them, like in the ESP32.

Maybe I will make a more complex PIO program when I get more experience with it. It would be nice to make the program generate the vertical and horizontal sync signals automatically, and free 2 bits of image data for colors: we could have 3:3:2 bits for red:green:blue, increasing the number of colors from 64 to 256. For now, though, the output is exactly the same as the ESP32 (2:2:2), which is nice because I don't have to make new homemade DAC boards to connect to the monitor. Still, a really good advantage of the PIO (even running my brain-dead program) over the I2S bus we use on the ESP32 is that it doesn't scramble the byte order.

Even after everything was running, I ended up with problems with the IRQ handler for the DMA completion (which restarts the DMA to keep the PIO fed with data). When the CPU got too busy (for example, in areas of the map with too many things on the screen), the IRQ was being delayed enough do disrupt the video signal, and my monitor really didn't like it. In the end I managed to make the render loop run entirely without CPU supervision (it runs entirely on DMA shoving data into the PIO), and it only generates and IRQ for frame counting, which the game code uses to synchronize the framebuffer swapping (to avoid screen tearing).

The only parts actually done right now are the rendering and joystick input (Wii nunchuk and classic controller only). The character control and collision detection will be done soon.