Fan control with dtoverlay on Raspberry

How to control a fan using Raspberry dtoverlay and Python


            

Another day working a bit for my SA818 radio node project. Today I tried active cooling with a 5V fan driven from RPIs 5V rail and controlled from GPIOs with a MOSFET (2N7002). I had only one 50mm SUNON fan, a large one with three wires. The additional yellow wire is the tachometer (provides information regarding fan rpms). This tacho wire is connected to GPIO24 which is pulled up to 3.3V rail through a 12 kΩ resistor. I was not aware until recently that you can turn on and off a fan using a special dtoverlay entry in /boot/firmware/config.txt where you specify the GPIO control pin and threshold temperature — CPU Temperature at which the fan turns on (°C × 1000), temperature that sets GPIO high — and, optional, the hysteresis, i.e. below temp at which the fan turns off (°C × 1000), default 10000. For example:

dtoverlay=gpio-fan,gpiopin=23,temp=50000,temp_hyst=5000

This means that GPIO23 is used to turn on when CPU temp reaches 50°C and turn off once CPU cools 5 degrees less than 50.

Testing my SA818 radio node. The fan is controlled from GPIO via a n-Channel MOSFET (2N7002). Gate connected to GPIO23 (RPi pin 16) via a 100 Ω resistor and the yellow wire pulled up to 3.3V thorugh a 12 kΩ.

I wrote a simple Python script to monitor rpm and temperature:

#!/usr/bin/env python3
import pigpio
import time

TACH_PIN = 24
PULSES_PER_REV = 2          # Adjust if your fan uses different pulses/rev
SAMPLE_TIME = 1.0           # seconds

def get_cpu_temperature():
    try:
        with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
            return int(f.read().strip()) / 1000.0
    except Exception:
        return None

def main():
    pi = pigpio.pi()
    if not pi.connected:
        print("ERROR: pigpiod not running.")
        return

    # Configure pin
    pi.set_mode(TACH_PIN, pigpio.INPUT)
    pi.set_pull_up_down(TACH_PIN, pigpio.PUD_UP)
    pi.set_glitch_filter(TACH_PIN, 100)   # ignore pulses shorter than 100 µs

    # Create callback (cumulative tally available via cb.tally())
    cb = pi.callback(TACH_PIN, pigpio.FALLING_EDGE)

    prev_tally = cb.tally()  # initial cumulative count
    print("Measuring CPU temperature + Fan RPM (Ctrl+C to stop)")

    try:
        while True:
            time.sleep(SAMPLE_TIME)
            total = cb.tally()           # cumulative pulses since cb creation
            pulses = total - prev_tally  # pulses in this interval
            prev_tally = total

            rpm = (pulses / PULSES_PER_REV) * (60.0 / SAMPLE_TIME)
            cpu_temp = get_cpu_temperature()

            if cpu_temp is None:
                temp_str = "N/A"
            else:
                temp_str = f"{cpu_temp:.1f}°C"

            print(f"CPU: {temp_str}    RPM: {int(rpm)}")

    except KeyboardInterrupt:
        print("\nExiting...")

    finally:
        cb.cancel()
        pi.stop()

if __name__ == "__main__":
    main()

For fun, I logged the values in a csv file and used them to display graphically:

Simply add the following function to Python script:

def log_to_csv(timestamp, temperature, rpm, filename="fan_log.csv"):
    """Append timestamp, CPU temperature, and RPM to a CSV file."""
    file_exists = os.path.isfile(filename)

    with open(filename, "a", newline="") as f:
        writer = csv.writer(f)

        # Write header only once
        if not file_exists:
            writer.writerow(["timestamp_s", "cpu_temp_C", "rpm"])

        writer.writerow([timestamp, f"{temperature:.2f}", int(rpm)])

Main takeaways

Very important for dtoverlay:

  • Temperatures must be in millidegrees Celsius.
  • You need to reboot after changing config.txt.
  • This method works on Raspberry Pi OS and any Pi with Device Tree enabled (default).
  • For the Pi 5’s fan control via rp1-fan, the parameters differ slightly
  • TODO: use a 4-wire fan whose rpm can be varied with CPU temperature

That’s it for now

73

SA818 radio node – version 2.0

I was pretty busy since August 2024 with a lot of other tasks, mostly job-related and did not have too much time for DIY and tinkering. And this included my SA818 project.

How to manage WordPress orphaned taxonomies

While developing my website I frequently got warnings. The one below means WordPress tried to access the property slug of an object that doesn’t exist (is null) and, as mentioned elsewhere, this usually happens when a menu, a snippet or other piece of code references a category that no longer exist.

Comments are closed.