Raspberry Pi 5 Bench Reference

Every pin, every bus, and the commands that drive them — pin multiplexing read out of pinctrl's own RP1 function table, everything else out of the official documentation source. Written for a 2 GB board running Raspberry Pi OS, from a blank SD card to a headless machine you never plug a monitor into.

SoC
BCM2712 · 4×A76 @ 2.4 GHz
2 GB die
BCM2712D0 stepping
I/O
RP1 · PCIe 2.0 ×4
OS
Raspberry Pi OS Trixie (Debian 13)
Boot
/boot/firmware · FAT32
01

The 40-pin header

Physically identical to every Pi since the B+, electrically driven by RP1 instead of the SoC. The alternate functions below come from the RP1 function table inside pinctrl, so they say what the silicon can actually do — several of them moved between Pi 4 and Pi 5.

40-PIN GPIO HEADER · J8Board landscape, USB ports to the right, header along the top edge. Pin 1 is the square pad at the left end, nearest USB-C.3.3 V LOGIC ONLY5 V on a GPIO kills the RP1. 16 mA a pin, 50 mA the lot. 13V3power rail50 mA total across both 3V3 pins25Vpower in/outstraight off the PSU, before the 3V3 reg 3GPIO2SDA1 · I2C-1 dataRP1 a3 · fixed 1k8 pull-up to 3V345Vpower in/outcan back-feed the board — see Traps 5GPIO3SCL1 · I2C-1 clockRP1 a3 · fixed 1k8 pull-up to 3V36GNDground 7GPIO4GPCLK0RP1 a0 · classic 1-Wire pin8GPIO14TXD0 · UART0 TXRP1 a4 (not a0 as on Pi 4) 9GNDground10GPIO15RXD0 · UART0 RXRP1 a4 (not a0 as on Pi 4) 11GPIO17general purposeRP1 a4 = RTS012GPIO18 ~PCM_CLK · PWM0_CH2RP1 a2 = I2S0_SCLK, a3 = PWM0_CHAN2 13GPIO27general purposeRP1 a2 = I2S0_SDO314GNDground 15GPIO22general purposeRP1 a0 = SD0_CLK, a3 = SDA316GPIO23general purposeRP1 a0 = SD0_CMD, a3 = SCL3 173V3power railshares the 50 mA budget with pin 118GPIO24general purposeRP1 a2 = I2S0_SDI2 19GPIO10SPI0 MOSIRP1 a020GNDground 21GPIO9SPI0 MISORP1 a022GPIO25general purposeRP1 a3 = MIC_CLK 23GPIO11SPI0 SCLKRP1 a024GPIO8SPI0 CE0RP1 a0 · /dev/spidev0.0 25GNDground26GPIO7SPI0 CE1RP1 a0 · /dev/spidev0.1 27GPIO0ID_SD · HAT EEPROMI2C-0 data — leave alone28GPIO1ID_SC · HAT EEPROMI2C-0 clock — leave alone 29GPIO5GPCLK1RP1 a030GNDground 31GPIO6GPCLK2RP1 a032GPIO12 ~PWM0_CHAN0RP1 a0 · true hardware PWM 33GPIO13 ~PWM0_CHAN1RP1 a0 · true hardware PWM34GNDground 35GPIO19 ~PCM_FS · PWM0_CH3RP1 a2 = I2S0_WS, a3 = PWM0_CHAN336GPIO16general purposeRP1 a4 = CTS0 37GPIO26general purposeRP1 a3 = MIC_DAT038GPIO20PCM_DINRP1 a2 = I2S0_SDI0 39GNDground40GPIO21PCM_DOUTRP1 a2 = I2S0_SDO0 ~ = hardware PWM reachable on this pin
5 V rail 3V3 rail Ground General GPIO I2C SPI UART PCM / I2S Hardware PWM HAT ID EEPROM

Names in the pill are what the pin is wired and configured for; the grey text outside is the RP1 alternate function that gets you there. Eight ground pins: 6, 9, 14, 20, 25, 30, 34, 39. The header is not hot-pluggable — power the board down before you wire anything to it.

Every alternate function RP1 can mux onto a header GPIO ALT5 = plain GPIO on every pin

Read straight from the RP1 function table in raspberrypi/utils/pinctrl. Bold = the function the Pi is actually set up to use. ALT5 is the RIO block — that is what pinctrl <n> op and every GPIO library select. ALT6 is the second RIO port, ALT7 is PIO (programmable I/O, available on GPIO 0–27 only). Check any pin live with pinctrl funcs 17.

GPIOpinALT0ALT1ALT2ALT3ALT4ALT8
GPIO027SPI0_SIO3DPI_PCLKTXD1SDA0SPI2_CE0
GPIO128SPI0_SIO2DPI_DERXD1SCL0SPI2_SIO1
GPIO23SPI0_CE3DPI_VSYNCCTS1SDA1IR_RX0SPI2_SIO0
GPIO35SPI0_CE2DPI_HSYNCRTS1SCL1IR_TX0SPI2_SCLK
GPIO47GPCLK0DPI_D0TXD2SDA2RI0SPI3_CE0
GPIO529GPCLK1DPI_D1RXD2SCL2DTR0SPI3_SIO1
GPIO631GPCLK2DPI_D2CTS2SDA3DCD0SPI3_SIO0
GPIO726SPI0_CE1DPI_D3RTS2SCL3DSR0SPI3_SCLK
GPIO824SPI0_CE0DPI_D4TXD3SDA0SPI4_CE0
GPIO921SPI0_MISODPI_D5RXD3SCL0SPI4_SIO0
GPIO1019SPI0_MOSIDPI_D6CTS3SDA1SPI4_SIO1
GPIO1123SPI0_SCLKDPI_D7RTS3SCL1SPI4_SCLK
GPIO1232PWM0_CHAN0DPI_D8TXD4SDA2AAUD_LEFTSPI5_CE0
GPIO1333PWM0_CHAN1DPI_D9RXD4SCL2AAUD_RIGHTSPI5_SIO1
GPIO148PWM0_CHAN2DPI_D10CTS4SDA3TXD0SPI5_SIO0
GPIO1510PWM0_CHAN3DPI_D11RTS4SCL3RXD0SPI5_SCLK
GPIO1636SPI1_CE2DPI_D12DSI0_TE_EXTCTS0
GPIO1711SPI1_CE1DPI_D13DSI1_TE_EXTRTS0
GPIO1812SPI1_CE0DPI_D14I2S0_SCLKPWM0_CHAN2I2S1_SCLKGPCLK1
GPIO1935SPI1_MISODPI_D15I2S0_WSPWM0_CHAN3I2S1_WS
GPIO2038SPI1_MOSIDPI_D16I2S0_SDI0GPCLK0I2S1_SDI0
GPIO2140SPI1_SCLKDPI_D17I2S0_SDO0GPCLK1I2S1_SDO0
GPIO2215SD0_CLKDPI_D18I2S0_SDI1SDA3I2S1_SDI1
GPIO2316SD0_CMDDPI_D19I2S0_SDO1SCL3I2S1_SDO1
GPIO2418SD0_DAT0DPI_D20I2S0_SDI2I2S1_SDI2SPI2_CE1
GPIO2522SD0_DAT1DPI_D21I2S0_SDO2MIC_CLKI2S1_SDO2SPI3_CE1
GPIO2637SD0_DAT2DPI_D22I2S0_SDI3MIC_DAT0I2S1_SDI3SPI5_CE1
GPIO2713SD0_DAT3DPI_D23I2S0_SDO3MIC_DAT1I2S1_SDO3SPI1_CE1
ALT1 is the parallel display interface (DPI) — all 24 data lines plus clock and syncs, which is why a DPI panel eats the entire header. ALT2 carries the second set of UARTs and I2S0; ALT3 the extra I2C buses, the second pair of PWM channels and the PDM microphone inputs; ALT4 the UART0 pins and modem control lines. GPIO 28–53 exist in RP1 but are not brought out to the header.

Pin electrical limits RP1 pads

Logic level3.3 V. Not 5 V tolerant — a 5 V signal on a GPIO destroys the RP1 pad
VIH / VILreads HIGH above ~2.0 V, LOW below ~0.8 V
Per pin16 mA absolute maximum; 8 mA is the sane working figure
All pins together50 mA total — that is the whole header, not each pin
3V3 pins (1, 17)~50 mA combined from the on-board regulator
5V pins (2, 4)straight off the PSU upstream of everything; no current limit but no protection either
Pull-ups~50 kΩ internal, software-selectable; GPIO2/3 also have fixed 1.8 kΩ pull-ups on the board
Power-on stateevery pin is an input with its default pull applied until something claims it
An LED and a resistor is fine. A relay, a motor, a servo or a metre of WS2812 is not. Switch those with a transistor or a driver board and give them their own supply, sharing only ground.

