Control a Cooling Fan on a Raspberry Pi 3
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
NolandTech 4 months ago
jeff.cartwright.562 3 months ago
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.
NolandTech 3 months ago
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.
cyrad 1 year ago
thanks again for your help on this
jeff.cartwright.562 1 year ago
igor90 3 years ago
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).
jeff.cartwright.562 3 years ago
igor90 3 years ago
I can't use 5V :(
snakeboy69 3 years ago
hudarsono 4 years ago
jeff.cartwright.562 4 years ago
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...
wufflesthespider 4 years ago
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.
WalterC77 5 years ago
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.
BornaR1 6 years ago
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()
BornaR1 6 years ago
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
jeff.cartwright.562 6 years ago
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.