Suggestion for PWM fan control of Raspberry Pi Zero 2W
The commonly shared PWM fan control examples weren’t quite right for my use case on the Raspberry Pi Zero 2W. Most are designed with the Pi 4 or 5 in mind, but the MeshAdv-Mini setup with a Zero 2W has different requirements. I needed a solution that was lightweight, headless, and tailored to the specific behavior of the fan.
Setup: Raspberry Pi Zero 2W (SSH access only, no desktop environment) Raspbian GNU/Linux 12 (Bookworm) Waveshare UPS HAT (C) (optional) MeshAdv-Mini enclosure with PWM-controlled fan PWM fan (typically 3-wire: red, black, blue) connected to the MeshAdv-Mini PWM fan header
Raspberry Pi Zero 2W: Automatic Fan Control with PWM + systemd This sets up automatic fan control using a Waveshare UPS HAT and GPIO-based PWM on a Pi Zero 2W. The fan spins only when needed and stops when idle.
Step 1: Calibrate Your Fan (Optional, Recommended)
sudo nano /root/fan_calibrate.py
Paste this:
#!/usr/bin/env python3
import RPi.GPIO as GPIO
import time
FAN_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(FAN_PIN, GPIO.OUT)
pwm = GPIO.PWM(FAN_PIN, 25)
pwm.start(0)
print("Enter fan speed (0-100). Type 'exit' or press Ctrl+C to quit.")
try:
while True:
val = input("Set fan speed: ").strip()
if val.lower() == 'exit':
break
try:
dc = int(val)
if 0 <= dc <= 100:
pwm.ChangeDutyCycle(dc)
else:
print("Enter a value from 0 to 100.")
except:
print("Invalid input.")
except KeyboardInterrupt:
pass
finally:
pwm.ChangeDutyCycle(0)
pwm.stop()
GPIO.cleanup()
print("Fan off. GPIO cleaned up.")
Then make it executable and run:
sudo chmod +x /root/fan_calibrate.py
sudo /root/fan_calibrate.py
Find the lowest duty cycle where the fan reliably starts (e.g., 25%) and remember that value.
Step 2: Create the Fan Control Script
sudo nano /usr/local/bin/fan_control.py
Paste this:
#!/usr/bin/env python3
import time
import RPi.GPIO as GPIO
FAN_PIN = 18
FAN_MIN = 25 # Minimum duty cycle for reliable spin
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(FAN_PIN, GPIO.OUT)
pwm = GPIO.PWM(FAN_PIN, 25)
pwm.start(0)
def get_cpu_temp():
try:
with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
return float(f.read()) / 1000.0
except:
return 0.0
try:
while True:
temp = get_cpu_temp()
if temp < 45:
fan_speed = 0
elif temp > 70:
fan_speed = 100
else:
fan_speed = (temp - 45) * 4
if 0 < fan_speed < FAN_MIN:
fan_speed = FAN_MIN
pwm.ChangeDutyCycle(fan_speed)
print(f"CPU Temp: {temp:.1f}°C | Fan: {int(fan_speed)}%")
time.sleep(15)
except KeyboardInterrupt:
pass
finally:
pwm.ChangeDutyCycle(0)
pwm.stop()
GPIO.cleanup()
Then:
sudo chmod +x /usr/local/bin/fan_control.py
Step 3: Create the systemd Service
sudo nano /etc/systemd/system/fan_control.service
Paste this:
[Unit]
Description=MeshAdv Mini Fan Control Script
After=network.target
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/fan_control.py
Restart=on-failure
User=root
[Install]
WantedBy=multi-user.target
Then enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable fan_control.service
sudo systemctl start fan_control.service
Verifying It Works
Watch live output:
sudo journalctl -u fan_control.service -f
Check CPU temp:
cat /sys/class/thermal/thermal_zone0/temp
(straight °C = divide by 1000)
Stress test (optional):
yes > /dev/null &
yes > /dev/null &
Let temp rise above 45°C — the fan should start. Then:
killall yes
**Note: This solution was developed and tested in my specific environment with the help of ChatGPT. It works reliably on a Raspberry Pi Zero 2W running Raspbian Bookworm with the MeshAdv-Mini and a PWM-controlled fan. Your setup may require minor adjustments, especially if your Python environment or GPIO configuration differs. Always test and calibrate based on your specific hardware.