init
This commit is contained in:
1 parent
9916e919b1
commit
e0ceb22cc0
4 files changed
+277
-2
No files matched your search
@@ -1,3 +1,51 @@
|
||||
# gpu-fan-ctrl
|
||||
# GPU Fan Controller — Delta QFR1212GHE
|
||||
|
||||
GPU Fan Controller — Delta QFR1212GHE
|
||||
Автоматическое управление вентилятором на Tesla V100-PCIE-32GB по температуре GPU.
|
||||
|
||||
## Подключение
|
||||
|
||||
| Вентилятор | Arduino Uno |
|
||||
|---|---|
|
||||
| красный (+12V) | к блоку питания |
|
||||
| чёрный (GND) | GND блока питания |
|
||||
| жёлтый (tach) | D2 |
|
||||
| синий (PWM) | D9 |
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
git clone ...
|
||||
sudo cp scripts/gpu_fan_ctrl.sh /root/
|
||||
sudo chmod +x /root/gpu_fan_ctrl.sh
|
||||
|
||||
sudo cp systemd/gpu-fan-ctrl.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gpu-fan-ctrl.service
|
||||
|
||||
tail -f /root/gpu_fan.log
|
||||
```
|
||||
|
||||
## Ручное управление
|
||||
|
||||
```bash
|
||||
stty -F /dev/ttyUSB0 115200
|
||||
echo "6" > /dev/ttyUSB0 # полная
|
||||
echo "3" > /dev/ttyUSB0 # средний
|
||||
echo "s" > /dev/ttyUSB0 # статус
|
||||
echo "RPM" > /dev/ttyUSB0 # обороты
|
||||
```
|
||||
|
||||
## Режимы
|
||||
|
||||
| Режим | PWM | Температура |
|
||||
|---|---|---|
|
||||
| STOP | 10000 | — |
|
||||
| STANDBY | 8000 | < 23°C |
|
||||
| LOW | 5000 | 23–28°C |
|
||||
| MED | 3000 | 28–33°C |
|
||||
| HIGH | 2500 | 33–39°C |
|
||||
| FULL | 0 (2 мин) | > 45°C ИЛИ рост 3°C/2с |
|
||||
|
||||
## Подпись
|
||||
|
||||
Собрано при участии Qwen3.8-35B-A3B-Q4_K_M @ Tesla V100 32GB (PID 3666) на debi9.
|
||||
@@ -0,0 +1,112 @@
|
||||
// GPU Fan Controller — Delta QFR1212GHE (4-pin PWM, 12V)
|
||||
// 6 режимов: 1=STOP, 2=STANDBY, 3=LOW, 4=MED, 5=HIGH, 6=FULL(2 мин)
|
||||
// RPM читается через тахометр (жёлтый → D2), PWM на синий → D9
|
||||
// PWM инвертирован: 0 = стоп, 99 = полная (Delta QFR1212GHE)
|
||||
// Фан не гаснет полностью — минимум 1000 (10%)
|
||||
|
||||
const int FAN_PIN = 9;
|
||||
const int TACH_PIN = 2;
|
||||
|
||||
volatile unsigned long tachPulses = 0;
|
||||
unsigned long lastRpm = 0;
|
||||
unsigned long lastDataTime = 0;
|
||||
|
||||
const int MODE_STOP = 10000;
|
||||
const int MODE_STANDBY = 8000;
|
||||
const int MODE_LOW = 5000;
|
||||
const int MODE_MED = 3000;
|
||||
const int MODE_HIGH = 2500;
|
||||
const int MODE_FULL = 0;
|
||||
const int MODE_FULL_TIME = 60000;
|
||||
|
||||
int currentPWM = MODE_STANDBY;
|
||||
unsigned long modeFullStart = 0;
|
||||
bool inModeFull = false;
|
||||
|
||||
char buf[16];
|
||||
|
||||
void setup() {
|
||||
pinMode(FAN_PIN, OUTPUT);
|
||||
pinMode(TACH_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(TACH_PIN), tachISR, FALLING);
|
||||
Serial.begin(115200);
|
||||
|
||||
// Timer1: Fast PWM, ICR1=10000, prescaler 64 → 10Hz
|
||||
TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11);
|
||||
TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS12);
|
||||
ICR1 = 10000;
|
||||
OCR1A = MODE_STANDBY;
|
||||
lastDataTime = millis();
|
||||
lastRpm = millis();
|
||||
Serial.println("FAN CONTROLLER READY - modes 1-6 + RPM");
|
||||
}
|
||||
|
||||
void tachISR() { tachPulses++; }
|
||||
|
||||
void applyPWM(int pwm) {
|
||||
if (pwm < MODE_FULL) pwm = MODE_FULL;
|
||||
if (pwm > MODE_STOP) pwm = MODE_STOP;
|
||||
OCR1A = pwm;
|
||||
currentPWM = pwm;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
unsigned long now = millis();
|
||||
|
||||
if (Serial.available()) {
|
||||
int n = Serial.readBytesUntil('\n', buf, 15);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
char c = buf[0];
|
||||
|
||||
if (c >= '1' && c <= '6') {
|
||||
lastDataTime = now;
|
||||
inModeFull = false;
|
||||
modeFullStart = 0;
|
||||
|
||||
switch (c) {
|
||||
case '1': applyPWM(MODE_STOP); Serial.println("MODE: 1 STOP"); break;
|
||||
case '2': applyPWM(MODE_STANDBY); Serial.println("MODE: 2 STANDBY"); break;
|
||||
case '3': applyPWM(MODE_LOW); Serial.println("MODE: 3 LOW"); break;
|
||||
case '4': applyPWM(MODE_MED); Serial.println("MODE: 4 MED"); break;
|
||||
case '5': applyPWM(MODE_HIGH); Serial.println("MODE: 5 HIGH"); break;
|
||||
case '6':
|
||||
applyPWM(MODE_FULL);
|
||||
inModeFull = true;
|
||||
modeFullStart = now;
|
||||
Serial.println("MODE: 6 FULL (60s)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == 'R' || c == 'r') {
|
||||
noInterrupts();
|
||||
unsigned long p = tachPulses;
|
||||
tachPulses = 0;
|
||||
interrupts();
|
||||
Serial.print("RPM:"); Serial.println(p * 60);
|
||||
}
|
||||
|
||||
if (c == 's' || c == 'S') {
|
||||
Serial.print("PWM:"); Serial.println(currentPWM);
|
||||
if (inModeFull) Serial.print("FULL:"); Serial.println((now - modeFullStart) / 1000);
|
||||
Serial.print("DATA:"); Serial.println((now - lastDataTime) / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inModeFull && (now - modeFullStart >= MODE_FULL_TIME)) {
|
||||
applyPWM(MODE_MED);
|
||||
inModeFull = false;
|
||||
Serial.println("FULL TIMEOUT -> MODE_MED");
|
||||
}
|
||||
|
||||
if (now - lastRpm >= 1000) {
|
||||
noInterrupts();
|
||||
unsigned long p = tachPulses;
|
||||
tachPulses = 0;
|
||||
interrupts();
|
||||
Serial.print("RPM:"); Serial.println(p * 60);
|
||||
lastRpm = now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# GPU Fan Controller — Delta QFR1212GHE
|
||||
# Режимы: 2=STANDBY(<25), 3=LOW(<30), 4=MED(<35), 5=HIGH(<40), 6=FULL(>45 или burst)
|
||||
# Режим 1 (STOP) — не используется, фан всегда крутится минимум на standby.
|
||||
# Порог burst: temp > 45°C ИЛИ рост >= 3°C за 2 секунды.
|
||||
|
||||
LOG=/root/gpu_fan.log
|
||||
DEV=/dev/ttyUSB0
|
||||
stty -F "$DEV" 115200
|
||||
exec 3>"$DEV"
|
||||
exec 1>>"$LOG" 2>&1
|
||||
|
||||
# Пороги
|
||||
TH_COLD=25
|
||||
TH_LOW=30
|
||||
TH_MED=35
|
||||
TH_HIGH=40
|
||||
TH_BURST=45
|
||||
TH_BURST_ROISE=3
|
||||
BURST_WINDOW=2
|
||||
BURST_DURATION=120
|
||||
|
||||
# Хистерезис (мёртвая зона 2°)
|
||||
HYST=2
|
||||
|
||||
prevTemp=0
|
||||
prevTime=0
|
||||
burstTimer=0
|
||||
lastMode=0
|
||||
noDataCount=0
|
||||
|
||||
echo "[START] $(date) GPU Fan Ctrl PID=$$"
|
||||
|
||||
while true; do
|
||||
TEMP=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader -i 0 | tr -cd '0-9')
|
||||
now=$(date +%s)
|
||||
|
||||
if [ -z "$TEMP" ] || ! [ "$TEMP" -gt 0 ] 2>/dev/null; then
|
||||
noDataCount=$((noDataCount + 1))
|
||||
if [ $noDataCount -ge 10 ]; then
|
||||
echo "[ERROR] nvidia-smi failed 10+ times, aborting"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1; continue
|
||||
fi
|
||||
noDataCount=0
|
||||
|
||||
# Burst: temp rose TH_BURST_ROISE in BURST_WINDOW
|
||||
if [ $prevTemp -gt 0 ] && [ $((prevTime != 0 ? now - prevTime : 99999)) -le $BURST_WINDOW ]; then
|
||||
rise=$((TEMP - prevTemp))
|
||||
if [ $rise -ge $TH_BURST_ROISE ]; then
|
||||
burstTimer=$BURST_DURATION
|
||||
echo "[BURST-rise] rise=$rise°C in $((now-prevTime))s → mode 6"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Если в burst-режиме — держим режим 6
|
||||
if [ $burstTimer -gt 0 ]; then
|
||||
burstTimer=$((burstTimer - 1))
|
||||
if [ $lastMode -ne 6 ]; then
|
||||
echo "[MODE 6] full speed (burst, ${burstTimer}s remain)"
|
||||
lastMode=6
|
||||
echo "6" >&3
|
||||
fi
|
||||
prevTemp=$TEMP; prevTime=$now
|
||||
sleep 1; continue
|
||||
fi
|
||||
|
||||
# Если burst истёк И temp >= TH_BURST — новый burst
|
||||
if [ $TEMP -ge $TH_BURST ]; then
|
||||
burstTimer=$BURST_DURATION
|
||||
echo "[MODE 6] full speed (re-burst: $TEMP°C)"
|
||||
lastMode=6; prevTemp=$TEMP; prevTime=$now
|
||||
echo "6" >&3
|
||||
sleep 1; continue
|
||||
fi
|
||||
|
||||
# Обычная логика с хистерезисом
|
||||
newMode=5 # default: HIGH (35-39)
|
||||
|
||||
if [ $TEMP -lt $((TH_COLD - HYST)) ]; then
|
||||
newMode=2 # STANDBY (< 23)
|
||||
elif [ $TEMP -lt $((TH_LOW - HYST)) ]; then
|
||||
newMode=3 # LOW (< 28)
|
||||
elif [ $TEMP -lt $((TH_MED - HYST)) ]; then
|
||||
newMode=4 # MED (< 33)
|
||||
fi
|
||||
|
||||
# Отправляем только если режим изменился
|
||||
if [ $newMode -ne $lastMode ]; then
|
||||
echo "[MODE $newMode] temp=$TEMP°C (hysteresis: no-change zone)"
|
||||
lastMode=$newMode
|
||||
echo "$newMode" >&3
|
||||
fi
|
||||
|
||||
prevTemp=$TEMP; prevTime=$now
|
||||
sleep 1
|
||||
done
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=GPU Fan Controller (Delta QFR1212GHE, Tesla V100 32GB)
|
||||
After=network-online.target nvidia.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/root/gpu_fan_ctrl.sh
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in new issue
Block a user