Raspberry Pi PINAS LEDs
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 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
Pin map for PiNAS for LEDs - Disk Activity Extra LEDS
| GPIO | Header pin | Notes | Led Use |
|---|---|---|---|
| 27 | 13 | X1009 Hard Disk 1 | sda |
| 18 | 29 | X1009 Hard Disk 2 | sdb |
| 19 | 35 | X1009 Hard Disk 3 | sdc |
| 20 | 38 | - Disk Space | |
| 21 | 40 | Led 05 - Disk Space | |
| 22 | 15 | Led 06 - Disk Space |
Wiring
GPIOxx ──┬── [ R ] ──▶|── GND
│ LED
Resistor on either side of the LED is fine. Anode ( +ve ) is the long leg, connect it to GPIO. The cathode ( -ve, short leg ) connects 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.
All the pins available
On the standard 40-pin Raspberry Pi header, the GPIOs generally available for LEDs are: Physical Pin GPIO 3 GPIO2 5 GPIO3 7 GPIO4 8 GPIO14 10 GPIO15 11 GPIO17 12 GPIO18 13 GPIO27 15 GPIO22 16 GPIO23 18 GPIO24 19 GPIO10 21 GPIO9 22 GPIO25 23 GPIO11 24 GPIO8 26 GPIO7 29 GPIO5 31 GPIO6 32 GPIO12 33 GPIO13 35 GPIO19 36 GPIO16 37 GPIO26 38 GPIO20 40 GPIO21
That's 26 GPIO pins in total.
Since you're using 12, you have 14 more available if you're happy to repurpose the interface pins:
Pin 3 – GPIO2 (I²C SDA)
Pin 5 – GPIO3 (I²C SCL)
Pin 8 – GPIO14 (UART TX)
Pin 10 – GPIO15 (UART RX)
Pin 12 – GPIO18
Pin 13 – GPIO27
Pin 19 – GPIO10 (SPI MOSI)
Pin 21 – GPIO9 (SPI MISO)
Pin 23 – GPIO11 (SPI SCLK)
Pin 24 – GPIO8 (SPI CE0)
Pin 26 – GPIO7 (SPI CE1)
Pin 32 – GPIO12
Pin 33 – GPIO13
Pin 35 – GPIO19
Current limits
The limiting factor is usually current, not the number of GPIOs.
Aim for 2–5 mA per LED—modern LEDs are plenty bright at that current.
Don't exceed 16 mA per GPIO (absolute maximum).
Keep the total GPIO current below about 50 mA across the entire Pi for reliable operation.
If you're using 330 Ω resistors on a 3.3 V GPIO with a typical red LED, you're drawing roughly 3–5 mA per LED, so 26 LEDs is generally achievable if not all are driven at maximum current simultaneously.
Yep, if you're using the X1009, I'd leave the I²C pins alone.
That means you should reserve:
Pin 3 – GPIO2 (SDA)
Pin 5 – GPIO3 (SCL)
Assuming you're not using SPI or the UART, that leaves these 12 additional GPIOs beyond the ones you're already using: Physical Pin GPIO Notes 8 GPIO14 UART TX (available if serial console disabled) 10 GPIO15 UART RX 12 GPIO18 Good general-purpose GPIO 13 GPIO27 Good general-purpose GPIO 19 GPIO10 SPI MOSI 21 GPIO9 SPI MISO 23 GPIO11 SPI SCLK 24 GPIO8 SPI CE0 26 GPIO7 SPI CE1 32 GPIO12 Good general-purpose GPIO 33 GPIO13 Good general-purpose GPIO 35 GPIO19 Good general-purpose GPIO
So with the X1009 using I²C:
12 LEDs already connected
12 more GPIOs potentially available
24 LEDs total on the 40-pin header without adding expanders.
If you don't need SPI or the UART either, those pins are perfectly fine for LEDs. If you do need more than 24 indicators in the future, an I²C GPIO expander like an MCP23017 would normally be the next step—but since your X1009 is already on I²C, it can happily share the same bus with additional I²C devices.
And then is there a disk activity for the sda-sde on the X1009 to get an activity led ?
Yes, but not via a dedicated GPIO signal from the X1009.
The X1009 is essentially a SATA HAT. The SATA drives (/dev/sda–/dev/sde) appear as normal Linux block devices, but the HAT doesn't expose individual hardware activity LED pins for each drive.
You have a few options:
Software activity LEDs (recommended) – This is probably the best fit for what you're already building. Your daemon can monitor disk I/O statistics and drive a GPIO LED for each drive.
Read /sys/block/sdX/stat or /proc/diskstats.
If the read/write sector counts change between polls, flash the corresponding LED.
Polling every 100–250 ms gives a responsive activity LED.
Use the Linux LED trigger – The kernel provides a disk-activity LED trigger, but it's a single global activity indicator, not per-drive.
Tap SATA hardware signals – SATA activity signaling exists electrically, but the X1009 doesn't bring those signals out to headers. Modifying the hardware to extract them would be quite involved.
Since you've already written a GPIO LED daemon (we've worked on it over several conversations), I'd go with option 1. It integrates nicely with your existing framework and lets you have:
GPIO17 → sda
GPIO18 → sdb
GPIO27 → sdc
GPIO22 → sdd
GPIO23 → sde
or any mapping you prefer.
The implementation is straightforward: cache the previous values from /sys/block/sdX/stat (or /proc/diskstats), compare them each polling interval, and pulse the LED whenever the counters increase. This works regardless of the filesystem or RAID configuration because it monitors block-device activity directly.
Lets turn this into use GPIO17 for sda 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
Yep. For a disk, the simplest thing is to read /sys/block/sda/stat. The first 11 fields are documented by the kernel, and activity is indicated by the read/write counters increasing.
Here's a version hardcoded to GPIO17 and /dev/sda:
from gpiozero import LED from time import sleep
LED_PIN = 17 DEVICE = "sda"
POLL = 0.05 # seconds between reads BLINK = 0.03 # LED on-time
led = LED(LED_PIN)
def disk_activity(dev): with open(f"/sys/block/{dev}/stat") as f: fields = f.read().split()
# reads completed + writes completed
return int(fields[0]) + int(fields[4])
prev = disk_activity(DEVICE)
while True: sleep(POLL) now = disk_activity(DEVICE)
if now > prev:
led.on()
sleep(BLINK)
led.off()
prev = now
Or, if you want to keep it flexible like your network version:
import sys from gpiozero import LED from time import sleep
DEVICE = sys.argv[1] if len(sys.argv) > 1 else "sda" LED_PIN = int(sys.argv[2]) if len(sys.argv) > 2 else 17
POLL = 0.05 BLINK = 0.03
led = LED(LED_PIN)
def disk_activity(dev): with open(f"/sys/block/{dev}/stat") as f: fields = f.read().split() return int(fields[0]) + int(fields[4])
prev = disk_activity(DEVICE)
while True: sleep(POLL) now = disk_activity(DEVICE)
if now > prev:
led.on()
sleep(BLINK)
led.off()
prev = now
That lets you start it like:
python3 disk-led.py sda 17 python3 disk-led.py sdb 18 python3 disk-led.py sdc 27 python3 disk-led.py sdd 22 python3 disk-led.py sde 23
This is very lightweight—the kernel updates /sys/block/*/stat in memory, so even polling every 50 ms for five drives has negligible CPU overhead.
Did I not say GPIO17 Pin 11 was in use ?
You did. My mistake.
From your earlier list, you're already using:
Pin 11 → GPIO17 ✅
Pin 7 → GPIO4
Pin 29 → GPIO5
Pin 31 → GPIO6
Pin 36 → GPIO16
Pin 38 → GPIO20
Pin 40 → GPIO21
Pin 15 → GPIO22
Pin 16 → GPIO23
Pin 18 → GPIO24
Pin 22 → GPIO25
Pin 37 → GPIO26
And you've ruled out:
GPIO2/GPIO3 (Pins 3 & 5) for the X1009's I²C.
So the next free GPIOs I'd use are: Disk GPIO Physical Pin sda GPIO27 Pin 13 sdb GPIO18 Pin 12 sdc GPIO12 Pin 32 sdd GPIO13 Pin 33 sde GPIO19 Pin 35
Just change:
LED_PIN = 27 DEVICE = "sda"
or, if using the parameterised version:
python3 disk-led.py sda 27 python3 disk-led.py sdb 18 python3 disk-led.py sdc 12 python3 disk-led.py sdd 13 python3 disk-led.py sde 19
That keeps all of your existing LEDs intact and avoids the I²C pins.