PWM Regulated Fan Based on CPU Temperature for Raspberry Pi

72,523

103

95

Introduction: PWM Regulated Fan Based on CPU Temperature for Raspberry Pi

Many cases for Raspberry Pi come with a little 5V fan in order to help cooling the CPU. However, these fans are usually pretty noisy and many people plug it on the 3V3 pin to reduce the noise. These fans are usually rated for 200mA which is pretty high for the 3V3 regulator on the RPi. This project will teach you how to regulate the fan speed based on CPU temperature. Unlike most of tutorials covering this subject, we won't only turn on or off the fan, but will control its speed like it's done on mainstream PC, using Python.

Step 1: Parts Needed

For this project, we will use only a few components that are usually included in electronics kits for hobbyist that you can find on Amazon, like this one.

  • Raspberry Pi running Raspbian (but should work with other distribs).
  • 5V Fan (but a 12V fan could be used with an adapted transistor and a 12V power supply).
  • NPN transistor that supports at least 300mA, like a 2N2222A.
  • 1K resistor.
  • 1 diode.

Optional, to put the components inside the case (but not done yet):

  • A little piece of protoboard, to solder the components.
  • Large heat shrink, to protect the board.

Step 2: Electrical Connections

Resistor can be plug in either way, but be careful about transistor's and diode's direction. Diode's cathode must be connected to the +5V (red) wire, and anode must be connected to the GND (black) wire. Check your transistor doc for Emitter, Base and Collector pins. Fan's ground must be connected to the Collector, and Rpi's ground must be connected to Emitter.

In order to control the fan, we need to use a transistor that will be used inopen collector configuration. By doing this, we have a switch that will connect or disconnect the ground wire from the fan to the ground of the raspberry pi.

A NPN BJT transistor conducts depending on the current that flows in its gate. The current that will be allowed to flow from the collector (C) to the emitter (E) is:

Ic = B * Ib

Ic is the current that flows through the collector the emitter, Ib is the current that flows through the base to the emitter, and B (beta) is a value depending on each transistor. We approximate B = 100.

As our fan is rated as 200mA, we need at least 2mA through the base of the transistor. The tension between the base and the emitter (Vbe) is considered constant and Vbe = 0,7V. This means that when the GPIO is on, we have 3.3 - 0.7 = 2.6V at the resistor. To have 2mA through that resistor, we need a resistor of, maximum, 2.6 / 0.002 = 1300 ohm. We use a resistor of 1000 ohm to simplify and keep a margin of error. We will have 2.6mA through the GPIO pin which is totally safe.

As a fan is basically an electrical motor, it is an inductive charge. This means when the transistor stops conducting, the current in the fan will continue flowing as an inductive charge tries to keep the current constant. This would results in a high voltage on the ground pin of the fan and could damage the transistor. That's why we need a diode in parallel with the fan which will make the current flow constantly through the motor. This type of diode setup is called a Flywheel diode

Step 3: Program to Control the Fan Speed

To control fan speed, we use a software PWM signal from the RPi.GPIO library. A PWM Signal is well adapted to drive electric motors, as their reaction time is very high compared to the PWM frequency.

Use the calib_fan.py program to find the FAN_MIN value by running in the terminal:

python calib_fan.py

Check several values between 0 and 100% (should be around 20%) and see what is the minimum value for your fan to turn on.

You can change the correspondence between temperature and fan speed at the beginning of the code. There must be as many tempSteps as speedSteps values. This is the method that is generally used in PC motherboards, moving points on a Temp / Speed 2-axis graph.

Step 4: Run the Program at Startup

To run the program automatically at startup, I made a bash script where I put all the programs I want to launch, and then I launch this bash script at startup with rc.locale

  1. Create a directory /home/pi/Scripts/ and place the fan_ctrl.py file inside that directory.
  2. In the same directory, create a file named launcher.sh and copy the script bellow.
  3. Edit the /etc/rc.locale file and add a new line before the "exit 0": sudo sh '/home/pi/Scripts/launcher.sh'

launcher.sh script:

#!/bin/sh
#launcher.sh # navigate to home directory, then to this directory, then execute python script, then back home
locale
cd /
cd /home/pi/Scripts/
sudo python3 ./fan_ctrl.py &
cd /

If you want to use it with OSMC for example, you need to start it as a service with systemd.

  1. Download the fanctrl.service file.
  2. Check the path to your python file.
  3. Place fanctrl.service in /lib/systemd/system.
  4. Finally, enable the service with sudo systemctl enable fanctrl.service.

This method is safer, as the program will be automatically restarted if killed by the user or the system.

Raspberry Pi Contest 2017

Participated in the
Raspberry Pi Contest 2017

First Time Author Contest 2018

Participated in the
First Time Author Contest 2018

22 People Made This Project!

Recommendations

  • The 1000th Contest

    The 1000th Contest
  • Battery Powered Contest

    Battery Powered Contest
  • Hand Tools Only Challenge

    Hand Tools Only Challenge

95 Discussions

0
itainelken
itainelken

