If you are trying to generate a clean, jitter-free PWM on Raspberry Pi for motor control, precision LED dimming, or acoustic avoidance, you must use hardware PWM. The direct answer: hardware PWM on the Raspberry Pi 4 is strictly limited to GPIO 12, 13, 18, and 19. Any other pin relies on software PWM, which uses CPU interrupts and introduces severe timing jitter that will cause motor whine and visible LED flicker.
This guide targets the Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS Bookworm (64-bit). We will wire a 12V DC fan through a logic-level MOSFET, write a robust Python script using the pigpio daemon, and debug the most common socket errors that halt embedded projects.
Hardware vs. Software PWM: The BCM2711 Silicon Reality
The Broadcom BCM2711 SoC inside the Pi 4 has two dedicated hardware PWM channels: PWM0 and PWM1. This silicon reality dictates how you design your circuit:
- PWM0 drives GPIO 12 and GPIO 18.
- PWM1 drives GPIO 13 and GPIO 19.
Software PWM (available on all other GPIO pins) works by asking the Linux kernel to toggle the pin high and low via timers. Under heavy CPU load, the kernel delays these timers, resulting in a jittery duty cycle. For a 12V fan, this jitter translates into an audible, high-pitched whine. Hardware PWM offloads this to dedicated silicon, guaranteeing a perfect square wave regardless of OS load.
Parts List & Pin Mapping (Raspberry Pi 4 Model B)
Do not use a standard IRF520 MOSFET. The Pi outputs 3.3V on its GPIO pins, and an IRF520 requires 10V at the gate to fully open its channel. You must use a logic-level MOSFET that fully saturates at 3.3V, such as the IRLZ44N or IRLB8721.
| Component | Exact Model / Spec | Purpose |
|---|---|---|
| Microcontroller | Raspberry Pi 4 Model B (4GB) | PWM signal generation (BCM2711) |
| MOSFET | IRLZ44N (Logic-Level, N-Channel) | Switches 12V load based on 3.3V gate signal |
| Gate Resistor | 100Ω (1/4W) | Limits inrush current to the gate capacitance |
| Pulldown Resistor | 10kΩ (1/4W) | Prevents floating gate during Pi boot sequence |
| Flyback Diode | 1N4007 or 1N5819 (Schottky) | Clamps inductive kickback from the fan motor |
| Load | 12V DC PWM-compatible Fan | Target inductive load |
Pin Mapping Table
| Pi 4 Physical Pin | BCM GPIO | Connects To |
|---|---|---|
| Pin 12 | GPIO 18 (PWM0) | 100Ω Resistor → MOSFET Gate |
| Pin 14 | GND | MOSFET Source & 10kΩ Pulldown |
| Pin 2 | 5V (Optional) | Pi logic reference (Not used for 12V load) |
Step-by-Step Wiring Procedure
Before touching any wires, ensure the Pi is powered down and the 12V supply is disconnected. Inductive loads like fans will generate voltage spikes that can fry the Pi’s SoC if wired incorrectly.
- Place the Pulldown Resistor: Connect the 10kΩ resistor between the MOSFET’s Gate (pin 1) and Source (pin 3). Why? When the Pi boots, GPIO pins float in a high-impedance state. Without a pulldown, ambient noise can partially turn on the MOSFET, causing it to overheat and fail before the OS even loads.
- Wire the Gate Signal: Connect Pi GPIO 18 (Physical Pin 12) through the 100Ω gate resistor to the MOSFET Gate. The resistor prevents high-frequency ringing and limits the current spike when charging the MOSFET’s internal gate capacitance.
- Connect the Load and Flyback Diode: Connect the 12V fan’s positive wire to your 12V power supply. Connect the fan’s negative wire to the MOSFET’s Drain (pin 2). Place the 1N4007 diode in parallel with the fan, with the cathode (stripe) facing the 12V positive. This provides a safe path for the inductive kickback when the MOSFET switches off.
- Complete the Ground Path: Connect the MOSFET’s Source (pin 3) to the Pi’s GND (Physical Pin 14). Crucial: You must also tie the 12V power supply’s ground to the Pi’s GND. If the grounds are not shared, the 3.3V gate signal has no reference potential and the MOSFET will not switch.
Compilable Python Code: Hardware PWM Fan Ramp
We use the pigpio library because it provides direct access to the BCM2711 hardware PWM clock registers. Note that pigpio defines duty cycle in millionths (0 to 1,000,000), not percentages. A 50% duty cycle is 500000.
import pigpio
import time
import sys
# --- PIN DEFINITIONS ---
# GPIO 18 is Physical Pin 12, mapped to Hardware PWM0
PWM_PIN = 18
PWM_FREQ = 25000 # 25kHz avoids acoustic fan whine
# Initialize pigpio connection to the local daemon
try:
pi = pigpio.pi()
if not pi.connected:
raise ConnectionError("Failed to connect to pigpiod daemon.")
except Exception as e:
print(f"[FATAL] {e}")
sys.exit(1)
try:
print(f"Starting hardware PWM on GPIO {PWM_PIN} at {PWM_FREQ}Hz...")
# Ramp up from 0% to 100% over 5 seconds
for duty_percent in range(0, 101, 5):
# pigpio hardware_PWM expects duty cycle in millionths (0-1000000)
duty_millionths = int(duty_percent * 10000)
pi.hardware_PWM(PWM_PIN, PWM_FREQ, duty_millionths)
print(f"Duty Cycle: {duty_percent}%")
time.sleep(0.25)
# Hold at 100% for 3 seconds
time.sleep(3)
# Ramp down to 0%
for duty_percent in range(100, -1, -5):
duty_millionths = int(duty_percent * 10000)
pi.hardware_PWM(PWM_PIN, PWM_FREQ, duty_millionths)
time.sleep(0.25)
except KeyboardInterrupt:
print("\n[INFO] Interrupted by user.")
finally:
# Always clean up: set duty to 0 and stop PWM
pi.hardware_PWM(PWM_PIN, 0, 0)
pi.stop()
print("[INFO] PWM stopped and GPIO cleaned up.")
Debugging: "Can't connect to pigpio at localhost(8888)"
If you run the script above and immediately hit a wall, you will likely see this exact traceback:
pigpio.error: "Can't connect to pigpio at localhost(8888)"
Did you start the pigpio daemon? E.g. sudo pigpiod
Unlike RPi.GPIO which runs entirely in user-space (and is largely deprecated on modern 64-bit Bookworm), pigpio requires a background daemon (pigpiod) to interface with the hardware registers securely. Here are the first three things to check when this fails:
- The Daemon is Not Running: By default,
pigpioddoes not start on boot. You must enable it via systemd. Run:
sudo systemctl enable pigpiod
sudo systemctl start pigpiod
Verify it is active withsystemctl status pigpiod. - Port 8888 is Blocked or Bound: The daemon listens on TCP port 8888. If you have a firewall (like
ufw) enabled, or another service is bound to that port, the socket connection will be refused. Check port usage withsudo lsof -i :8888. - You Are Using a Raspberry Pi 5: The Pi 5 uses the new RP1 southbridge chip, which completely changes the GPIO memory mapping. The legacy
pigpioC-library has limited/experimental support for Pi 5 hardware PWM. If you are on a Pi 5, you must switch to therpi-lgpiobackend or use the officiallibgpiodPython bindings instead ofpigpio.
sudo pigpiod -n 0.0.0.0 to allow remote socket connections. Be aware this bypasses local authentication; only do this on isolated, trusted LANs.
Extending and Simplifying the Build
Once you have a stable hardware PWM signal, you can adapt the circuit to fit your specific project constraints.
How to Extend: Closed-Loop PID Thermal Control
To turn this into a smart thermal manager, add an I2C temperature sensor like the TMP117 or BME280. Wire the sensor’s SDA/SCL to GPIO 2 and 3. In your Python loop, read the temperature and feed it into a simple PID controller (using the simple-pid library) to dynamically adjust the duty_millionths value. This maintains an exact chassis temperature rather than relying on a static fan curve.
How to Simplify: Software PWM for LEDs
If you are just dimming a 5V LED strip and don’t care about microsecond jitter, drop the pigpio daemon requirement entirely. Use the built-in gpiozero library with software PWM. It requires zero daemon setup and works on any GPIO pin. The 1kHz software jitter is completely invisible to the human eye when driving LEDs, saving you the overhead of managing background services.
Frequently Asked Questions
Can I output hardware PWM on Raspberry Pi GPIO 17 or 27?
No. The BCM2711 silicon only routes the dedicated hardware PWM channels to GPIO 12, 13, 18, and 19. If you attempt to use pigpio.hardware_PWM() on GPIO 17, the library will throw an error or silently fall back to software PWM depending on the wrapper. For any pin outside the 12/13/18/19 cluster, you are restricted to software PWM.
Why does my 12V DC fan whine or buzz when using Raspberry Pi PWM?
Acoustic whine occurs when the PWM frequency falls within the human hearing range (typically 20Hz to 20kHz). The coils inside the fan motor physically vibrate at the switching frequency. To eliminate this, set your PWM_FREQ to 25000 (25kHz). This pushes the switching noise above human hearing. Note that pushing frequencies above 30kHz can cause excessive heat in standard MOSFETs due to switching losses, so 25kHz is the sweet spot for the IRLZ44N.
Do I absolutely need a logic-level MOSFET for Raspberry Pi PWM?
Yes. The Raspberry Pi GPIO pins output 3.3V when HIGH. A standard MOSFET like the IRF520 has a gate threshold voltage (Vgs) of around 4V to 10V. At 3.3V, it will barely open, acting like a high-value resistor rather than a switch. This will cause the MOSFET to overheat rapidly and potentially damage your Pi’s GPIO bank. Always check the datasheet for the Vgs(th) specification and ensure it is rated for 3.3V logic.






