Permalänk
Medlem

Hemmagjord automatisk fläktstyrning

Jag har haft problem med fläktstyrningen på två moderkort från ASUS (det är nog mest deras mjukvara som ger mig huvudvärk). Eftersom jag har en custom-loop är jag mer intresserad av vattnets temperatur än nyckfulla värden från processorn, särskilt när det oftast är grafikkorten som går varma. Fram tills nu har jag manuellt ställt om profiler och kört fläktarna frikostigt för att "vara på den säkra sidan", på bekostnad av ljudnivå förstås. När jag försöker designa en fläktkurva som låter datorn bli tyst så kan jag räkna med att fläktarna då och då varvar upp till 100% i alla fall tack vare dåliga mätvärden från moderkortet. Det är slut med sådant trams från och med idag.

Målbild
Se till att en mikrokontroller stabilt styr fläktarna baserat på temperaturmätningar från vattenloopen. Temperaturen bör stanna under 50 grader och datorn ska vara tystare än tidigare.

Teknisk lösning
* Mät temperaturen i loopen, låt en Arduino ta emot den signalen
* Räkna om mätvärdet - om det ligger mellan 35 och 50 så skicka motsvarande signalnivå över PWM
* Använd en fläkthubb med PWM och egen strömförsöjning för att fördela signalen till fläktarna

* Mät och kontrollera att intervallet 35 - 50 var bra, justera vid behov.

Prylar och inköp
* Arduino - jag valde en Uno Rev3 och en "prototyping shield" som gör det lättare att löda fast komponenter utan att göra det permanent på själva arduinon.
* Fläkthubb - Inet rekommenderade den här från Arctic, jag kommer använda 5 portar. Den behöver vara så dum att den bara tar in PWM som styrsignal, alltså går exempelvis Corsairs Commander Pro bort.
* Termometer för vattenkylning - När jag byggde om loopen för tre år sen skaffade jag den här från AlphaCool. Det är en termoresistor som normalt pluggas in på ett moderkort. Det kräver pyttelite meck för att få den att funka med en Arduino.
* Förbrukningsvaror inom elektronik - lite solid tråd, prylar för lödning, en resistor på 5kOhm, lite pin-headers
* Interna USB-portar för att driva Arduinon (det hade gått att tappa av 12V från PSUn, men jag hade redan förberett detta för ett annat projekt. Med USB istället kan jag prata serieport med fläktstyrningen och se temperaturen över tid).
* En dopp-termometer som du kan använda för att mäta temperaturen i vattnet - Jag har en digital stektermometer av enklare slag som lätt går att sticka ner i reservoaren.

Prislapp för projektet (för prylar som köptes för detta syfte):
Arctic Fläkthubb - 149 kr
Alphacool Termometer - 149 kr
Arduino Uno - 279 kr
Adafruit Proto-shield - 149 kr

Summa: 726 kr
(plus lite förbrukningsmateriel och USB-kabel)

Hur gör man

1. Förbered Arduinon. På proto-skölden har jag ett par han-pinnar för att sätta kontakten från fläkthubben - den har samma kontakt som en 4-pin-fläkt, men bara två kablar - control och sense. Jag satte fast fyra pinnar och kopplade control till port 3 med en fastlödd tråd - övriga icke kopplade. För termoresistorn behövs en spänningsdelare, och det funkar ungefär såhär:

Arduinon kan mäta spänning mellan 0 och 5 volt. Om man har 5V och 0V (finns på prylen), sätter två identiska resistorer i serie, och mäter mitt emellan dem så får du värdet 2.5V. Om resistorerna är olika och den ena dessutom varierar i motstånd så ändrar sig mätvärdet därefter. Om R2 är större än R1 blir mätningen högre, och tvärtom.

Termoresistorn funkar på exakt det sättet - den varierar i motstånd beroende på temperaturen. Jag hade tur när jag satte den som R1 i min krets, jag får små mätvärden vid låga temperaturer och stora mätvärden vid höga. Att mäta och räkna ut om den ska vara R1 eller R2 på förhand är för fegisar.

Lödning för termoresistorn: Två st. header-pinnar på 5V och en 3-länkad pad, resistor 5kOhm på andra sidan padden och GND, en kabel från samma pad till en analog input, jag valde A0. I bilden nedan syns alla komponenter på kortet utom kablarna som jag drog på baksidan. Röda pilar visar en kabel (för termoresistorn), gula pilar visar den andra som går från den översta header-pinnen för fläktkontakten till pin 3 på skölden.

