Drücke „Enter”, um zum Inhalt zu springen.

Noctua NF-R8 PWM mit einem Arduino Nano Every steuern

Jan 0
geschätzte Lesedauer: 5 Minuten

Nach meinen ersten Gehversuchen mit Temperaturmessung und Lüftersteuerung wollte ich jetzt einen PWM-Lüfter mit meinem Arduino Nano Every steuern.

Die Grundlagen

Theoretisch ist so einfach: Mein Lüfter hat einen Spannungsbereich von 0 – 5V und wenn ich 5V anlege, dreht er auf Volllast, bei 0V steht er. Wenn ich aber mit einem Arduino Nano Every herangehe, kann der keine z.B. 2,5V. Das erreicht er nur indem der Chip an dem entsprechenden Port den Strom ganz schnell ein- und wieder ausschaltet. Um beim Beispiel der 2,5V zu bleiben, entspricht einer gleichmäßigen Verteilung von 5V und Leerlauf.

PWM anhand eines Bildes erklärt

Bei dem Bild muss man sich nur vorstellen, dass dieser Vorgang innerhalb einer sehr kurzen Zeit passiert. Und damit kommen wir zur Problemstellung.

Die richtige Frequenz

Der Arduino Nano Every kann PWM. Der Befehl dazu lautet: analogWrite(pin, wert). Als Wert kann aus einem Bereich von 0 bis 255 wählen. D.h. 255 entspricht 100% Duty Cycle. Nur gibt es da ein Problem: Der Arduino Nano Every nutzt dafür eine Frequenz, die knapp unter 1000Hertz liegt.

Aber PC-Lüfter haben sich auf einen Standard von 25.000Hz geeinigt. Der von mir gewählte Noctua NF-R8 PWM gibt in seiner Spezifikation einen Bereich von 21 bis 26kHz an. Somit sollte die Problemstellung klar sein.

Die Schaltung

Ich bleibe bei meinem bisherigen Ansatz, dass ich die Temperatur messen möchte und in Abhängigkeit des gemessenen Wertes die Geschwindigkeit am Lüfter einstellen will. Lüfter, die mit PWM arbeiten, machen die Schaltung schon mal einfacher. Der Lüfter bekommt über eine externe Stromversorgung dauerhaft 12V angelegt und wird dann mit PWM über eine extra Leitung gesteuert.

Fritzing Schaltplan zum Anschluss eines PWM Lüfters
Schaltplan für Temperaturmessung mit einem LK-Temp2, zwei LEDs, einem PWM-Lüfter und einem Arduino Nano Every

Die Versuchsreihe

Natürlich habe ich erst mal versucht den Lüfter mit analogWrite zu steuern. Aber das ging schon grundsätzlich schief. Legte ich 12V an den Lüfter an, drehte der sofort auf Volllast. Schaltete ich den Arduino dazu, sank die Leistung sofort auf ca. 75%. Und je nachdem, was ich für einen Wert ich auf den Port schrieb, wurde der Lüfter ein bisschen langsamer.

Nach etwas Recherche öffneten sich jetzt zwei Wege. Zum einen konnte ich direkt über den internen Timer im Arduino gehen, oder eine Bibliothek wählen, die das für mich erledigt. Um genau zu sein, ist die MegaCoreX keine Bibliothek, sondern eine Board-Plattform für die Arduinos.

Also lud ich das Paket herunter, wählte alles entsprechend meines Arduino Nano Every aus. Aber es brachte keinen Erfolg. Für mich war an der Stelle auch das Rätsel, wie analogWriteFrequency funktionieren soll, wenn man keinen Port angibt.

Vibe Coding

Ich wollte mich schon wieder an das Arduino-Forum wenden, als ich auf meiner Suche nach einer Lösung auf eine Idee kam. Google bietet bei der Suche schon immer ein KI-Ergebnis an, also warum nicht ein bisschen Vibe-Coding mit Gemini. Ich muss zugeben, dass ich dem Thema kritisch gegenüberstehe, aber es als angenehmes Erlebnis empfand.

Gemini kam immer mit neuen Ideen, die alle fehlschlugen, bis wir dann im vierten oder fünften Versuch einen grundsätzlichen Test machten, ob es überhaupt mit einer LED funktioniert. Anhand dessen arbeiteten wir uns wieder vorwärts, bis ich dann irgendwann müde war.

Bis zu dem Zeitpunkt war mein Fazit zum Programmieren mit KI, dass Gemini zwar die ganzen Dokumente und Spezifikationen zwar gescannt hat, aber der komplette Workflow überhaupt keinen Sinn ergab. Konkret hatten wir zum Schluss eine eigene Funktion für den Timer-Interrupt geschrieben, die ca. 50.000 mal pro Sekunde aufgerufen wurde. Nachdem das Ergebnis funktionierte, fing aber Gemini wieder mit dem Code vom ersten Versuch an und ich musste ihr klar machen, dass wir das alles schon hatten und dass es nicht funktioniert hat.

Nach 2-3 Stunden voller Tests und Versuche gab ich noch die letzten Ergebnisse in den Prompt ein und bekam einen letzten Hinweis: Stelle sicher, dass GND vom Arduino mit dem GND (schwarz) des Lüfters verbunden ist. Genau die Frage hatte ich mir schon die ganze Zeit gestellt! Wenn ich da mit einem Kabel reingehe, wo bleibt da die Erdung?