Pins that are already spoken for

GPIO0 / GPIO1pins 27/28 — I2C-0 to the HAT ID EEPROM. Probed at boot. Don't use them
GPIO2 / GPIO3fixed 1.8 kΩ pull-ups, so they can't be used as clean inputs for anything else
GPIO14 / GPIO15free on Pi 5 by default — the console lives on the debug header, not here
GPIO18–21the I2S pins; any DAC/mic HAT takes all four
GPIO7–11SPI0 once dtparam=spi=on is set
GPIO4the conventional 1-Wire pin; dtoverlay=w1-gpio defaults here
What is actually claimed right now: pinctrl -P get draws the whole header as ASCII art with each pin's current function and level — the fastest way to see what a HAT has taken.

Reading the header from the board itself

pinoutASCII board + pin diagram, from GPIO Zero. No sudo needed
pinctrl -P getthe live header, laid out as it sits on the board
pinctrl funcs 17every alt function GPIO17 can take
pinctrl -p get 7same, addressed by header pin number instead of GPIO
raspi-gpiothe old name for pinctrl; still symlinked, don't write new scripts against it
gpioinfokernel's view: which driver holds which line, and its label
# the whole header, as it physically sits
pinctrl -P get

# is GPIO17 free, and what is it doing?
pinctrl get 17
#  17: ip    pd | lo // GPIO17 = input
#      ^     ^    ^      ^
#      mode  pull level  current function
02

The board

Two chips do the work. BCM2712 is the processor; RP1 is a Raspberry Pi–designed I/O controller on the far end of a PCIe 2.0 ×4 link that owns USB, Ethernet, the 40-pin header, the camera and display ports, and every low-speed bus. Nothing on the header talks to the SoC directly any more.

Silicon what is where

BCM27124×Cortex-A76 @ 2.4 GHz, 512 kB L2 each, 2 MB shared L3, Armv8.2-A, 64-bit only in practice
BCM2712D0the stepping on your 2 GB board — ~33% smaller die with the unused blocks removed. Functionally identical, slightly lower idle power
VideoCore VIIGPU, 800 MHz. OpenGL ES 3.1, Vulkan 1.2
HEVC decoder4Kp60 hardware decode. No H.264 hardware encode — that block was dropped after Pi 4
RP1~12×12 mm BGA. 2 Cortex-M3s, 8-channel DMA, 12-bit ADC, 64 kB SRAM. Gigabit MAC, 2 USB 3.0 + 2 USB 2.0, 2 MIPI 4-lane transceivers, SPI/UART/I2C/PWM/I2S/GPIO
DA9091 "Gilmour"the PMIC — also an ADC you can read (vcgencmd pmic_read_adc) and the reason the board has a real power button
RTCon-board, battery-backed from J5. Works with no battery fitted, just loses time when unplugged
LPDDR4X-42672 GB on your board. Not upgradeable, not overclockable
The two-chip split matters when you debug. If the header, USB or Ethernet is dead but the board boots, suspect RP1 or the PCIe link to it — LED code 4 long, 3 short is literally “RP1 not found”.

Connectors and headers silkscreen designators

J8the 40-pin GPIO header
J2two bare pads next to the RTC connector — bridge with a normally-open momentary switch for an off-board power button
J5 (BAT)2-pin JST-SH, RTC backup battery. Near-left corner, right of the USB-C jack
FAN4-pin JST-SH PWM fan, between the header and the USB 2.0 ports. Firmware drives it; draws from the USB current budget
UART3-pin JST-SH debug console, 115200 8N1. This is /dev/ttyAMA10 and it is the primary UART on Pi 5
PCIe16-way FPC on the left edge — Gen 2.0 ×1, Gen 3.0 if you ask for it
CAM/DISP 0 & 1two 22-pin 0.5 mm FPC ports, each does camera or display. Needs the narrow Pi 5 cable, not a Pi 4 one
PowerUSB-C. PD-negotiated 5 V/5 A preferred; 5 V/3 A works with peripherals capped at 600 mA
PoE4-pin header, 802.3at PoE+ with the PoE+ HAT
Connecting a HAT can reset the PMIC. Always unplug the power before fitting one.

Identify the board you have

cat /proc/cpuinfothe Revision line is the board revision code
cat /proc/device-tree/model“Raspberry Pi 5 Model B Rev 1.0”
raspinfoeverything at once — revision, EEPROM, config.txt, dmesg, USB tree. Paste this into forum posts
vcgencmd otp_dumpthe OTP fuses; row 30 holds the revision code
free -ha 2 GB board reports ~1.9 Gi total
vcgencmd get_config total_memtotal RAM in MB as the firmware sees it
uname -maarch64 means you installed the 64-bit image — you want this
cat /etc/os-releaseDebian codename: trixie or bookworm

What 2 GB actually costs you

The 2 GB board is the same silicon at the same clocks. Everything below is about memory, not speed.

Desktop idle~600–700 MB used with the full desktop, leaving you a little over 1 GB
Lite idle~150–200 MB. This is the image to pick for a headless board
Chromiuma handful of heavy tabs will exhaust it. This is the one workload where 2 GB really bites
Compilingmake -j4 on a big C++ tree will OOM. Use -j2 and a swap file
Docker / k3sfine for a few small containers; not fine for a stack
LLMs, big Python MLno
Everything elsePi-hole, Home Assistant Core, MQTT, NAS, print server, camera, GPIO work, kiosk with one tab — comfortable

See §13 for the tuning that makes 2 GB behave.

03

Blank card to booting board

You need a card, a reader, and Raspberry Pi Imager on your laptop. Do the whole configuration in Imager before you write — a headless Pi that boots without Wi-Fi credentials or SSH enabled is a Pi you have to re-image.

The whole procedure Imager 2.x

  1. Get Raspberry Pi Imager from raspberrypi.com/software (or brew install --cask raspberry-pi-imager). You need 2.0 or later — 1.9.x cannot customise a Trixie image.
  2. Device: Raspberry Pi 5.
  3. OS: for a headless board pick Raspberry Pi OS (64-bit) Lite, under “Raspberry Pi OS (other)”. Take the 64-bit build; 32-bit gains you nothing on a Pi 5.
  4. Storage: your card. Read the device name twice.
  5. Open the Customisation tab and fill in every sub-tab: hostname, locale/city, user + password, Wi-Fi SSID + password + country, and turn SSH on (paste a public key if you have one).
  6. Write, let it verify, eject.
  7. Card into the Pi, power in, wait ~60 s. ssh you@hostname.local.
A 32 GB A2-rated card is the sweet spot. Below 16 GB you will fight for space; above 64 GB gains nothing unless you are storing media. Card speed class matters far more than capacity — a slow card makes a Pi 5 feel like a Pi 3.

What Imager writes and how to write it yourself

Imager's customisation is just files dropped on the FAT32 boot partition, which mounts as bootfs on your laptop and as /boot/firmware on the running Pi. You can edit them on any machine, including after a failed first boot.

user-datacloud-init. Users, passwords, SSH keys, hostname, timezone, interface enables. The modern mechanism, on Trixie images from Nov 2025 onward
network-configcloud-init. Wi-Fi SSID, password, regulatory domain, static IPs
meta-datamust exist for cloud-init to run at all; can be empty
firstrun.shthe legacy Bookworm mechanism, driven by cmdline.txt. Still honoured
custom.tomlthe intermediate Bookworm-era format. Superseded by cloud-init
sshan empty file. Its presence enables the SSH server on first boot
userconf.txtone line, username:hashed-password — creates the first user with no Imager involved
config.txtfirmware settings, read before Linux starts
cmdline.txtthe kernel command line. One line. No line breaks, ever
Which mechanism your image uses depends on its date, not its name. Look at the boot partition after writing: if user-data is there, it is cloud-init. If firstrun.sh is there, it is legacy. Edit whichever you find.

Hand-written cloud-init Trixie images

Drop these three files on bootfs. This is the whole headless setup without opening Imager's customisation tabs.

user-data
#cloud-config
hostname: pi5
manage_etc_hosts: true
timezone: America/New_York

users:
  - name: george
    groups: users,adm,dialout,audio,netdev,video,
            plugdev,gpio,spi,i2c,render,sudo
    shell: /bin/bash
    lock_passwd: false
    # openssl passwd -6   →  paste the $6$... hash here
    passwd: $6$rounds=4096$abc...
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3Nza... you@laptop
    sudo: ALL=(ALL) NOPASSWD:ALL

rpi:
  i2c: true
  spi: true
  serial: true
  onewire: false

enable_ssh: true
network-config
network:
  version: 2
  wifis:
    renderer: NetworkManager
    wlan0:
      dhcp4: true
      optional: true
      regulatory-domain: "US"
      access-points:
        "My Network":
          password: "correcthorsebattery"
