PWM fan on Raspberry Pi 4

 · 4 min · torgeir

Pi Rpi4 Fan Pvm

Trondheim had a few crazy warm days with around ~28°C early this summer and I had a few of these Noctua NF-A4x10 5V PWM laying around. So I decided to try and mount one on the Raspberry Pi 4 running our home assistant, to make it run a little cooler.

These are roughly the steps I took.

This was needed to make pip available on arch for arm.

sudo pacman -S --needed base-devel
sudo pacman -Syu python-pip
python -m ensurepip
pip install RPi.GPIO

I should probably have used a venv for this, as pip packages installed this way from the os wide python may dissappear after an upgrade when python is upgraded. It’ll do for now. On the raspberry pi debian os you can install pip with apt install python3-pip. RPi.GPIO is already present here.

I drilled e few holes in the case, freehand.

Yes, the uneven spacing bothers me. A lot. I swear I will never do it again.

So I mounted the fan on the outside, to pull air out, kinda to cover it up, using the provided case fan vibration dampeners that came in the box. Before you rush to the comment section (that does not exist yet) to say that they are mounted the wrong way around - I know, it was easier.

PWM, Pulse Width Modulation, is a technique to control the average power output by rapidly switching a digital signal between HIGH and LOW, to simulate an analog signal’s variable voltage level. It runs on a given frequency, that determines how fast the switching between the HIGH and LOW states occur.

This way the PWM fan can run off of the digital GPIO pins of the Raspberry Pi.

The following will set pin 14 to output (using broadcom chip-specific pin numbers, BCM) and have lib RPi.GPIO control it in PWM mode, and start the fan a duty cycle of 50%. I found some other code that used 50kHz as the frequency, and it seemed to work fine. You can try this directly in a python repl.

>>> import RPi.GPIO as GPIO
>>> GPIO.setmode(GPIO.BCM)
>>> GPIO.setup(14, GPIO.OUT)
>>> pwm = GPIO.PWM(14, 50)
>>> pwm.start(50)

The fan needs the following connections on the Pi:

  • The yellow wire connects to 5V
  • The black wire connect to GND
  • The blue wire connects to the PWM signal

I exposed the fan wire and cut away the green wire, its for signalling the fan RPM speed and is not needed for this.

For max confusion I connected a green end to the yellow wire, a white end to the black wire and an orange end to the blue wire. (I’m kidding, I didnt have matching colored wire ends that stuck onto the pins of the Pi.)

These plug into the 2nd, 3rd and 4th pins on the Pi:

  • 2nd pin on the outer row from the top (pin 4, 5V): Yellow fan wire.
  • 3rd pin on the outer row from the top (pin 6, GND): Black fan wire.
  • 4th pin on the outer row from the top (pin 8, GPIO 14): Blue fan wire.

Here’s a great site to look it up if you are unsure. Some heatshrink nicely covered up the mess of wrong colors.

To actually know that my script was running and the fan was not just running at 100% all the time, I started it off at 100% and adjusted it to 25% after some time. There’s really no need for the while True loop in the script below, I merely used it for experiments.

import time
import RPi.GPIO as gpio

gpio.setmode(gpio.BCM)
gpio.setup(14, gpio.OUT)

pwm = gpio.PWM(14, 50)
pwm.start(100)

while True:
  time.sleep(1)
  pwm.ChangeDutyCycle(25)

def signal_handler(sig, frame):
  pwm.stop()
  gpio.cleanup()
  raise KeyboardInterrupt

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

25% was about as low as I could go for the fan duty without the fan stopping, so I kept it there. This is barely audible and works quite well.

One thing to note is that the python program that sets the fan duty cycle, either with pwm.start() or pwm.ChangeDutyCycle(), needs to keep running to keep the fan running on the chosen frequency.

I created a systemd service to make the script run on boot.

cat <<EOF > /etc/systemd/system/fan.service
[Unit]
Description=pwm-fancontrol

[Service]
ExecStart=/usr/bin/python /root/fan.py

[Install]
WantedBy=multi-user.target
EOF

And enabled the service to run at once.

sudo systemctl enable --now fan.service
Created symlink /etc/systemd/system/multi-user.target.wants/fan.service → /etc/systemd/system/fan.service.

The Pi definitely ran a lot cooler, around the low to mid 40°C, compared to somewhere around 50°C without it, doing normal home assistant stuff. Ambient temperature in the room was around 25°C. You can measure the temperatures of the CPU on the Pi with something like lm_sensors.

It would probably run the even cooler with the fan in a push configuration, mounted inside the case, but this worked well enough that I kept it.

Resources

Edits

[2023-08-20 Sun]

  • It was 50kHz, not 1kHz. I don’t think it made a huge difference.
  • Mention how to install on raspberry pi os