Tutorial: Variable speed fan for Raspberry temperature controller

Last modified 14 hours

Do you want to, like me, reduce Raspberry Pi fan noise (or even eliminate it)? If so, here is the ultimate solution to our noise problems: the variable speed fan for Raspberry Pi. An easy and cheap solution (a couple of euros).

Following this tutorial, you will be able to build, with very few components, a small controller for the Raspberry Pi to regulate the speed of its fan depending on its temperature and we will complete it with a small Python program to do the monitoring and control work.

Whenever I write a blog post, my top priority is always to that provides value for the readerthat actually provides knowledge. Not write empty articles that just skim over a topic with information that is already all over the internet.

Why have I written this tutorial on how to set up a temperature-controlled variable speed Raspberry fan, when there are already many on the internet?

Because the articles I found seemed too biased, or didn't seem to explain the basics well, in a way that I didn't think they could. understandable.

In addition, I have found that most tutorials for implementing temperature-based PWM speed control on the Raspberry Pi THEY DON'T DO IT WELL (I will explain why later).

This article (and the other articles it links to, which I have written in support of this tutorial), attempt to cover the topic in depth but understandable, trying to that anyone can easily understand it just by reading the blog and with no prior preparation..
I hope you enjoy it!

I have several Raspberry Pi running at home for different tasks. They are small devices, with low power consumption and very stable in their operation (they can be running continuously for months without needing a reboot).

I have used the original Raspberry Pi (the v1), the Raspberry Pi 2, the Raspberry Pi 3 and its "in-between" versions (with the "B" or "+" added) and temperature has never been an issue. They have all worked great for years with no heat problems.

The last one I added, a Raspberry Pi 4, has much more power and performance than the previous ones and that means that it has some cooling requirements far above those of their predecessorsCan a Raspberry Pi 4 be used without a fan? Yes, but it is not advisable to run a Raspberry Pi 4 without a fan to refrigerate it.

It is assumed that if we put a Raspberry Pi 4 it is because we need its power in some moments (if not, why use a Raspberry Pi 4?, better to put one of the previous models, which are cheaper and consume less) and in this case, we should use a Raspberry Pi 4 because we need its power in some moments. a cooling fan is almost indispensable.

The disadvantage of adding a fan to the Raspberry Pi 4 is that ceases to be a silent 100% device to become a minor nuisance (especially in a quiet bedroom or office).

How loud is a Raspberry Pi 4?

The Raspberry Pi 4 itself does not make noisehas no moving parts, what makes noise is its fan

The noise level of a 5V fan for a Raspberry Pi 4 can vary depending on the model and make of the fan, as well as its design and build quality. However, in general, small 5V fans tend to be quite quiet, especially if powered at 3.3V (yes, 5V fans powered at 3.3V so they spin slower and make less noise).

The noise level of a fan is generally measured in decibels (dB), and small 5V fans designed for Raspberry Pi often have a noise level of around 20 to 30 dB. This is relatively quiet and should not be annoying in most situations.

It is important to note that there are some factors that can influence the noise level, such as the speed at which the fan operates and the quality of the fan's bearing. If you need particularly quiet operation, you can opt for fans with high quality bearings, such as fluid bearings or ball bearings, which tend to generate less noise than simpler bearings.

In any case, if noise is a concern for you, you can look for fans designed specifically for low-noise applications or those with speed control options to adjust fan speed as needed, which can help you maintain a balance between temperature and noise.

Taking all of the above into account, we can say that the fan noise of a Raspberry Pi 4, once we have chosen the fan (better or worse, higher or lower quality) and connected it, will always spin at the same speed, cool in the same way and make the same noise..

