Control a Cooling Fan on a Raspberry Pi 3

76K2625

Intro: Control a Cooling Fan on a Raspberry Pi 3

Add a fan to a raspberry pi 3, with control to turn it on and off as required.

An easy way to add a fan is to simply connect the fan leads to a 3.3V or 5V pin and to ground. Using this approach, the fan will run all the time.

I think it is much more interesting to turn the fan on when it reached or surpassed a high temperature threshold, and then turn it off when the CPU was cooled below a low temperature threshold.

The instructable assumes you have a Raspberry Pi 3 setup and running and you want to add a fan. In my case, I am using Kodi on OSMC.

STEP 1: ​CPU Performance and Temperature

There are no actions here. This is just background information and you can skip to the next step:

A heat sink is enough for most Raspberry Pi 3 applications and a fan is not required.

An overclocked raspberry pi should use a fan.

On kodi, if you do not have an MPEG-2 license key, then you might get a thermometer icon, which indicates the need for either a license or a fan.

The Raspberry Pi 3's CPU is spec'd to run between -40°C to 85°C. If the CPU temperature exceeds 82°C, then the CPU's clock speed will be slowed until the temperature drops below 82°C.

An increase in CPU temperature will make semiconductors run slower because increasing the temperature increases the resistance. However, an increase in temperature from 50°C to 82°C has negligible impact on a Raspberry Pi 3's CPU performance.

If the temperature of the Raspberry Pi 3' CPU is above 82°C, then the CPU is throttled (clock speed is lowered). If the same load is applied, then the CPU may have a difficult time throttling it back fast enough, especially if it is overclocked. Because semiconductors have negative temp coefficient, when the temperature exceeds specs then the temperature might runaway, and the CPU may fail and you will need to toss the Raspberry Pi.

Running the CPU at high temperature, shortens the CPU's life span.

STEP 2: GPIO Pins and Resistors

There are no actions here. This is just background information and you can skip to the next step:

Because I am not an electrical engineer and followed instructions from projects on the net, by doing so I damaged a fair number of GPIO pins and ultimately had to toss more than one Raspberry Pi. I also tried overclocking and ended up throwing away a few Raspberry Pis that would no longer work.

A common application is to add a push button to a Raspberry Pi. Inserting a push button between a 5V or 3.3V pin and a ground pin, effectively creates a short when the button is pushed. Because there is no load between the voltage source and ground. The same happens when a GPIO pin is used for 3.3V output (or input).

Another problem, is when an input pin is not connected, it will 'float', which means the value read is undefined and if your code is taking action based on the value read, it will be have erratically.

A resistor is required between a GPIO pin and anything it connects to.

GPIO pins have internal pull up and pull down resistors. These can be enabled with the GPIO library setup function:

GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)

GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

Or a physical resistor can be inserted. In this instructable, I used a physical resistor, but you can try the internal resistor and enable with the GPIO library.

From Arduino Playground website in the Appendix Reference:

"A pull-up resistor weakly "pulls" the voltage of the wire it is connected to towards its voltage source level when the other components on the line are inactive. When the switch on the line is open, it is high-impedance and acts like it is disconnected. Since the other components act as though they are disconnected, the circuit acts as though it is disconnected, and the pull-up resistor brings the wire up to the high logic level. When another component on the line goes active, it will override the high logic level set by the pull-up resistor. The pull-up resistor assures that the wire is at a defined logic level even if no active devices are connected to it."

STEP 3: Parts

You can use most anything, but these are the parts I used.

Parts:

  • NPN S8050 transistor
    • 250 pieces assorted $8.99, or about $0.04
  • 110 Ohm Resistor
    • 400 resistors for $5.70, or about $0.01
  • Micro Fan, requirements in the description or specifications:
    • about $6.00
    • brushless
    • silent
    • lowest Amp or Watts as compared to a similar fan
    • In the description, look for something like "working voltage of 2V-5V"
  • female-female and male-female jumper wires
  • breadboard
  • Raspberry Pi 3
  • 5.1V 2.4A power supply

Notes:

  • Text enclosed in spades is meant to be replaced by your data, ♣your-data♣

STEP 4: Schematic

