Drehzahlmessung mit Lichtschranke
Die Drehzahlmessung basiert auf Basis einer rotierenden Loch- oder Kontrastscheibe, wo mit einer IR-Lichtschranke oder Reflexionslichtschranke Rechteckimpulse erzeugt werden. Die Anzahl der innerhalb einer definierten Zeit gezählten Impulse, oder die gemessene Zeit die vergeht, bis eine definierte Anzahl von Impulsen gezählt wurde, ist jeweils ein Maß für die Drehzahl.
Für den Testaufbau verwende ich die Ventilatorflügel meines Lüfters als "Lochscheibe" und eine IR-Lichtschranke (im nachfolgenden Bild rechts oben).
Die Auflösung der Messung ist abhängig von der Anzahl der "Löcher" und von der Messdauer. Um eine kurze Messdauer bei hoher Auflösung zu erreichen, müsste die Anzahl der Löcher bzw. Kontrastunterschiede viel höher sein als im Testaufbau.
Bei 7 Löcher (wie im Testaufbau), einer Messdauer von 1 Sekunde und bei z.B. 350 gemessenen Impulsen kann man daraus eine Drehzahl von 3000 U/min errechnen.
Werden unter gleichen Bedingungen 351 Impulse gemessen, errechnet sich daraus bereits eine Drehzahl von 3008,5 U/min. Das ergibt also eine Auflösung von 8,5 U/min je Impuls.
Nun kann man durch Verdoppelung der Messzeit die Auflösung halbieren, wobei sich eine längere Messzeit aber negativ auf das Regelverhalten ausüben würde. Eine Verdoppelung der Lochanzahl, würde ebenfalls eine Halbierung der Auflösung ergeben.
Im Testaufbau ist die Lochanzahl durch die Ventilatorflügelzwischenräume vorgegeben, jedoch kann man durch Zählung jeweils der steigenden und der fallenden Flanken bei 7 Impulsen 14 Flanken pro Umdrehung zählen, wodurch eben die Messwertauflösung halbiert wird. Dadurch erreicht man bei 1 Sekunde Messzeit eine Auflösung von ca. 4 U/min. Für eine Lüfterregelung ist diese Auflösung durchaus annehmbar, für viele Anwendungen aber sicher zu ungenau.
Beim Testaufbau der Regelung ist also auf einen Kompromiss zwischen Messwertauflösung und Messzeit einzugehen.
Programm zur Drehzahlerfassung
Die Messwerterfassung und Drehzahlberechnung erfolgt über einen vom Regelungs-Arduino unabhängigen Mikrocontroller Attiny45 mit 8 MHz Taktfrequenz.
Die Arbeitsteilung erfolgt aus folgendem Grund: Die Impulse der IR-Lichtschranke (sowohl die steigende als auch die fallende Flanke) werden über Interrupts vom Attiny erfasst und bei höheren Drehzahlen kommen schon einige Interrupts zusammen. Selbst bei nur 14 Impulsen pro Umdrehung sind das bei 100 Umdrehungen pro Sekunde (= 6000 Umdrehungen pro Minute) 1400 Interrupts pro Sekunde. Damit nun keiner dieser Impulse "verloren geht", hat der Attiny fast nichts anderes zu tun, als nur diese Impulse zu zählen. Würde man diese Aufgabe mit dem Uno durchführen, würde es vermutlich zu Zeitproblemen kommen, insbesondere bei hohen Drehzahlen.
Im Hauptteil des Programms (loop) macht der Attiny auch nichts anderes als Flanken zählen - Drehzahl berechnen - Flanken zählen - Drehzahl berechnen - usw. Der Zählvorgang findet während eines definierten Zeitraumes (z.B. 1 sec) statt, wenn der Attiny im "delay (MessZeitDelay)" verharrt und nur auf Zählinterrupts wartet. Danach wird aus der Anzahl der gezählten Interrupts die Drehzahl berechnet. Dieser Wert kann nun, vom drehzahlregelnden Uno über I2C-Schnittstelle abgefragt werden. Da der Attiny zwischendurch noch etwas Zeit hat, zeigt er jede I2C-Abfrage des Uno mit einem kurzem LED-Blinken an und eine zweite LED blinkt nach jeweils 10 Umdrehungen.
Für die I2C-Verbindung mit dem Uno benötigt der Attiny die Library "TinyWireS". Im Gegensatz zum Arduino, wo die "Wire"-Library sowohl die Funktion des Arduino als Master als auch als Slave abdeckt, gibt es beim Attiny dafür 2 getrennte Libraries - TinyWireM und TinyWireS. Das "M" und das "S" steht für "Master" bzw. für "Slave".
Einen Link zu TinyWireS (und TinyWireM) findet ihr hier: Fremd-Libraries
Wie man einen Attiny mit Hilfe eines Arduino programmiert findet ihr hier: Attiny programmieren
Hier nun das fertige Programm: Motordrehzahl_Slave_V4.ino.txt
//Drehzahlmessung
//Code für Attiny45/85
//Author Retian
//Version 4#include <TinyWireS.h>
#define LedPin1 1 //LED 1 an Pin1
#define LedPin4 4 //LED 2 an Pin4
#define I2CAddress 0x09 //definiert die I2C-Adresse des Attinyvolatile unsigned int ImpZaehler = 0;
int Drehzahl = 0;
double MessZeit = 0;
double StartZeit = 0;
double EndZeit = 0;
unsigned int ImpAnzahl = 0;
const byte ImpProUmdr = 14; // 7 Rotorflügel * 2 Impulse pro Flügel = 14 Impulse pro Umdrehung
int MessZeitDelay = 1000; //Zeit in Millisekundenvolatile uint8_t RegPosition = 0;
volatile uint8_t I2CRegister[2];void setup() {
pinMode(LedPin4, OUTPUT);
digitalWrite(LedPin4, HIGH);
delay(1000);
digitalWrite(LedPin4, LOW);
pinMode(LedPin1, OUTPUT);TinyWireS.begin(I2CAddress);
TinyWireS.onRequest(requestEvent);//Setzen der Attiny-Register für Pin Change Interrupt
GIMSK |= B00100000; //enable pin change interrupt
SREG |= B10000000; //global interrupt enable
PCMSK |= B00001000; //enable pin change interrupt 3
}void loop() {
ImpZaehler = 0;
StartZeit = micros();
delay(MessZeitDelay);
EndZeit = micros();
MessZeit = (EndZeit - StartZeit) / 1000000;
Drehzahl = (int)(0.5 + ((ImpZaehler / (float)ImpProUmdr) / MessZeit * 60));
I2CRegister[0] = highByte(Drehzahl);
I2CRegister[1] = lowByte(Drehzahl);
}//Interrupt-Service-Routine
ISR(PCINT0_vect)
{
ImpZaehler++; //Impulszähler wird um 1 erhöht//Alle 10 Umdrehungen leuchtet LED1 für 10 Drehimpulse auf
static int Anzahl = 0;
Anzahl++;
if (Anzahl == 10) digitalWrite(LedPin1, LOW);
if (Anzahl == (int)(ImpProUmdr * 10))
{
digitalWrite(LedPin1, HIGH);
Anzahl = 0;
}
}void requestEvent()
{
//LED 2 leuchtet für 5 ms bei Beginn einer I2C-Übertragung zum Master auf
if (RegPosition == 0)
{
digitalWrite(LedPin4, HIGH);
delay(5);
digitalWrite(LedPin4, LOW);
}//Bei jedem Aufruf der Routine wird von TinyWireS jeweils nur 1 Byte übertragen
TinyWireS.send(I2CRegister[RegPosition]);
RegPosition++;
if (RegPosition > 1) RegPosition = 0;
}