meta-data
# empty is fine, but the file must exist

The no-Imager fallback works on any image

If cloud-init is not present, these two files still bring up SSH and a user. Wi-Fi you will have to do over Ethernet or a keyboard.

# on bootfs, from your laptop
touch /Volumes/bootfs/ssh

# make a password hash
openssl passwd -6
# Password: ······
# $6$Kx7...

echo 'pi:$6$Kx7...' > /Volumes/bootfs/userconf.txt
Username rulesstarts with a letter; lowercase, digits, _ and - only; 31 chars max
No default usersince 2022 there is no pi/raspberry account. If you set nothing, the board stops on first boot and asks
macOS path/Volumes/bootfs
Linux path/media/$USER/bootfs
Windows pathwhatever drive letter the small FAT32 partition took
Your laptop can only see the boot partition. The root filesystem is ext4 and invisible to macOS and Windows — anything that has to live in /etc has to go through cloud-init or a first boot with a keyboard.

Writing a card from the command line

# macOS — find the disk, then unmount but don't eject
diskutil list
diskutil unmountDisk /dev/disk4

# rdisk4, not disk4: raw device, ~10x faster
sudo dd if=2025-11-24-raspios-trixie-arm64-lite.img \
        of=/dev/rdisk4 bs=4m status=progress

# Linux
lsblk
sudo dd if=image.img of=/dev/sdX bs=4M conv=fsync status=progress
sync
of= is not undoable. Check the device name against the size reported by diskutil list / lsblk before you press return. Imager's verify step is worth the extra minute.
04

Headless: Wi-Fi, SSH, and finding the thing

From Bookworm onwards Raspberry Pi OS manages networking with NetworkManager. wpa_supplicant.conf on the boot partition does nothing any more — every guide older than 2023 that tells you to drop one there is wrong.

Wi-Fi from the command line nmcli

# the radio is disabled until a country is set — do this first
sudo raspi-config nonint do_wifi_country US
nmcli radio wifi on

# what's in range
nmcli dev wifi list

# join (prompts for the password, keeps it out of your shell history)
sudo nmcli --ask dev wifi connect "My Network"

# hidden SSID
sudo nmcli --ask dev wifi connect "My Network" hidden yes

# open network
sudo nmcli dev wifi connect "CoffeeShop"
nmcli con showevery saved connection; --active for the live ones
nmcli dev statusinterface list and state
nmcli con up/down namebring a saved connection in or out
nmcli con delete nameforget a network
nmcli con mod name connection.autoconnect-priority 10prefer this network over the others; higher wins, negative is last resort
nmcli radio wifi offkill the radio (saves ~50 mA)
nmtuithe whole thing as a menu, if you're on a console
/etc/NetworkManager/system-connections/the keyfiles themselves, root-only, one per network
WLAN country is not optional. On every Pi since the 3B+ the radio stays off until a regulatory domain is set. If nmcli dev wifi list comes back empty on a brand-new board, this is why.

Static IP, the NetworkManager way

A DHCP reservation on your router is the better answer. If you must pin it on the device:

# see the connection name first
nmcli con show

sudo nmcli con mod "My Network" \
  ipv4.method manual \
  ipv4.addresses 192.168.1.50/24 \
  ipv4.gateway 192.168.1.1 \
  ipv4.dns "192.168.1.1 1.1.1.1"

sudo nmcli con up "My Network"
Never edit /etc/dhcpcd.conf. dhcpcd is gone. Editing it silently does nothing, and it is the single most common piece of stale Pi advice on the internet.
Ethernet, wired
# the default wired profile is usually called this
sudo nmcli con mod "Wired connection 1" ipv4.method auto
ip -br addr        # quick look at every interface

Finding the board on the network

ssh you@host.localmDNS. Works out of the box from macOS and Linux; Windows 10+ mostly
ping host.localthe first thing to try when SSH won't connect
arp -afrom your laptop; look for a d8:3a:dd or 2c:cf:67 MAC prefix
nmap -sn 192.168.1.0/24sweep the subnet
hostname -Ion the Pi: its addresses, space-separated
ip -br addron the Pi: one short line per interface
avahi-browse -aton the Pi: everything advertising itself over mDNS
Card in, no lights, nothing on the network? Watch the LED next to the power button. Steady green = running. Red only = the firmware never got going: bad card, bad image, or a power supply that can't hold 5 V. Codes in §7.

SSH: keys, hardening, and the console

# from your laptop, once
ssh-keygen -t ed25519 -C "pi5"
ssh-copy-id pi@pi5.local

# then, on the Pi, turn passwords off
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
  /etc/ssh/sshd_config
sudo systemctl restart ssh
sudo raspi-config nonint do_ssh 0enable the SSH server (0 = on, 1 = off — inverted, yes)
systemctl status sshis it actually listening
ssh-keygen -R hostafter re-imaging, when your laptop screams about a changed host key
rpi-connect onRaspberry Pi Connect — shell and screen sharing through the browser, no port forwarding
rpi-connect signinprints a URL to link the device to your account
screen /dev/ttyUSB0 115200the 3-pin debug UART, when the network is the thing that's broken
05

The first ten minutes

Update, set the machine up the way you want it, and learn the one interactive tool that hides most of the config file editing.

Do these, in this order

# 1 — packages and firmware
sudo apt update && sudo apt full-upgrade -y

# 2 — the boot EEPROM
sudo rpi-eeprom-update -a

# 3 — anything that needs a reboot, gets one
sudo reboot

# 4 — check it's healthy
vcgencmd measure_temp
vcgencmd get_throttled     # want throttled=0x0
df -h /                    # the fs auto-expands on first boot
free -h
rpi-update is not the update command. It installs unreleased kernels and firmware and can leave you unbootable. apt full-upgrade is how firmware ships. Only run rpi-update if a Raspberry Pi engineer tells you to.

raspi-config, mapped sudo raspi-config

1 System OptionsS1 Wireless LAN · S2 Audio · S3 Password · S4 Hostname · S5 Boot/Auto-login · S6 Network at boot · S7 Splash · S8 Power LED
2 Display Optionsresolution, underscan, screen blanking, composite
3 Interface OptionsI1 SSH · I2 RPi Connect · I3 VNC · I4 SPI · I5 I2C · I6 Serial Port · I7 1-Wire · I8 Remote GPIO
4 Performance OptionsP2 Overlay FS · P3 Fan · P4 USB current limit
5 LocalisationL1 Locale · L2 Timezone · L3 Keyboard · L4 WLAN country
6 Advanced OptionsA1 Expand FS · A2 Network interface names · A3 Proxy · A4 Boot order · A5 Bootloader version · A8 PCIe speed · A11 Shutdown behaviour · A13 WLAN power save · A14 Link-local fallback
8 Updateupdates raspi-config itself
Scripting it — the nonint interface
# 0 = enable, 1 = disable. Backwards. Every time.
sudo raspi-config nonint do_ssh 0
sudo raspi-config nonint do_i2c 0
sudo raspi-config nonint do_spi 0
sudo raspi-config nonint do_onewire 0
sudo raspi-config nonint do_serial_hw 0      # UART pins on
sudo raspi-config nonint do_serial_cons 1    # login shell off
sudo raspi-config nonint do_hostname pi5
sudo raspi-config nonint do_wifi_country US
sudo raspi-config nonint do_change_timezone America/New_York
sudo raspi-config nonint do_expand_rootfs
sudo raspi-config nonint do_boot_behaviour B1   # console, no autologin

# read the current state of anything
sudo raspi-config nonint get_can_expand
sudo raspi-config nonint get_i2c            # 0 = on

Users, groups and permissions

sudo adduser nameinteractive; creates the home dir and prompts for a password
sudo usermod -aG group useradd to a group. Keep the -a or you replace every group they're in
gpiothe group that gets /dev/gpiomem* without sudo
i2c, spisame idea for /dev/i2c-* and /dev/spidev*
dialoutneeded for /dev/ttyAMA* and USB serial adapters
video, rendercamera and GPU access
groupswhat you're in right now. Log out and back in after a change
passwdchange your own password
sudo visudoedit sudoers safely; syntax-checks before it saves
A user created by Imager or cloud-init is already in all of these. A user you add later is not — sudo usermod -aG gpio,i2c,spi,dialout,video,render name.

The software you'll want

sudo apt install -y \
  i2c-tools python3-smbus2 \        # i2cdetect, i2cget, i2cset
  python3-gpiozero python3-lgpio \  # GPIO from Python
  gpiod \                           # gpioget/gpioset/gpiomon
  git vim tmux htop \
  rsync curl jq \
  raspi-gpio                        # pulls in pinctrl
