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.
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.
| GPIO | pin | ALT0 | ALT1 | ALT2 | ALT3 | ALT4 | ALT8 |
|---|---|---|---|---|---|---|---|
| GPIO0 | 27 | SPI0_SIO3 | DPI_PCLK | TXD1 | SDA0 | — | SPI2_CE0 |
| GPIO1 | 28 | SPI0_SIO2 | DPI_DE | RXD1 | SCL0 | — | SPI2_SIO1 |
| GPIO2 | 3 | SPI0_CE3 | DPI_VSYNC | CTS1 | SDA1 | IR_RX0 | SPI2_SIO0 |
| GPIO3 | 5 | SPI0_CE2 | DPI_HSYNC | RTS1 | SCL1 | IR_TX0 | SPI2_SCLK |
| GPIO4 | 7 | GPCLK0 | DPI_D0 | TXD2 | SDA2 | RI0 | SPI3_CE0 |
| GPIO5 | 29 | GPCLK1 | DPI_D1 | RXD2 | SCL2 | DTR0 | SPI3_SIO1 |
| GPIO6 | 31 | GPCLK2 | DPI_D2 | CTS2 | SDA3 | DCD0 | SPI3_SIO0 |
| GPIO7 | 26 | SPI0_CE1 | DPI_D3 | RTS2 | SCL3 | DSR0 | SPI3_SCLK |
| GPIO8 | 24 | SPI0_CE0 | DPI_D4 | TXD3 | SDA0 | — | SPI4_CE0 |
| GPIO9 | 21 | SPI0_MISO | DPI_D5 | RXD3 | SCL0 | — | SPI4_SIO0 |
| GPIO10 | 19 | SPI0_MOSI | DPI_D6 | CTS3 | SDA1 | — | SPI4_SIO1 |
| GPIO11 | 23 | SPI0_SCLK | DPI_D7 | RTS3 | SCL1 | — | SPI4_SCLK |
| GPIO12 | 32 | PWM0_CHAN0 | DPI_D8 | TXD4 | SDA2 | AAUD_LEFT | SPI5_CE0 |
| GPIO13 | 33 | PWM0_CHAN1 | DPI_D9 | RXD4 | SCL2 | AAUD_RIGHT | SPI5_SIO1 |
| GPIO14 | 8 | PWM0_CHAN2 | DPI_D10 | CTS4 | SDA3 | TXD0 | SPI5_SIO0 |
| GPIO15 | 10 | PWM0_CHAN3 | DPI_D11 | RTS4 | SCL3 | RXD0 | SPI5_SCLK |
| GPIO16 | 36 | SPI1_CE2 | DPI_D12 | DSI0_TE_EXT | — | CTS0 | — |
| GPIO17 | 11 | SPI1_CE1 | DPI_D13 | DSI1_TE_EXT | — | RTS0 | — |
| GPIO18 | 12 | SPI1_CE0 | DPI_D14 | I2S0_SCLK | PWM0_CHAN2 | I2S1_SCLK | GPCLK1 |
| GPIO19 | 35 | SPI1_MISO | DPI_D15 | I2S0_WS | PWM0_CHAN3 | I2S1_WS | — |
| GPIO20 | 38 | SPI1_MOSI | DPI_D16 | I2S0_SDI0 | GPCLK0 | I2S1_SDI0 | — |
| GPIO21 | 40 | SPI1_SCLK | DPI_D17 | I2S0_SDO0 | GPCLK1 | I2S1_SDO0 | — |
| GPIO22 | 15 | SD0_CLK | DPI_D18 | I2S0_SDI1 | SDA3 | I2S1_SDI1 | — |
| GPIO23 | 16 | SD0_CMD | DPI_D19 | I2S0_SDO1 | SCL3 | I2S1_SDO1 | — |
| GPIO24 | 18 | SD0_DAT0 | DPI_D20 | I2S0_SDI2 | — | I2S1_SDI2 | SPI2_CE1 |
| GPIO25 | 22 | SD0_DAT1 | DPI_D21 | I2S0_SDO2 | MIC_CLK | I2S1_SDO2 | SPI3_CE1 |
| GPIO26 | 37 | SD0_DAT2 | DPI_D22 | I2S0_SDI3 | MIC_DAT0 | I2S1_SDI3 | SPI5_CE1 |
| GPIO27 | 13 | SD0_DAT3 | DPI_D23 | I2S0_SDO3 | MIC_DAT1 | I2S1_SDO3 | SPI1_CE1 |
Pin electrical limits RP1 pads
Pins that are already spoken for
dtparam=spi=on is setdtoverlay=w1-gpio defaults herepinctrl -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
# 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
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
vcgencmd pmic_read_adc) and the reason the board has a real power buttonConnectors and headers silkscreen designators
/dev/ttyAMA10 and it is the primary UART on Pi 5Identify the board you have
Revision line is the board revision codeaarch64 means you installed the 64-bit image — you want thistrixie or bookwormWhat 2 GB actually costs you
The 2 GB board is the same silicon at the same clocks. Everything below is about memory, not speed.
make -j4 on a big C++ tree will OOM. Use -j2 and a swap fileSee §13 for the tuning that makes 2 GB behave.
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
- Get Raspberry Pi Imager from
raspberrypi.com/software(orbrew install --cask raspberry-pi-imager). You need 2.0 or later — 1.9.x cannot customise a Trixie image. - Device: Raspberry Pi 5.
- 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.
- Storage: your card. Read the device name twice.
- 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).
- Write, let it verify, eject.
- Card into the Pi, power in, wait ~60 s.
ssh you@hostname.local.
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.
cmdline.txt. Still honouredusername:hashed-password — creates the first user with no Imager involveduser-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.
#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:
version: 2
wifis:
renderer: NetworkManager
wlan0:
dhcp4: true
optional: true
regulatory-domain: "US"
access-points:
"My Network":
password: "correcthorsebattery"
# 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
_ and - only; 31 chars maxpi/raspberry account. If you set nothing, the board stops on first boot and asks/Volumes/bootfs/media/$USER/bootfs/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.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"
--active for the live onesnmcli 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"
/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.# 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
d8:3a:dd or 2c:cf:67 MAC prefixSSH: 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
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
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
-a or you replace every group they're in/dev/gpiomem* without sudo/dev/i2c-* and /dev/spidev*/dev/ttyAMA* and USB serial adapterssudo 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
upgrade refuses to do/var/cache/apt/archives — reclaims real space on a small cardPython: 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.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.dtbo overlays, plus a README that documents every parameter of every onebcm2712-rpi-5-b.dtb is yours/boot/firmware is the FAT partitionraspi-configsudo 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
int or str dumps them all[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
[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
serial0 is the debug UART on a Pi 5/ rw yourselfBoot, 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
0xf41halt. Drops idle-off draw from ~1.2 W to ~0.01 WBOOT_ORDER nibbles
| Value | Mode | Notes |
|---|---|---|
| 0x1 | SD CARD | the microSD slot |
| 0x2 | NETWORK | PXE / TFTP |
| 0x3 | RPIBOOT | USB device mode; always put this last, it has no timeout |
| 0x4 | USB-MSD | a USB mass-storage device |
| 0x6 | NVME | an SSD on the PCIe connector |
| 0x7 | HTTP | HTTP boot over Ethernet |
| 0xe | STOP | stop and flash the error pattern; needs a power cycle |
| 0xf | RESTART | loop back to the first mode |
sudo raspi-config → 6 Advanced → A4 Boot Order sets
the common ones without hex.The power button
dtoverlay=gpio-shutdown for a soft-off button on any GPIO, if you'd rathersudo 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.
| Long | Short | Meaning |
|---|---|---|
| 0 | 3 | generic failure to boot |
| 0 | 4 | start*.elf not found |
| 0 | 7 | kernel image not found |
| 0 | 8 | SDRAM failure |
| 0 | 9 | insufficient SDRAM |
| 0 | 10 | in HALT state — this one is normal after halt |
| 1 | 2 | SD card overcurrent — the card is shorted, throw it away |
| 2 | 1 | partition not FAT |
| 2 | 2 | failed to read from partition |
| 2 | 4 | file signature / hash mismatch |
| 3 | 1 | SPI EEPROM error |
| 3 | 2 | SPI EEPROM write-protected |
| 3 | 3 | I2C error |
| 3 | 4 | secure-boot configuration invalid |
| 4 | 3 | RP1 not found |
| 4 | 4 | unsupported board type |
| 4 | 5 | fatal firmware error |
| 4 | 6/7 | power 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*
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-rp1pd = down, pn = none)a0–a8)# 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
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.
pinctrl-rp1-cCHIP=$(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
What no longer works
RuntimeError or silent nonsenselgpio/rgpio from the same author insteadimport 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
when_pressed / when_released — edge-driven under the hoodbounce_time=) — mechanical switches bounce for 1–20 msBuses
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
dtoverlay=i2c3-pi5 on GPIO4/5, i2c2-pi5, i2c0-pi5…dtparam=i2c_arm_baudrate=400000. 100k is the default and the safe onei2cdetect? 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
dtoverlay=spi0-2cs, or spi0-0cs to free both and drive CS yourselfdtoverlay=spi1-1cs — GPIO19/20/21 with CE on GPIO18apt install python3-spidev, then spi.open(0,0)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/serial0 points here by defaultuart0-pi5 … uart4-pi5 — note the -pi5 suffixearlycon=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
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
func=4 in the overlay1-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
from gpiozero import CPUTemperature is the internal one; DS18B20 is just a file readI2S, PIO and the odd ones
dtparam=i2s=onhifiberry-dacplus, iqaudio-dac, max98357a…dtoverlay=gpio-ir for receive, pwm-ir-tx for transmitpinctrl -P get shows you who won.
The device tree is resolved at boot and the first claim usually wins silently.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
Booting off NVMe or USB
- Boot from the SD card, fit the drive, confirm it appears in
lsblk. - 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-cloneor the desktop's SD Card Copier. - Change the boot order:
sudo rpi-eeprom-config --edit # BOOT_ORDER=0xf416 NVMe → SD → USB → repeat # PCIE_PROBE=1 only for non-HAT+ adapters
sudo reboot, thenlsblkto confirm/is onnvme0n1p2.
Storage housekeeping
/etc/fstabUSB
usb_max_current_enable=1Camera, display and audio
Two 22-pin FPC ports, each of which can be a camera or a display. The camera stack is
libcamera — raspistill 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
apt install python3-picamera2rpicam-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
display_auto_detect=1apt
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
dtparam=audio=off alongside itPower, 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 bits0x50005 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
POWER_OFF_ON_HALT=1Cooling
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.
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
ondemand by defaultcpufrequtils; powersave to pin it low# a mild, widely-stable overclock — Active Cooler required # /boot/firmware/config.txt arm_freq=2800 over_voltage_delta=50000
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
- 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.
- Turn on zram. Compressed swap in RAM, ~3:1 on typical pages. It is the single highest-value change on a small board.
- Give it a real swap file too, on NVMe if you have one, as an overflow behind zram.
- Cut
gpu_mem? Not on a Pi 5 — memory is allocated dynamically by the CMA driver and the oldgpu_memsplit doesn't apply. - Disable what you don't use: Bluetooth, Wi-Fi if you're on Ethernet, Avahi, ModemManager, triggerhappy, cups.
- 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
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
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
pi5.local resolve, and you will lose your only way in.What fits, and what doesn't
--disable-dev-shm-usagemake -j2, not -j4, and add swapCommand 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
vcgencmd commands lists them allSystem & services
POWER_OFF_ON_HALTProcesses & monitoring
-9 if it won't gosysstatnetstatget_throttledFiles, transfer & networking
xzf to extractHardware inspection
lm-sensors; also /sys/class/hwmon/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
nmcli/boot/firmware/config.txt. There's a compatibility symlink on some images and not on othersrpicam-still / rpicam-vidrpicam-hello in Bookwormfull-upgrade; plain upgrade will hold back the packages that matterWiring traps
pinctrl -p switches to physical if you mustSoftware and system traps
gpiodetect | grep pinctrl-rp1gpioset gpiochip0 17=1 vs gpioset -c gpiochip0 17=1-z to daemonise or -t to toggle[all]a [pi5] filter applies to everything after it. Close your conditional blocksvcgencmd get_throttled before you blame the cardsudo poweroff, every timessh-keygen -R pi5.local on your laptop