commit 9f785a17f5e8b833ea6ad9a8cba67e958136c4ab Author: lhye200 Date: Sat May 9 16:31:36 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cbbdcb --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/* +*.spec +venv/* +dist/* +__pycache__/* +*.pyc \ No newline at end of file diff --git a/auto-fan.service b/auto-fan.service new file mode 100644 index 0000000..1b0a90c --- /dev/null +++ b/auto-fan.service @@ -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 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..7149b24 --- /dev/null +++ b/build.sh @@ -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" diff --git a/die_loop.py b/die_loop.py new file mode 100644 index 0000000..e31d1e8 --- /dev/null +++ b/die_loop.py @@ -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() diff --git a/fan.py b/fan.py new file mode 100644 index 0000000..3421c22 --- /dev/null +++ b/fan.py @@ -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() diff --git a/fan_autopid.py b/fan_autopid.py new file mode 100644 index 0000000..bc4f875 --- /dev/null +++ b/fan_autopid.py @@ -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() diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..759c90c --- /dev/null +++ b/install.sh @@ -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 \ No newline at end of file diff --git a/mat_cal.py b/mat_cal.py new file mode 100644 index 0000000..7a9c6f2 --- /dev/null +++ b/mat_cal.py @@ -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()