2. Låt fläktarna sitta i sin nuvarande styrning men koppla in tempmätaren och Arduinon och börja mäta. Om lödningarna gick bra så kommer du kunna se analoga värden på A0 Här är ett kort program som kommer hjälpa oss vid mätningen:

void setup() { Serial.begin(9600); } void loop() { int temp = analogRead(A0); Serial.println(temp); delay(1000); }

Kör programmet och starta Serial Monitor. Du borde nu se ett nytt värde varje sekund. Ta fram din stektermometer eller motsvarande och mät på vattnet. Du vill ha en temperatur och ett Arduino-värde för den lägsta respektive högsta temperaturen som din loop kan tänkas vara i. Ju längre ifrån varandra desto bättre. Låt datorn gå på tomgång med maxade fläktar för lägsta värdet, och stäng i princip av fläktarna och kör hård belastning på de komponenter som är med i loopen för högsta. Här är jag igång med mätning:

Mina mätningar såg ut såhär:

B: 390, 28C A: 664, 62C

Tanken är att vi ska räkna ut ett linjärt samband mellan den riktiga temperaturen och mätvärdet, så att vi kan bestämma gränserna för fläktkurvan med hjälp av temperaturer vi kan relatera till. Såhär ser matten ut. Vi har våra två punkter, vi ska räkna ut parametrarna för linjen, och därefter kunna räkna ut mätvärdet för alla temperaturer mellan A och B.

y = mx + b, antingen gör du matematiken för hand, eller dunkar in dina värden i en kalkylator för ekvationen på nätet. Gör det på båda hållen, en gång där mätvärdet är x och en annan där temperaturen är x. På så vis får du två ekvationer så att vi kan räkna på båda hållen.

I mitt fall fick jag y = 8.06 * x + 164.35, där x är temperatur. Vi testar:
Vad borde mätvärdet vid 35 grader vara? y = 8.06 * 35 + 164.35 = 446,45 alltså 446 eller 447 troligtvis.

3. Låt Arduinon visa mätvärden och temperatur, och lägg till en dämpning.
Jag vill att mitt system ska reagera lagom långsamt så att signalbrus och konstiga mätningar inte får fläktarna att hoppa fram och tillbaka. Därför la jag till en matematisk dämpning av mätvärdet. Om du tycker 1/10 är för segt så kan du experimentera lite med siffrorna.

double temp = 400; // börja på ett rimligt startvärde, // använd flyttalstypen double för att hantera decimaler ... void loop() { // Låt det nya värdet endast påverka snittet med en tiondel temp = (9 * temp + analogRead(A0))/10; ... }

Vid den här punkten har jag en realtidstermometer på vattenkylningen som verkar stämma ganska väl med stektermometern. Det ser ut såhär:

4. PWM för fläktar. PWM funkar så att styrenheten sänder en signal som med jämna mellanrum hoppar mellan 0 och i detta fallet 5V. Bredden (hur länge) på signalen vid 5V avgör själva informationen, såhär:

De gröna markeringarna visar vilken takt signalen har, och den är alltid samma oavsett om vi sänder 25% eller 75%. För en Arduino Uno är frekvensen i regel ungefär 500 Hz. Det räcker utmärkt för vanliga saker som LEDs eller analoga mätare eller vad vi nu än skulle vilja skicka PWM till, men det kommer funka sådär med våra fläktar.

Specifikationen för PWM för fläktar är 5V och en frekvens på 25 kHz, alltså 50x oftare än våra vanliga 500 Hz. Som tur är finns det ett sätt att även på en Arduino Uno komma åt den interna timern och styra om PWM-signalen för vårt syfte.

Jag följde en guide på fdossena.com som förklarar ingående vad det är som får det här att fungera. Om du läser artikeln kommer du se att på port 9 och 10 kan vi få en bättre upplösning på 320 värden och bara 79 på port 3. Av strategiska skäl (andra funktioner kommer byggas på den här arduinon, och då behöver jag port 9 och 10) valde jag ändå port 3 och nöjer mig med 79 värden i upplösning. Till vår kod behöver vi två funktioner:

