This commit is contained in:
2026-05-09 16:31:36 +08:00
commit 9f785a17f5
8 changed files with 340 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
build/*
*.spec
venv/*
dist/*
__pycache__/*
*.pyc

16
auto-fan.service Normal file
View File

@@ -0,0 +1,16 @@
[Unit]
Description=Auto Raspberry Pi 4-Line Fan
After=pigpiod.service network.target
[Service]
User=root
Group=root
WorkingDirectory=/opt/Auto_Fan
ExecStart=/opt/Auto_Fan/fan
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

13
build.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
source venv/bin/activate
# 安装打包工具 PyInstaller
# pip install pyinstaller
# 清理旧的构建文件
rm -rf build dist fan.spec
# 将 fan.py 打包为单文件可执行程序
pyinstaller --onefile fan.py
echo "打包完成!可执行文件位于 dist/fan"

17
die_loop.py Normal file
View File

@@ -0,0 +1,17 @@
from multiprocessing import Process
import os
def burn():
while True:
pass
if __name__ == "__main__":
processes = []
for _ in range(os.cpu_count()):
p = Process(target=burn)
p.start()
processes.append(p)
for p in processes:
p.join()

149
fan.py Normal file
View File

@@ -0,0 +1,149 @@
import pigpio
import time
import json
import math
class LowPassFilter:
def __init__(self,fc,Ts):
self.fc = fc
self.Ts = Ts
b = 2.0 * math.pi * self.fc * self.Ts
self.alpha = b / (b + 1)
self.last_value = 0
def filter(self,value):
out = self.last_value + self.alpha * (value - self.last_value)
self.last_value = out
return out
# =====================
# GPIO 定义
# =====================
PWM_PIN = 18
TACH_PIN = 23
# =====================
# PID 参数(需要调)
# =====================
Kp = 1.0
Ki = 0.01
Kd = 0.03
TARGET_TEMP =60.0 # 目标温度
# =====================
# 全局变量
# =====================
pulse_count = 0
last_time = time.time()
integral = 0
last_error = 0
# =====================
# TACH 回调
# =====================
def tach_callback(gpio, level, tick):
global pulse_count
pulse_count += 1
# =====================
# 读取 CPU 温度
# =====================
def get_temp():
with open("/sys/class/thermal/thermal_zone0/temp") as f:
return int(f.read()) / 1000
# =====================
# 计算 RPM
# =====================
def get_rpm():
global pulse_count, last_time
now = time.time()
dt = now - last_time
count = pulse_count
pulse_count = 0
last_time = now
# 2 脉冲/转
rpm = (count / 2) / dt * 60
return rpm
# =====================
# PID 控制器
# =====================
def pid_control(temp):
global integral, last_error
error = temp - TARGET_TEMP
integral += error
intergral = max(-100,min(100,integral))
derivative = error - last_error
output = Kp * error + Ki * integral + Kd * derivative
last_error = error
return output
if __name__=="__main__":
temp_low_pass = LowPassFilter(1,0.1)
duty_low_pass = LowPassFilter(0.5,0.1)
# =====================
# 初始化 pigpio
# =====================
pi = pigpio.pi()
if not pi.connected:
exit()
pi.set_mode(TACH_PIN, pigpio.INPUT)
pi.set_pull_up_down(TACH_PIN, pigpio.PUD_UP)
pi.callback(TACH_PIN, pigpio.FALLING_EDGE, tach_callback)
# 设置 PWM25kHz
pi.set_PWM_frequency(PWM_PIN, 25000)
pi.set_PWM_range(PWM_PIN, 255)
# =====================
# 主循环
# =====================
try:
while True:
temp = temp_low_pass.filter(get_temp())
rpm = get_rpm()
pid_out = pid_control(temp)
# 转换为 PWM限制范围
duty = int(duty_low_pass.filter(max(0, min(255, int(pid_out * 5)))))
# 最小转速保护
if duty < 10:
duty = 10
pi.set_PWM_dutycycle(PWM_PIN, duty)
# print(f"\rTemp={temp:.1f}C RPM={rpm:.0f} PWM={duty} ", end="", flush=True)
# 把实时状态写到内存盘(/dev/shm 不伤SD卡其他程序直接读这个JSON即可
try:
with open("/dev/shm/fan_status.json", "w") as f:
json.dump({"temp": temp, "rpm": rpm, "pwm": duty, "is_stalled": (duty > 100 and rpm < 500)}, f)
except Exception:
pass
# 风扇故障检测
if duty > 100 and rpm < 500:
print("\n⚠️ Fan may be stalled!")
time.sleep(0.1)
except KeyboardInterrupt:
pass
finally:
pi.set_PWM_dutycycle(PWM_PIN, 0)
pi.stop()

105
fan_autopid.py Normal file
View File

@@ -0,0 +1,105 @@
import pigpio
import time
PWM_PIN = 18
TACH_PIN = 23
TARGET_TEMP = 40.0
# 初始参数
Kp = 1.0
Ki = 0.0
Kd = 0.0
integral = 0
last_error = 0
last_temp = None
pulse_count = 0
last_time = time.time()
pi = pigpio.pi()
pi.set_PWM_frequency(PWM_PIN, 25000)
pi.set_PWM_range(PWM_PIN, 255)
def tach_cb(gpio, level, tick):
global pulse_count
pulse_count += 1
pi.set_mode(TACH_PIN, pigpio.INPUT)
pi.set_pull_up_down(TACH_PIN, pigpio.PUD_UP)
pi.callback(TACH_PIN, pigpio.FALLING_EDGE, tach_cb)
last_tem = 0
def get_temp():
global last_tem
with open("/sys/class/thermal/thermal_zone0/temp") as f:
temp_filtered = 0.8 * last_tem + 0.2 * int(f.read()) / 1000
last_tem = temp_filtered
return temp_filtered
def get_rpm():
global pulse_count, last_time
now = time.time()
dt = now - last_time
count = pulse_count
pulse_count = 0
last_time = now
return (count / 2) / dt * 60
def adaptive_pid(temp):
global Kp, Ki, Kd, integral, last_error, last_temp
error = temp - TARGET_TEMP
integral += error
integral = max(min(integral, 100), -100)
derivative = error - last_error
# ===== 自适应调参 =====
if last_temp is not None:
temp_rate = temp - last_temp
# 响应太慢 → 增大 Kp
if abs(error) > 5 and abs(temp_rate) < 0.1:
Kp += 0.001
# 震荡 → 降低 Kp提高 Kd
if abs(temp_rate) > 0.5:
Kp *= 0.95
Kd += 0.0005
# 长期偏差 → 增大 Ki
if abs(error) > 2:
Ki += 0.0001
last_temp = temp
last_error = error
output = Kp * error + Ki * integral + Kd * derivative
return output
try:
while True:
temp = get_temp()
rpm = get_rpm()
out = adaptive_pid(temp)
duty = max(10, min(255, int(out * 5)))
pi.set_PWM_dutycycle(PWM_PIN, duty)
print(f"\rT={temp:.1f}C RPM={rpm:.0f} PWM={duty} Kp={Kp:.2f} Ki={Ki:.2f} Kd={Kd:.2f} ", end="")
time.sleep(0.05)
except KeyboardInterrupt:
pass
finally:
pi.set_PWM_dutycycle(PWM_PIN, 0)
pi.stop()

13
install.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
rm -rf /opt/Auto_Fan
rm -rf /etc/systemd/system/auto-fan.service
mkdir -p /opt/Auto_Fan
cp -rf dist/fan /opt/Auto_Fan/
chmod +x /opt/Auto_Fan/fan
cp -rf auto-fan.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable auto-fan
systemctl start auto-fan

21
mat_cal.py Normal file
View File

@@ -0,0 +1,21 @@
from multiprocessing import Process
import numpy as np
import os
def burn():
a = np.random.rand(2000, 2000)
b = np.random.rand(2000, 2000)
while True:
# 浮点 + SIMD + 内存访问
np.dot(a, b)
if __name__ == "__main__":
procs = []
for _ in range(os.cpu_count()):
p = Process(target=burn)
p.start()
procs.append(p)
for p in procs:
p.join()