sudo apt updaterefresh the package lists
sudo apt full-upgradethe correct upgrade on Raspberry Pi OS — it will add and remove packages, which upgrade refuses to do
apt-cache search termfind a package
apt-cache show pkgversion, size, dependencies
sudo apt purge pkgremove including its config files
sudo apt autoremovedrop orphaned dependencies
sudo apt cleanempty /var/cache/apt/archives — reclaims real space on a small card
apt list --installedeverything currently on the box

Python: the venv rule

Since Bookworm, pip install outside a virtual environment is refused — that is PEP 668, not a Raspberry Pi decision. There are exactly two correct answers.

# 1 — system-wide library, from Debian
sudo apt install python3-gpiozero python3-numpy

# 2 — anything else, in a venv
python3 -m venv ~/.venvs/proj
source ~/.venvs/proj/bin/activate
pip install adafruit-circuitpython-bme280
# prompt now reads (proj) — deactivate to leave

# a venv that can still see the apt-installed system packages
python3 -m venv --system-site-packages ~/.venvs/gpio
--break-system-packages exists and you should not use it. It lets pip overwrite Debian-managed files, and the breakage surfaces weeks later during an apt upgrade.
06

Config files and the device tree

Almost everything raspi-config and the Control Centre do ends up as one line in /boot/firmware/config.txt. It is read by the firmware before Linux exists, which is why a typo there can leave a board that never reaches a login prompt.

Where things live

