Control a Cooling Fan on a Raspberry Pi 3

Published

Introduction: 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

Share

    Recommendations

    • Clocks Contest

      Clocks Contest
    • Creative Misuse Contest

      Creative Misuse Contest
    • Water Contest

      Water Contest

    3 Discussions

    0
    user
    BornaR1

    Question 4 months 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()

    0
    user
    BornaR1

    Question 4 months 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

    1 more answer

    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.