run-fan requires an S8050 NPN transistor and a resistor to be connected as follows:

The flat side of S8050 faces this way >

  • S8050 pin c: connects to black (-) wire on fan
  • S8050 pin b: connects to 110 Ohm Resistor and to GPIO pin 25
  • S8050 pin e: connects to ground GPIO pin
  • fan red (+): connects to 3.3v GPIO pin on raspberry pi 3

GPIO pin 25 is used, but it can be changed to any GPIO input pin

STEP 5: Get the Script

Login to your raspberry pi with one of the following:

$ ssh osmc@♣ip-address♣
$ shh osmc@♣osmc-hostname♣.local

And then you can download the script using:

$ sudo wget "https://raw.githubusercontent.com/dumbo25/rpi-fan/master/run-fan.py" 

I am using kodi on osmc, and the user is osmc. If you have user pi, then just change all occurrences of osmc with pi in the script and in the service.

Make the script executable.

$ sudo chmod +x run-fan.py

I turn the fan on on at 60 C. If the start temperature is set too low, the fan will turn on cool the CPU down, and by the time the fan is turned off the temperature is almost back up to start temperature. Try 45 C to see this effect. I am not sure what the optimal temperature is.

STEP 6: Automatically Start Up the Script

To get run-fan to start automatically, use systemd

Login to your raspberry pi with one of the following:

$ ssh osmc@♣ip-address♣
$ shh osmc@♣osmc-hostname♣.local

And then you can download the systemd service file using:

$ sudo wget "<a href="https://raw.githubusercontent.com/dumbo25/rpi-fan/master/run-fan-service" "="">https://raw.githubusercontent.com/dumbo25/rpi-fan/...</a>

Or, you can create a systemd service file by copying the contents of the run-fan service from github and then running:

$ sudo nano /lib/systemd/system/run-fan.service 

Paste the contents from github in the file

ctrl-o, ENTER, ctrl-x to save and exit the nano editor

The file must be owned by root and it must be in /lib/systemd/system. The commands are:

$ sudo chown root:root run-fan.service
$ sudo mv run-fan.service /lib/systemd/system/.

After any changes to /lib/systemd/system/run-fan.service:

$ sudo systemctl daemon-reload 
$ sudo systemctl enable run-fan.service 
$ sudo reboot 

After rebooting your Raspberry Pi, the fan should work!

If you have issues with the script starting on re-boot, then check the systemd topic in the Troubleshooting Appendix.

STEP 7: Appendix: References

STEP 8: Appendix: Updates

To do: merge RF receiver circuit board with fan controller

STEP 9: Appendix: Troubleshooting

Checking the systemd service

To ensure the run-fan.service in systemd is enabled and running, run one or more of the commands:

$ systemctl list-unit-files | grep enabled 
$ systemctl | grep running | grep fan 
$ systemctl status run-fan.service -l 

If there are any issues with starting the script using systemd, then examine the journal using:

$ sudo journalctl -u run-fan.service 

To check if run-fan.py is running:

$ cat /home/osmc/run-fan.log

16 Comments

Hi. Can you please explain why you are using a 110 Ohm Resistor?
I published this in 2018. Forgive me if I do not remember exactly why I used a 110-ohm resistor.

IIRC, I faced the following issues with the fan controller. 1) I thought I was buying the same fan each time, but it turns out there were two different fan types. The code would work on one type and not the other. 2) I mistakenly assumed the Raspberry Pi GPIO pin resistors were not working properly or reliably. I didn't realize until later, I had two different fan types. 3) Because the fans were 5.5V and the GPIO pin was only supplying 3.3V, on startup before my code was running, the circuit was either burning out the pin or starting in the wrong state (I don't remember). At one point, I had 7 Media Raspberry Pi's running Kodi. My family (especially my wife) and house guests used these RPIs, I needed them to work every time. Using a physical resistor, removed the issues users were seeing.

My raspberry Kodi media centers had a lot of devices attached. For example, various LEDs (running, booting, on/off switch), IR receiver for remote control, and fan. I believe my first Kodi was running on an RPi2 and the RPi2, when all these devices were running sometimes insufficient current was supplied to the RPi core, and it would get into a funky state. (Later, I realized the power supplies that came with the RPi2 kits were poor quality.)

