Teil des offenen Fraktionssammlerprojektes ist der Tropfenzähler. Ein Fraktionssammler wird zum Beispiel Chromatographieanlagen zum Auffangen definierter Flüssigkeitsmengen verwendet. Da die in Flüssigkeit gelösten Substanzen wertvoll, giftig oder radioaktiv sein können, ist eine Absetzen von Flüssigkeit außerhalb der dafür vorgesehenen Gefäße unbedingt zu vermeiden.
Der Tropfenzähler kann zur Fraktionierung nach Tropfenzahl eingesetzt werden, er hat aber vor allem die Aufgabe den Wechsel der Fraktionen auf den Zeitpunkt unmittelbar zu begrenzen oder auf Grund der berechneten Zeiten zwischen 2 Tropfen einen gesicherten Übergang zu gewährleisten.
Eine Gabellichtschranke (Photounterbrecher) ist zentraler Bestandteil des Tropfenzählers. Die lichte Weite der Gabel ist so groß zu wählen, dass ein freier Fall der Tropfen auch unter Bewegung sicher gewährleistet ist.
Die verwendete Lichtschranke besitzt eine Weite von 10 mm. Datenblatt Die Firma Sparkfun bietet ein Breakoutboard an, dem die geeignete Belegung und der Vorwiderstand (220 Ω) für die Verwendung mit dem Arduino UNO oder MEGA zu entnehmen ist.
Hieraus wurde ein fliegender Aufbau auf dem Breadboard entwickelt. Die „Beinchen“ der Lichtschranke sind dünn und beweglich, so dass es bei ungeeigneter Handhabung leicht zu Kurzschlüssen kommen kann. Eine Verwendung unmittelbar auf dem Breadboard ist nicht möglich.
Aufgabe dieses ersten Teils ist zunächst die sichere Erfassung fallender Tropfen. Zusätzlich soll anhand des Zeitabstandes zwischen zwei nacheinander fallenden Tropfen die Tropfgeschwindigkeit pro Minute geschätzt werden.
Hier der Code:
// dropcounter /***************************************************/ int photoInterrupterPin = 2; // Signal der Gabellichtschranke, Pegel bei Unterbrechung des Lichtweges "low" sonst "high" int interruptNumber = 0; // Pin 2 des Arduino Uno / Mega ist mit Interruptnummer 0 verbunden int dropsPM = 0; // Ergebnisangabe in Tropfen pro Minute volatile int dropNumber = 0; // Variable zum Speichern der Tropfenzahl. "volatile" gewährleistet saubere Übergabe int tempDropNumber = 0; // dient zur Zwischenspeicherung der Tropfenzahl volatile unsigned long deltaTime = 0; // Zeitabstand zwischen 2 aufeinanderfolgenden Unterbrechungen volatile unsigned long lastInterruptTime = 0; // Zwischenspeicherung des letzten Aufrufzeitpunktes /***************************************************/ void setup() // Initialisierung der Pins, Einhängen und Typisieren des Interrupts { pinMode(photoInterrupterPin, INPUT); // Pin 2 als Eingang definieren attachInterrupt(interruptNumber, interruptroutine, FALLING); // Immer wenn an Pin 2 der Pegel fällt, löst das Interrupt 0 aus // und die Funktion "interruptroutine" wird aufgerufen Serial.begin(38400); // Kommunikation mit Baudrate von 38400 initialisieren // langsamere Geschwindigkeiten führen zu Datenverlusten } /***************************************************/ void loop() // Hauptprogrammschleife { if ((dropNumber > tempDropNumber) and (dropNumber > 1)) // Hat sich die Tropfenzahl erhöht? { // Zeit zwischen 2 Tropfen nur bei mehr als 1 Tropfen sinnvoll dropsPM = 60000 / deltaTime; // 60000 ms = 1 Minute / Zeitdifferenz zwischen 2 Tropfen deltaTime = 0; // Zeitdifferenz wieder auf Null setzen tempDropNumber = dropNumber; // Aktuelle Tropfenzahl zwischenspeichern Serial.println(dropNumber); Serial.print("Tropfen pro Minute: "); Serial.println(dropsPM); } delay(5); // 5 ms warten. Das entspricht ebenfalls der Wartezeit der // Interruptroutine. Es geht also keine Zeit verloren. } /******************************************************/ void interruptroutine() // Wird aufgerufen, immer wenn der Pegel an Pin 2 abfällt. { unsigned long interruptTime = millis(); // Zeitpunkt zu dem der Interrupt aufgerufen wird deltaTime = interruptTime - lastInterruptTime; // Da erst ab dem 2. Tropfen gezählt werden soll, gibt es // einen vorangegangenen Zeitpunkt eines Interuoptaufrufs if (deltaTime > 5) // Zeit zwischen 2 aufeinander folgenden Dropfen muss 5 ms { // überschreiten (verhindert Nebenefekte der Hardware) dropNumber++; // Tropfenzahl wird um "1" erhöht wenn der Abstand zwischen } // 2 Ereignissen länger als 5 ms war. else // sonst { // war 's wohl ein Fehler und deltaTime = 0; // der Zeitabstand wird auf "0" zurückgesetzt } lastInterruptTime = interruptTime; // Zeitpunkt des aktuellen Pegelwechsels wird zwischengespeichert. }
Quellen, die mich zu dieser Art des Codes bewegt haben:
„Using a KY040 Rotary Encoder with Arduino“ von Big Dan The Blogging Man
„Interrupts“ von Nick Gammon
„Arduino Interruptsteuerung (Teil 1)“ von Erik Bartmann
Die sichere Erfassung zeitkritischer Vorgänge erfordert den Einsatz von Interrupts. Der Arduino UNO verfügt über 2 Pins / digitale Eingänge zur Erfassung externer Interrupts. Es sind dies die Pins 2 und 3. Hier wird Pin 2 als Eingang verwendet. Der Signalausgang der Gabellichtschranke funktioniert als Schmitt-Trigger. Die Signaländerungen verlaufen also sehr steil und schnell, so dass es kaum zu einer „Fehlinterpretation“ in Bezug auf eine Unterbrechung des Lichtweges kommen kann. Wird der Lichtweg unterbrochen fällt die Spannung am Signalausgang auf nahezu 0 V. Die Interruptroutine muss also stets bei fallendem Pegel („falling“)aufgerufen werden. Wird sie bei sich ändernden Peglen („change“) aufgerufen, ergeben sich pro Tropfenfall 2 Signale. Das erste entsteht zu Beginn der Unterbrechung des Lichtweges und das zweite beim Verlassen des Lichtweges durch den fallenden Tropfen.
Die Sprache des Arduino sieht die Funktion „millis()“ zur Bestimmung der Zeit vom Einschalten des Prozessors vor. Wie der Name nahe legt erfolgt die Angabe in Millisekunden. Damit lässt sich ein Zeitraum von bis zu 50 Tagen erfassen, da die Funktion den Datentyp „Long“ als Ergebnis liefert. Eine genauere Erfassung in z.B. Mikrosekunden wäre zwar möglich, ist auf Grund der Größe der Tropfen und der maximal möglichen Fallgeschwindigkeit nicht nötig.
Die Ermittlung einer Zeitdifferenz erfordert die Bestimmung von mindestens 2 Zeitpunkten. Die Bestimmung der Tropfgeschwindigkeit kann also erst vom zweiten Tropfendurchgang an erfolgen, weshalb „dropNumber > 1“ eine notwendige Bedingung ist, die abgefragt wird sobald die Tropfenzahl „dropNumber“ größer als die vorangegangene temporär gespeicherte Tropfenzahl „tmpDropNumber“ ist. Die temporäre Variable wird mit „0“ initialisiert. Somit wäre „dropNumber > temDropNumer“ schon für den ersten fallen Tropfen erfüllt. Eine alternative – aber weniger transparente – Lösung wäre die Initialisierung von tempDropNumber mit „1“.
Die Kommunikation des Arduino über die serielle Schnittstelle mit dem PC (Serial Monitor) kostet Zeit. Experimentell stellte sich heraus, dass erst ab einer Baudrate von mehr als 38400 eine zuverlässige Tropfenerkennung möglich ist. Einer weiteren Erhöhung der Baudrate steht allerdings nichts im Wege.
Das eingefügte „delay(5)“ bgrenzt den unnötigen Energieverbrauch der Prozessors und damit Erwärmung. Die verwendeten 5 ms entsprechen der Zeit die ohnehin in der Interruptroutine gewartet wird. Der gewählte Zeitrahmen stammt auf dem Encoder-Beispiel und funktionierte auch hier.
Klare Plastikfolie oder Polycarbonat stören den Strahlengang nicht. Durch einen Ring aus Polycarbonat kann eine Kontamination der IR-LED oder des Sensors vermieden werden.
Die Folgeversion der Software soll zusätzlich eine Funktion zur Näherung der Tropfgeschwindigkeit aus der Mittelung mehrerer Tropfzeitdifferenzen enthalten. Ein Ausgangssignal auf einen Digitalausgang soll zudem den sicheren und frei definierbaren Zeitrahmen für die Bewegung des Tropfenzählers zum nächsten Gefäß signalisieren.