Raspberry Pi GPIO — LED Reference
40-pin header (Pi 1 B+ / 2 / 3 / 4 / 5 / Zero). All GPIO pins are 3.3 V logic. Per-pin source/sink limit ≈ 16 mA, total across the header ≈ 50 mA. Target 5–10 mA per LED to stay well inside those budgets.
Pinout map
3V3 (1) (2) 5V
GPIO2 SDA (3) (4) 5V
GPIO3 SCL (5) (6) GND
GPIO4 (7) (8) GPIO14 TXD
GND (9)(10) GPIO15 RXD
GPIO17 (11)(12) GPIO18 PWM0
GPIO27 (13)(14) GND
GPIO22 (15)(16) GPIO23
3V3 (17)(18) GPIO24
GPIO10 MOSI(19)(20) GND
GPIO9 MISO(21)(22) GPIO25
GPIO11 SCLK(23)(24) GPIO8 CE0
GND (25)(26) GPIO7 CE1
ID_SD (27)(28) ID_SC <- reserved (HAT EEPROM)
GPIO5 (29)(30) GND
GPIO6 (31)(32) GPIO12 PWM0
GPIO13 PWM1(33)(34) GND
GPIO19 PWM1(35)(36) GPIO16
GPIO26 (37)(38) GPIO20
GND (39)(40) GPIO21
Pins safe for LEDs
Free to use without conflicting with default peripherals:
| GPIO | Header pin | Notes | Led Use |
|---|---|---|---|
| 4 | 7 | Power LED | |
| 5 | 29 | Ethernet Activity LED | |
| 6 | 31 | Led 01 - Disk Space | |
| 12 | 32 | hardware PWM0 | Led XX - |
| 13 | 33 | hardware PWM1 | Led XX - |
| 16 | 36 | Led 02 - Disk Space | |
| 17 | 11 | Led 03 - Disk Space | |
| 18 | 12 | hardware PWM0 | Led XX - |
| 19 | 35 | hardware PWM1 | Led XX - |
| 20 | 38 | Led 04 - Disk Space | |
| 21 | 40 | Led 05 - Disk Space | |
| 22 | 15 | Led 06 - Disk Space | |
| 23 | 16 | Led 07 - Disk Space | |
| 24 | 18 | Led 08 - Disk Space | |
| 25 | 22 | Led 09 - Disk Space | |
| 26 | 37 | Led 10 - Disk Space | |
| 27 | 13 |
GPIO 12 / 13 / 18 / 19 are the hardware PWM channels — use these if you want smooth brightness/fade.
Pins to avoid (or use with care)
| GPIO | Pin(s) | Why |
|---|---|---|
| 0, 1 | 27, 28 | Reserved for HAT ID EEPROM — do not use |
| 2, 3 | 3, 5 | I²C, has 1.8 kΩ pull-ups to 3V3 — pulled high |
| 7, 8 | 26, 24 | SPI0 CE1/CE0 |
| 9, 10, 11 | 21, 19, 23 | SPI0 MISO / MOSI / SCLK |
| 14, 15 | 8, 10 | UART TXD/RXD — serial console by default |
If the corresponding interface is disabled in raspi-config these pins become regular GPIO and are fine for LEDs.
Wiring
GPIOxx ──┬── [ R ] ──▶|── GND
│ LED
Resistor on either side of the LED is fine. Anode (long leg, flat side of bulb is cathode) toward GPIO, cathode toward GND.
Resistor values (3.3 V supply)
R = (3.3 − Vf) / I. Picking the next-larger standard E12 value.
| LED colour | Vf (typ.) | 10 mA → calc | Use | 5 mA → calc | Use |
|---|---|---|---|---|---|
| Red | 2.0 V | 130 Ω | 150 Ω | 260 Ω | 330 Ω |
| Yellow | 2.1 V | 120 Ω | 150 Ω | 240 Ω | 270 Ω |
| Orange | 2.0 V | 130 Ω | 150 Ω | 260 Ω | 330 Ω |
| Green (std) | 2.1 V | 120 Ω | 150 Ω | 240 Ω | 270 Ω |
| Green (hi-b) | 3.0 V | 30 Ω | 47 Ω | 60 Ω | 100 Ω |
| Blue | 3.0 V | 30 Ω | 47 Ω | 60 Ω | 100 Ω |
| White | 3.0 V | 30 Ω | 47 Ω | 60 Ω | 100 Ω |
A 220 Ω resistor is a safe universal default for any colour from a 3.3 V GPIO — gives roughly 6 mA on a red, ~1.5 mA on a blue (dim but visible). Use 330 Ω if you only need an indicator.
Quick test (Python, gpiozero)
from gpiozero import LED
from time import sleep
led = LED(17) # BCM numbering — header pin 11
while True:
led.on(); sleep(0.5)
led.off(); sleep(0.5)
For PWM brightness use PWMLED(12) etc. on one of the hardware-PWM-capable pins.
Reading a PC fan tach (3rd pin) on GPIO
A standard 3-pin PC fan has: GND, +12 V, TACH. (A 4-pin fan adds a PWM control input — same tach pin.) The tach output is an open-collector pulse — the fan pulls the line low through an internal transistor, and an external pull-up supplies the high level. Most PC fans emit 2 pulses per revolution, so:
RPM = (pulses_counted / seconds) * 60 / 2
Wiring
The fan must be powered from 12 V (its own supply, not the Pi). Only the tach line goes to the Pi, with a pull-up to 3.3 V so the high level is GPIO-safe:
+12V ──────────────────── fan red (+12V)
GND ──┬───────────────── fan black (GND)
│
└───────────────── Pi GND (common ground is mandatory)
Pi 3V3 ── [ 10 kΩ ] ──┬── fan yellow/green (TACH)
└── Pi GPIOxx (input)
10 kΩ to 3.3 V is the standard value. Do not pull up to 5 V or 12 V — the fan's open-collector output will happily sit there, but it puts an over-voltage on the Pi input and will damage it.
If the fan turns out to have an internal pull-up to 12 V (uncommon but it happens on some server fans — measure the tach pin to GND with the fan running, before connecting to the Pi), the line will swing 0 → 12 V and you need a level shifter or a simple divider (e.g. 10 kΩ from tach to GPIO, 3.9 kΩ from GPIO to GND gives ~3.3 V at the GPIO from a 12 V high).
Pin choice
Any free GPIO from the "safe for LEDs" table above works as an input — there's no special hardware capture peripheral needed at PC-fan speeds (a 3000 RPM fan is only 100 Hz on the tach line). GPIO 4, 17, 27, 22, 5, 6, 16, 26 are all fine.
Quick test (Python, gpiozero)
from gpiozero import Button # convenient edge-counting wrapper
from time import sleep
tach = Button(4, pull_up=False) # external pull-up to 3V3 is on the wire
pulses = 0
def _tick():
global pulses
pulses += 1
tach.when_pressed = _tick # counts falling edges
while True:
pulses = 0
sleep(1.0)
print(f"{pulses * 30} RPM") # pulses/sec * 60 / 2
For better accuracy at low RPM, measure the period between two edges instead of counting over a fixed window.
Network activity LED (GPIO 5)
The kernel doesn't drive an activity LED on a header GPIO directly, so a tiny userspace daemon polls the interface's byte counter from /proc/net/dev and blinks the LED on every change. Resistor + wiring follow the LED section above (220 Ω from GPIO 5 to LED anode, cathode to GND).
One script, parameterised by interface and LED pin via CLI args — the same binary drives an eth0 LED, a wlan0 LED, or both at once on different GPIOs.
#!/usr/bin/env python3
"""Blink an LED briefly whenever <iface> has RX/TX traffic.
Usage: net-activity-led.py <iface> [gpio] [min_bytes]
iface - e.g. eth0, end0, wlan0
gpio - BCM pin number (default 5)
min_bytes - ignore counter deltas below this (default 0; use ~1500 on wlan0
to filter beacons / mDNS / background chatter)
"""
import sys
from gpiozero import LED
from time import sleep
IFACE = sys.argv[1]
LED_PIN = int(sys.argv[2]) if len(sys.argv) > 2 else 5
MIN_BYTES = int(sys.argv[3]) if len(sys.argv) > 3 else 0
POLL = 0.05 # seconds between counter reads
BLINK = 0.03 # LED on-time per detected activity tick
led = LED(LED_PIN)
def iface_bytes(name):
with open("/proc/net/dev") as f:
for line in f:
n, _, rest = line.partition(":")
if n.strip() == name:
cols = rest.split()
return int(cols[0]) + int(cols[8]) # rx_bytes + tx_bytes
return 0
prev = iface_bytes(IFACE)
while True:
sleep(POLL)
now = iface_bytes(IFACE)
if now - prev > MIN_BYTES:
led.on(); sleep(BLINK); led.off()
prev = now
Install it:
sudo install -m 0755 net-activity-led.py /usr/local/bin/net-activity-led.py
Interface name on Pi 4/5 may be end0 rather than eth0 for wired — check with ip -br link. The wireless interface is wlan0 on all current Pi OS releases.
Setup A — eth0 on GPIO 5
/etc/systemd/system/net-activity-led-eth.service:
[Unit]
Description=Ethernet activity LED (eth0 → GPIO 5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/bin/net-activity-led.py eth0 5
Restart=on-failure
RestartSec=2
User=pi
Group=gpio
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
Setup B — wlan0 on GPIO 5
/etc/systemd/system/net-activity-led-wlan.service:
[Unit]
Description=WLAN activity LED (wlan0 → GPIO 5)
Requires=sys-subsystem-net-devices-wlan0.device
After=sys-subsystem-net-devices-wlan0.device network-online.target
Wants=network-online.target
[Service]
Type=simple
# 3rd arg = min bytes per tick; ~1500 filters Wi-Fi beacons / mDNS chatter.
ExecStart=/usr/bin/python3 /usr/local/bin/net-activity-led.py wlan0 5 1500
Restart=on-failure
RestartSec=2
User=pi
Group=gpio
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
Two differences from setup A:
Requires=/After=sys-subsystem-net-devices-wlan0.device— udev creates this unit automatically once the kernel publisheswlan0, so the script doesn't start before the interface exists.- The extra
1500argument enables the noise filter so background beacons / ARP / mDNS don't keep the LED on solid.
Enable
Pick whichever setup you want (or run both on different GPIOs by giving each its own pin in ExecStart):
sudo systemctl daemon-reload
sudo systemctl enable --now net-activity-led-eth.service # or -wlan
systemctl status net-activity-led-eth.service
journalctl -u net-activity-led-eth.service -f
The same polling pattern works for other activity sources — swap iface_bytes() for a reader of /proc/diskstats (disk), /proc/stat (CPU) or /sys/class/thermal/thermal_zone0/temp (thermal threshold) and blink on change.