Running OCaml 5 with multicore support on bare-metal Raspberry Pi Pico 2 W (RP2350, ARM Cortex-M33).
Target Hardware: Raspberry Pi Pico 2 W (RP2350, dual ARM Cortex-M33, CYW43 WiFi) Build Host: Raspberry Pi 5 (ARM64) OCaml Version: 5.5.0+dev mtelvers/ocaml arm32-multicore branch
- Full OCaml 5 runtime with garbage collection
- Standard library support
- Domain.spawn multicore support (dual-core)
- Native ARMv8-M code generation
- WiFi networking with TCP/IP (lwIP)
- Pio (effects-based I/O for Pico, following Eio conventions)
- Raspberry Pi 5 (or similar ARM64 Linux system) as build host
- Raspberry Pi Pico 2 W connected via USB
# Build tools and ARM toolchains
sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi \
libstdc++-arm-none-eabi-newlib build-essential gcc-arm-linux-gnueabi
# Pico SDK
git clone https://github.com/raspberrypi/pico-sdk.git ~/pico-sdk --branch master --depth 1
cd ~/pico-sdk && git submodule update --init
# Add to ~/.bashrc
export PICO_SDK_PATH="$HOME/pico-sdk"git clone https://github.com/mtelvers/ocaml.git ~/ocaml
cd ~/ocaml
git checkout arm32-multicoreThe cross-compiler build requires a host OCaml compiler of the same version:
cd ~/ocaml
./configure
make world.opt -j4
sudo make installcd ~/ocaml
make distclean
./configure --target=arm-linux-gnueabi
make crossopt -j4This builds ocamlopt.opt - a compiler that runs on ARM64 but generates ARM32 code with ARMv8-M support.
Verify:
./ocamlopt.opt -config | grep architecture
# Should show: architecture: armgit clone https://github.com/mtelvers/pico_ocaml
cd ~/pico_ocaml
mkdir -p build && cd build
PICO_SDK_PATH=~/pico-sdk PICO_BOARD=pico2_w cmake ..make runtime # Build OCaml runtime library (required first time)make -j4Output: build/ocaml_pico.uf2
- Hold BOOTSEL button while connecting Pico 2 W via USB
- It mounts as a USB mass storage device (RP2350)
- Copy the UF2 file:
cp build/ocaml_pico.uf2 /media/$USER/RP2350/minicom -D /dev/ttyACM0 -b 115200# After changing OCaml source files (hello.ml, net.ml, pio.ml):
make -j4
# After changing ~/ocaml/runtime/* files:
make runtime && make -j4pico_ocaml/
├── CMakeLists.txt # Main build configuration
├── hello.ml # Main OCaml program
├── net.ml # Network module (WiFi, TCP)
├── pio.ml # Pio module (effects-based I/O)
├── pico_main.c # C entry point
├── ocaml_stubs.c # POSIX stubs for bare-metal
├── net_stubs.c # Network C stubs (lwIP integration)
├── memmap_flash.ld # Custom linker script (code in flash)
├── lwipopts.h # lwIP configuration
├── pthread.h # Pthread stub header
├── unistd.h # POSIX stub header
├── sys/
│ ├── mman.h # mmap stub header
│ └── ioctl.h # ioctl stub header
├── cmake/
│ └── build_runtime.cmake # Runtime build script
├── lib/
│ └── ocaml_runtime/ # Built runtime library
│ └── libasmrun.a
└── build/
└── ocaml_pico.uf2 # Flashable firmware
external print_endline : string -> unit = "pico_print"
external cyw43_init : unit -> int = "ocaml_cyw43_init"
external cyw43_led_set : bool -> unit = "ocaml_cyw43_led_set"
external sleep_ms : int -> unit = "ocaml_sleep_ms"
let blink_led () =
for _ = 1 to 5 do
cyw43_led_set true;
sleep_ms 200;
cyw43_led_set false;
sleep_ms 200
done
let () =
print_endline "OCaml 5 on Pico 2 W";
ignore (cyw43_init ());
(* Spawn work on second core *)
let d = Domain.spawn blink_led in
Domain.join d;
print_endline "Done!"(* Console output *)
external print_endline : string -> unit = "pico_print"
external print_int : int -> unit = "pico_print_int"
(* CYW43 WiFi chip (Pico W / Pico 2 W) *)
external cyw43_init : unit -> int = "ocaml_cyw43_init"
external cyw43_led_set : bool -> unit = "ocaml_cyw43_led_set"
(* Timing *)
external sleep_ms : int -> unit = "ocaml_sleep_ms"
external time_ms : unit -> int = "ocaml_time_ms"
(* GPIO (for non-W boards) *)
external gpio_init : int -> unit = "ocaml_gpio_init"
external gpio_set_dir_out : int -> unit = "ocaml_gpio_set_dir_out"
external gpio_put : int -> bool -> unit = "ocaml_gpio_put"Rebuild the stdlib in ~/ocaml:
cd ~/ocaml && make -C stdlib clean && make -C stdlib allopt
cd ~/pico_ocaml/build && make -j4Verify all objects are ARMv8-M:
arm-none-eabi-objdump -f file.o | grep architecture
# Should show: armv8-m.mainThe Pico 2 W has limited RAM (~404KB available for heap). Reduce memory usage by:
- Using fewer stdlib modules
- Reducing data structure sizes
- The runtime uses 8KB pools for the major heap