From a technical point-of-view, the resistor limits current (I=V/R, 3.3V/110 Ohms) to the NPN S8050 transistor. IIRC, the fans were 5V and the RPi was providing only 3.3V, which was fine (i.e., no problem with the Voltage). Because of the devices connected, I didn't want the fan to draw to much current either from the RPi2 or through the GPIO pin.

(I may not have this part correct.) My RPi2 Kodi media centers were impacted by power supply noise, which started when the fan turned on. This noise would negatively impact proper operation. The 110-resistor eliminated the power noise.

Why 110 and not 1K or 10K? I hadn't built any circuits since college and had a small collection of parts. For a long time, I only had 110 and I would just string these in series until I got close to the desired value. I believe I breadboarded the circuit and added a 110-Ohm resistor and the issues went away. I should have calculated the needed resistor size.
I know it was a while ago back in 2018, but I appreciate your reply.
I'm in the middle of a project and got stuck on the resistor part. I also forgot the RPi 2 only had a 3.3volts output. I must have followed this guide in the past because I found the wires and transistor with a 120ohm resistor already made up. I plugged it into the 5v and neutral lead was reading 2.2 - 2.8volts, so it's actually close to the required 3.3 volts and was spinning, but obviously slower than it should. I found the formula R = (Us - 0.7)hFE / fan current, but if I use that it's asking for a 7800ohm resistor, which makes more sense since stepdown from 5v to 3.3v requires a 10kohm, so I was wondering why you/I used a 110/120omh resistor, but I'm still not sure why it works.
question how do you uninstall the script. I installed it and I think I messed up somewhere.
thanks again for your help on this
This works great! Thank you.
But my fan works too slow on 3.3V. Can I connect it to 5V pin on Raspberry Pi 4 B? I'm using the same components (I mean resistor and transistor).
Ok, I found an answer in the article.
I can't use 5V :(
This works well. Was just what I was looking for, thanks.
I have been trying this without resistor, but its not working. Does resistor really necessary and why?
So, I played around a lot with this. The easiest way is to just hook it up to 3v gpio and ground. The fan runs all the time.

If you don't have a resistor, then the GPIO pins have an internal resistor, and you can try that. The reason for a resistor is all explained in step 2. I am not sure I can explain it any better by repeating the above.

Are you using the transistor?

If this is too complicated, then try this: https://sites.google.com/site/cartwrightraspberryp...

I'd been fighting with another version of this found elsewhere, but getting the GPIO working within the OSMC build was proving impossible. This worked a charm. Thanks!

You do have one minor issue with your instructions though. You're creating the service file in the correct area ($ sudo nano /lib/systemd/system/run-fan.service), but then suggesting moving the file to the correct area (it's already there) - it'd error out for a newbie (like me) and cause some hair loss.

Good work.
From Raspbian 4.4 (AFAIK) the gpio-fan overlay was added to the device tree. When using a raspberry pi 3 model B, I just have to add the following lines in /boot/config.txt:

dtoverlay=gpio-fan
dtparam=gpiopin=<#>
dtparam=temp=<millicelsius>

gpiopin is the GPIO pin (BCM value!) used as the output signal (default 12); temp is the minimum temperature in millicelsius at which the GPIO pin activates (default 55000). The hardware configuration suggested in the instructable remains the same.

A python instance controlling the GPIO might as well be considered a factor that heats up the Raspberry in the first place; the instance may eat 100% of a single core, heating the Broadcom way past 60°C by itself with no further load. And yes, with the proper passive heatsink and standard clocking (and wise programming) the Raspberry does not heat up past 55-60°C, even under heavy load, without Python scripts leeching in the background. That's why I usually try to avoid Python scripts; they might be considered both a pathogen and a palliative.

Hi, i managed to rewrite it a bit and tailor it, it turns on the fan at a specific temp and lowers it down while checking the temp every 1s, once it cools down it turns off the fan and keeps it off until the temp hits the max temp again, log is only updating every second while keeping the fan off :)

i have it on latest OSMC

#!/usr/bin/env python

#########################

#

# run-fan turns a fan on and off when temperature exceeds temperature

