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:

  1. Requires=/After=sys-subsystem-net-devices-wlan0.device — udev creates this unit automatically once the kernel publishes wlan0, so the script doesn't start before the interface exists.
  2. The extra 1500 argument 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.