If we want to reduce the noise, taking advantage of the fan always running at maximum (in fact, we don't even need the fan to be always running) the solution is to have a variable speed fan for Raspberry Pi.

Why should I put a fan on a Raspberry Pi 4?

It is good to put a fan on a Raspberry Pi 4 or other similar electronic devices for several reasons:

  1. Refrigeration: The Raspberry Pi 4 can generate heat, especially when running CPU or GPU intensive applications. A fan helps dissipate that heat more efficiently, which prevents the device's temperature from rising too high. Operating at lower temperatures can help prevent overheating, which can negatively affect the Raspberry Pi's performance and lifespan.
  2. Increased performance and stability: Keeping the temperature of the CPU and other components at a lower temperature can allow the Raspberry Pi to run more steadily and without slowdowns caused by overheating.
  3. Damage prevention: Excessive heat can damage electronic components over time. A fan can extend the life of the Raspberry Pi and prevent long-term damage.
  4. Consistent performance under intensive workloads: If you plan to use your Raspberry Pi for intensive tasks, such as game emulation, machine learning or video transcoding, a fan can be essential to maintain consistent performance and avoid thermal throttling.
  5. Reduced fan speed and noise: You don't need the fan to be at full speed all the time. You can set the fan to run only when the temperature reaches a certain threshold, which helps reduce noise and power consumption.

All of the above confirms what we suspected, putting a fan on a Raspberry Pi 4 is a good idea if you plan to use it for tasks that generate heat or in environments where the ambient temperature is high. It will help keep the Raspberry Pi running efficiently and prolong its lifespan by keeping temperatures under control.

And since we're putting a fan, we'd better put a variable speed fan for Raspberry Pi.

What is the quietest Raspberry Pi 4 fan?

I can tell you, without fear of contradiction, that the quietest fan is the fan that is switched off..

We can put better quality fans, lower voltage, graphene or Kryptonite bearings, but all of them, without exception, make noise.

If we want a fan to be silent, the best way is to turn it off when it is not necessary and put it to the minimum speed to keep the temperature of the Raspberry Pi 4 under control (it is not necessary that it is always "always on").as cold as possible").

However, if I have something to tell you: As a general rule, Raspberry 4 fans are very small (usually 40mm) and this means that they have to rotate very fast in order to provide a significant amount of air.

To move the same amount of air, a smaller fan (such as a 40 mm fan) must spin approximately 9 times faster than a larger fan (such as a 120 mm fan).

The speed ratio is calculated as follows:

Where:

  • Dlarge fanis the diameter of the large fan (in this case 120 mm).
  • Dsmall fanis the diameter of the small fan (in this case 40 mm).

If we substitute the values in the formula:

In summary, if we want to have the quietest possible fan, we should look at:

  1. Switch off the fan when it is not needed
  2. Adjust the fan speed to the minimum necessary at any given time
  3. Mount the larger fan that it is possible for us to

The fan for the Raspberry Pi 4

For our variable speed fan we are going to use a small 5V DC fan (if you're curious, we're going to use a 5V DC fan), click here to see the different types of fans that can be used on a Raspberry).

This is a simple fan that only has a DC motor that moves the blades. In this case, its speed will depend on the voltage we feed it with.

It has only two wires, the positive and negative power wires (which, by the way, you can connect backwards and the fan will rotate in the opposite direction).

If you want more details about why I have chosen this type of fan, why I have decided to vary its speed via PWM and many more details about PWM fan speed control, you will like the following blog article:

Although it is not obligatory to read this article, it is interesting to do so, as it lays the groundwork for what we will see below.

If you find that you get lost in the following explanations, come back here and read the article "Controlling the speed of a fan with PWM".

How do we regulate the fan speed of the Raspberry Pi 4?

We are going to do this by generating a PWM signal and amplifying it, with a small circuit, as the Raspberry Pi cannot directly supply a PWM signal with the power needed to move a fan.

To do this, we are going to write a small Python program that generates a PWM signal that has a Duty cycle variable depending on how fast you need the fan to spin to cool the Raspberry Pi 4.

We will make the Raspberry Pi 4 run this program automatically and every 15 seconds (configurable) the program will modify, if necessary, the PWM signal to increase or decrease the fan speed.

This programme shall have a minimum value of Duty cycle PWM signal to ensure that the fan always moves and never blocks because it does not have enough energy to overcome inertia and start moving.

In addition, we are going to write a small auxiliary program that will allow us to find out what is the Minimum duty cycle for the fan to start and what is the Minimum duty cycle so that the fan does not stop, once it is in motion (both Working cycles need not be the same, and they will not be).

For our PWM signal we will use the following parameters:

  • Amplitude: 3.3V
  • Duty cycleIt will go from the minimum that allows the fan to start (or not to stop, if it is already moving) to 100%.
  • Frequency: 25Khz

Just by feeding the fan with this PWM signal, instead of connecting it directly to 5V (or 3.3V), we will have a variable speed fan on the Raspberry Pi.

The PWM signal amplifier electronic circuit

The normal power consumption of small 5V 40x40mm fans is approximately 150mA.

The Raspberry Pi can only safely provide 16mA, according to the official Raspberry Pi documentation (and 50mA simultaneously, adding up all its pins).

We are going to add a very simple electronic circuit to our Raspberry Pi 4, with a transistor that, with this small control signal, of limited intensity, is capable of generating an equal signal but that can reach a minimum of 200 or 300mA.

Such circuits are often referred to as "drivers.

There are two main ways of doing it (very similar) and I'll leave you with both, so you can choose the one you prefer (although my recommendation is to use the MOSFET version).

PWM driver with BJT transistor

This is the schematic of our driver with BJT transistor:

Unless you already have the components (especially the BJT transistor), I recommend you to build the PWM driver with MOSFET transistor. I leave you below.

This is the prototype of the BJT transistor driver I built for testing.

