Papst-Lüfter Drehzahlerfassung!

Hallo,

für eine Temperaturregelung möchte ich ,weil verfügbar, 120mm Papst Lüfter einsetzen. Diese haben ein Tachosignal, dem ich mich als erstes annehmen will.
Datenblatt: http://docs-europe.electrocomponents.com/webdocs/0ecf/0900766b80ecf17d.pdf
Den Lüfter betreibe ich mit max. 12 V. Das Tachosignal lege ich über einen 2,2k Widerstand an 5V und an einen Eingangspin. Der Lüfter zieht das Das Tachosignal pro Umdrehung zweimal auf Masse.

Hier nun mein Code den ich mir überlegt habe um das Tachosignal auszuwerten.
Achtung Anfänger :slight_smile:

const int  takt_ventPin = 16;

int rpm_vent = 0;
int takt_vent = 0;
int lastTakt_vent = 0;
int takt_ventCounter = 0;
long previousMillis_vent = 0;
unsigned long currentMillis = 0;
int interval_vent = 10000;


void setup() {
  Serial.begin(115200);
  pinMode(takt_ventPin, INPUT);
}
void loop() {
  takt_vent = digitalRead(takt_ventPin);
  currentMillis = millis();

  if(currentMillis - previousMillis_vent > interval_vent) {
    previousMillis_vent = currentMillis;
    rpm_vent = takt_ventCounter * 3;
    Serial.print("1/min ");
    Serial.println(rpm_vent);
    Serial.print("number of Puls:  ");
    Serial.println(takt_ventCounter);
    takt_ventCounter = 0;
  }
  if (takt_vent != lastTakt_vent) {
    // if the state has changed, increment the counter
    if (takt_vent == LOW) {
      takt_ventCounter++;
    }
  }
  lastTakt_vent = takt_vent;
}

Ich frage alle 10 sec. die gezählten LOW Signale ab rechne auf Minuten um und setze den counter zurück. Das ganze wird per serial monitor ausgegeben.
Funktioniert auch, für sich alleine, problemlos.
Nun meine Frage, kann ich das ganze eleganter umsetzen, der Code scheint mir sehr Zeitkritisch, wenn ich den Sketch woanderst einbaue und der Controller ist gerade mit was anderem beschäftigt komme ich auf falsche Werte.

Lass einen Interrupt zählen.
Z.B. damit hier: Google Code Archive - Long-term storage for Google Code Project Hosting.
oder damit: http://arduino.cc/en/Reference/AttachInterrupt

Es gibt auch eine Möglichkeit einen Hardware-Zähler über ein Pin Zählen zu lassen und den Überlauf als Interrupt abfangen zu lassen. Das hätte den Vorteil, das die Interrupt Routine deutlich seltener aufgerufen wird. Bzw. könntest Du damit sogar in der ISR (Interrupt Service Routine) die Geschwindigkeit anhand der vergangenen millis() seit dem letzen Aufruf berechnen und eine globale Variable aktuell halten, ohne das sich dein Hauptprogramm drumm kümmern muss.
Leider hab ich auf die schnelle kein Beispiel gefunden. Wäre toll, wenn da jemand was beisteuern könnte.
Mario.

Bei deinem "Problem" würde ich mit Interrupts arbeiten. In deinem Fall mit dem Mode Falling. Das heißt es wird eine bestimmte Funktion (hier countup)ausgeführt wenn du ein Fallende flanke erkannt wird.
Hier mal der modifizierte Code du musst aber den Tacho Eingang auf die Klemme D2 setzten da hier der Interrupt 0 hinterlegt ist. Damit mss es dan funktioniern. Hier bekommst du jede Fallende Flanke mit.

int rpm_vent = 0;
int takt_vent = 0;
int lastTakt_vent = 0;
int takt_ventCounter = 0;
long previousMillis_vent = 0;
unsigned long currentMillis = 0;
int interval_vent = 10000;


void setup() {
  Serial.begin(115200);
  attachInterrupt(0, countup, FALLING); // Interrupt 0 Eingang ist Pin D2

}
void loop() {
  takt_vent = digitalRead(takt_ventPin);
  currentMillis = millis();

  if(currentMillis - previousMillis_vent > interval_vent) {
    noInterrupts();
    previousMillis_vent = currentMillis;
    rpm_vent = takt_ventCounter * 3;  
    Serial.print("1/min ");
    Serial.println(rpm_vent);
    Serial.print("number of Puls:  ");
    Serial.println(takt_ventCounter);
    takt_ventCounter = 0;
    interrupts();
  }
}

void countup(){
  takt_ventCounter++;
}

Gruß
Der Dani

Danke euch beiden für die links und das Codebsp.
So komme ich erstmal weiter, ich merke wie weit ich noch weg bin vom "Programmieren".
Habe ich das richtig verstanden, dass es auf die Timer im Controller hinausläuft, millis(); verwendet Timer0, int.0 verwendet Timer1, das Mega2560 Board hat 6 unabhängige interrupt also 6Timer +1 für millis, oder ist das Quatsch?