/boot/firmware/config.txtfirmware settings, overlays, overclock. Bookworm moved this from /boot
/boot/firmware/cmdline.txtkernel command line. One line. A newline here breaks the boot
/boot/firmware/overlays/the compiled .dtbo overlays, plus a README that documents every parameter of every one
/boot/firmware/*.dtbbase device trees; bcm2712-rpi-5-b.dtb is yours
/etc/fstabmounts. /boot/firmware is the FAT partition
/etc/NetworkManager/system-connections/saved Wi-Fi, root-readable only
/etc/hostname, /etc/hostschange both together, or use raspi-config
/sys/firmware/devicetree/base/the live device tree as the kernel sees it
/proc/device-tree/a symlink to the same thing, shorter to type
Back up config.txt before you edit it. sudo cp /boot/firmware/config.txt{,.bak}. If the board stops booting, put the card in your laptop and restore it — bootfs is FAT32 and mounts anywhere.

config.txt for a Pi 5 dtparam & dtoverlay

# --- interfaces -----------------------------------------
dtparam=i2c_arm=on            # I2C-1 on GPIO2/3 → /dev/i2c-1
dtparam=i2c_arm_baudrate=400000
dtparam=spi=on                # SPI0 → /dev/spidev0.0, 0.1
dtparam=i2s=on
dtparam=audio=on

# --- extra buses ----------------------------------------
dtoverlay=i2c3-pi5            # I2C-3 on GPIO4/5
dtoverlay=uart0-pi5           # UART0 on GPIO14/15
dtoverlay=w1-gpio,gpiopin=4   # 1-Wire
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
dtoverlay=pwm-pio,gpio=17     # PIO-driven PWM, Pi 5 only, any GPIO 0–27

# --- storage & PCIe -------------------------------------
dtparam=pciex1                # alias: nvme
dtparam=pciex1_gen=3          # out of spec — see Traps

# --- hardware -------------------------------------------
camera_auto_detect=1
display_auto_detect=1
auto_initramfs=1
dtoverlay=vc4-kms-v3d
max_framebuffers=2
arm_64bit=1
disable_fw_kms_setup=1

# --- power ----------------------------------------------
usb_max_current_enable=1      # only with a real 5 A supply
dtparam=rtc_bbat_vchg=3000000 # charge the RTC cell at 3.0 V

# --- model-specific -------------------------------------
[pi5]
dtparam=uart0_console=off
[all]

Overlay syntax, and how to discover it

dtoverlay -loverlays loaded right now
dtoverlay -aevery overlay available on this image
dtoverlay -h namethat overlay's parameters, straight from the README
sudo dtoverlay name param=valload one now, without rebooting. Gone at reboot — for experiments
sudo dtoverlay -r nameunload it again
dtparam -lthe base parameters and their current values
vcgencmd get_config itemwhat the firmware actually parsed — int or str dumps them all
/boot/firmware/overlays/README6 000 lines documenting every overlay. The real reference
Conditional filters
[pi5]        # Pi 5 only
[cm5]        # CM5 only
[pi4]        # Pi 4 only
[none]       # disable a block without deleting it
[all]        # reset back to everything — always end with this
[HDMI:1]     # settings for the second HDMI port
Filters are sticky. Everything after [pi5] applies only to a Pi 5 until the next filter. Forgetting the closing [all] is a classic way to lose settings you thought you'd made.

cmdline.txt

Space-separated kernel parameters, all on one line. A typical Pi 5 line:

console=serial0,115200 console=tty1 root=PARTUUID=abc12345-02
rootfstype=ext4 fsck.repair=yes rootwait
cfg80211.ieee80211_regdom=US
console=where kernel messages go. serial0 is the debug UART on a Pi 5
root=PARTUUID=which partition to mount. Don't touch unless you know why
rootwaitwait for the storage device to appear — essential on USB/NVMe
quietsuppress the boot log
splashshow the splash image
logo.nologodrop the raspberry logos
systemd.unit=rescue.targetsingle-user boot when you've broken something
init=/bin/shthe nuclear option: skip systemd entirely, get a root shell. Remount / rw yourself
Line breaks in cmdline.txt do not work. Your editor's automatic trailing newline is fine; a newline between parameters is not. The board will boot to a blank screen and tell you nothing.
07

Boot, EEPROM and the power button

A Pi 5 has no ROM-resident bootloader worth speaking of — it loads one from a SPI EEPROM on the board. That EEPROM holds the boot order, the power behaviour and a handful of recovery settings, and it is updated separately from the OS.

Editing the bootloader config

# read it
rpi-eeprom-config

# edit it — opens $EDITOR, applies on reboot
sudo rpi-eeprom-config --edit

# update the bootloader itself to the latest release
sudo rpi-eeprom-update -a && sudo reboot

# is it current?
sudo rpi-eeprom-update
#  BOOTLOADER: up to date  →  CURRENT date == LATEST date
BOOT_ORDERhex nibbles, read right to left. Default 0xf41
POWER_OFF_ON_HALT=1puts the PMIC in STANDBY on halt. Drops idle-off draw from ~1.2 W to ~0.01 W
WAIT_FOR_POWER_BUTTON=1after a power cut, wait for a button press instead of booting
PCIE_PROBE=1needed to boot from a non-HAT+ PCIe device
BOOT_UART=1firmware debug output on the primary UART, 115200 8N1
BOOT_WATCHDOG_TIMEOUT=nreset if the OS hasn't started in n seconds. For unattended boards
SD_BOOT_MAX_RETRIES=nretry a flaky card before falling through to the next mode
REBOOT_ON_FATAL_ERROR=1default; hard-resets after flashing the error pattern three times

BOOT_ORDER nibbles

ValueModeNotes
0x1SD CARDthe microSD slot
0x2NETWORKPXE / TFTP
0x3RPIBOOTUSB device mode; always put this last, it has no timeout
0x4USB-MSDa USB mass-storage device
0x6NVMEan SSD on the PCIe connector
0x7HTTPHTTP boot over Ethernet
0xeSTOPstop and flash the error pattern; needs a power cycle
0xfRESTARTloop back to the first mode
Read right to left, up to eight nibbles. 0xf41 = SD, then USB-MSD, then repeat. 0xf416 = NVMe, then SD, then USB, then repeat — the one you want with an M.2 HAT+. 0xf14 = USB first. 0xf21 = SD then network.
sudo raspi-config6 Advanced → A4 Boot Order sets the common ones without hex.

The power button

First power-upboots automatically; you don't need to press anything
Short press, runningdesktop: a shutdown/reboot/logout dialog. Lite: starts a clean shutdown immediately
Two quick presseson the desktop, shuts down without the dialog
Press and holdhard shutdown. Last resort, same as pulling the plug
Press when offboots the board, as long as power is still connected
J2 padsbridge with a normally-open momentary switch for an external button — same behaviour
GPIO3 shutdowndtoverlay=gpio-shutdown for a soft-off button on any GPIO, if you'd rather
Never yank the power on a running Pi. ext4 journals, but the FAT boot partition doesn't, and a card interrupted mid-write is the single most common cause of a Pi that stops booting. sudo poweroff, wait for the LED to go red, then unplug.

LED codes when it won't boot

Long flashes first, then short ones, then a two-second pause and repeat. On a Pi 5 this is the single bi-colour LED next to the power button.

LongShortMeaning
03generic failure to boot
04start*.elf not found
07kernel image not found
08SDRAM failure
09insufficient SDRAM
010in HALT state — this one is normal after halt
12SD card overcurrent — the card is shorted, throw it away
21partition not FAT
22failed to read from partition
24file signature / hash mismatch
31SPI EEPROM error
32SPI EEPROM write-protected
33I2C error
34secure-boot configuration invalid
43RP1 not found
44unsupported board type
45fatal firmware error
46/7power failure type A / B

RTC and wake alarms

The RTC keeps time with no battery while powered, and with a coin cell across a power cut. It can also wake the board from a near-dead state — about 3 mA — which is how you build a time-lapse rig that isn't plugged into a monitor.

# 1 — allow the deep state
sudo rpi-eeprom-config --edit
#   POWER_OFF_ON_HALT=1
#   WAKE_ON_GPIO=0

# 2 — sleep for ten minutes, then come back
echo +600 | sudo tee /sys/class/rtc/rtc0/wakealarm
sudo halt

# battery charging is OFF by default; 3.0 V for the official cell
#   /boot/firmware/config.txt:
#   dtparam=rtc_bbat_vchg=3000000
grep . /sys/class/rtc/rtc0/charging_voltage*
Rechargeable lithium-manganese only. The official part is a rechargeable coin cell with a JST-SH plug. A primary (non-rechargeable) CR2032 will be worn out in weeks by the RTC's backup draw, and a Li-ion cell must never be connected here.
08

Driving the GPIO

Pi 5 broke every library that pokes the SoC's GPIO registers directly, because the GPIO is no longer in the SoC. Anything that works now goes through the kernel's character device, /dev/gpiochipN, or through /dev/gpiomem under RP1's own driver.

pinctrl the debug tool

Writes GPIO registers directly, ignoring whatever kernel driver owns the pin. Superb for finding out what is going on; not the thing to build an application on.

pinctrl getevery GPIO, one per line
pinctrl -P getthe 40-way header drawn as it sits on the board
pinctrl -lthe detected GPIO controllers — confirms pinctrl-rp1
pinctrl set 17 op dhoutput, drive high
pinctrl set 17 op dloutput, drive low
pinctrl set 17 ip puinput with pull-up (pd = down, pn = none)
pinctrl set 12 a0force an alternate function (a0a8)
pinctrl lev 17just the level, 1 or 0 — scriptable
pinctrl poll 17,27watch for edges and print them. A crude logic analyser up to a few hundred kHz
pinctrl funcs 9-11alt functions for a range, CSV
pinctrl -p ...address by header pin number instead of GPIO number
# blink an LED on GPIO17 without installing anything
pinctrl set 17 op
while :; do pinctrl set 17 dh; sleep .5; pinctrl set 17 dl; sleep .5; done
Group gpio, not sudo. Raspberry Pi OS ships a udev rule that hands /dev/gpiomem* to the gpio group, so members can run pinctrl unprivileged. On other distributions you'll need sudo.

libgpiod the kernel interface

Trixie ships libgpiod 2.x and the command syntax changed. Bookworm has 1.6. Check with gpioget --version before you copy a recipe off the internet.

gpiodetectlist the chips and their labels. Find the one labelled pinctrl-rp1
gpioinfoevery line, its name, direction, and which driver has claimed it
gpioget -c gpiochip0 17read a line — v2 syntax
gpioset -c gpiochip0 17=1drive a line — v2. Holds it until the process exits
gpioset -t 500ms -c gpiochip0 17=1toggle every 500 ms. Ctrl-C to stop
gpioset -z -c gpiochip0 17=1set, then detach — the only way to leave a value behind
gpiomon -c gpiochip0 27timestamped edge events
gpionotify -c gpiochip0 17watch for other processes requesting/releasing the line (v2 only)
gpioget gpiochip0 17the v1 form — Bookworm. No -c
The gpiochip number is not stable. It has been 0 and 4 on different Pi 5 kernels, and can move again. Never hard-code it — resolve it: CHIP=$(gpiodetect | grep pinctrl-rp1 | cut -d' ' -f1).

Python: GPIO Zero

Pre-installed, works on Pi 5, and is the answer for almost everything.

from gpiozero import LED, Button, PWMLED, MotionSensor
from signal import pause
from time import sleep

led = LED(17)              # BCM numbering, always
led.on(); sleep(1); led.off()
led.blink(on_time=.5, off_time=.5)   # non-blocking
led.toggle()

button = Button(2, pull_up=True, bounce_time=.05)
button.when_pressed  = led.on
button.when_released = led.off
button.wait_for_press()
print(button.is_pressed, button.is_held)

dim = PWMLED(12)
dim.value = 0.35           # 0.0 – 1.0
dim.pulse()

pause()                    # park the main thread on callbacks
LED, PWMLED, RGBLEDoutputs
Button, MotionSensor, LightSensorinputs with debounce and callbacks
Servo, AngularServosoftware-timed; jitters under load
Motor, RobotH-bridge drivers
DistanceSensorHC-SR04, with the divider on echo
CPUTemperature, LoadAverageinternal sources you can wire to an LED
GPIOZERO_PIN_FACTORY=lgpioforce the backend if autodetection picks wrong

What no longer works

RPi.GPIODead on Pi 5. It writes BCM283x registers that aren't there. RuntimeError or silent nonsense
rpi-lgpioa drop-in shim with the RPi.GPIO API on top of lgpio. Works, with caveats — it can't claim a line another driver already holds, so enabling SPI/I2C/serial makes those pins “busy”
/sys/class/gpiothe sysfs interface. Deprecated for years, removed from modern kernels
wiringPiunmaintained since 2019. The forks are a lottery
pigpio / pigpiodno Pi 5 support in the original. Use lgpio/rgpio from the same author instead
bcm2835 C librarysame problem — direct SoC register access
lgpiouse this for low-level C/Python on Pi 5
libgpiodor this, if you want the portable kernel API
If a tutorial starts with import RPi.GPIO as GPIO, it predates the Pi 5. Either translate it to gpiozero or install rpi-lgpio (and remove python3-rpi.gpio first — they conflict).

Interrupts, edges and hogs

Edge detectionevery GPIO can generate rising, falling or both. The kernel does the waiting; you don't poll
gpiozero callbackswhen_pressed / when_released — edge-driven under the hood
gpiomonthe shell equivalent, with timestamps
Debouncedo it in software (bounce_time=) — mechanical switches bounce for 1–20 ms
dtoverlay=gpio-hoghave the kernel claim a pin at a fixed level from boot, before userspace runs
dtoverlay=gpio-shutdowna pin that triggers a clean shutdown
dtoverlay=gpio-poweroffa pin driven at power-off, to tell external hardware
dtoverlay=gpio-ledexpose a GPIO as a kernel LED with triggers (heartbeat, disk activity…)
dtoverlay=gpio-keyturn a button into a keyboard event, no daemon needed
dtoverlay=gpio-fanthermally controlled fan on a plain GPIO
09

Buses

All of these live in RP1 and all of them are off until you turn them on. Bus numbering on a Pi 5 is not the same as on a Pi 4 — check ls /dev/i2c-* rather than assuming.

I2C GPIO2/3 · /dev/i2c-1

# enable
sudo raspi-config nonint do_i2c 0     # or dtparam=i2c_arm=on
sudo apt install -y i2c-tools

# which buses exist
ls /dev/i2c-*
i2cdetect -l

# who's out there (bus 1 = the header pins)
i2cdetect -y 1

# read and write registers
i2cget -y 1 0x76 0xd0          # chip ID of a BME280
i2cset -y 1 0x76 0xf4 0x27
i2cdump -y 1 0x76              # the whole register map
/dev/i2c-1GPIO2 (SDA) / GPIO3 (SCL) — pins 3 and 5. The one you want
/dev/i2c-0GPIO0/1, the HAT ID EEPROM. Leave it alone
Extra busesdtoverlay=i2c3-pi5 on GPIO4/5, i2c2-pi5, i2c0-pi5
Speeddtparam=i2c_arm_baudrate=400000. 100k is the default and the safe one
Pull-ups1.8 kΩ already fitted on GPIO2/3. Don't add more — most breakout boards also have their own
Address clashtwo devices on the same address need a second bus or a mux (TCA9548A)
Nothing in i2cdetect? Check wiring first (SDA↔SCL swapped is the classic), then that the device is powered from 3V3, not 5V, then that you're on bus 1.

SPI GPIO7–11 · /dev/spidev0.n

sudo raspi-config nonint do_spi 0     # or dtparam=spi=on
ls -l /dev/spidev*
#  /dev/spidev0.0  → CE0, GPIO8, pin 24
#  /dev/spidev0.1  → CE1, GPIO7, pin 26
MOSIGPIO10, pin 19
MISOGPIO9, pin 21
SCLKGPIO11, pin 23
CE0 / CE1GPIO8 / GPIO7, pins 24 / 26
More chip selectsdtoverlay=spi0-2cs, or spi0-0cs to free both and drive CS yourself
SPI1dtoverlay=spi1-1cs — GPIO19/20/21 with CE on GPIO18
Speedset per transfer by the driver, not in config.txt. Tens of MHz are reachable
Pythonapt install python3-spidev, then spi.open(0,0)
A HAT that wants SPI usually also wants a spare GPIO for a data/command or reset line — check the datasheet before you assume pins 19–26 are all it needs.

UART the Pi 5 rearrangement

This is the biggest behavioural difference from a Pi 4. On a Pi 5 the primary UART is UART10 on the dedicated 3-pin debug header, not GPIO14/15.

/dev/ttyAMA10the debug header. /dev/serial0 points here by default
/dev/ttyAMA0UART0 on GPIO14/15, once you enable it
UART0–UART4all PL011, all disabled by default. No mini UART on a Pi 5
Overlaysuart0-pi5uart4-pi5 — note the -pi5 suffix
Bluetoothon its own dedicated UART, so it doesn't fight you for GPIO14/15 the way it did on a Pi 3
enable_uart=1routes kernel logging to GPIO14/15 when nothing is on the debug header
enable_rp1_uart=1routes late firmware debug output to GPIO14/15
earlyconearlycon=pl011,0x107d001000,115200n8 in cmdline.txt for very early boot messages
# UART on the header pins, no login shell on it
sudo raspi-config nonint do_serial_hw 0
sudo raspi-config nonint do_serial_cons 1
# config.txt:  dtoverlay=uart0-pi5

# talk to it
sudo apt install -y minicom
minicom -D /dev/ttyAMA0 -b 115200
3.3 V only. A 5 V TTL adapter will damage the pin. And cross the wires: your TX goes to their RX.

PWM

RP1's PWM0 has four channels. Two reach the header at ALT0 (GPIO12/13) and two at ALT3 (GPIO18/19). Pi 5 also gains PIO-driven PWM, which works on any GPIO 0–27.

# config.txt — hardware PWM on GPIO12 and GPIO13
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4

# after a reboot, find the chip (the number moves between kernels)
ls /sys/class/pwm/
cd /sys/class/pwm/pwmchip0

# 50 Hz, 1.5 ms pulse — a servo's centre
echo 0 | sudo tee export
echo 20000000 | sudo tee pwm0/period       # ns
echo  1500000 | sudo tee pwm0/duty_cycle
echo 1        | sudo tee pwm0/enable
GPIO12 / GPIO13PWM0_CHAN0 / CHAN1 at ALT0 — func=4 in the overlay
GPIO18 / GPIO19PWM0_CHAN2 / CHAN3 at ALT3 — shared with I2S
dtoverlay=pwm-pio,gpio=nPIO-assisted PWM on any GPIO 0–27, up to 4 at once. Pi 5 only
dtoverlay=pwm-gpio,gpio=nsoftware PWM in the kernel. Cheap, jittery
gpiozero PWMLED / Servosoftware-timed from userspace. Fine for LEDs, visibly twitchy for servos
Audio clashthe analogue audio path uses PWM channels. Don't expect both

1-Wire

# config.txt
dtoverlay=w1-gpio,gpiopin=4      # default pin is GPIO4

# after reboot — one directory per device
ls /sys/bus/w1/devices/
#  28-3c01d607f0a1   28-xxxx = DS18B20 family

cat /sys/bus/w1/devices/28-*/temperature   # millidegrees C
cat /sys/bus/w1/devices/28-*/w1_slave       # raw, with CRC line
Pull-up4.7 kΩ from the data line to 3V3. Not optional — the bus is open-drain
Parasitic powerworks, but give the sensor its own 3V3 if you can
Many sensorsall share one pin; each has a unique 64-bit ROM code
gpiozerofrom gpiozero import CPUTemperature is the internal one; DS18B20 is just a file read