As you can see, the plate is very dirty (let's say it is very dirty). "worked".), due to the large number of tests I did with different variations:

Different transistors in configuration high-side y low-sidedifferent resistor values, many measurements, etc...

Even using normal components "through hole". (not surface mount), as you can see it was a very compact size, so you can fit it inside the Raspberry Pi 4 case.

In the following article you have all the information on the PWM driver with BJT transistor:

PWM driver with MOSFET transistor

This is the type of driver I recommend you build.. Among other advantages, it is more efficient in operation than the BJT transistor driver and it heats up less.

This is the schematic of our PWM signal driver with MOSFET transistor:

As you can see, it is very similar to the BJT transistor driver we saw before.

In the following article you have all the information on the PWM driver with transistor BJT:

Variable speed fan control software for the Raspberry Pi 4

I am working on the software.

This is the part of the article where most work remains to be done.What there is is a work in progress.

For the time being, I leave you something preliminary so that you can get started, understand how it works and see how simple it is. None of the code you see below is guaranteed to be the definitive code.

Please note that until I publish it, there may be changes (and there will be).

Although what we are about to do is very simple and shouldn't cause any problems, the risk is always there, so I recommend that you make a backup copy (I suggest you directly clone your Micro SD card before touching anything).

Generating a PWM signal with the Raspberry Pi 4

Generating PWM signal on the Raspberry Pi with Python is not difficult at all, but it is important to know how, because it is, either I am very wrong, or the vast majority of information on the internet is wrong..

Here's why most of the tutorials for implementing temperature-based PWM speed control on the Raspberry Pi that I've seen on the internet have been for the following reasons THEY DON'T DO IT WELL.

Inicialmente, hice todo este proyecto con la biblioteca RPi.GPIO (que es la recomendada, y con la que lo hace el 99% de gente en internet) y notaba ciertos ruidos en el ventilador.

Atribuía los ruidos y la poca suavidad en el funcionamiento a que no era un ventilador de buena calidad, pero, haciendo pruebas con el osciloscopio, vi que algo no estaba bien…

The PWM signal generated was horrendous, full of noise, at a frequency that was not the one I had set and both the frequency and duty cycle values were very unstable.

The most curious thing was that the system worked and if I hadn't tested thoroughly with the oscilloscope I would never have known how bad it was turning out. Surely there are millions of systems like this 'functioning'. in the world without knowing the problems they have.

I did some research and found that the library RPi.GPIO does not generate the PWM signals by hardware, but by software. (aunque utilices un pin hardware), y lo peor, es que tiene problemas con las señales de más de unos pocos kilohercios.

Utilizando una señal PWM con una frecuencia de 1Khz, por ejemplo, el sistema funcionaba, pero el ventilador hacía un ruido, perfectamente audible, que era peor que tenerlo funcionando a plena potencia.

Una desventaja adicional importante es que cuando la señal PWM se genera por software, es la CPU de la Raspberry Pi la que hace todo el trabajo (y es mucho, porque tiene que estar continuamente atendiendo y activando y desactivando el pin) con lo que el consumo de CPU puede ser alto.

I am still documenting all this, testing, writing and recording videos so that I can explain it well. I will include the information shortly.

The solution was to use a different library, instead of the RPi.GPIO. One that supports hardware PWM.

Hay algunos proyectos (los que menos) que utilizan algún tipo de control PWM por hardware, pero la mayoría tienen problemas o limitaciones.

  • Se basan en proyectos obsoletos, o que por debajo los utilizan, o sin soporte como wiringPi
  • Necesitan un demonio corriendo (como pigpio), lo que complica la instalación y arquitectura

Generating a hardware PWM signal with Raspberry Pi 4

You can generate a PWM signal on a pin of a Raspberry Pi 4, in a very easy way, using Python and the library rpi-hardware-pwm.

The first thing to do is to prepare the Raspberry Pi to be able to use the two hardware PWM channels it has.

You have to edit the file /boot/config.txt and include the line dtoverlay=pwm-2chan

To do this, from the console run:

sudo nano /boot/config.txt

And after the last line beginning with dtoverlay (which is possibly annotated, with a pad ‘#’), includes the line:

dtoverlay=pwm-2chan

This will enable hardware PWM in the Default GPIO: GPIO_18 for PWM0 and GPIO_19 for PWM1.

If you want, you can use (instead of the previous line), the line:

dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4

This will enable hardware PWM in the Alternative GPIOs: GPIO_12 for PWM0 and GPIO_13 for PWM1.

Now restart the Raspberry Pi, for the changes to take effect, with:

sudo reboot

When the Raspberry Pi starts up again, go back into the console and install the library rpi-hardware-pwm with:

sudo apt-get update
sudo apt-get install rpi-hardware-pwm

Test programme and calibration of the PWM signal for the specific fan

The Raspberry Pi is now ready to generate a hardware PWM signal.

Now we need software, which allows us to do two things:

  • Prove in an easy way that it does indeed work.
  • Find out the minimum duty cycles so that our particular fan (each one is different) does not stall.

To do this I have created a very simple program that allows us to call it with three parameters in the command line of the Raspberry Pi:

  1. 0 to use the channel PWM0 at GPIO_18 PWM0 and PWM1 at GPIO_19 (or PWM0 on GPIO_12 PWM0 and PWM1 on GPIO_13, if you have chosen the alternative pins in the file /boot/config.txt.
  2. The frequency in hertz. For example, 25000 for 25Khz.
  3. The duty cycle (a number between 0 and 100).

To exit and stop the PWM, just press [Enter].

You can also interrupt and exit the programme with Ctrl-C. In this case the PWM signal will not stop.

The code is as follows:

import sys
from rpi_hardware_pwm import HardwarePWM

def set_pwm(pin, frequency, duty_cycle):
    pwm = HardwarePWM(pin, frequency).
    pwm.start(duty_cycle)
    input("Press Enter to stop the PWM...")
    pwm.stop()

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Usage: python pwm_generator.py   ")
        sys.exit(1)

    pin = int(sys.argv[1])
    frequency = float(sys.argv[2])
    duty_cycle = float(sys.argv[3])

    try:
        set_pwm(pin, frequency, duty_cycle).
    except KeyboardInterrupt:
        pass

Save this program in a file called "pwm_generator.py".

You can run the program with the following command:

python pwm_generator.py   .

Replaces:

  • <canal_pwm> By the PWM channel you want to use (0 or 1). For example, a 0 to use PWM0 channel of GPIO_18.
  • . By the frequency in Hertz that you want the PWM signal to have. For example, 25000 for 25Khz.
  • <work_cycle> by the duty cycle you want, e.g. "50" for a 50% duty cycle.

For example, to generate on PWM channel 0 (PWM0) on GPIO_18 a signal at 25Khz with a duty cycle of the 75%, you have to run on the console:

python hardware_pwm_generator.py 0 25000 75

Raspberry Pi CPU temperature readings

Let's see what we have to do to read the temperature of the CPU of the Raspberry Pi in a simple way, to go little by little and to be understandable. Later we will see how to modify the PWM signal based on the temperature.

To read the CPU temperature of a Raspberry Pi you can use the library psutil..

Make sure you have the library psutil. installed on your Raspberry Pi. You can install psutil with the following command:

sudo apt-get update
sudo apt-get install psutil

Here is a simple Python program that uses the psutil library to read the CPU temperature and prints it to the console every 5 seconds:

import psutil
import time

def get_cpu_temperature():
    try:
        temperature = psutil.sensors_temperatures()['coretemp'][0].current
        return temperature
    except Exception as e:
        print(f "Error in getting CPU temperature: {e}")
        return None

def main():
    try:
        while True:
            temperature = get_cpu_temperature()

            if temperature is not None:
                print(f "CPU Temperature: {temperature}°C")

            time.sleep(5)

    except KeyboardInterrupt:
        pass

if __name__ == "__main__":
    main()

You can write it in the file temperature_reader.py (as we did before with pwm_generator.py), with:

sudo nano temperature_reader.py

Different versions of Raspberry Pi and Linux may call the temperature sensor by a name different from coretemp.

If you get an error like "Error getting CPU temperature: 'coretemp'."probably because your sensor is not called coretemp and you will have to find out his name.

Once you know the name, you will have to substitute in line 6 the name coretemp by the name of your sensor.

For example, on both the Raspberry Pi 3 with which I started testing and the Raspberry Pi, this sensor was named cpu_thermal.

You can use this small code to find out by printing out the list of all available sensors to understand which names are present in your system:

import psutil
import time

def get_cpu_temperature():
    try:
        sensors_data = psutil.sensors_temperatures()
        if 'coretemp' in sensors_data:
            temperature = sensors_data['coretemp'][0].current
            return temperature
        else:
            print("No data found for sensor 'coretemp'. Sensors available:", sensors_data.keys())
            return None
    except Exception as e:
        print(f "Error getting CPU temperature: {e}")
        return None

def main():
    try:
        while True:
            temperature = get_cpu_temperature()

            if temperature is not None:
                print(f "CPU Temperature: {temperature}°C")

            time.sleep(5)

    except KeyboardInterrupt:
        pass

if __name__ == "__main__":
    main()

This script will print the list of available sensors on your system if it cannot find the sensor. coretemp. By running the script, you will be able to see which sensor names are present, and you can adjust the code accordingly to read the temperature from the correct sensor.

Temperature reading program and fan speed adjustment

To read the CPU temperature of a Raspberry Pi 4 and generate a PWM signal with a duty cycle proportional to the CPU temperature, we will use, as we have seen before, the library psutil. to obtain the CPU temperature and, again, the library rpi-hardware-pwm to generate the PWM signal. Below, I provide you with a program that does this:

# Import the rpi-hardware-pwm library
from rpi_hardware_pwm import HardwarePWM

# Import psutil library for reading CPU temperature
import psutil

# Import time library to use the sleep function
import time

# Import the atexit library to use the atexit function
import atexit

# Define the PWM channel to use (Channel 0, or PWM0, by default is assigned to GPIO_18)
channel_pwm = 0

# Define the working frequency in Hz
frequency = 25000

# Define the minimum and maximum temperature for activating the PWM output in degrees Celsius
temp_min = 45
temp_max = 60

# Define minimum and maximum duty cycle in percentage
min_cycle = 55
cycle_max = 100

# Define degrees of hysteresis
hysteresis = 2

# Define PWM channel and frequency
pwm = HardwarePWM(channel_pwm, frequency)

# Make the fan stop when the program ends
atexit.register(pwm.stop)

# Create a variable to store the previous duty cycle
previous_cycle = 0

# Create a variable to store the elapsed time since the last temperature printout
time = 6

# Create a variable to store the duty cycle time
duty_cycle = 0

# Create a global variable for debugging mode
debug = True

# Create a function to print debug messages only if debugging mode is active
def print_debug(message):
    # If debug mode is True, print the message to the console.
    if debug:
        print(message)

# Define function to calculate duty cycle proportional to temperature
def calculate_duty_cycle(temp, temp_min, temp_max, cycle_min, cycle_max, hysteresis):
    # If temp is less than min, return 0.
    if temp  (temp_min + hysteresis):
            cycle = (temp - temp_min) * (cycle_max - cycle_min) / (temp_max - temp_min) + cycle_min.
        # If not, use the minimum cycle
        else:
            cycle = cycle_min
        # If cycle greater than 100, use 100
        if cycle > 100:
            cycle = 100
        # Return cycle rounded to nearest integer
        return round(cycle)

# Start the PWM generator with a duty cycle of 0%
pwm.start(duty_cycle)

# Create an infinite loop
while True:
    # Read the CPU temperature in degrees Celsius.
    temp = psutil.sensors_temperatures()['cpu_thermal'][0].current

    # Calculate the duty cycle using the defined function
    duty_cycle = calculate_duty_cycle(temp, temp_min, temp_max, cycle_min, cycle_max, cycle_max, hysteresis)

    # If more than 5 seconds have elapsed since the last temperature printout, print the temperature on the console
    if time >= 5:
        # print_debug(f "CPU temperature is {temp:.2f} °C. Duty cycle is {duty_cycle:.0f}%")
        # Reset elapsed time
        time = 0
    # If not, increase elapsed time by 1 second
    else:
        time += 1

    # If fan starts from standstill (previous duty cycle was 0 and current duty cycle is not), start with a cycle of 100% for 1 second to overcome inertia
    if previous_cycle == 0 and duty_cycle != 0:
        pwm.change_duty_cycle(100)
        print_debug(f "Starting fan.... (temperature {temp:.2f}ºC)")
        # print_debug(f "Changed duty cycle to {duty_cycle}% (1)")
        # print_debug(f "Previous duty cycle {previous_cycle}% (1)")
        time. Sleep(1)

    # Change the duty cycle of the PWM
    pwm.change_duty_cycle(duty_cycle)
    previous_cycle = duty_cycle
    print_debug(f "Temperature: {temp:.2f}ºC. Duty_cycle: {duty_cycle:.0f}%")

    # Wait 5 seconds before repeating the loop.
    time. Sleep(5)

You can save the programme in a file named temperature_pwm_controller.py by means of:

sudo nano temperature_pwm_controller.py

This program reads the CPU temperature every 5 seconds and adjusts the PWM duty cycle according to the temperature.

There are several parameters that you can adjust according to your needs and preferences.

  • channel_pwm = 0: Allows you to choose the PWM channel you want to use.
  • frequency = 25000The frequency of the PWM signal. Note that a lower frequency signal will cause audible noise which can be annoying.
  • temp_min = 45The temperature below which the fan will be stopped.
  • temp_max = 60The temperature you do not want to exceed. From this temperature the duty cycle of the PWM signal will be 100%.
  • cycle_min = 55PWM signal cycle for your fan and preferences. Do not set too low a signal that may cause the fan not to spin.
  • cycle_max = 100The maximum duty cycle at which you want the PWM signal to be generated.
  • hysteresis = 1To ensure that the fan is not continually stopping and starting, this is the "grey zone" value. The temperature will have to vary more than this value for the fan to switch between running and stopped.

Make it start automatically when the Raspberry Pi 4 boots up

It wouldn't make much sense if every time we started the Raspberry Pi we had to go into the console and run a program, so we're going to automate it.

To make the script run automatically when the Raspberry Pi boots up, there are several ways to do this.

Automatic execution with systemctl

To run a Python script at startup on a Raspberry Pi using systemd, you can follow these steps:

In this example the name of the script is temperature_pwm_controller.py and the full path where it is is /home/pi/:

  1. Create the Python script:
    Make sure you have a Python script named temperature_pwm_controller.py on the road /home/pi/.
  2. Make sure the script is executable:
   chmod +x /home/pi/temperature_pwm_controller.py
  1. Create a service file for systemd:
    Create a service file in the location /etc/systemd/system/. You can name it, for example, temperature_pwm_controller.service.
[Unit]
Description=Temperature-based PWM controller
After=multi-user.target

[Service]
Type=simple
ExecStartPre=/bin/sleep 30
ExecStart=/home/pi/temperature_pwm_controller.py

[Install]
WantedBy=multi-user.target

Be sure to replace /home/pi/temperature_pwm_controller.py with the full path to your Python script, if you have modified it.

  1. Recharge systemd:
   sudo systemctl daemon-reload
  1. Enables and starts the service:
   sudo systemctl enable temperature_pwm_controller.service
   sudo systemctl start temperature_pwm_controller.service
  1. Check the status of the service:
   sudo systemctl status temperature_pwm_controller.service

You can stop the service with:

   sudo systemctl stop temperature_pwm_controller.service

You can run the service with:

   sudo systemctl start temperature_pwm_controller.service

These steps should set up your script temperature_pwm_controller.py to run automatically on startup of your Raspberry Pi using systemd. Be sure to adjust the paths and filenames to suit your specific needs.

Automatic execution with rc.local

Theoretically, it is also possible to do so in this way, but I have not been able to get it to work properly with rc.local. I leave it here just in case systemctl gives you problems, and you want to try it this way, or if you see the error and want to leave the solution in the comments.

The archive rc.localis executed after all system services are started.

To use this method, you have to edit the file rc.local and add the command to run your script before the "exit 0" line. For example, if your script is called temperature_pwm_controller.py and it is in the /home/pi directory, you would have to put something like this:

python3 /home/pi/temperature_pwm_controller.py &

You can edit the file rc.local with:

sudo nano /etc/rc.local

Note that we don't need to run the script every fifteen seconds as it is actually always running, doing its job every 15 seconds.

It is important to mention that the use of rc.local. has decreased on more modern systems, as many Linux distributions have adopted more advanced boot systems such as systemd. However, it is still possible to find systems that use rc.local.especially in older environments or customised configurations.

Finishing touches to the code

To finalise the code and leave it roundIf you want, you can polish up some details.

I haven't implemented it yet, I will do it when it is more stable, but I leave it here, as an idea, in case you want to implement it.

Automatic mode

We can include a parameter, -on_pi_startwhen calling the script from systemctl so that the script knows about it and works in automatic: Things like disabling the debugif it was activated, or set a different test interval.

python3 /home/pi/temperature_pwm_controller.py --on_pi_start

To let the script know that it has been executed in this way, we can add the following lines to the script:

# Check if the program has been executed at Raspberry Pi startup
if "--on_pi_start" in sys.argv:
    debug = False
else:
    atexit.register(pwm.stop)

I have also modified atexit.register(pwm.stop) to only run in interactive mode, stopping the fan at the end of the script.

We will import the 'sys' library to be able to read the arguments we have passed on the command line.

# Import module sys
import sys

Adding information to the log (syslog)

Since all's well that ends well, let's be good neighbours with the Linux system and send to the system log (syslog) information about whether the script has booted correctly or encountered a problem.

I have yet to include it in temperature_pwm_controller.py when it is more stable.

To send this information to the log, we are going to use the Python syslog module, which allows you to send messages to the system using the function syslog.syslog(priority, message). The priority argument indicates the severity level of the message, and the message argument is the text you want to send.

For example, you can do something like this:

# Import syslog module
import syslog

# Try to run the script
try:
    # Here is the code for your script.
    # If all goes well, send a success message to syslog with priority INFO
    syslog.syslog(syslog.LOG_INFO, "Script executed successfully")
except Exception as e:
    # If an error occurs, send an error message to syslog with priority ERR.
    syslog.syslog(syslog.LOG_ERR, f "Script failed with error: {e}")

You will then be able to see in the syslog log if your script has been executed correctly or if there has been a problem.

With this we will have a variable speed fan on the Raspberry Pi.

The final code (the script or program that does the magic)

This is the full script as of today, 27/11/2023.

It is not finished yet, as I have encountered some problems that I have had to work out.

The fact that I am finding it difficult does not discourage me at all, quite the contrary. This means that the subject is not so simple and that this article will have a lot of value.

The truth is that I have learned a lot along the way. I didn't expect that such an apparently simple subject would give me so many headaches.

For the time being, I leave here the code I have, as it is now.

#!/usr/bin/env python3

# Import the rpi-hardware-pwm library
from rpi_hardware_pwm import HardwarePWM

# Import psutil library for reading CPU temperature
import psutil

# Import time library for using the sleep function
import time

# Import sys module
import sys

# Import atexit library to use the atexit function
import atexit


# Define the test interval in seconds (every how many seconds to read the temperature and set the duty cycle of the PWM signal)
test_interval = 10

# Define the PWM channel to be used (Channel 0, or PWM0, by default is assigned to GPIO_18)
pwm_channel = 0

# Define the working frequency in Hz
frequency = 25000

# Define minimum and maximum temperature to activate the PWM output in degrees Celsius
temp_min = 50
temp_max = 65

# Define minimum and maximum duty cycle in percentage
min_cycle = 60
cycle_max = 100

# Define degrees of hysteresis
hysteresis = 2

# Define PWM channel and frequency
pwm = HardwarePWM(channel_pwm, frequency)

# Make the fan stop when the program ends
# atexit.register(pwm.stop)

# Create a variable to store the previous duty cycle
previous_cycle = 0

# Create a variable to store the elapsed time since the last temperature printout
time = 6

# Create a variable to store the duty cycle time
duty_cycle = 0

# Create a global variable for debugging mode
debug = True

# Create a function to print debug messages only if debugging mode is active
def print_debug(message):
    # If debug mode is True, print the message to the console.
    if debug:
        print(message)

# Define function to calculate duty cycle proportional to temperature
def calculate_duty_cycle(temp, temp_min, temp_max, cycle_min, cycle_max, hysteresis):
    # If temp is less than min, return 0.
    if temp  (temp_min + hysteresis):
            cycle = (temp - temp_min) * (cycle_max - cycle_min) / (temp_max - temp_min) + cycle_min.
        # If not, use the minimum cycle
        else:
            cycle = cycle_min
        # If cycle greater than 100, use 100
        if cycle > 100:
            cycle = 100
        # Return cycle rounded to nearest integer
        return round(cycle)

# Check if the program has been executed on Raspberry Pi startup
if "--on_pi_start" in sys.argv:
    debug = False
else:
    atexit.register(pwm.stop)

# Start PWM generator with a duty cycle of 0%
pwm.start(100)
time.sleep(1)

# Create an infinite loop
while True:
    # Read the CPU temperature in degrees Celsius.
    temp = psutil.sensors_temperatures()['cpu_thermal'][0].current

    # Calculate the duty cycle using the defined function
    duty_cycle = calculate_duty_cycle(temp, temp_min, temp_max, cycle_min, cycle_max, cycle_max, hysteresis)

    # If more than 5 seconds have elapsed since the last temperature printout, print the temperature on the console
    if time >= 5:
        # print_debug(f "CPU temperature is {temp:.2f} °C. Duty cycle is {duty_cycle:.0f}%")
        # Reset elapsed time
        time = 0
    # If not, increase elapsed time by 1 second
    else:
        time += 1

    # If the fan starts from stop (previous duty cycle was 0 and current duty cycle is not), start with a cycle of 100% for 1 second to overcome inertia
    if previous_cycle == 0 and duty_cycle != 0:
        pwm.change_duty_cycle(100)
        # pwm.start(100)
        print_debug(f "Starting fan... (temperature {temp:.2f}ºC)")
        # print_debug(f "Changed duty cycle to {duty_cycle}% (1)")
        # print_debug(f "Previous {previous_duty_cycle}% (1)")
        time.sleep(1)

    # Change PWM Duty Cycle
    pwm.change_duty_cycle(duty_cycle)
    # pwm.start(duty_cycle)
    previous_cycle = duty_cycle
    print_debug(f "Temperature: {temp:.2f}ºC. Duty_cycle: {duty_cycle:.0f}%")

    # Wait the number of seconds we have specified as the test interval before repeating the loop.
    time.sleep(test_interval)

Connecting the PWM driver to the Raspberry Pi 4 and starting it up

Now we have everything we need and we can put the pieces together, hardware and software, and get our Raspberry Pi variable speed fan up and running.

Choose the Raspberry Pi 4 pin to which we are going to connect the driver.

All of the Raspberry Pi 4's output pins can output a PWM signal, but it should be noted that not all pins are the same when it comes to generating a PWM signal.

  • Software PWM pins: They will generate the signal by software. It will be the CPU of the Raspberry Pi 4 in charge of generating the signal and will consume CPU resources, like any other program.
  • Hardware PWM pins: These will generate the signal by hardware. The CPU of the Raspberry Pi 4 will only have to ask one of its "hardware PWM generators" to generate the desired signal and it will not have to do anything else. The hardware specialised in generating PWM signals will take care of its generation and the CPU will not have to do anything, so it will not consume CPU resources, leaving all its capacity and power for other programs.
Image source: raspberry.org

The pins 12/13 and 18/19 generate the PWM signal by hardwarewhile all other pins generate the PWM signal by software.

Image source: raspberry.org

I recommend that you use one of the pins 12/13 and 18/19, unless you have a good reason not to, such as the following:

The PWM hardware and the headphone jack use the same circuitry as the Raspberry Pi, so the you will not be able to use them at the same time. In other words, if you use the headphone jack on the Raspberry Pi 4 you will have to use PWM in software, with all its limitations.

IMPORTANT: If you do not use pin 18, remember to update the code with the pin you have decided to use.. These examples are set up to use GPIO 18 (pin 12 on the Raspberry Pi 4 expansion port).

PWM signal calibration for your fan

As we saw in detail in the article "Controlling the speed of a fan with PWM"As we mentioned earlier in this article, each fan responds differently to PWM signals.

Each fan has a minimum duty cycle for the fan to start spinning from standstill. On some fans it may be 30% and on others 70% so you need to test it yourself with your fan.

Each fan also has a minimum Duty Cycle so that it does not stop when it is already in motion. You will not be able to lower the duty cycle to, say, 3%, expecting the fan to run very slowly. With so little power it will stop long before it gets down to that 3%.

To give you an idea, the fan I'm testing right now stops when the duty cycle drops to about 50%.

You have to keep in mind that both Work Cycles do not have to be the same (and they won't be).

The fan needs more energy (higher duty cycle) to overcome the resistance to start up and will need less energy to keep moving once it is already moving.

All of the above means that you will need to calibrate your specific fan to find these two Duty Cycle values.

You can easily calibrate your fan by using the "Basic programme"I have left you before (you can click here to go to it).

You will have to run the programme several times with the fan stopped, by going to each time the Duty Cycle, until the fan starts up. Once you have found that "Minimum duty cycle from standstill"You can use that value, increasing it by a percentage as a safety margin (you can try a 10%).

Then, with the fan running, you will have to run the programme several times, downloading each time the Duty Cycle, until the fan stops. Once you have found that "Minimum duty cycle from in motion"You can use that value, increasing it by a percentage as a safety margin (you can try a 10%).

Super simple speed control, no software

The Raspberry Foundation, knowing that the Raspberry Pi 4 would need a fan, natively included in its operating system the ability to enable temperature control easily from one of the Raspberry Pi's configuration menus.

The control possibilities are rather limited as only a temperature can be specified, below which the fan will stop and start again, at 100% speed, when it exceeds this temperature. Speed control is not included.

It is a simple functionality, but it is available natively, without having to install any additional software.

Note that by not regulating the speed, but only turning the fan on and off (0 or 100%) it can be even noisier than having the fan at full power if these changes occur often. I recommend you try it to see how it works in your particular case.

What if my Raspberry Pi is not the 4, what about other models?

The Raspberry Pi 5 is too new and I haven't used it yet, so I won't be able to tell you much about it, but I have had all the models before 4 and I can tell you about them.

I can tell you that, on the previous Raspberry Pi models, 1, 2, 3 and their variations, the power consumption is much lower than on the Raspberry Pi 4 and I have never needed to use a fan and its temperature has always been kept within very reasonable limits.

However, if you want or need to add the fan to one of these models, it won't be difficult.

The PWM driver is exactly the same as the one I used for the Raspberry Pi 4 and the software will probably work as is, or with minimal changes.

All the research and development has been done with a Raspberry Pi 3, only when it was finished I have installed it on the Raspberry Pi 4. The reason is simply that the only Raspberry Pi 4 that I have was occupied for things of home automation, and I could not be removing it.

If you try it with other versions, I ask you to leave a message in the comments and then I can update the article with that information.

At the moment I can tell you:

Raspberry Pi 4

I haven't tested it yet, but I will do so soon. It's really the model it's mainly aimed at.

Raspberry Pi 3

It is the model on which I have done the research and development so it should work.

Raspberry Pi 2

When it is finished I will try it in this version, although it is not very useful because it doesn't heat up and doesn't need a fan.

No confundas hardware y software de tu Raspberry Pi

Lo que acabamos de ver es respecto al hardware de la Raspberry Pi, pero también el software puede ser diferente.

La versión concreta de sistema operativo que tenía instalada en la Raspberry Pi 3 era: «Raspbian GNU/Linux 11 (bullseye) armv71 (32bit) con user space de 32 bit.

El sistema operativo que tenía instalado en la Raspberry Pi 4 era: «Raspbian GNU/Linux 11 (bullseye) aarch64 (64bit) con user space de 32 bit.

Si has seguido el tutorial y encuentras algo diferente o que no te funciona no estaría de más de comprobaras tu versión.

Para ver la versión del sistema operativo instalada en tu Raspberry Pi, puedes utilizar el siguiente comando:

cat /etc/os-release

Este comando te mostrará el nombre y la versión del sistema operativo que se está ejecutando en tu dispositivo.

Si deseas saber si el sistema operativo es de 32 o 64 bits, puedes utilizar el siguiente comando:

uname -m

Este comando te mostrará la arquitectura del procesador de tu Raspberry Pi. Si la salida es armv7l, entonces estás utilizando un sistema operativo de 32 bits. Si la salida es aarch64, entonces estás utilizando un sistema operativo de 64 bits 12.

Para ver si el espacio de usuario de tu Raspberry Pi es de 32 o 64 bits, puedes utilizar el siguiente comando:

getconf LONG_BIT

Este comando te mostrará la arquitectura del procesador de tu Raspberry Pi. Si la salida es 32, entonces estás utilizando un espacio de usuario de 32 bits, si la salida es 64, entonces estás utilizando un espacio de usuario de 64 bits.

What next?

In this article we have seen what we have to do to control the Raspberry Pi's fan speed depending on the temperature of your CPU.

If you have not done so, I suggest you read the following articles, which provide more information on each of the aspects we have summarised here:

Leave a comment