Welcome!

However you got here – maybe you experimented with mecrisp or zeptoforth, but decided that you prefer muforth’s tethered Forth model, or maybe an internet search for “pico forth” brought you here – welcome!

It’s very easy to set up your Pico so that you can interactively explore its possibilities. Essentially all that is necessary is to program a small amount of code into the flash. We do this for two reasons:

Since PICOBOOT’s support for erasing and programming the flash seems to be very broken – I spent a lot of time trying to make it work and never succeeded – we are going to generate a UF2 file and use that to program the flash, through the Pico’s “drag and drop” interface.

How to generate the UF2 file

In general – but especially for ARM and RISC-V targets – I have tried to create “board” files that load all the code necessary to write code for the chip (the Forth “toolchain”), and also load support for any serial or USB bootloaders or debug interfaces that exist on the target chip or board.

In the case of the Pico, there is a USB-based “bootloader” called PICOBOOT which will allow us to interactively read and write the Pico’s memory and i/o spaces, and also to execute code. But the Pico’s ROM also contains a USB mass storage interface, which pretends to be a FAT filesystem, and if we create a UF2 file, we can simply copy this over to the Pico, and the ROM will burn the contents into the flash.

The instructions below will create a UF2 file containing the ARM Forth kernel, and an intentionally blank “stage2” loader. The stage2 loader normally sets up the execute-in-place (XIP) hardware on the Pico so that the rest of the program can execute directly from the flash. Since we are not going to be using the Pico this way, by programming an “invalid” stage2 (it lacks a valid CRC32), we will guarantee that the Pico always boots into PICOBOOT.

To create the UF2, do this:

  cd muforth/mu
  ./muforth -d kernel-in-flash -f target/ARM/board/raspberry-pi-pico.mu4 \
      pico-uf2 bye > pico-kernel.uf2

The backslash is a line continuation character. You should concatenate the “muforth” line and the following line into one, eliding the "\".

This will load the Pico’s board file, and tell it to generate a flash image containing an empty stage2 and the ARM kernel. -d means “define a symbol”, which we can use to conditionally load code; -f means “read and interpret the following file contaning muforth code”.

pico-uf2 generates the UF2 (binary) file; bye exits; and > is the normal shell redirect, which creates a the pico-kernel.uf2 file.

Drag and drop or command-line copy pico-kernel.uf2 to the mounted Pico. (Details will vary depending on your OS.)

Interacting with the flash-resident Forth kernel

We reload the same file, but in a slightly different way, and then connect to it:

  ./muforth -d kernel-in-flash -f target/ARM/board/raspberry-pi-pico.mu4
  pico

This should show a message that says “PICOBOOT v2” or “PICOBOOT v3”, depending on how new your Pico is. My originals – which I bought in 2021 – say v2; two Pico H’s that I bought in March 2023 say v3.

You are now connected, and can dump memory and execute code. A few fun things to try:

To dump the ROM:

  0 du

This starts the interactive “dump” utility. “q” quits; RET goes forward; BS goes backwards, “C” shows ASCII, bytes, and words; “i” will start disassembling (or try to); “d” returns to dumping.

Warning: the ROM is 16 KiB in size, but if you try to read the last 16 bytes, the Pico will hang!

To dump the flash:

  @flash du

@flash means “origin of the flash”. The first 256 bytes will be ff; at 1000_0100 you will see something interesting. This is the ARM kernel that we just programmed via the UF2 file. You can switch to disassembling with “i”, or quit (“q”) and start over thusly:

  1000_0100 dis

and you can read through the disassembled code for the kernel.

You can dump the RAM too:

  @ram du

Again, the first part is a lot of “ff” because I use the beginning of RAM as a buffer to work around a big failing of PICOBOOT: it refuses to read a lot of the address space of the chip, so I use a small utility word that gets copied into the Pico’s RAM which will happily read anything; it copies what it read into the buffer; and then I use PICOBOOT’s read command to read the contents of the RAM buffer over USB back to the host.

After about 256 bytes of “ff” you will again see some interesting stuff. “i” or dis are helpful to try to disassemble things that might be code.

Most interesting of all, you can execute code. A simple test is this:

  1 2 +

This will push two values onto the (host) stack, copy them over to the Pico’s stack, and then execute the word +, which will add them and push the result.

  target words

will show the words that have been defined. If you are familiar with Forth, you should recognize most of these words.

But this is only a test! What we really want to be doing is this:

Interacting with a purely RAM-resident system

Type bye or ^C (my favorite) to exit muforth, and then reload the board file a third time:

  ./muforth -f target/ARM/board/raspberry-pi-pico.mu4
  pico

This time we are loading the ARM kernel, and some tools I used to experiment with the QSPI interface and the flash chip on the Pico, into RAM, to make it easier to interact with. Try the same dump (“du”) commands, and try executing +, as above. Everything should work the same way, but now we are completely ignoring the contents of the flash.

On beyond zebra...

I haven’t written much code (yet) that interacts with the i/o devices (peripherals) on the Pico, but there is an “equates” file that defines the addresses of all the registers; the Pico board file automatically loads this. The equates file is here: mu/target/ARM/raspi/rp2040-equates.mu4.

You’ll have to carefully read the RP2040 datasheet to figure what to do next, but writing Forth code in the natural way will work: when used in a colon word, an equate turns into a literal, and the contents of the address can be fetched and stored in the Forth manner – using @ and !.

I hope this gets you started!