Build a Swift executable for the Raspberry Pi Pico without the Pico C SDK. Tested on macOS 14 and Linux (Ubuntu 22.04).
Swift forum discussion thread: https://forums.swift.org/t/embedded-swift-on-the-raspberry-pi-pico-rp2040-without-the-pico-sdk/69338
-
Download and install a current nightly Swift toolchain ("Trunk Development (main)"). Tested with 2024-02-15, newer versions will probably work too. The download is a
.pkginstaller that will install the toolchain into/Library/Developer/Toolchainsor~/Library/Developer/Toolchains. -
Install LLVM using Homebrew:
brew install llvm
Note: This is necessary even though Xcode already comes with LLVM. We need (a) a linker that can produce
.elffiles, and (b) the toolllvm-objcopy. Xcode currently doesn’t provide these. Don’t worry, installing LLVM from Homebrew, won’t mess up your Xcode setup; Homebrew won’t place these tools into yourPATH. -
Put the
ld.lldexecutable (this is the linker from LLVM you just installed) in yourPATH. SwiftPM will be looking for this program and it needs to be able to find it. I have a~/bindirectory that's already in myPATH, so I put a symlink told.lldin that folder:ln -s /opt/homebrew/opt/llvm/bin/ld.lld ~/bin/ld.lld(If you’re on an Intel Mac, the path to your Homebrew may vary.)
Alternatively, you could put this symlink in
/usr/local/binor some other directory in yourPATH. Note: I advise against putting thellvm/bindirectory itself in yourPATHas Xcode and other build tools might get confused which one to use, but I haven’t tested this. -
Install
elf2uf2-rsandprobe-rs(requires a working Rust installation; see https://www.rust-lang.org/tools/install for guidance):cargo install elf2uf2-rs --locked cargo install probe-rs --features cli
We’ll use these tools to create the UF2 file for the Pico and/or flash the ELF file to the Pico. If you have other tools to do these jobs, feel free to use them. These are not required for the actual build process.
-
Install a current nightly Swift toolchain ("Trunk Development (main)"). You can probably use swiftly or swiftenv, or use one of the official Swift Docker images (I tested with
swiftlang/swift:nightly-jammy). -
Install the
objcopytool if you don't have it already. You probably already have this installed, typeobjcopy --versionto verify. If not, install it with your distribution's package manager.We use
objcopyin one of the build steps for the second-stage bootloader to convert a linked.elffile into the raw binary machine code representation (to compute the checksum for the bootloader). -
Install
elf2uf2-rsandprobe-rs(requires a working Rust installation; see https://www.rust-lang.org/tools/install for guidance):cargo install elf2uf2-rs --locked cargo install probe-rs --features cli
We’ll use these tools to create the UF2 file for the Pico and/or flash the ELF file to the Pico. If you have other tools to do these jobs, feel free to use them. These are not required for the actual build process.
The normal SwiftPM commands swift build and swift run won’t work. You need to call swift package link with the correct arguments (see below) to build. This will use our custom SwiftPM command plugin for building and linking.
# Build and link final executable App.elf
swift package --triple armv6m-none-none-eabi \
--toolchain /Library/Developer/Toolchains/swift-latest.xctoolchain/ \
link \
--objcopy /opt/homebrew/opt/llvm/bin/llvm-objcopyAdjust the path to the Swift toolchain you downloaded accordingly. It should be either /Library/Developer/… or ~/Library/Developer/…. Note that we’re passing in the path to llvm-objcopy from the Homebrew LLVM install.
Note: as of 2024-02-22, the macOS build produces dozens of warnings of the type "Class xyz is implemented in both [path to toolchain] and [path to Xcode]. One of the two will be used. Which one is undefined." I don't understand why these messages appear, but they don’t seem to be a problem.
# Build and link final executable App.elf
swift package --triple armv6m-none-none-eabi linkThe build will produce an RP2040 executable in .build/plugins/Link/outputs/App.elf.
If you have a Raspberry Pi Debug Probe or a second Pico that you can use as a debug probe, use probe-rs to flash it to the Pico:
probe-rs run --chip RP2040 .build/plugins/Link/outputs/App.elfNote: If probe-rs shows an error of the form "ERROR probe_rs::cmd::run: Failed to attach to RTT continuing..." after it says "Finished", you can ignore it. Use Ctrl+C to exit probe-rs.
Alternatively, use elf2uf2-rs to create a UF2 file which you can then copy to the Pico in BOOTSEL mode:
# Creates .build/plugins/Link/outputs/App.elf
elf2uf2-rs .build/plugins/Link/outputs/App.elfYou should see the Pico’s onboard LED blinking.
Note: If the LED is blinking very fast (to the point where it appears to be permanently on), try disconnecting and reconnecting the Pico from power. After reconnecting, you should observe the blinking to be approximately 20× slower. This is caused by a bug in our after-boot init code. See issue 7 for details.