init
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
build/*
|
||||||
|
*.spec
|
||||||
|
venv/*
|
||||||
|
dist/*
|
||||||
|
__pycache__/*
|
||||||
|
*.pyc
|
||||||
16
auto-fan.service
Normal file
16
auto-fan.service
Normal 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
13
build.sh
Executable 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
17
die_loop.py
Normal 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
149
fan.py
Normal 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)
|
||||||
|
|
||||||
|
# 设置 PWM(25kHz)
|
||||||
|
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
105
fan_autopid.py
Normal 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
13
install.sh
Executable 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
21
mat_cal.py
Normal 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()
|
||||||
Reference in New Issue
Block a user