I2S, PIO and the odd ones

I2S0GPIO18 (SCLK), 19 (WS/LRCLK), 20 (SDI), 21 (SDO) at ALT2. dtparam=i2s=on
Audio HATsuse the vendor overlay — hifiberry-dacplus, iqaudio-dac, max98357a
PDM microphonesMIC_CLK / MIC_DAT on GPIO25/26/27 at ALT3
PIORP1's programmable I/O, like the Pico's. GPIO0–27, ALT7. Bit-bang protocols in hardware
rp1-pio-uartan extra UART synthesised in PIO, on pins of your choosing
DPIALT1 on every pin — a 24-bit parallel RGB panel, at the cost of the whole header
GPCLK0/1/2GPIO4/5/6 at ALT0 — a clean programmable clock output for a camera or codec
IRdtoverlay=gpio-ir for receive, pwm-ir-tx for transmit
Two things want the same pin? pinctrl -P get shows you who won. The device tree is resolved at boot and the first claim usually wins silently.
10

PCIe, storage and USB

The FPC connector on the left edge is a real PCIe Gen 2.0 ×1 link straight into RP1's host bridge. With an M.2 HAT+ and an NVMe drive it turns the Pi 5 into a machine that boots in seconds and stops wearing out SD cards.

Bringing up the PCIe slot

# A HAT+ device is detected automatically. Anything else:
#   /boot/firmware/config.txt
dtparam=pciex1              # alias: dtparam=nvme

# out-of-spec Gen 3 — faster, occasionally unstable
dtparam=pciex1_gen=3        # or raspi-config → 6 → A8

# after reboot
lspci
sudo lspci -vv | grep -i 'LnkSta:'   # negotiated speed and width
ls /dev/nvme*
sudo nvme list                       # apt install nvme-cli
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,MODEL
Gen 3 is not certified. Raspberry Pi qualified the connector at Gen 2.0 (5 GT/s). Gen 3.0 works on most drives and cables and corrupts data on some. If you enable it, test hard before you trust it, and keep backups either way.

Booting off NVMe or USB

  1. Boot from the SD card, fit the drive, confirm it appears in lsblk.
  2. Copy the system across. The easiest route is Raspberry Pi Imager, running on the Pi — it will write a fresh image to the NVMe. To clone what you already have, use rpi-clone or the desktop's SD Card Copier.
  3. Change the boot order:
    sudo rpi-eeprom-config --edit
    #   BOOT_ORDER=0xf416      NVMe → SD → USB → repeat
    #   PCIE_PROBE=1           only for non-HAT+ adapters
  4. sudo reboot, then lsblk to confirm / is on nvme0n1p2.
0xf416NVMe first, then SD, then USB
0xf14USB first, then SD
raspi-config → A4B1 SD first · B2 NVMe/USB first · B3 network
Keep the SD slot workingleaving SD in the order costs nothing and saves you if the SSD dies
An NVMe drive is the single biggest quality-of-life upgrade for a Pi 5, more than RAM. Boot time, apt, and anything touching a database all stop being SD-card-bound.

Storage housekeeping