Ich hab mir das mit dem Counter nochmal angesehen.
Sowohl TIMER0 (8bit) als auch TIMER1 (16bit) können als Zähler für externe Signale verwendet werden. Dafür zuständig ist das Register TCCR (Im Fall des TIMER1) das TCCR1B
In diesen Registern gibt es 3 Clock-Select Bits welche die Input-Pins T0 für Timer0 (Pin 4) und T1 für Timer1 (Pin 5) als "Taktquelle" für den Timer (oder in diesem Fall ehe Counter) für steigende oder fallende Flanke auswählen können.
Hängt nun an diesen Pins eine Taktquelle (dein Tachosignal) kann der Atmega das mit bis zu 50% seiner eigenen Taktfrequenz zählen (Beim Arduino also mit 16/2 = 8 MHz). Bei jedem Überlauf wird ein Interrupt ausgelöst, der dann Deine ISR anspringen kann.
Nehmen wir mal an, Du verwendest TIMER0 und Pin 4 als Eingang und läßt den Timer im "normal" Mode laufen (immer zählen von 0 bis 255 und Interrupt auslösen bei Überlauf) und nehmen wir weiterhin an, der Lüfter läuft mit 1000 U/min.
Das wären 1000 * 2 Impulse / 60 Sekunden = 33,3333 Impusle pro Sekunde. Bei 256 Counts bis zum Überlauf, sind das dann 256 / 33,33333 = 7,68 Sekunden bis zum Überlauf. Bei 2000 U/min wären es dann 3,34 Sekunden. Das ist schon recht lange, bis man wieder einen aktualisierten Wert bekommt. Je langsamer der Lüfter läuft, desto länger die Wartezeit. Dafür gibt es aber den sogenannten CTC Modus. Hier kann man den Wert, bei dem ein Interrupt ausgelöst wird, selbst festlegen. Damit kann man die Zahl der Counts herunter setzen um schneller ein Ergebnis zu bekommen.

das bsp. von Volvodani habe ich jetzt so verstanden und auch ausprobiert, dass die Variable "takt_ventCounter" immer erhöht wird, unabhängig vom Restprogramm und egal welchen Interrupt ich nehme. Ist diese Aussage so richtig?

int rpm_vent = 0;
int takt_vent = 0;
int lastTakt_vent = 0;
int takt_ventCounter = 0;
long previousMillis_vent = 0;
unsigned long currentMillis = 0;
int interval_vent = 10000;


void setup() {
  Serial.begin(115200);
  attachInterrupt(3, countup, FALLING); // Interrupt 3 Eingang ist beim Mega2560 Pin D20

}
void loop() {
  
  currentMillis = millis();

  if(currentMillis - previousMillis_vent > interval_vent) {
    noInterrupts();
    previousMillis_vent = currentMillis;
    rpm_vent = takt_ventCounter * 3;
    Serial.print("1/min ");
    Serial.println(rpm_vent);
    Serial.print("number of Puls:  ");
    Serial.println(takt_ventCounter);
    takt_ventCounter = 0;
    interrupts();
  }
}
void countup(){
  takt_ventCounter++;
}

Was ich nicht verstehe, wie hängen die Interrupts mit den Timern zusammen?

@mkl0815
Ist das nicht gefährlich die Timer zu manipulieren weil ich mal gelesen habe, dass z.B millis() Timer0 verwendet.

Folgendes bsp. habe ich gefunden:

// For Attiny85
// Author: Nick Gammon
// Date: 29 November 2012

void setup() 
 {
  pinMode (0, OUTPUT);  // pin 5  // OC0A
  pinMode (1, OUTPUT);  // pin 6  // OC0B
  pinMode (4, OUTPUT);  // pin 3  // OC1B
  
  // Timer 0, A side
  TCCR0A = _BV (WGM00) | _BV (WGM01) | _BV (COM0A1); // fast PWM, clear OC0A on compare
  TCCR0B = _BV (CS00);           // fast PWM, top at 0xFF, no prescaler
  OCR0A = 127;                   // duty cycle (50%)

  // Timer 0, B side
  TCCR0A |= _BV (COM0B1);        // clear OC0B on compare
  OCR0B = 63;                    // duty cycle (25%)

  // Timer 1
  TCCR1 = _BV (CS10);           // no prescaler
  GTCCR = _BV (COM1B1) | _BV (PWM1B);  //  clear OC1B on compare
  OCR1B = 31;                   // duty cycle (25%)
  OCR1C = 127;                  // frequency 
  }  // end of setup

void loop() { }

Hier sieht man wie man auf die Timer0, und Timer 1, zumindest beim tiny85, Einfluss nehmen kann . "//duty cycle" und "//frequency", verstehe ich ja noch. Aber wie die Zeilen für den "fast PWM Mode" aufgebaut sind oder die deklaration vom prescaler kapiere ich nicht, was ist z.B. "_BV"?
Ich habe Anhand vom Datenblatt versucht die Geschichte nachzuvollziehen, aber das ganze übersteigt meinen Horizont :blush:

