PWM Fan Control

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.