// Internal magic for having 25 kHz PWM void setupTimer2() { // Set PWM frequency to about 25khz on pin 3 (timer 2 mode 5, prescale 8, count to 79) TIMSK2 = 0; TIFR2 = 0; TCCR2A = (1 << COM2B1) | (1 << WGM21) | (1 << WGM20); TCCR2B = (1 << WGM22) | (1 << CS21); OCR2A = 79; OCR2B = 0; } void setPWM2(int f) { // safety limit the value to within 9 and 79 f=f<0?0:f>79?79:f; OCR2B = (uint8_t) f; } void setup() { Serial.begin(9600); setupTimer2(); }

Funktionen setPWM2() är sedan redo att ta emot värden 0-79 för att sända 25 kHz PWM till port 3!

5. Linjär mappning. Jag vill att fläktarna ska gå på tomgång tills vi når 35 grader, därefter öka linjärt upp till 50 grader och sedan fortsätta på 100%. Ett bra sätt att få detta att funka är att göra en linjär mappningsfunktion. Kurvan ska se ut så här:

Jag lånade kod av mig själv från ett systerprojekt. Egentligen är det samma linjära ekvation vi hade ovan, fast med tillagda gränser så att vi inte får negativa eller andra otillåtna värden utanför vårt spann.

// a: lower input bound // b: higher input bound // c: lower output bound // d: higher output bound int linearMap(double a, double b, double c, double d, double x) { return (int) min(d, max(c, (((x-a)*(d-c))/(b-a))+ c)); }

För att få kurvan i grafen ovan så hade det sett ut såhär: y = linearMap(35, 50, 0, 79, x). Det blir väldigt bekvämt i koden att säga "mellan 35 och 50 vill jag ha värden mellan 0 och 79", eller vad som än råkar passa. I det här fallet kastar jag även om värdet till ett heltal efter att all matte har gjorts med doubles. Som du minns så är temp-värdet också en double eftersom jag aggregerade ett medelvärde. Testkör med en fri fläkt genom hubben och stressa datorn till extrema temperaturer för att se att mappningen funkar. Här är hela koden, inklusive att jag skriver ut vilket PWM-värde som skickades för den givna temperaturen. För bättre upplösning så mappar jag inte från celsius till PWM utan från mätvärde till PWM. Du kommer se det i koden nedan.

double temp = 400; // Internal magic for having 25 kHz PWM void setupTimer2(){ //Set PWM frequency to about 25khz on pin 3 (timer 2 mode 5, prescale 8, count to 79) TIMSK2 = 0; TIFR2 = 0; TCCR2A = (1 << COM2B1) | (1 << WGM21) | (1 << WGM20); TCCR2B = (1 << WGM22) | (1 << CS21); OCR2A = 79; OCR2B = 0; } void setPWM2(int f){ f=f<0?0:f>79?79:f; OCR2B = (uint8_t) f; } int linearMap(double a, double b, double c, double d, double x) { return (int) min(d, max(c, (((x-a)*(d-c))/(b-a))+ c)); } void setup() { Serial.begin(9600); pinMode(3, OUTPUT); setupTimer2(); } void loop() { temp = (9 * temp + analogRead(A0))/10; double celsius = (temp * 0.124) - 20.39; Serial.print(temp); Serial.print("\t"); Serial.print(celsius); Serial.print(" C\t"); int pwm = linearMap(446.41, 567.29, 0, 79, temp); Serial.print(pwm); Serial.print("\n"); setPWM2(pwm); delay(1000); }

6. Slutmontering och resultat. Efter buggfixar är det dags att montera fullskaligt. Flytta alla relevanta fläktar till hubben, placera hubb och Arduino på rimliga ställen i chassit och starta maskinen. Du kommer troligtvis behöva ändra i BIOS att det är okej att starta utan fläktsignal om du inte redan har gjort det. Kör igång och se vad resultatet blev.

Mitt system består av två grafikkort och processorn på samma vattenloop. Den har dessutom två stora radiatorer (2x140mm + 3x120mm). Under mining-belastning på båda grafikkorten drar datorn 700W vid väggen, temperaturen i loopen hamnar med den nya styrningen på 42 grader, temperaturen på korten är 45-47 grader och fläkthastigheten är ungefär 40%. När jag slår på och stänger av miningen så tar det ett bra tag för fläktarna att ändra hastighet, dels tack vare dämpningen i koden men även pga. "tröghet" i vattnets värmekapacitet. Effekten är i alla fall att jag inte märker att fläkten ändrar hastighet.

Jag är totalnöjd.

Rätt antal nollor… matte är svårt.
Visa signatur

Vad har jag i min dator? Kopparrör.