<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>rpi-hal | Blog</title><description>Bare-metal Rust for the Raspberry Pi: an embedded-hal HAL, Embassy support, application bring-up, and a UART loader.</description><link>https://joeferner.github.io/</link><language>en</language><fh:complete/><atom:link rel="self" href="https://joeferner.github.io/rpi-hal/blog/rss.xml"/><item><title>Introducing rpi-hal</title><link>https://joeferner.github.io/rpi-hal/blog/introducing-rpi-hal/</link><guid isPermaLink="true">https://joeferner.github.io/rpi-hal/blog/introducing-rpi-hal/</guid><description>Bare-metal Rust on the Raspberry Pi, with no operating system underneath.</description><pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;rpi-hal&lt;/code&gt; is a hardware abstraction layer for running Rust directly on a
Raspberry Pi: no Linux, no operating system of any kind, just your
program, the hardware, and a crate that knows how to talk to it. It currently
covers the Pi 1 and Pi Zero, the Pi 2, the Pi 3 and, in a preliminary
way, the Pi 4, in both 32-bit and 64-bit builds where the chip has both.
The Pi Zero 2 W and the Pi 5 are on the list for later; the
&lt;a href=&quot;https://joeferner.github.io/rpi-hal/supported-devices/&quot;&gt;supported devices&lt;/a&gt; page tracks where each
board stands.&lt;/p&gt;
&lt;p&gt;If you have written firmware for an STM32 or an RP2040 in Rust, it is
meant to feel familiar. If you haven’t, it is meant to be a reasonable
place to start.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-is&quot;&gt;What it is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Rust embedded ecosystem has settled on a shape for a HAL. A
peripheral access crate (PAC) describes the registers; the HAL wraps
them in types that own the hardware they drive; and the HAL implements
the &lt;a href=&quot;https://docs.rs/embedded-hal&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;embedded-hal&lt;/code&gt;&lt;/a&gt; traits, so that a
driver written against &lt;code dir=&quot;auto&quot;&gt;OutputPin&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;I2c&lt;/code&gt; works on any chip that
provides them. &lt;code dir=&quot;auto&quot;&gt;stm32xx-hal&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;rp2040-hal&lt;/code&gt; look this way, and so does
&lt;code dir=&quot;auto&quot;&gt;rpi-hal&lt;/code&gt;, on purpose: the same naming, the same typestate pattern (a
pin’s mode is part of its type, so writing to an input doesn’t compile),
and the &lt;code dir=&quot;auto&quot;&gt;embedded-hal&lt;/code&gt; traits implemented &lt;em&gt;beside&lt;/em&gt; the crate’s own
methods rather than instead of them.&lt;/p&gt;
&lt;p&gt;Here is a complete program that blinks an LED on GPIO4:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#![no_std]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#![no_main]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; embedded_hal&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;digital&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;OutputPin;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; rpi_hal&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;gpio&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;{Input, Pin};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; rpi_hal&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;pac;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; rpi_hal&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;timer&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Timer;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#[panic_handler]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;panic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_info&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;core&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;panic&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;PanicInfo) &lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rpi_hal&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;halt&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#[no_mangle]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pub&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extern&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;C&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kmain&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripherals&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unsafe&lt;/span&gt;&lt;span&gt; { pac&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Peripherals&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;steal&lt;/span&gt;&lt;span&gt;() };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;led&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Pin&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, Input&gt;&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;peripherals&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GPIO&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;into_output&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Timer&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;peripherals&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SYSTMR&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;led&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_high&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;timer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delay_ms&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;led&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_low&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;timer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delay_ms&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Apart from &lt;code dir=&quot;auto&quot;&gt;kmain&lt;/code&gt; where you might expect &lt;code dir=&quot;auto&quot;&gt;main&lt;/code&gt;, nothing in it is
particular to a Pi. That is the goal: a basic application written
against &lt;code dir=&quot;auto&quot;&gt;embedded-hal&lt;/code&gt; for another chip should port with only the
setup lines changed.&lt;/p&gt;
&lt;p&gt;What sits underneath is more than an MCU HAL usually carries, because a
Pi is not a microcontroller. &lt;code dir=&quot;auto&quot;&gt;rpi-hal&lt;/code&gt; brings up the MMU and the caches,
installs the exception vectors and routes interrupts, and wakes the
other three cores if you ask it to. Then come the peripherals: GPIO, the
UARTs, SPI, I2C, PWM, PCM/I2S, DMA, the SD card, USB host, the
VideoCore mailbox and more. The
&lt;a href=&quot;https://github.com/joeferner/rpi-hal/blob/main/hal/README.md&quot;&gt;README&lt;/a&gt;
keeps the status of every one of them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-bare-metal-on-a-pi-at-all&quot;&gt;Why bare metal on a Pi at all&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A Pi is usually a small Linux computer, and for most things that is
exactly what you want. But it is also a very capable piece of hardware:
four cores, hundreds of megabytes to a few
gigabytes of RAM, HDMI, USB host, Ethernet, and on some boards Wi-Fi and
Bluetooth. Running without an operating system changes what that
hardware is like to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The machine is entirely yours.&lt;/strong&gt; No scheduler preempts you, no
kernel decides when your interrupt is serviced, no background service
takes a core. Timing is whatever your code makes it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;So is the memory.&lt;/strong&gt; No kernel reserves a slice of it, and there is
no process address space to fit inside: your program can use all of
the RAM the board has, apart from the share you give the GPU in
&lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The SD card barely matters.&lt;/strong&gt; It doesn’t carry a Linux distribution,
only the Pi’s boot firmware and your image, a few megabytes in all, so
the smallest card you can find is plenty. And nothing writes to it
unless your program decides to: no logs, no swap, no package cache
wearing it out in the background. A card that is written only when you
say so lasts far longer than one under an operating system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It starts when the power does.&lt;/strong&gt; There’s no distribution to boot, no
filesystem to check, nothing to shut down cleanly. Pull the plug
whenever you like: unless your program is in the middle of writing
to the card, there is nothing on it to corrupt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You see everything.&lt;/strong&gt; There is no driver between you and a register,
which is as much a way to learn how the hardware actually works as it
is a way to control it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The price is that there is no driver between you and a register. Linux
gives you a working USB stack, a network stack and a filesystem for
free; on bare metal someone has to write them. Doing that work once, in
crates other people can build on, is what this project is for.&lt;/p&gt;
&lt;p&gt;One thing bare metal does &lt;em&gt;not&lt;/em&gt; get rid of is the Pi’s own boot
firmware. On every Pi the GPU starts first, reads &lt;code dir=&quot;auto&quot;&gt;bootcode.bin&lt;/code&gt;,
&lt;code dir=&quot;auto&quot;&gt;start.elf&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt; from the SD card, and only then loads your
image and releases the ARM cores. &lt;code dir=&quot;auto&quot;&gt;rpi-hal&lt;/code&gt; takes over from that point.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-family&quot;&gt;The family&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;rpi-hal&lt;/code&gt; is the foundation. Three more crates live beside it in the
same repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://joeferner.github.io/rpi-hal/crates/embassy/&quot;&gt;rpi-hal-embassy&lt;/a&gt;&lt;/strong&gt; brings
&lt;a href=&quot;https://embassy.dev&quot;&gt;Embassy&lt;/a&gt; to the Pi: a time driver on the System
Timer, an executor that sleeps with &lt;code dir=&quot;auto&quot;&gt;wfe&lt;/code&gt; and wakes on &lt;code dir=&quot;auto&quot;&gt;sev&lt;/code&gt;, and
&lt;code dir=&quot;auto&quot;&gt;embassy-net&lt;/code&gt; drivers for the onboard Ethernet. Async Rust, with tasks
instead of a main loop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://joeferner.github.io/rpi-hal/crates/kickstart/&quot;&gt;rpi-kickstart&lt;/a&gt;&lt;/strong&gt; is the bring-up every
application ends up writing anyway: a console with &lt;code dir=&quot;auto&quot;&gt;logln!&lt;/code&gt;, a heap, a
config file on the SD card, storage, networking with DHCP and SNTP, a
small web server, Prometheus metrics, and over-the-air updates. Each is
a cargo feature, so you take only what you use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://joeferner.github.io/rpi-hal/crates/loader/&quot;&gt;rpi-loader&lt;/a&gt;&lt;/strong&gt; makes development
bearable. Without it, every change means pulling the SD card, copying a
file and putting it back. With it, you copy one loader image to the
card once, and from then on &lt;code dir=&quot;auto&quot;&gt;rpi-loader&lt;/code&gt; uploads your build over a
USB-serial cable and starts it, in seconds. It can also read and write
the card in place, so the card can stay in the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All four are on crates.io, are dual-licensed MIT or Apache-2.0, and
follow their own version numbers.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-to-start&quot;&gt;How to start&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The quickest way is the &lt;a href=&quot;https://joeferner.github.io/rpi-hal/start/&quot;&gt;project selector&lt;/a&gt;: pick your
board, and it hands you a zip with a project ready to build. That
project has the right target and linker settings, a &lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt; for
the board, the matching loader image, and a &lt;code dir=&quot;auto&quot;&gt;main.rs&lt;/code&gt; that logs a
heartbeat over the UART console. If you prefer the command line, the
same template is available through &lt;code dir=&quot;auto&quot;&gt;cargo generate&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cargo-generate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/joeferner/rpi-hal&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Either way, &lt;code dir=&quot;auto&quot;&gt;make sd-card&lt;/code&gt; fetches the firmware files and the loader
image for the card, and &lt;code dir=&quot;auto&quot;&gt;make run&lt;/code&gt; builds, uploads and boots your
program. The &lt;a href=&quot;https://joeferner.github.io/rpi-hal/getting-started/&quot;&gt;getting started&lt;/a&gt; guide walks
through the wiring and the first boot.&lt;/p&gt;
&lt;p&gt;The code is at
&lt;a href=&quot;https://github.com/joeferner/rpi-hal&quot;&gt;github.com/joeferner/rpi-hal&lt;/a&gt;.
Issues and pull requests are welcome, and so are reports of which boards
you’ve run it on.&lt;/p&gt;
</content:encoded><category>rpi-hal</category></item><item><title>rpi-loader: stop moving the SD card</title><link>https://joeferner.github.io/rpi-hal/blog/rpi-loader/</link><guid isPermaLink="true">https://joeferner.github.io/rpi-hal/blog/rpi-loader/</guid><description>Flash one small image to the card once, then upload every build over a serial cable.</description><pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every bare-metal Raspberry Pi project starts with the same loop. Build.
Power the board off. Pull the SD card, put it in the computer, copy
&lt;code dir=&quot;auto&quot;&gt;kernel7.img&lt;/code&gt; over, eject it, put it back, power on, and watch the
serial console to see whether it worked. Then do it all again for a
one-line change.&lt;/p&gt;
&lt;p&gt;It is slow, it wears out the card and the slot, and it is miserable once
the board lives in an enclosure. A microcontroller has SWD or a USB
bootloader for exactly this; a Pi has nothing of the kind.
&lt;a href=&quot;https://joeferner.github.io/rpi-hal/crates/loader/&quot;&gt;rpi-loader&lt;/a&gt; is that missing piece.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-idea&quot;&gt;The idea&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Put one small image on the card, once. From then on the Pi boots into a
loader that sits on the UART waiting to be told what to do, and the card
never has to move again.&lt;/p&gt;
&lt;p&gt;It is a resident command agent rather than a one-shot bootloader: it
answers one command, then waits for the next, for as long as the board
is powered. Each run of the host tool is its own process and reconnects
with a fresh handshake, so the order you start things in doesn’t matter.
Power the Pi first or start the tool first; either way they find each
other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-looks-like&quot;&gt;What it looks like&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Find the cable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rpi-loader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# /dev/ttyUSB0  FTDI TTL232R-3V3  0403:6001  ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Upload a build to RAM, jump to it, and stay attached as a terminal, so
the program’s output appears where you typed the command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rpi-loader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--load-addr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0x8000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;target/kernel7.img&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is the everyday loop, and it is what a project from the
&lt;a href=&quot;https://joeferner.github.io/rpi-hal/start/&quot;&gt;project selector&lt;/a&gt; runs as &lt;code dir=&quot;auto&quot;&gt;make run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To leave the terminal, press &lt;strong&gt;Ctrl-]&lt;/strong&gt;, not Ctrl-C. Every other key,
Ctrl-C included, goes to the board, which is what lets you interrupt
something running &lt;em&gt;there&lt;/em&gt; rather than the tool on your machine.&lt;/p&gt;
&lt;p&gt;The loader can also reach the SD card in place, so editing &lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt;
doesn’t mean touching the card either:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rpi-loader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sd-read&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/config.txt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./config.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# edit it, then put it back&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rpi-loader&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sd-write&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./config.txt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/config.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There are a few more commands: listing, deleting and making directories
on the card, a plain terminal for watching a kernel that is already
running, and programming the ID EEPROM a HAT or a custom board carries.
The &lt;a href=&quot;https://github.com/joeferner/rpi-hal/blob/main/loader/README.md&quot;&gt;README&lt;/a&gt;
has the full list.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;getting-it&quot;&gt;Getting it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The host tool is on crates.io:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rpi-loader&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The loader images are on the
&lt;a href=&quot;https://github.com/joeferner/rpi-hal/releases/latest&quot;&gt;release page&lt;/a&gt;,
or already inside a project zip from the
&lt;a href=&quot;https://joeferner.github.io/rpi-hal/start/&quot;&gt;project selector&lt;/a&gt;, with the boot firmware and a
&lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt; beside them. Copy those to the card, connect a 3.3 V
USB-serial cable to GPIO14 and GPIO15 (pins 8 and 10, ground on pin 6),
and power on. The &lt;a href=&quot;https://joeferner.github.io/rpi-hal/getting-started/&quot;&gt;getting started&lt;/a&gt; guide
walks through it.&lt;/p&gt;
&lt;p&gt;After that the card stays in the board.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-goes-on-the-card&quot;&gt;What goes on the card&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The card needs one FAT32 partition and only a handful of files:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bootcode.bin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The first stage of the Pi’s boot firmware, which brings up SDRAM and loads &lt;code dir=&quot;auto&quot;&gt;start.elf&lt;/code&gt;. Not on a Pi 4, whose boot EEPROM does this job.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;start.elf&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;start4.elf&lt;/code&gt; on a Pi 4)&lt;/td&gt;
&lt;td&gt;The GPU firmware proper. It reads &lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt;, sets up the hardware, loads the kernel image and releases the ARM cores.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;fixup.dat&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;fixup4.dat&lt;/code&gt; on a Pi 4)&lt;/td&gt;
&lt;td&gt;Tells &lt;code dir=&quot;auto&quot;&gt;start.elf&lt;/code&gt; how memory is split between the GPU and the ARM. Released as a pair with &lt;code dir=&quot;auto&quot;&gt;start.elf&lt;/code&gt;; mismatched, the board won’t boot.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;LICENCE.broadcom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The licence the firmware files are distributed under.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The firmware’s settings for your board: 32- or 64-bit, which kernel file to load, and anything else the board needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kernel7.img&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kernel8.img&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;kernel.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The loader, renamed to the one filename your board’s firmware loads. Which name depends on the CPU, and, on a Pi 3 or Pi 4, on &lt;code dir=&quot;auto&quot;&gt;arm_64bit&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;config.txt&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first four come from Raspberry Pi’s
&lt;a href=&quot;https://github.com/raspberrypi/firmware&quot;&gt;firmware repository&lt;/a&gt;, and a
project zip has all six with the right names already.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;getting-out-of-the-way&quot;&gt;Getting out of the way&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Pi’s firmware loads every kernel at the same address: &lt;code dir=&quot;auto&quot;&gt;0x8000&lt;/code&gt; for a
32-bit image, &lt;code dir=&quot;auto&quot;&gt;0x80000&lt;/code&gt; for a 64-bit one. That includes the loader, which
is a problem, because the kernel it is about to receive expects that
address too, and nothing can overwrite the code it is running from.&lt;/p&gt;
&lt;p&gt;So the loader’s first act, before anything else, is to copy itself to
&lt;code dir=&quot;auto&quot;&gt;0x00200000&lt;/code&gt; and jump into the copy. It is linked to run from there, and
its stack sits above it, which leaves the whole region from the load
address up to the copy free for the incoming image.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-wire&quot;&gt;The wire&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The handshake runs at 115200 baud, which every USB-serial cable manages.
For bulk transfers the two ends then agree on something faster (1.5
Mbaud by default) and drop back to 115200 afterwards, so the next
command, or the program just booted, finds the link where it expects.&lt;/p&gt;
&lt;p&gt;Data moves in chunks, each with its own CRC and each acknowledged before
the next is sent. That is flow control, not caution: the Pi’s UART holds
only 16 bytes in its receive FIFO, and a host that streamed freely would
overrun it. A bad chunk is sent again, and the whole image is checked
once more at the end.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;refusing-to-do-damage&quot;&gt;Refusing to do damage&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The loader checks every upload’s address against its own memory map, so
a wrong &lt;code dir=&quot;auto&quot;&gt;--load-addr&lt;/code&gt; fails with an error instead of overwriting the
loader in the middle of the transfer.&lt;/p&gt;
&lt;p&gt;On the card, &lt;code dir=&quot;auto&quot;&gt;sd-write&lt;/code&gt; receives the whole file into RAM before writing
any of it, so an interrupted upload leaves the old file as it was. And
the loader keeps no state between commands: it mounts the card afresh
for each one, so resetting the board between two commands loses
nothing.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;one-image-per-execution-state&quot;&gt;One image per execution state&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A loader hands off in the execution state it is running in, so a 32-bit
loader boots 32-bit kernels and a 64-bit loader boots 64-bit ones. With
three chips, two of which can run either way, that makes five images:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Boards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bcm2837&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;kernel7.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pi 2, Pi 3, 32-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bcm2837&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;kernel8.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pi 2 v1.2, Pi 3, 64-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bcm2711&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;kernel7.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pi 4, 32-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bcm2711&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;kernel8.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pi 4, 64-bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bcm2835&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;kernel.img&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pi 1, Pi Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The project selector picks the right one for the board you choose, and
names it the way your board’s firmware expects.&lt;/p&gt;
</content:encoded><category>rpi-loader</category></item></channel></rss>