Question 10 days ago

can I use a LED instead of a diode?

0
Sykobee
Sykobee

25 days ago

I built this and it runs - although I like the look of the protoboard version posted in the images.

To get the systemd.service running on Raspberry Pi OS (64-bit in my case, but shouldn't matter), the fanctrl.service (in /lib/systemd/system/fanctrl.service) needs to be modified as follows:

[Unit]
Description=PWM Fan Control for Raspberry Pi
After=ssh.service
[Service]
Type=simple
User=pi
ExecStart=/usr/bin/python3 /home/pi/Scripts/fan_ctrl.py
Restart=always
[Install]
WantedBy=default.target

Start with:

sudo systemctl start fanctrl.service

Check it is running (you should see it listed) with:

systemctl | grep fan

fanctrl.service loaded active running PWM Fan Control for Raspberry Pi

Permanently enable with:

sudo systemctl enable fanctrl.service

Test with a heavy load - I used Java Minecraft and it turned on during map generation and turned off after quitting, but it never became loud (although I have not overclocked this Pi).

0
lacchinig
lacchinig

5 weeks ago

Is it possible to use a PNP transistor instead of NPN?
Naturally changing the electric scheme.
I have in BC 139, I think it has a similar performances.

1
alecsandes
alecsandes

Question 2 months ago

Hi, I wonder how could I print the percentage of work of the fan? I would like to include this info in Conky widget. I am new to recent programming Langauages but I was using back in the days C++, java, html and pascal in high school.
Thanks!

0
Googliola
Googliola

10 months ago

I have a noctua fan (NF-A4x10 5V) with 3 wires. Can anyone tell me how to go about with the PWM wire? Do I need transistor and resistor? I could not find any infos how to wire it - well, maybe I have but as I am a total dimwit with electronics I just did not understand.....

Speaking of which: Using an RPi 3B, what GPIOs can or cannot be used for (soft?) PWM? For the sake of a small JST connector, I would like to use either GPIO02 (pin 03) or GPIO03 (pin 05)...hmmmmm

0
Aerandir14
Aerandir14

Reply 10 months ago

Hi,
3 wires computer fan don't have a PWM wire. The 3rd pin is for fan speed sensing.
You'd need the 5V PWM version (4 pins) to be able to use this tutorial without the transistor.
Even with a 4 pin fan, You would still need the resistor though. Noctua specifies a maximum current of 5mA in case of internal failure of the fan (https://noctua.at/media/wysiwyg/Noctua_PWM_specifications_white_paper.pdf). I'd try with a 1K resistor on the PWM wire and see how it goes.

Anyway, with a 3 pin wires, you have no choice but using an external resistor and transistor as explained in this tutorial.

0
perepo
perepo

Reply 5 months ago

Hi,
I already have this noctua fan (the three pin). Once built the circuit it seems to work ...but in my case it only starts spinning at 100%. I am using the 1k resistor
¿Could It be something related to the circuit or should I use another resistor?
Thanks for sharing the project, quite nice!


0
Aerandir14
Aerandir14

Reply 3 months ago

Somebody else reported being able to spin that fan at 70% min, I think it doesn't handle low speeds

0
ll2thextreme
ll2thextreme

5 months ago

I know this thread is a bit old, but I am hoping someone can answer my question. Is it possible to set this up to control 2 fans, not just one? Can I just use a second GPIO pin? I have built an NAS server with 2 RAID hard drives and all enclosed inside a vintage NES console. I have one fan on the Raspberry Pi itself to cool it and a second fan attached to the NES to help circulate air and keep the hard drives cool. I would really like to have them both controlled by the CPU temp because they are very loud. I currently have the case fan on the 3.3v pin to help with the noise. Is it possible to setup both of these fans to be controlled by the PWM software described in the guide?
0
Aerandir14
Aerandir14

Reply 3 months ago

Sure, but it would be probably better to use the same fans.
This should do the trick :
#!/usr/bin/python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys

# Configuration
FAN_PIN_1 = 21 # BCM pin used to drive 1st fan transistor's base
FAN_PIN_2 = 22 # BCM pin used to drive 2nd fan transistor's base
WAIT_TIME = 1 # [s] Time to wait between each refresh
FAN_MIN = 20 # [%] Fan minimum speed.
PWM_FREQ = 25 # [Hz] Change this value if fan has strange behavior

# Configurable temperature and fan speed steps
tempSteps = [50, 70] # [°C]
speedSteps = [0, 100] # [%]

# Fan speed will change only of the difference of temperature is higher than hysteresis
hyst = 1

# Setup GPIO pin
GPIO.setmode(GPIO.BCM)
GPIO.setup(FAN_PIN_1, GPIO.OUT, initial=GPIO.LOW)
fan1 = GPIO.PWM(FAN_PIN_1, PWM_FREQ)
GPIO.setup(FAN_PIN_2, GPIO.OUT, initial=GPIO.LOW)
fan2 = GPIO.PWM(FAN_PIN_2, PWM_FREQ)
fan1.start(0)
fan2.start(0)


i = 0
cpuTemp = 0
fanSpeed = 0
cpuTempOld = 0
fanSpeedOld = 0

# We must set a speed value for each temperature step
if len(speedSteps) != len(tempSteps):
print("Numbers of temp steps and speed steps are different")
exit(0)

try:
while 1:
# Read CPU temperature
cpuTempFile = open("/sys/class/thermal/thermal_zone0/temp", "r")
cpuTemp = float(cpuTempFile.read()) / 1000
cpuTempFile.close()

# Calculate desired fan speed
if abs(cpuTemp - cpuTempOld) > hyst:
# Below first value, fan will run at min speed.
if cpuTemp < tempSteps[0]:
fanSpeed = speedSteps[0]
# Above last value, fan will run at max speed
elif cpuTemp >= tempSteps[len(tempSteps) - 1]:
fanSpeed = speedSteps[len(tempSteps) - 1]
# If temperature is between 2 steps, fan speed is calculated by linear interpolation
else:
for i in range(0, len(tempSteps) - 1):
if (cpuTemp >= tempSteps[i]) and (cpuTemp < tempSteps[i + 1]):
fanSpeed = round((speedSteps[i + 1] - speedSteps[i])
/ (tempSteps[i + 1] - tempSteps[i])
* (cpuTemp - tempSteps[i])
+ speedSteps[i], 1)

if fanSpeed != fanSpeedOld:
if (fanSpeed != fanSpeedOld
and (fanSpeed >= FAN_MIN or fanSpeed == 0)):
fan1.ChangeDutyCycle(fanSpeed)
fan2.ChangeDutyCycle(fanSpeed)
fanSpeedOld = fanSpeed
cpuTempOld = cpuTemp

# Wait until next refresh
time.sleep(WAIT_TIME)


# If a keyboard interrupt occurs (ctrl + c), the GPIO is set to 0 and the program exits.
except KeyboardInterrupt:
print("Fan ctrl interrupted by keyboard")
GPIO.cleanup()
sys.exit()

0
mascka
mascka

5 months ago

I have a laptop fan that not starting on low pmw, speed, i have to help him with my finger to get it running. On boot, its start on full speed.
My question, how can i give him a bigger speed to start before the loop begin?
I changed the value of fa.start to 100 but nothing, added before while fan.ChangeDutyCycle(100) but again, nothing. It needs a little push to spin up on lowest values that are set in the script. then, its works flawless.

0
Aerandir14
Aerandir14

Reply 3 months ago

Hi,
Try that just before the while loop starts:
...
try:
fan.ChangeDutyCycle(100)
time.sleep(2)
while 1:
...
0
Fastiee
Fastiee

Question 5 months ago

Great guide. I'm new to this.
Used a Noctua 40mm 5v fan and it has some trouble spinning up until ~70% pwm speed. Not sure why. Any tips?
(it will spin if you give it a push after 20% up until ~70% where it can start on its own.)

I
used the same components as the guide even though my fan is rated for
0,05A, maybe thats why. Did some math and it looks like I would need a
5200 Ohm resistor if I do the same math as in the guide for my 0,05A
(50mA) fan. so a minimum of 5mA through the resistor as in the guide I
get
2.6 / 0,0005 = 5200 Ohm.
Would that help the fan spin at lower PWM signals?

1
Aerandir14
Aerandir14

Answer 3 months ago

Hi, thank you.
No it shouldn't make things better. The calculated resistor value is a maximum value, it can be lower, it will just pull more amps from the RPi's GPIO.
I guess this fan can't run at lower speed ...

0
ERKL
ERKL

Answer 4 months ago

Actually it’s 52 Ohm... as 50mA = 0.05A so in the equation 2.6/0.05=52

0
ERKL
ERKL

Reply 4 months ago

Nevermind... late hours... you’re right :)

0
mab776
mab776

Tip 7 months ago

Your circuit is really nice, I've played with it a little and I've seen that adding a
100~470uF capacitor at the VCC GND wires of the fan makes it sound way better!
It removes the 25 Hz ticking sound by smoothing the PWM waveform. It transforms
the square waves to almost a triangle wave. But you may have to recalibrate the
PWM after adding the capacitor because the fan speed is boosted by the
capacitor.

0
lernfaehig
lernfaehig

Reply 5 months ago

Hi there, the fan control is working fine but at low speeds my fan is making buzzing noises. I'd very much like to try adding the capacitor but to be honest I have no idea on where in the schematic to add it. Could someone be so kind to post a schematic with the added capacitor?
Thanks in advance.

0
endid13
endid13

Reply 5 months ago

Sup! 25Hz pwm signal is really hard to be flatten, it requires for huge capacitors which would be not so efficient. So, I increased pwm frequency to 1kHz and added a 33uF capacitor between ground and the transistor base, thus making a simple low-pass filter. Sounds way better now!

0
zircondev
zircondev

Reply 5 months ago

Hi, could you say how to increase the pwm frequency? Also was the capacitor you used like 25V 33uF?