lsblk -fdevices, filesystems, UUIDs, mount points
df -hfree space per filesystem
du -sh /var/* | sort -hfind what's eating the card
sudo blkidUUIDs and PARTUUIDs for /etc/fstab
sudo raspi-config nonint do_expand_rootfsfill the card — normally automatic on first boot
sudo fsck -f /dev/sda1check an unmounted filesystem
sudo mount /dev/sda1 /mntquick manual mount
udisksctl mount -b /dev/sda1mount as a user, no fstab entry
sudo journalctl --vacuum-size=50Mthe log is often the biggest thing you can delete
raspi-config → 4 → P2read-only overlay FS — makes a kiosk immune to power cuts

USB

2 × USB 3.05 Gb/s, on separate controllers — unlike Pi 4, they don't share bandwidth
2 × USB 2.0480 Mb/s
600 mA totalthe peripheral budget with a 3 A supply
1.6 A totalwith a real 5 V/5 A supply, or usb_max_current_enable=1
lsusb -tthe device tree, showing which controller each port is on
sudo dmesg -wwatch a device enumerate as you plug it in
vcgencmd get_config usb_max_current_enableis the limit lifted?
Bus-powered HDDsmarginal at 600 mA. Use a powered hub or a proper supply
The fan connector shares the USB current budget. A 3 A supply, a spinning fan and a bus-powered disk is a combination that browns out under load — and shows up as filesystem corruption, not as an error message.
11

Camera, display and audio

Two 22-pin FPC ports, each of which can be a camera or a display. The camera stack is libcameraraspistill and raspivid are gone, and the libcamera-* names were renamed to rpicam-* in Bookworm.

rpicam-apps

# is the camera seen at all?
rpicam-hello --list-cameras

# five-second preview
rpicam-hello
rpicam-hello -t 0 -n             # forever, no preview window

# stills
rpicam-still -o shot.jpg
rpicam-still -o shot.jpg --width 1920 --height 1080
rpicam-still -o shot.png --encoding png
rpicam-still -o shot.jpg --raw   # also writes shot.dng
rpicam-still -o day%04d.jpg --timelapse 60000 -t 3600000

# video
rpicam-vid -t 10s -o clip.h264
rpicam-vid -t 10s --codec libav -o clip.mp4
rpicam-vid -t 0 --codec mjpeg --segment 1 -o f%05d.jpg

# network stream
rpicam-vid -t 0 --inline -o - | \
  ffmpeg -i - -f mpegts udp://192.168.1.20:1234
rpicam-hellopreview / smoke test
rpicam-jpegthe simplest stills capture
rpicam-stillstills with the full raspistill-era option set
rpicam-vidvideo; H.264, MJPEG, YUV420, or libav into a container
rpicam-rawunprocessed Bayer frames
rpicam-detectcapture on object detection; needs a TFLite build
picamera2the Python API. apt install python3-picamera2
camera_auto_detect=1in config.txt; loads the right overlay for an official module
No H.264 hardware encode on a Pi 5. The block was removed. rpicam-vid falls back to software encoding, which on a 2 GB board means 1080p30 is comfortable and 4K is not. Decode is fine — HEVC 4Kp60 in hardware.

Display

2 × micro-HDMIup to 4Kp60 on one, or 4Kp30 on both simultaneously
HDMI0the port nearest the USB-C jack. Use this one for a single monitor
DSIeither FPC port, with display_auto_detect=1
Compositevia test pads on the underside, not a jack
No analogue audio jackgone on Pi 5. HDMI, USB, or an I2S HAT
kmsprint, kmsprint -mconnected monitors and every mode they claim
wlr-randrset resolution under Wayland/labwc
vcgencmd display_power 0blank the HDMI output (1 to restore)
raspi-config → 2 Displayscreen blanking, underscan, headless resolution
Trixie runs Wayland (labwc) by default. X11-only tools may misbehave; apt install rpd-x-core if you need the old stack. On a headless board none of this matters — install Lite and save the memory.

Audio

aplay -lplayback devices
arecord -lcapture devices
speaker-test -c2 -twavconfirm something comes out
alsamixerlevels, F6 to switch card
wpctl status, wpctl set-volumePipeWire is the sound server from Bookworm on
raspi-config → 1 → S2pick the default output
dtparam=audio=onenables the on-board (HDMI/PWM) audio path
I2S DAC HATsuse the vendor overlay and usually dtparam=audio=off alongside it
12

Power, heat and health

A Pi 5 asks for more power than any Pi before it and will quietly throttle or corrupt data if it doesn't get it. Two commands tell you whether the board is happy.

Is it healthy?

vcgencmd measure_temp        # temp=47.2'C
vcgencmd get_throttled       # throttled=0x0  ← what you want
vcgencmd measure_clock arm   # frequency(0)=2400000000
vcgencmd measure_volts core
vcgencmd pmic_read_adc       # every rail, current and voltage
uptime; free -h; df -h /
get_throttled bits
0x1undervoltage right now
0x2Arm frequency capped now
0x4throttled now
0x8soft temperature limit active now
0x10000undervoltage has occurred since boot
0x20000frequency capping has occurred
0x40000throttling has occurred
0x80000soft temp limit has been hit
0x50005 and friends mean your power supply is not up to it. Undervoltage is the root cause of most “my Pi is unstable / my SD card keeps corrupting” reports. Buy the 27 W supply.

Power supply

Official 27 W5.1 V / 5 A over USB-C PD. The supported configuration
5 V / 3 Aboots and runs, but peripherals are capped at 600 mA total
Bare board idle~800 mA typical
usb_max_current_enable=1lifts the cap to 1.6 A. Only do this with a supply that can genuinely deliver 5 A
No USB-PPSno Pi supports it; some laptop chargers only offer PPS and will under-deliver
Multi-port chargersplugging in a second device renegotiates and can reboot a powered-down Pi
Powering via the 5V header pinsworks, and bypasses every protection circuit on the board. Only with a supply you trust absolutely
Off-state draw~1.2 W by default; ~0.01 W with POWER_OFF_ON_HALT=1

Cooling

A Pi 5 needs active cooling to hold 2.4 GHz under sustained load. The Active Cooler is ~$5 and it is not optional for anything that runs the CPU hard.

< 50 °Cfan off
50 °C30% speed
60 °C50%
67.5 °C70%
75 °C100%
80–85 °CArm cores progressively throttled
85 °Chard limit; CPU and GPU throttled. temp_limit can be lowered, never raised
# speed thresholds come down 5 °C on the way back (hysteresis)
# start the fan earlier — config.txt
dtparam=fan_temp0=45000        # millidegrees
dtparam=fan_temp0_hyst=5000
dtparam=fan_temp0_speed=100

# watch it
watch -n1 'vcgencmd measure_temp; \
  cat /sys/class/thermal/thermal_zone0/temp'
cat /sys/devices/platform/cooling_fan/hwmon/hwmon*/fan1_input  # RPM

Clocks and governors

arm_freq2400 MHz stock on a Pi 5
arm_freq_min1500 MHz — the idle floor
core_freq910 MHz · v3d_freq 960 · isp_freq 910
sdram_freq4267 MHz. Not overclockable on a Pi 5
over_voltage_deltamicrovolts added to whatever DVFS calculated. The Pi 5 way to overvolt
temp_limit85 default; values above 85 are clamped
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governorondemand by default
sudo cpufreq-set -g performancefrom cpufrequtils; powersave to pin it low
vcgencmd measure_clock armthe frequency right now, straight from the firmware
# a mild, widely-stable overclock — Active Cooler required
#   /boot/firmware/config.txt
arm_freq=2800
over_voltage_delta=50000
Overclocking a 2 GB board buys you very little: your ceiling is memory, not MHz. Fit the cooler, leave the clocks alone, and spend the effort on §13 instead.
13

Living in 2 GB

The 2 GB Pi 5 is the same machine with less headroom. The difference between “fine” and “thrashing” is mostly choosing Lite, adding compressed swap, and knowing which three processes are eating your memory.

The tuning, in order of payoff

  1. Install Lite. The desktop costs ~500 MB before you open anything. On a headless board that is a quarter of your RAM spent on a screen you never look at.
  2. Turn on zram. Compressed swap in RAM, ~3:1 on typical pages. It is the single highest-value change on a small board.
  3. Give it a real swap file too, on NVMe if you have one, as an overflow behind zram.
  4. Cut gpu_mem? Not on a Pi 5 — memory is allocated dynamically by the CMA driver and the old gpu_mem split doesn't apply.
  5. Disable what you don't use: Bluetooth, Wi-Fi if you're on Ethernet, Avahi, ModemManager, triggerhappy, cups.
  6. Cap the journal so logs don't quietly consume the card.
# zram — compressed swap in RAM
sudo apt install -y zram-tools
sudo nano /etc/default/zramswap
#   ALGO=zstd
#   PERCENT=50          → ~1 GB of zram on a 2 GB board
#   PRIORITY=100        → used before any disk swap
sudo systemctl restart zramswap
zramctl; swapon --show

# a disk swap file behind it
sudo dphys-swapfile swapoff
sudo sed -i 's/^CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' /etc/dphys-swapfile
sudo dphys-swapfile setup && sudo dphys-swapfile swapon

# prefer reclaiming cache over swapping, but do swap when needed
echo 'vm.swappiness=100' | sudo tee /etc/sysctl.d/99-zram.conf
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.d/99-zram.conf
swappiness=100 is correct with zram and wrong without it. Swapping to compressed RAM is cheap; swapping to an SD card is a stall. If you have no zram, keep swappiness low.

Finding the memory

free -h                        # the headline numbers
#   available  ← this is the number that matters, not "free"

ps aux --sort=-%mem | head -12 # the top consumers
htop                           # F6 → sort by MEM, F5 → tree view
sudo systemd-cgtop -m          # memory per service
smem -tk -s uss                # apt install smem — per-process, shared pages counted once

# what services are even running
systemctl list-units --type=service --state=running
systemd-analyze blame          # and what slowed the boot down

# has anything been OOM-killed?
sudo journalctl -k | grep -i 'out of memory'
dmesg | grep -i oom
availablewhat a new process could actually get, counting reclaimable cache. Use this
freenever used by anything. A low number here is healthy
buff/cachepage cache. Instantly reclaimable, not a leak
USSmemory that dies with the process. The honest per-process figure

Services worth switching off

# each of these frees 5–40 MB and some boot time
sudo systemctl disable --now bluetooth
sudo systemctl disable --now ModemManager
sudo systemctl disable --now triggerhappy
sudo systemctl disable --now cups cups-browsed
sudo systemctl disable --now avahi-daemon   # ← breaks .local names

