Standalone tools for the Raspberry Pi Pico

In my previous entry, I described my process for building a program for my Raspberry Pi Pico without relying on the provided SDK.

One thing I didn't discuss was how to avoid the other big dependency, the GNU ARM Embedded Toolchain. The toolchain is a free download from ARM, but it's a hulking ~700 megs uncompressed.

As far as I can tell, the download there is simply a stock prebuilt version of the standard GNU toolchain elements: GCC, binutils, and newlib (a minimal libc). Some basic invocations of du show that much of the space is taken up by files in arm-none-eabi/lib and lib/gcc/arm-none-eabi/10.2.1. Those are directories for, respectively, non-compiler-provided and compiler-provided target libraries. In other words, libraries you might want to link into your program.

For one thing, those directories are both full of multilib variants of all of the libraries, for targeting different ARM ISAs or build configurations. (macOS and friends organize this sort of thing much more cleanly using fat binaries.)

This makes sense for the people running ARM's web site, so that they only have to offer a single download. But we know from the datasheet that the RP2040's CPU runs ARMv6-M code, so only the v6-m directories is really needed, not the 33 variants that are actually present:[1]

$ file thumb/v6-m/nofp/libc.a
thumb/v6-m/nofp/libc.a: current ar archive
$ find . -name libc.a | wc -l

But, moreover, the entire point of my exercise was not to use any of these libraries anyway, so I actually just don't need them at all.

The non-compiler-provided (arm-none-eabi/lib) libraries come from newlib. The compiler-provided (lib/gcc/arm-none-eabi/10.2.1) libraries come from GCC. As it happens, I'm not writing any C code either, so I can jettison both projects entirely.

In fact, the only tools my makefile actually uses are as, ld[2], and objcopy.[3] All of those come from the GNU binutils project.

I could try to extract those files from the ARM-provided toolchain, but in the spirit of self-reliance, I figured I could build them myself:

$ git clone git://
$ cd binutils-gdb
$ mkdir build-arm-eabi
$ cd build-arm-eabi
$ ../configure --target=arm-eabi --prefix=/opt/local/ --disable-nls
$ make CFLAGS="-Wno-error" all-gas all-ld all-binutils
$ make DESTDIR=`pwd`/install install-gas install-ld install-binutils

arm-eabi is the GCC-style target for the ARM Embedded ABI.

The --disable-nls is because, while I do have a gettext installed on my system in /opt/local/, the binutils makefiles are apparently not smart enough to point at the the corresponding libintl.h in /opt/local/include/ and instead barf trying to find it in my standard sysroot. I don't need a localized build anyway.

When all is said and done, the resultant install root is much smaller:

$ du -sh install
 22M    install

And even that could be pared down a bit by manually removing things like objdump, strip, ar, ranlib, etc.—things that are installed by the all-binutils goal above but which we didn't actually care about.

  1. Incidentally, this proliferation of multilib variants is also a pain in the ass when building GCC, and this is affecting another project I'm working on. ↩︎

  2. And even the dependence on ld is pretty weak—I only need it to adjust the load address of the ELF section. ↩︎

  3. And pad_checksum, which is just a Python script in pico-sdk↩︎