Last active
August 15, 2023 21:36
-
-
Save topalex/17f6a66f0249de610e98b0e3a071ffc3 to your computer and use it in GitHub Desktop.
Raspberry PI fan control with hardware PWM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
from rpi_hardware_pwm import HardwarePWM | |
import time | |
import sys | |
import syslog | |
import os | |
import signal | |
# Configuration | |
PWM_CHANNEL = 0 # Hardware PWM channel | |
PWM_FREQ = 25 # [Hz] Change this value if fan has strange behavior | |
WAIT_TIME = 1 # [s] Time to wait between each refresh | |
# Configurable temperature and fan speed steps | |
tempSteps = [60, 70, 80] # [°C] | |
speedSteps = [40, 60, 100] # [%] | |
# Fan speed will change only of the difference of temperature is higher than hysteresis | |
hyst = 1 | |
# Will keep fan running until temperature drops below lowest of tempSteps minus this value | |
shutdown_hyst = 10 # [°C] | |
# Setup hardware PWM | |
pwm = HardwarePWM(pwm_channel=PWM_CHANNEL, hz=PWM_FREQ) | |
pwm.start(0) | |
i = 0 | |
fanSpeed = 0 | |
cpuTempOld = 0 | |
fanSpeedOld = 0 | |
systemdRun = bool(os.environ.get('INVOCATION_ID')) | |
if systemdRun: | |
syslog.openlog("fancontrol", logoption=syslog.LOG_PID) | |
def logger(message): | |
if systemdRun: | |
syslog.syslog(message) | |
else: | |
print(message) | |
def error(error): | |
pwm.stop() | |
if systemdRun: | |
syslog.syslog(syslog.LOG_ERR, error) | |
sys.exit(-1) | |
else: | |
sys.exit(error) | |
def get_temp(): | |
cpuTempFile = open("/sys/class/thermal/thermal_zone0/temp", "r") | |
cpuTemp = float(cpuTempFile.read()) / 1000 | |
cpuTempFile.close() | |
return cpuTemp | |
def stop(): | |
pwm.stop() | |
sys.exit() | |
def handle_sigterm(sig, frame): | |
logger("Interrupted by SIGTERM") | |
stop() | |
signal.signal(signal.SIGTERM, handle_sigterm) | |
# We must set a speed value for each temperature step | |
if len(speedSteps) != len(tempSteps): | |
error("Error: Numbers of temp steps and speed steps are different") | |
logger("Starting, CPU temp %.1f'C" % get_temp()) | |
try: | |
while 1: | |
# Read CPU temperature | |
cpuTemp = get_temp() | |
# Calculate desired fan speed | |
if abs(cpuTemp - cpuTempOld) > hyst: | |
# Below first value, fan will run at min speed. | |
if cpuTemp < tempSteps[0]: | |
if (cpuTemp < tempSteps[0] - shutdown_hyst or fanSpeed == 0): | |
fanSpeed = 0 | |
else: | |
fanSpeed = speedSteps[0] | |
# Above last value, fan will run at max speed | |
elif cpuTemp >= tempSteps[len(tempSteps) - 1]: | |
fanSpeed = speedSteps[len(tempSteps) - 1] | |
# If temperature is between 2 steps, fan speed is calculated by linear interpolation | |
else: | |
for i in range(0, len(tempSteps) - 1): | |
if (cpuTemp >= tempSteps[i]) and (cpuTemp < tempSteps[i + 1]): | |
fanSpeed = round((speedSteps[i + 1] - speedSteps[i]) | |
/ (tempSteps[i + 1] - tempSteps[i]) | |
* (cpuTemp - tempSteps[i]) | |
+ speedSteps[i], 1) | |
if (fanSpeed != fanSpeedOld): | |
logger("CPU temp %.1f'C, set fan speed to %.1f%%" % (cpuTemp, fanSpeed)) | |
pwm.change_duty_cycle(fanSpeed) | |
fanSpeedOld = fanSpeed | |
cpuTempOld = cpuTemp | |
# Wait until next refresh | |
time.sleep(WAIT_TIME) | |
# If a keyboard interrupt occurs (ctrl + c), the PwM enable is set to 0 and the program exits. | |
except KeyboardInterrupt: | |
logger("Interrupted by keyboard") | |
stop() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Unit] | |
Description=FanControl Service | |
After=multi-user.target | |
[Service] | |
Restart=always | |
ExecStart=/usr/bin/python3 /opt/FanControl/fancontrol.py | |
[Install] | |
WantedBy=default.target |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
from rpi_hardware_pwm import HardwarePWM | |
import sys | |
# Configuration | |
PWM_CHANNEL = 0 # Hardware PWM channel | |
PWM_FREQ = 25 # [Hz] Change this value if fan has strange behavior | |
# Setup hardware PWM | |
pwm = HardwarePWM(pwm_channel=PWM_CHANNEL, hz=PWM_FREQ) | |
pwm.start(0) | |
try: | |
while 1: | |
fanSpeed=float(input("Fan Speed: ")) | |
pwm.change_duty_cycle(fanSpeed) | |
except(KeyboardInterrupt): | |
print("Fan ctrl interrupted by keyboard") | |
pwm.stop() | |
sys.exit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To enable hardware PWM control you will need:
/boot/config.txt
:dtoverlay=pwm-2chan
to use GPIO_18 as the pin for PWM0 and GPIO_19 as the pin for PWM1dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
to use GPIO_12 as the pin for PWM0 and GPIO_13 as the pin for PWM1sudo pip3 install rpi-hardware-pwm