- Published on
树莓派变速风扇调试
- Authors
- Name
- NickHoo
先前为树莓派淘了个好看又便宜的外壳,11块包邮还送了个简易小风扇。小风扇上岗之后就开始不眠不休的全速狂转,最后可能是磨损严重,嘎嘎响,只能淘汰掉换新的。
兼顾 变速、颜值、价格,新风扇选了 25块不包邮的 Argon Mini Fan
。
硬件安装
软件配置
Raspberry Pi OS 系统 - 官方驱动
官方系统软件配置
The instructions below will start the Mini Fan at CPU Temp 55 degrees (tempt 55000). You may set your desired fan initiation temperature as per your requirements:
In Raspberry Pi OS, open a new terminal window
Enter sudo nano
/boot/config.txt
Add the following line:
dtoverlay=gpio-fan,gpiopin=18,temp=55000
Save changes and exit by pressing Ctrl+X
Ubuntu 系统 - Python脚本
Google 一番,原理大概是:
常开一个监测程序,根据不同的 CPU 温度,gpio 输出不同的档位的电信号,从而控制风扇转速。从上方官方配置中可知,风扇连接的 gpio 控制针脚为 18 。
预设 温度达到 45度 风扇慢转,温度达到 55度 风扇满转。分步调试如下:
安装依赖
pip install RPi.GPIO
单步验证 ,在Python终端交互模式下依次执行:
import RPi.GPIO as GPIO GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) fan = GPIO.PWM(18,50) fan.start(100) # 风扇开转 fan.stop() # 风扇挺转
功能定制: 单步验证通过后,便可开始展开调试 定时读取温度、循环判断等逻辑。github 上也有大量的现成代码,挑选个最近比较新的为模版魔改定制(试过两个star多的旧项目不兼容)。
结合下一节系统服务管理模块,配置 开机自启、通过服务名启动、停止等。但是调试时发现通过
systemctl stop fan
停掉服务时,python进程已经没了,风扇却还在转。应该是服务stop时,进程直接被杀掉了,PWM端口的高电位未被重置。Google一番,确实。如下是魔改后的完整代码,引入了 signal ,对关停信号做出响应:
/apps/gpio/fan.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO ### https://pypi.org/project/RPi.GPIO/
import time
import signal #### 处理 systemmd stop https://stackoverflow.com/questions/18499497/how-to-process-sigterm-signal-gracefully
import sys
import os
import atexit
# Configuration
FAN_PIN = 18 # BCM pin used to drive PWM fan
WAIT_TIME = 10 # [s] Time to wait between each refresh
PWM_FREQ = 50 # [kHz] 25kHz for Noctua PWM control
# Configurable temperature and fan speed
MIN_TEMP = 45
MIN_TEMP_DEAD_BAND = 5
MAX_TEMP = 55
FAN_LOW = 1
FAN_HIGH = 100
FAN_OFF = 0
FAN_MAX = 100
# Variable definition
outside_dead_band_higher = True
# Get CPU's temperature
def getCpuTemperature():
res = os.popen('cat /sys/class/thermal/thermal_zone0/temp').readline()
temp = float(res)/1000
#print("temp is {0}".format(temp)) # Uncomment for testing
return temp
# Set fan speed
def setFanSpeed(speed):
fan.start(speed)
return()
# Handle fan speed
def handleFanSpeed(temperature, outside_dead_band_higher):
# Turn off the fan if lower than lower dead band
if outside_dead_band_higher == False:
setFanSpeed(FAN_OFF)
#print("Fan OFF") # Uncomment for testing
return
# Run fan at calculated speed if being in or above dead zone not having passed lower dead band
elif outside_dead_band_higher == True and temperature < MAX_TEMP:
step = float(FAN_HIGH - FAN_LOW)/float(MAX_TEMP - MIN_TEMP)
temperature -= MIN_TEMP
setFanSpeed(FAN_LOW + ( round(temperature) * step ))
#print(FAN_LOW + ( round(temperature) * step )) # Uncomment for testing
return
# Set fan speed to MAXIMUM if the temperature is above MAX_TEMP
elif temperature > MAX_TEMP:
setFanSpeed(FAN_MAX)
#print("Fan MAX") # Uncomment for testing
return
else:
return
# Handle dead zone bool
def handleDeadZone(temperature):
if temperature > (MIN_TEMP + MIN_TEMP_DEAD_BAND/2):
return True
elif temperature < (MIN_TEMP - MIN_TEMP_DEAD_BAND/2):
return False
# Reset fan to 100% by cleaning GPIO ports
def resetFan():
GPIO.cleanup() # resets all GPIO ports used by this function
### systemmd stop
class GracefulKiller:
kill_now = False
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
def exit_gracefully(self, *args):
self.kill_now = True
###
try:
# Setup GPIO pin
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(FAN_PIN, GPIO.OUT, initial=GPIO.LOW)
fan = GPIO.PWM(FAN_PIN,PWM_FREQ)
# setFanSpeed(FAN_OFF)
# Handle fan speed every WAIT_TIME sec
killer = GracefulKiller() ###
while not killer.kill_now:
temp = float(getCpuTemperature())
outside_dead_band_higher = handleDeadZone(temp)
handleFanSpeed(temp, outside_dead_band_higher)
time.sleep(WAIT_TIME)
resetFan()
except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt
resetFan()
atexit.register(resetFan)
服务
接上一节,为实现 通过服务名启停风扇,定义如下服务:
/usr/lib/systemd/system/fan.service
[Unit]
Description=Fan
#Documentation=https://blog.driftking.tw/2019/11/Using-Raspberry-Pi-to-Control-a-PWM-Fan-and-Monitor-its-Speed/
#Documentation=https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html
# After=
# Requires=
[Service]
Type=simple
User=root
Group=root
ExecStart=/apps/gpio/fan.py
ExecReload=/apps/gpio/fan.py
KillMode=mixed
[Install]
WantedBy=multi-user.target
[Service]
服务模块里ExecStart
、ExecReload
指定上一节的Python脚本绝对地址,重点在KillMode
需指定为 mixed ,这样在服务 stop 时,不会直接 kill 进程,而是通知 进程,从而在 Python 脚本内重置PWM针脚电位,正常关停风扇。systemd 更多配置详情可参考 阮一峰老师的 systemd 入门教程:实战篇
最终效果就是通过如下命令来便捷操作风扇服务了:
systemctl start fan # 开启
systemctl stop fan # 停止
systemctl enable fan # 开机自启
systemctl disable fan # 禁用开机自启
总结
Python 语法已经忘得差不多了,主要是思路理清,语法照猫画虎。
单步验证通很重要,最初尝试其他gpio库时没做验证也是走了一些弯路。
按需开风扇好处多多:安静、省电、减少风扇损耗,还能根据风扇声音估计判断系统负载(比如风扇一旦狂转估计是 jellyfin 在转码 或 Mac SMB 在备份)。
后续
备忘:
Ubuntu 21.04 默认的 gcc 版本为 10
部分旧语法的C代码可能会报错,安装 指定版本的 gcc:
sudo apt install gcc-8 g++-8 gcc-9 g++-9 gcc-10 g++-10
使用指定版本的 gcc 编译:
gcc-9 -Wall -o pwmfan pwmfan.c -lwiringPi -lcrypt -lm -lrt -lpthread