# radios off entirely, at the firmware level
#   /boot/firmware/config.txt
dtoverlay=disable-bt-pi5
dtoverlay=disable-wifi-pi5

# cap the journal
sudo mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=50M\nStorage=volatile\n' | \
  sudo tee /etc/systemd/journald.conf.d/size.conf
sudo systemctl restart systemd-journald
Don't disable avahi-daemon on a headless board unless you have a fixed IP — it is what makes pi5.local resolve, and you will lose your only way in.

What fits, and what doesn't

Pi-hole / AdGuard~60 MB. Trivial
MQTT broker~15 MB
Samba / NFS file servercomfortable; disk-bound, not RAM-bound
Home Assistant Core~400 MB. Fits. HA OS / Supervised does not
Node-RED~120 MB
Docker, 2–3 small containersfine; the daemon itself is ~80 MB
k3s~500 MB before a single workload. Painful
Chromium kiosk, one tabworks. Add --disable-dev-shm-usage
Chromium, real browsingthis is where 2 GB hurts most
Compiling a kernelmake -j2, not -j4, and add swap
Any local LLMno
OpenCV / camera pipelinesfine at 720p–1080p, tight at 4K
14

Command index

The commands you will actually type on this board, grouped. Use the filter box in the header bar to narrow every card at once.

Raspberry Pi specific

sudo raspi-configthe configuration TUI
sudo raspi-config nonint fn argthe same, scriptable. 0 = enable
pinoutASCII pin diagram for the board you're on
pinctrlread/write GPIO state and muxing
raspinfoa complete diagnostic dump
vcgencmd cmdask the firmware; vcgencmd commands lists them all
sudo vclog --msgthe VideoCore firmware log
sudo rpi-eeprom-config -eedit the bootloader config
sudo rpi-eeprom-update -aupdate the bootloader
sudo rpi-updatebleeding-edge firmware. Don't
rpi-connect on|off|signinremote shell and screen through the browser
dtoverlay -l|-a|-hdevice tree overlays
dtparam -lbase device tree parameters
kmsprint -mdisplay modes
rpicam-hello|still|vid|rawthe camera applications

System & services

systemctl status unitstate, recent log lines, PID
sudo systemctl start|stop|restart unitnow
sudo systemctl enable|disable --now unitat boot, and now
systemctl list-units --type=serviceeverything loaded
sudo systemctl daemon-reloadafter editing a unit file
journalctl -u unit -n 50last 50 lines for one service
journalctl -ffollow the whole log
journalctl -b -p errerrors from this boot only
sudo journalctl --vacuum-size=50Mshrink the log
systemd-analyze blamewhat made the boot slow
sudo rebootrestart
sudo poweroffclean shutdown; wait for the red LED
sudo halthalt without cutting power — pairs with POWER_OFF_ON_HALT
uptimehow long, and load average
hostnamectlhostname, OS, kernel, machine ID
timedatectlclock, timezone, NTP sync state
crontab -eper-user scheduled jobs
@reboot cmda crontab line that runs once at boot

Processes & monitoring

htopinteractive process viewer. F6 sort, F5 tree, F9 kill
ps aux --sort=-%memfattest processes first
topalways installed, if htop isn't
kill pid, killall nameTERM, then -9 if it won't go
pgrep -a namefind PIDs by name, with their command lines
free -hmemory; watch available
vmstat 1per-second CPU, memory, swap and I/O
iostat -x 1disk latency and utilisation. sysstat
sudo iotopwhich process is hitting the card
sudo lsof -iopen network sockets, with owners
ss -tulpnlistening ports. Replaces netstat
sudo dmesg -wkernel ring buffer, live
watch -n1 cmdre-run something every second
stress-ng --cpu 4 --timeout 300sload it up while you watch get_throttled

Files, transfer & networking

scp file user@pi5.local:~copy a file over SSH
rsync -avz --progress src dstthe right way to move directories; resumable
sftp user@pi5.localinteractive file transfer
ssh -L 8080:localhost:80 user@pi5.localtunnel a port to your laptop
tar czf x.tar.gz dircompress; xzf to extract
find / -name pattern 2>/dev/nulllocate a file
grep -rn text dirsearch file contents
ip -br addrone line per interface
ip routerouting table and default gateway
nmcliall network configuration. See §4
ping -c4 1.1.1.1is the network up at all
dig hostDNS resolution
curl -I urlheaders only — quick reachability test
iperf3 -c serveractual throughput, not what the router claims
sudo ufw allow 22 && sudo ufw enablea firewall in two commands

Hardware inspection

lscpucores, caches, features, current MHz
lsblk -fblock devices and filesystems
lsusb -tUSB tree by controller
lspci -vvthe PCIe bus — your NVMe and RP1 itself
lsmodloaded kernel modules
sudo modprobe modload one by hand
i2cdetect -y 1scan the I2C bus
gpiodetectGPIO controllers and their labels
gpioinfoevery line and who holds it
sensorslm-sensors; also /sys/class/hwmon/
udevadm info -a -n /dev/xevery attribute, for writing a udev rule
ls /proc/device-tree/the live device tree
15

Traps

The things that cost an evening. Most of them are advice that was correct on an older Pi and is wrong here.

Advice that expired

wpa_supplicant.conf on bootdoes nothing since Bookworm. NetworkManager owns Wi-Fi. Use Imager, cloud-init, or nmcli
/etc/dhcpcd.confdhcpcd is not installed. Editing it is a no-op that looks like it should work
/boot/config.txtmoved to /boot/firmware/config.txt. There's a compatibility symlink on some images and not on others
RPi.GPIOfundamentally incompatible with RP1. Use gpiozero, lgpio, or libgpiod
/sys/class/gpioremoved from the kernel. The character device is the interface now
raspistill / raspividgone. rpicam-still / rpicam-vid
libcamera-hellorenamed rpicam-hello in Bookworm
gpu_mem=meaningless on a Pi 5 — memory is allocated dynamically
pip install <anything>refused outside a venv since Bookworm. PEP 668
Default pi/raspberry logindoesn't exist. You must create a user at image time
apt upgradeuse full-upgrade; plain upgrade will hold back the packages that matter

Wiring traps

5 V on a GPIOdestroys the RP1 pad. There is no tolerance and no protection. Level-shift anything that isn't 3.3 V
Off-by-one on the headera one-pin slip puts 5 V into a GPIO. Count from the square pad, twice
BCM vs physical numberingevery library uses BCM (GPIO17); the diagrams show both. pinctrl -p switches to physical if you must
Extra I2C pull-upsGPIO2/3 already have 1.8 kΩ. Adding a breakout's 4.7 kΩ is usually fine; three devices' worth is not
50 mA across the whole headernot per pin. A row of LEDs will exceed it
Servos off the 5V pinthe stall current browns out the board. Separate supply, common ground
Hot-plugging a HATcan reset the PMIC. Power down first
Pi 4 camera cablewrong size. The Pi 5 uses the narrower 22-pin 0.5 mm FPC
No ground between boardssignals need a return path. Always tie grounds together

Software and system traps

Hard-coded gpiochip numberhas been 0 and 4 on different Pi 5 kernels. Resolve it by label: gpiodetect | grep pinctrl-rp1
libgpiod 1.x vs 2.x syntaxBookworm ships 1.6, Trixie ships 2.2. gpioset gpiochip0 17=1 vs gpioset -c gpiochip0 17=1
gpioset exits and the pin dropsv2 holds the line only while the process lives, by design. Use -z to daemonise or -t to toggle
Serial console on GPIO14/15on a Pi 5 the console is on the debug header, not the GPIO pins. Different problem than on a Pi 4
A newline in cmdline.txtsilent boot failure. It must be one line
Missing [all]a [pi5] filter applies to everything after it. Close your conditional blocks
Editing config.txt with a Windows editorCRLF line endings are tolerated, but a smart-quote or a BOM is not
Undervoltage read as “bad SD card”a weak supply corrupts writes. Check vcgencmd get_throttled before you blame the card
PCIe Gen 3not certified. Silent data corruption is a real reported failure mode
usb_max_current_enable with a 3 A supplylets peripherals pull more than the supply can give. Instability that looks like software
Yanking powerthe FAT boot partition has no journal. sudo poweroff, every time
SSH host key changedafter re-imaging. ssh-keygen -R pi5.local on your laptop
No WLAN country setthe radio simply stays off and reports no networks. Not a driver problem
rpi-lgpio “GPIO busy”it goes through the kernel, so it can't take a pin that SPI/I2C/serial already owns. Disable the bus or pick another pin
32-bit image on a Pi 5works, wastes the CPU, and confuses half the packaging. Use arm64
Upgrading Bookworm to Trixie in placeexplicitly unsupported. Re-image