# thresholds.

#

# The fan comes with a MiuZei case, and can run on 3.3V (low speed) or

# 5V (high speed). The fan can be plugged into either 3.3V or 5.5v GPIO

# pin and to a ground GPIO pin, but that means the fan will always run

# and that isn't very much fun. Any fan can be used as long it can operate

# at 3.3V

#

# run-fan was tested on a raspberry pi running kodi on osmc using a 3.3v

# GPIO

#

#########################

#########################

#

# run-fan requires an S8050 NPN transistor and a resistor to be connected

# as follows:

# flat side of S8050 faces this way >

# S8050 pin c: connects to black (-) wire on fan

# S8050 pin b: connects to 110 Ohm Resistor and to GPIO pin 25

# S8050 pin e: connects to ground GPIO pin

# fan red (+): connects to 3.3v GPIO pin on raspberry pi 3

#

# GPIO pin 25 is used, but it can be changed

#

#########################

#########################

#

# run-fan starts automatically using systemd

#

# Create a systemd service file using:

# $ sudo nano /lib/systemd/system/run-fan.service

#

# with the contents as shown below

# remove # and leading spaces:

# [Unit]

# Description=run fan when hot

# After=meadiacenter.service

#

# [Service]

# # If User and Group are not specified as root, then it won't work

# User=root

# Group=root

# Type=simple

# ExecStart=/usr/bin/python /home/osmc/run-fan.py

# Restart=Always

#

# [Install]

# WantedBy=multi-user.target

#

# end of the run-fan.service

# ctrl-o, ENTER, ctrl-x to save and exit the nano editor

#

# After any changes to /lib/systemd/system/run-fan.service:

# sudo systemctl daemon-reload

# sudo systemctl enable sample.service

# sudo reboot

#

# Ensure the run-fan.service in systemd is enabled and running:

# systemctl list-unit-files | grep enabled

# systemctl | grep running | grep fan

# systemctl status run-fan.service -l

#

# If there are any issues with starting the script using systemd,

# then examine the journal using:

# sudo journalctl -u run-fan.service

#

#########################

#########################

#

# The original script is from:

# Author: Edoardo Paolo Scalafiotti <edoardo849@gmail.com>

# Source: https://hackernoon.com/how-to-control-a-fan-to-co...

#

#########################

#########################

import os

import time

import signal

import sys

import RPi.GPIO as GPIO

import datetime

#########################

sleepTime = 1# Time to sleep between checking the temperature

# want to write unbuffered to file

fileLog = open('/home/osmc/run-fan.log', 'w+', 0)

#########################

# Log messages should be time stamped

def timeStamp():

t = time.time()

s = datetime.datetime.fromtimestamp(t).strftime('%Y/%m/%d %H:%M:%S - ')

return s

# Write messages in a standard format

def printMsg(s):

fileLog.write(timeStamp() + s + "\n")

#########################

class Pin(object):

pin = 25 # GPIO or BCM pin number to turn fan on and off

def __init__(self):

try:

GPIO.setmode(GPIO.BCM)

GPIO.setup(self.pin, GPIO.OUT)

GPIO.setwarnings(False)

printMsg("Initialized: run-fan using GPIO pin: " + str(self.pin))

except:

printMsg("If method setup doesn't work, need to run script as sudo")

exit

# resets all GPIO ports used by this program

def exitPin(self):

GPIO.cleanup()

def set(self, state):

GPIO.output(self.pin, state)

# Fan class

class Fan(object):

fanOff = True

def __init__(self):

self.fanOff = True

# Turn the fan on or off

def setFan(self, temp, on, myPin):

if on:

printMsg("Turning fan on " + str(temp))

else:

printMsg("Turning fan off " + str(temp))

myPin.set(on)

self.fanOff = not on

# Temperature class

class Temperature(object):

cpuTemperature = 0.0

startTemperature = 0.0

stopTemperature = 0.0

def __init__(self):

# Start temperature in Celsius

# Maximum operating temperature of Raspberry Pi 3 is 85C

# CPU performance is throttled at 82C

# running a CPU at lower temperatures will prolong its life

self.startTemperature = 55.0

# Wait until the temperature is M degrees under the Max before shutting off