Die Interrupt die ich in dem Programm nutze haben nix mit den Timern zu tun mit der Konfiguration wird wenn an dem Eingang D2 eine Fallende Flanke erkannt wird zu der definierten Funktion gesprungen hier countup. Man muss die Befehle innerhalb der Interrupt funktion so kurz halten wie möglich. Für die berrechung innnerhalb deines Seketches habe ich die Interrtupts ausgeschaltet (damit die Daten Konsistent sind). Nach der Berrechung werden die Interrupts wieder eingeschaltet. Es ist nicht egal welchen Interrupt du nimmst. die beiden Interrupts sind feste Eingängen zugeordent beim UNO z.B. Interrupt 0 = Pind D2 // Interrupt 1 = Pin D3. Schau mal hier
http://arduino.cc/en/Reference/AttachInterrupt

Gruß
Der Dani

Ich habe jetzt diese Seite gefunden:

ich denke so langsam steige ich mit den "Timern" durch, da wird einem das manuelle eingreifen genau erklärt. Allerdings steht da auch:

One last thing to note- certain timer setups will actually disable some of the Arduino library functions. Timer0 is used by the functions millis() and delay(), if you manually set up timer0, these functions will not work correctly.
Additionally, all three timers underwrite the function analogWrite(). Manually setting up a timer will stop analogWrite() from working.

So wie ich das ganze verstanden habe, wäre wohl die "Timer-Interrupt" lösung von mkl0815 die eleganteste, aber bei einem großen Projekt störend, oder man lagert das ganze aus z.B auf einen attiny?

So, habe da Thema nochmal aufgegriffen,

ich arbeite jetzt mit folgendem Code, mit Interrupts. Die Timermanipulation schien mir jetzt zu aufwändig, weil es sich so schwierig in andere Projekte integrieren lässt.

unsigned long time_puls = 0;
unsigned long pre_time_puls = 0;
long previousMillis_vent = 0;
unsigned long currentMillis = 0;
int interval_vent = 3000;

void setup() {
  Serial.begin(115200);
  attachInterrupt(3, countup, FALLING); // Interrupt 3 Eingang ist beim Mega2560 Pin D20

}
void loop() {

  currentMillis = millis();
  if(currentMillis - previousMillis_vent > interval_vent) {
    previousMillis_vent = currentMillis;
    int drehzahl = (30000000/time_puls)+1;
    Serial.print("microsekunden pro Umdrehung: ");
    Serial.println(time_puls*2);
    Serial.print("U/min :");
    Serial.println(drehzahl);
  }
}
void countup(){
  noInterrupts();
  unsigned long m = micros();                // Microsekundenzähler auslesen
  unsigned long v = m - pre_time_puls;       // Differenz zum letzten Durchlauf berechnen
  time_puls = v;                           
  pre_time_puls = m;
  if (time_puls > 60000){                    // counter bei Stillstand zurücksetzen (Impulsdauer > 60000)
    time_puls = 0;
  }
  interrupts();
}

Ich habe aber noch ein paar Verständnissfragen.

Also, der Atmega läuft mit 16Mhz, d.h der Intterupt Eingang wird alle 16µs abgefragt, pro Takt?
Da wäre eine Impulsrate von 20ms ja unkritisch, oder wie schnell darf ich sein?
In der Funktion "countup" ist der Interrupt für die berechnung der Zeit ja ausgeschaltet, aber was passiert im "loop"? Wird da sozusagen bei jeden Interrupt in "countup" gesprungen und dann wieder zurück und an gleicher Stelle weitergemacht?

fckw:
Also, der Atmega läuft mit 16Mhz, d.h der Intterupt Eingang wird alle 16µs abgefragt, pro Takt?
Da wäre eine Impulsrate von 20ms ja unkritisch, oder wie schnell darf ich sein?
In der Funktion "countup" ist der Interrupt für die berechnung der Zeit ja ausgeschaltet, aber was passiert im "loop"? Wird da sozusagen bei jeden Interrupt in "countup" gesprungen und dann wieder zurück und an gleicher Stelle weitergemacht?

Nein, es wird wenn dann 1s / 16 000 000 Hz = 0,0000000625 Sekunden = 62,5 Nanosekunden abgefragt.
Die Funktion loop(), bzw. egal an welcher Stelle (außer die Funkion countup()) wird der aktuelle Coder unterbrochen und zur ISR verzweigt. Danach wird dann der normale Code weiter ausgeführt und zwar genau an der Stelle wo er unterbrochen wurde.

Erstmal danke für die erklärung.

hatte die 16µs noch von der Geschichte "Timer" im Kopf, allerdings kam der Wert von einem prescaler ( (1s / 16 000 000 Hz = 0,0000000625 Sekunden) * 256 = 0,000016s =16µs ) :wink: