Add a Silent Fan to your Raspberry Pi 4

Mark Zachmann
Home Wireless
Published in
5 min readAug 27, 2023

--

I’ve been trying to move from a Mac Mini to a Raspberry Pi for my living room audio for a year and finally was able to get a Pi 4B retail. Unlike the 3, the Pi 4B works great with Ubuntu and CamillaDsp but… it is also so fast that the CPU needs a fan for heat control.

One Pi 4 with Fan Installed in This Case

Since one clear goal of an audio system is that it be silent this seemed bad and thus ensued days of research and trial and error. Finally, I present two ways — one great and one acceptable — to add a completely silent fan to your Pi at near-zero CPU usage and no additional components.

The non-PWM acceptable answer

I found this out by mistake. I bought a 3-pin Noctua Fan (Noctua NF-A4x10 5V) and mistakenly thought it had PWM. At 5V it’s loud and one answer was to put a resistor in series. But, the 3.3V rail on the Pi 4 can drive about 1/2 A while the Fan draws 1/20 A so I just powered it with 3.3V which worked fine and was nearly dead-silent. You can’t control it so it’s always on but it’s quiet.

For these to work you must connect:

  • Power -> The 3.3V Pi rail : Pin 1
  • Ground -> Pin 6

The PWM great answer

By using this 4-pin fan instead: Noctua NF-A4x10 5V PWM we can get fine-grained control over the fan speed and now it’s completely silent. I have to put my ear on the grill to hear anything at 50%.

For these to work you must connect:

  • Power -> The 3.3V Pi rail : Pin 1
  • Ground -> Pin 6
  • PWM -> one PWM pin, I used Pin 12 (GPIO 18) (PWM0)
  • Sensor -> I used pin 18 but I ignore it
The Audio Pi in Use — it’s hard to see the fan on top
Wiring the PWM Fan

Fan Tests

I did a simple test of my Pi4B Ubuntu Server running idle (no audio) and acting like a crossover (at which it uses 16% of the cpu). I roughly tested the Pi4B running Ubuntu Desktop — it was about 10°C hotter than Server.

| Fan Spd      |Idle °C    |Running °C  |
|--------------|-----------|------------|
| 0 | 58* | oo |
| 25 | 55 | 54 |
| 50 | 45 | 47 |
| 100 | 38.5 | 40 |

When the fan is off the heat in this case builds up.
*Tested with no case it hit 58°C with no fan.

Software

The usual way to control the fan is using gpio-fan in the firmware/config.txt as an overlay. This enables on/off support using the PWM pin based on temperature.

If you want adjustable speed the usual control method is via Python. The problem with that is when I tried the Python software PWM it was taking up 60% of my CPU! Sure the CPU was running at a reasonable temperature but the last thing you want when doing audio streaming is some serious background task.

So, I found a library pi-gpio that does hardware PWM. It has to be built so clone the git repository then run sudo make install (or ./build). This puts a dynamic library into the linux system and then I wrote a simple C program that sets the PWM parameters and then exits (leaving the PWM running).

Running at startup

Before getting to the PWM software, I ended up setting the speed once and leaving it. It’s nice to have this done at startup. To enable that…

Build a service file, named fan_begin.service (or w/e) and edit the service file to run a shell script in the home folder.

sudo nano /lib/systemd/system/fan_begin.service
[Unit]
Description=Start the PWM CPU fan

[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/home/myname/fan_begin.sh

[Install]
WantedBy=multi-user.target

Then, the shell script just runs the app and logs output…

nano /home/myname/fan_begin.sh
#!/bin/bash
cd /home/myname
./setPWMFan 60 >>fan_begin.log

Note that for you to run this it’s sudo ./fan_begin.sh. When run by the service it runs at root. To get the script to run at startup just type

sudo systemctl enable fan_begin

If you want to change speed based on temperature that can be done via a more complex shell script. I just set the speed to 60% and leave it currently.

The PWM fan driver

The only resource this app uses is one hardware PWM when set but it does set the ‘global’ pwm clock.

Most of this code comes from the pi-gpio example C app, not from me.

/*
This sets the pwm percentage for a given Raspberry Pi port number

syntax: setPWMFan FanSpeed% [FanPortNumber]
where FanSpeed% = [0..100]
FanPortNumber = pi Gpio port (often 18)
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pi-gpio.h>

#define PWM0 18 // default - to physical pin 12

int isBCM2711(void) {
unsigned rev = get_revision();
unsigned PROC = (rev&0x0000F000)>>12;
return PROC==3;
}

int main(int argc, char *argv[]) {
// The following gives a precise 25kHz PWM signal
// Noctua recommended value
// CLOCK / (DIVIDER * RANGE)
// Pi3 & earlier have 19.2MHz clock
int DIVIDER = 6;
int RANGE = 128;
int pwmSetting = 100; // default
int pwmPort = PWM0; // default
if(isBCM2711()) {
// Pi4 has 54MHz clock
DIVIDER = 18;
RANGE = 120;
}

if( argc == 2 || argc == 3) {
// printf("argv==%s\r\n", argv[1]);
int ax = atoi(argv[1]);
if( ax >= 0 && ax <= 100) {
pwmSetting = ax;
}
if (argc == 3) {
// printf("argv==%s, %s\r\n", argv[1], argv[2]);
ax = atoi(argv[2]);
if( ax > 0) {
pwmPort = ax;
}
}
printf("Setting fan %d speed to %d\n", pwmPort, pwmSetting);
}
else
{
printf("This function takes one or two arguments\n\
setPwmFan Spd [Num]\n\
Speed=[0...100] for percent pwm .\n\
Num=GPIO port #\n");
return(1);
}

setup();

pwmSetGpio(pwmPort);

pwmSetMode(PWM_MODE_MS); // use a fixed frequency
pwmSetClock(DIVIDER);

pwmSetRange(pwmPort, RANGE);
pwmWrite(pwmPort, RANGE * pwmSetting / 100); // duty cycle set

cleanup();
return 0; // PWM output stays on after exit
}

The easiest way to build this is to put it in an empty folder and add a version of the Makefile from the pi-gpio library examples folder, replacing the list of C files with just this one. Then type make.

--

--

Mark Zachmann
Home Wireless

Entrepreneur, software architect, electrical engineer. Ex-academic.