self.stopTemperature = 50.0

printMsg("Start fan at: " + str(self.startTemperature))

printMsg("Stop fan at: " + str(self.stopTemperature))

def getTemperature(self):

# need to specify path for vcgencmd

res = os.popen('/opt/vc/bin/vcgencmd measure_temp').readline()

self.cpuTemperature = float((res.replace("temp=","").replace("'C\n","")))

# Using the CPU's temperature, turn the fan on or off

def checkTemperature(self, myFan, myPin):

self.getTemperature()

if self.cpuTemperature > self.startTemperature:

# need to turn fan on, but only if the fan is off

if myFan.fanOff:

myFan.setFan(self.cpuTemperature, True, myPin)

if self.cpuTemperature > self.stopTemperature:

# need to turn fan on, but only if the fan is off

if not myFan.fanOff:

myFan.setFan(self.cpuTemperature, True, myPin)

else:

# need to turn fan off, but only if the fan is on

if not myFan.fanOff:

myFan.setFan(self.cpuTemperature, False, myPin)

#########################

printMsg("Starting: run-fan")

try:

myPin = Pin()

myFan = Fan()

myTemp = Temperature()

while True:

myTemp.checkTemperature(myFan, myPin)

# Read the temperature every N sec (sleepTime)

# Turning a device on & off can wear it out

time.sleep(sleepTime)

except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt

printMsg("keyboard exception occurred")

myPin.exitPin()

fileLog.close()

except:

printMsg("ERROR: an unhandled exception occurred")

myPin.exitPin()

fileLog.close()

Hi, thank you so much for the tutorial it took me ages to find a working one, would you be able just to help me with one thing, mine is not turning off at the designed time, seems like it is only seeing the startTemperature, i tried adjusting the stopTemperature and sleepTime but didn't help out even if its -40 it will turn off as soon as it goes lower than 50:

# running a CPU at lower temperatures will prolong its life

self.startTemperature = 50.0

# Wait until the temperature is M degrees under the Max before shutting off

self.stopTemperature = self.startTemperature - 5.0

log:

osmc@osmc:~$ cat /home/osmc/run-fan.log

2018/03/03 17:45:17 - Starting: run-fan

2018/03/03 17:45:17 - Initialized: run-fan using GPIO pin: 25

2018/03/03 17:45:17 - Start fan at: 50.0

2018/03/03 17:45:17 - Stop fan at: 45.0

2018/03/03 17:45:17 - Turning fan on 52.6

2018/03/03 17:48:58 - Turning fan off 48.9

any ideas on how can i resolve this? :)

Much obliged

Can you check if you have the latest version of code from github? From your explanation, it seems there is a bug in the code.

My design or explanation may not be the best. Basically, turning the fan on is dependent on the temperature, and turning it off is dependent on meeting a threshold temperature.

However, the measurement of the temperature is not simultaneous with turning the fan on or off. There is a time delay, which allows the temperature to increase/decrease before it is reported. Depending on the application being run, the CPU can increase 20 degrees C in a few seconds. Increasing the delay may increase the size of this fluctuation.

The above is why even though the fan starts at 50 it doesn't report the temperature until it is 52.6, and while it shuts off at 45 it doesn't report the temperature until it has risen to 48.9.

Of course, the CPU cannot be cooled to (50-40) 10 degrees Celsius. Try using a higher starting temperature, and 5 degrees or so cooler for a stopping temperature. A raspberry pi can operate up to about 85 degrees Celsius. You will prolong the life of a CPU if it is kept away from the maximum operating temperature.

I am not sure but this project will not be effective outside an undetermined temperature range (say 50 - 85, below 50 the fan cannot cool enough and at or above 85 the fan will always be on). And I haven't spent the time to figure out the optimum temperature range, but my assumption is this depends on the application. While running Kodi, my settings work reasonably well. When I was trying to gather some data points, compute intensive applications pushed the temperature up very quickly. To do a proper analysis of the effective temperature range requires tools I do not have.

The implementation is relatively simple. This link describes a much more sophisticated implementation and may meet your needs: https://www.npmjs.com/package/rpi-fan-controller

Any improvements would be appreciated.