Also ging es den nächsten Morgen weiter. Und sofort funktionierte alles, wie gedacht. Jetzt konnten wir sogar wieder auf die ersten Versuche zurückgehen. Letztlich funktionierte es mit dem TCB0-Timer am besten und das Ergebnis übernahm ich dann. Deswegen musste ich dann auch an Port 6 wechseln, aber das störte mich nicht.

Fritzing Schaltplan zum Anschluss eines PWM Lüfters
Schaltplan für Temperaturmessung mit einem LK-Temp2, zwei LEDs, einem PWM-Lüfter und einem Arduino Nano Every
(mit Erdung)

Damit konnte ich den Code auch fertigstellen. Gemini hat auch den Cheat mit den 100% Duty Cycle erklärt. Durch die Berechnung wird trotzdem immer noch mal kurz auf 0 geschaltet, sodass als Maximum so ca. 90-95% der Volllast entstehen. Das lässt sich nur umgehen, indem man die Berechnung komplett umgeht und die Duty Cycle auf einen Wert größer als die Periode stellt, weswegen der Vergleich zwischen CCMPL und CCMPH immer wahr ist.

#include <DallasTemperature.h>
#include <OneWire.h>
#include <avr/io.h>

#define LK_TEMP2_PIN 4
#define MOTOR_OUT 6

#define LED_BASE_PIN 13
#define LED_LOW_PIN 12
#define LED_HIGH_PIN 11

// Globals
OneWire oneWire(LK_TEMP2_PIN);
DallasTemperature sensors(&oneWire);

void setup() {
  InitializePins();
  setupPWM();
  // Initialize temperature sensor
  sensors.begin();
  // TODO can be removed, but useful for debugging
  Serial.begin(9600);
  // further initializations
  Init();
}

void loop() 
{
  float temperature = ReadTemperature();
  if (temperature > 0 && temperature < 30.0)
  {
    SetTemperatureIndicators(HIGH, LOW, LOW);
    setFanSpeed(0);
  }
  else if (temperature >= 30.0 && temperature < 40.0)
  {
    SetTemperatureIndicators(HIGH, HIGH, LOW);
    setFanSpeed(50);
  }
  else if (temperature >= 40.0)
  {
    SetTemperatureIndicators(HIGH, HIGH, HIGH);
    setFanSpeed(100);
  }
  delay(10000);
}

void InitializePins()
{
  // set PINs to output
  pinMode(MOTOR_OUT, OUTPUT);
  pinMode(LED_BASE_PIN, OUTPUT);
  pinMode(LED_LOW_PIN, OUTPUT);
  pinMode(LED_HIGH_PIN, OUTPUT);

}

void setupPWM()
{
  // reset TCB0
  TCB0.CTRLA = 0; 
  
  // TCB_CCMPEN_bm = activate pin 6 output
  // TCB_CNTMODE_PWM8_gc = 8 bit PWM modus
  TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_PWM8_gc;

  // use 250kHz frequency from TCA0
  TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc;

  // I need 25kHz
  // 250.000 Hz / 25.000 Hz = 10 Ticks.
  // period (CCMPL) = 10 - 1 = 9. That's the max value for duty cycle
  // actual duty cycle (CCMPH) = value between 0 and 9
  TCB0.CCMPL = 9;
  TCB0.CCMPH = 0;

  // start timer
  TCB0.CTRLA |= TCB_ENABLE_bm;
}

void Init() 
{
  digitalWrite(LED_BASE_PIN, LOW);
}

void setFanSpeed(int percent) 
{
  // to achieve a complete stop of the fans, turn off the timer
  if (percent <= 0) 
  {
    TCB0.CCMPH = 0;
    return;
  }
  
  // a little cheating: to get 100% duty cycle you have to go higher than the period
  if (percent >= 100) 
  {
    TCB0.CCMPH = 10; 
    return;
  }
  
  int duty = (percent * 10) / 100; 
  TCB0.CCMPH = duty;
}

float ReadTemperature()
{
  float temp = 0.0;
  sensors.requestTemperatures();
  temp = sensors.getTempCByIndex(0);
  // TODO can be removed, but good for debugging
  Serial.print("Temperatur: ");
  Serial.print(temp);
  Serial.println(" C");
  return temp;
}

void SetTemperatureIndicators(int base, int low, int high)
{
  digitalWrite(LED_BASE_PIN, base);
  digitalWrite(LED_LOW_PIN, low);
  digitalWrite(LED_HIGH_PIN, high);
}

Und so sah dann der erste Testlauf an der Heizung aus. Ich hatte mich dann doch entschlossen, die Lüfter oberhalb des Heizkörper zu betrieben, weil der Luftstrom der Lüfter sich nicht direkt nach oben verbreitet.

Breadboard an dem die Lüfter und der Temperatursensor angeschlossen sind.
Testbetrieb mit Breadboard (Die Zeitschaltuhr ist für die Weihnachtsbeleuchtung)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

WordPress Cookie Hinweis von Real Cookie Banner