Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ a certain threshold.

To use this code, you'll have to install a fan. The full instructions can be
found on our guide: [Control Your Raspberry Pi Fan (and Temperature) with Python](https://howchoo.com/g/ote2mjkzzta/control-raspberry-pi-fan-temperature-python).

In addition to the guide above, in order to use soft PWM, you will need to add a capacitor to avoid audible noise induced by the fan at PWM frequency.
Values for the capacitor are highly dependent on PWM frequency; the higher the frequency, the less capacitance is needed.

In addition, the values for minimum PWM for the fan will be influenced by PWM frequency as well.

The default configuration uses a 100µF electrolytic capacitor between the two fan leads, and a 500Hz PWM frequency.

In order to test out the minimum speed of a different fan manually, you can use the module interactively:

```sh
$ python
```
```python
from fancontrol import *

fan = PWMOutputDevice(GPIO_PIN, frequency=PWM_FREQUENCY)
```
Now `fan.value` can be set to values between `0.0` and `1.0` in order to find the minimum value that still allows the fan to spin reliably.

Start with a high fan speed (such as `0.9`), and slowly decrease the speed until the fan stops. This value (in addition to a security margin) will be the `MIN_PWM` value for your specific PWM frequency, capacitance and fan type.
74 changes: 54 additions & 20 deletions fancontrol.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
#!/usr/bin/env python3

import time
import logging

from gpiozero import OutputDevice
from gpiozero import PWMOutputDevice

"""
ON_TEMP should be higher than the Raspi idle temperature (usually
between 50-70 degC, depending on the base load), otherwise the fan will
continuously switch ON and OFF.
OFF_TEMP should be reachable with the minimum fan speed in the idle state,
otherwise the fan will be continuously ON after triggered once (usually
around 50 degC).
"""

ON_THRESHOLD = 65 # (degrees Celsius) Fan kicks on at this temperature.
OFF_THRESHOLD = 55 # (degress Celsius) Fan shuts off at this temperature.
SLEEP_INTERVAL = 5 # (seconds) How often we check the core temperature.
GPIO_PIN = 17 # Which GPIO pin you're using to control the fan.
FULL_ON_TEMP = 85. # [degC] temp for max PWM
ON_TEMP = 75. # [degC] temp to switch on
OFF_TEMP = 55. # [degC] temp for min PWM
INTERVAL = 5. # [s] refresh interval
GPIO_PIN = 17 # Pin to connect transistor base
MAX_PWM = 1. # PWM at max on temp
MIN_PWM = .64 # PWM at lowest on temp
MIN_STARTUP_PWM = .9 # PWM to use on startup
PWM_FREQUENCY = 500 # [Hz] - with 100uF, and .7 min PWM
FAN_MODES = ["linear", "exponential"]
FAN_MODE = "exponential"


def get_temp():
Expand All @@ -27,25 +43,43 @@ def get_temp():
except (IndexError, ValueError,) as e:
raise RuntimeError('Could not parse temperature output.') from e

if __name__ == '__main__':
# Validate the on and off thresholds
if OFF_THRESHOLD >= ON_THRESHOLD:
raise RuntimeError('OFF_THRESHOLD must be less than ON_THRESHOLD')
def turn_on_or_off(pwm_device: PWMOutputDevice, temp: int) -> None:
if pwm_device.is_active and temp < OFF_TEMP:
pwm_device.off()
print("Turned off")
elif not pwm_device.is_active and temp >= ON_TEMP:
pwm_device.on()
pwm_device.value = MIN_STARTUP_PWM
print("Turned on")

fan = OutputDevice(GPIO_PIN)
def calc_pwm(pwm_range: float, temp_range: float, temp: float) -> float:
if FAN_MODE == "linear":
return pwm_range * (temp - OFF_TEMP) / temp_range + MIN_PWM
elif FAN_MODE == "exponential":
return (1.001 ** (3.7 * (temp - OFF_TEMP) / temp_range) ** 5 - 1) * pwm_range + MIN_PWM

def fancontrol_loop():
fan = PWMOutputDevice(GPIO_PIN, frequency=PWM_FREQUENCY)

pwm_range = MAX_PWM - MIN_PWM
temp_range = FULL_ON_TEMP - OFF_TEMP

while True:
temp = get_temp()
if fan.is_active:
pwm = calc_pwm(pwm_range, temp_range, temp)
fan.value = max(min(pwm, MAX_PWM), MIN_PWM)
turn_on_or_off(fan, temp)

print(f"Current temp|PWM: {temp:.2f}|{fan.value:.2f}")
time.sleep(INTERVAL)

# Start the fan if the temperature has reached the limit and the fan
# isn't already running.
# NOTE: `fan.value` returns 1 for "on" and 0 for "off"
if temp > ON_THRESHOLD and not fan.value:
fan.on()

# Stop the fan if the fan is running and the temperature has dropped
# to 10 degrees below the limit.
elif fan.value and temp < OFF_THRESHOLD:
fan.off()
if __name__ == "__main__":
assert ON_TEMP > OFF_TEMP, "ON_TEMP must be higher than OFF_TEMP"
assert ON_TEMP < FULL_ON_TEMP, "ON_TEMP must be lower than FULL_ON_TEMP"
assert MIN_STARTUP_PWM >= MIN_PWM, "MIN_STARTUP_PWM must be at least MIN_PWM"
assert MAX_PWM > MIN_PWM, "MAX_PWM must be higher than MIN_PWM"
assert FAN_MODE in FAN_MODES, f"FAN_MODE must be one of {str(FAN_MODES)}"

time.sleep(SLEEP_INTERVAL)
fancontrol_loop()