Zeit zwischen zwei steigendne Flanken messen - klappt nicht

Hallo Freunde,

eine kleine Spielerei um den Gaszähler abzulesen und die Leistung zu ermitteln und nebenbei eine Übung für mein eigentliches Projekt GSM-Modul...

Lichtschranke am Analogpin wird mittels Schwellenwerten getriggert. Das klappt.
Die Idee ist übrigens aus einem anderen Sketch entliehen.

Getestet wird momentan von Hand mit einem kleinen Spiegel.

Was nicht klappt ist die Berechnung von "duration".

Im Serial Monitor kommen immer 3...5 ms raus und die laufen ständig durch solange triggerState HIGH ist. Dann folgt die "0" von Serial.println(triggerState? 1 : 0);.

Auf dem LCD wird statt duration auch nur Unsinn abgebildet.

Lediglich triggerState wechselt wunschgemäß.

Offensichtlich habe ich da irgendwo einen Denkfehler drin, finde ihn aber nicht.

Kann bitte mal jemand unvoreingenommen da drauf schauen - ich seh den Wald grad vor lauter Bäumen nicht...

/*
unTested!
may not work!
 */

int sensorValueOff = 0;     // value read from the photo transistor when ir LED is off
int sensorValueOn = 0;      // value read from the photo transistor when ir LED is on
int triggerLevelLow = 300;  // trigger state and levels
int triggerLevelHigh = 600;
unsigned long oldMillis = 0; //counters to determine duration
unsigned long newMillis = 0;
unsigned long countMillis = 0;
unsigned long oldCountMillis = 0;
unsigned long duration = 0;
boolean triggerState = false; //the trigger itself
boolean lastTriggerState = false;

const int analogInPin = A0;  // Analog input pin that the photo transistor is attached to
const int irOutPin = 12; // Digital output pin that the IR-LED is attached to
const int ledOutPin = 13; // Signal LED output pin


#include <LiquidCrystal.h> // include the library code


LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // initialize the library with the numbers of the interface pins
/*
 The circuit:
 * LCD RS pin to digital pin 7
 * LCD Enable pin to digital pin 6
 * LCD D4 pin to digital pin 5
 * LCD D5 pin to digital pin 4
 * LCD D6 pin to digital pin 3
 * LCD D7 pin to digital pin 2
 * LCD R/W pin to ground
 * LCD VSS pin to ground
 * LCD VCC pin to 5V
 * 10K resistor:
 * ends to +5V and ground
 * wiper to LCD VO pin (pin 3) */
 
// the setup routine runs once when you press reset:

void setup()
{

 // initialize theese digital pins as an output.
pinMode(irOutPin, OUTPUT);
pinMode(ledOutPin, OUTPUT);


  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2); //initialize display with 16 columns ans two rows (row 0 and row 1)
  lcd.display();// Turn on the display
  lcd.clear(); //erase the display once
  // Print a message to the LCD.
  lcd.print("Booting...");
  delay(1000); //add some time to read the message
  lcd.clear(); //erase again
    lcd.setCursor(0, 0);
  lcd.print("TriggerState:");
  lcd.print(triggerState? 1 : 0);
}

/**
 * Detect and print a trigger event
 */
void detectTrigger(int val)
  {
  boolean nextState = triggerState; //could have been declared as false too
  if (val > triggerLevelHigh)
    {
    nextState = true;
    } 
    else if (val < triggerLevelLow)
      {
      nextState = false;
      }
    if (nextState != triggerState)
      {
      triggerState = nextState;
      lastTriggerState = !nextState; //triggerState and lastTriggerState are opposite only if triggerState changed
      lcd.setCursor(0, 0);
      lcd.print("TriggerState:");
      lcd.print(triggerState? 1 : 0);    
      Serial.println(triggerState? 1 : 0);   
      digitalWrite(ledOutPin, triggerState); // control internal LED
      }
    }

// the loop routine runs over and over again forever

void loop()
  {
  /* perform measurement */
  newMillis = millis();
  if ((newMillis - oldMillis) > 100)
    {
    oldMillis = newMillis;    
    digitalWrite(irOutPin, LOW);// turn IR LED off    
    delay(10);// wait 10 milliseconds    
    sensorValueOff = analogRead(analogInPin);// read the analog in value    
    digitalWrite(irOutPin, HIGH);// turn IR LED on
    delay(10);    
    sensorValueOn = analogRead(analogInPin);// read the analog in value     
    detectTrigger(sensorValueOn - sensorValueOff);// detect and output trigger
 //   Serial.println(sensorValueOn - sensorValueOff); //wird auf Serial ausgegeben 
    lcd.setCursor(0, 1);
    lcd.print(sensorValueOn - sensorValueOff);
    lcd.print("   "); //if the printed value is lass than four characters long, the previous characters may still be left and are erased now
    }   
  /*determine duration between two trigger events*/
  if ((triggerState != lastTriggerState) && (triggerState == true))
    {
    countMillis = millis();
    duration = countMillis - oldCountMillis;
    oldCountMillis = countMillis;  
    lcd.setCursor(6, 1);
    lcd.print(duration);
    Serial.println(duration);   
    }
  }

Sorry, mir ist absolut nicht klar, was Dein Code machen soll :frowning:

Wenn Du eine Lichtschranke bauen möchtest, dann ist es unsinnig, die Sendediode ständig ein- und auszuschalten. Das macht man allenfalls zum Stromsparen, und mißt dann nur während sie leuchtet.

Der Sinn hinter den Delays erschließt sich mir auch nicht, die bringen höchstens die Zeitmessung durcheinander.

Wieso liefert Deine Lichtschranke kein digitales Signal?

Zeig doch mal ein Schaltbild, um die Sache mit der Lichtschranke zu klären, und wie sie mit dem Gaszähler zusammenhängt.

DrDiettrich:
Sorry, mir ist absolut nicht klar, was Dein Code machen soll :frowning:

Das kann ich ändern...

Wenn Du eine Lichtschranke bauen möchtest, dann ist es unsinnig, die Sendediode ständig ein- und auszuschalten. Das macht man allenfalls zum Stromsparen, und mißt dann nur während sie leuchtet.

Das macht schon Sinn denn die Lichtschranke wird auf einem analogen Pin erfasst. Und zwar wird der Analogwert bei eingeschalteter und bei ausgeschalteter Senderdiode gemessen und durch die Differenzbildung wird der Einfluss des wechselnden Umgebungslichtes eliminiert.

Der Sinn hinter den Delays erschließt sich mir auch nicht, die bringen höchstens die Zeitmessung durcheinander.

Die Sendediode soll kurz Zeit haben aufzuleuchten oder zu verlöschen. Ich hätte das auch gerne mit millis() gemacht, mir fehlt aber die Idee, wie ich millis() in diese Befehlsfolge hineinbekomme. Wäre für Tips dankbar!

Wieso liefert Deine Lichtschranke kein digitales Signal?

Das digitale Signal wäre verrauscht durch Neonröhren, etc..
Durch die analoge Auswertung wird das Rauschen unterdrückt.

Zeig doch mal ein Schaltbild, um die Sache mit der Lichtschranke zu klären, und wie sie mit dem Gaszähler zusammenhängt.

Die Sendediode liegt von D12 über 220R auf GND.
Analog Pin A0 liegt über 10k an GND und wird durch den Fototransistor auf VCC gezogen. Das ist schon alles.

Ich hätte das auch gerne mit millis() gemacht, mir fehlt aber die Idee, wie ich millis() in diese Befehlsfolge hineinbekomme. Wäre für Tips dankbar!

Aber, gerne doch!

Ablaufsteuerung

Meine Standardantwort zu Ablaufsteuerungen:
Eine Stichworte Sammlung für Google Suchen:
Endlicher Automat,
State Machine,
Multitasking,
Coroutinen,
Ablaufsteuerung,
Schrittkette,
BlinkWithoutDelay,

Blink Without Delay
Der Wachmann

Multitasking Macros
Intervall Macros

Hallo,
zur Funktion "void detectTrigger(int val)". Ich bin der Meinung, dass die Triggerung bei jedem Durchlauf erfolgt. Man darf nur den Zusatnd aus der letzten Abtastzeit (tn-1) mit dem Wert der aktuellen Abtastzeit(tn) vergleichen. Nur dann erhält man die gewünschte Information.

Viele Grüße MKc

Es fehlt immer noch eine Angabe zur Rolle des Zählers. Läßt sich dort die Lichtschranke nicht so anbringen, daß kein Fremdlicht reinkommt?

Falls die 10ms Verzögerung den Einfluß von Leuchtstoffröhren (100Hz) filtern sollen, dann funktioniert das nicht für LED und andere Leuchtmittel, die mit anderen Frequenzen arbeiten.

     triggerState = nextState;
     lastTriggerState = !nextState; //triggerState and lastTriggerState are opposite only if triggerState changed

Damit sind triggerState und lastTriggerState immer komplementär, also redundant. Und

  /*determine duration between two trigger events*/
  if ((triggerState != lastTriggerState) && (triggerState == true))

ist immer true solange triggerState true ist.

combie:
Aber, gerne doch!

Danke, ich google das mal....

@Mkch:

Die Funktion detectTrigger soll den triggerState auf TRUE oder FALSE setzen bzw. gesetzt lassen je nachdm ob am Analogingang was größeres oder klineres anliegt als die beiden Grenzwerte.

Nur wenn sich triggerState ändert, wird auch lastTriggerState angepasst.

Oder etwa nicht?!

DrDiettrich:
Es fehlt immer noch eine Angabe zur Rolle des Zählers. Läßt sich dort die Lichtschranke nicht so anbringen, daß kein Fremdlicht reinkommt?

Nicht wirklich möglich ohne die direkte Ablesung übermäßig zu erschweren.

Falls die 10ms Verzögerung den Einfluß von Leuchtstoffröhren (100Hz) filtern sollen, dann funktioniert das nicht für LED und andere Leuchtmittel, die mit anderen Frequenzen arbeiten.

Die 10ms stellen nur eine Bedenkzeit zum Ein- und Ausschalten der LED dar. Hat mit der Filterfunktion nicht direkt zu tun.[/quote]

Damit sind triggerState und lastTriggerState immer komplementär, also redundant. Und

  /*determine duration between two trigger events*/

if ((triggerState != lastTriggerState) && (triggerState == true))



ist immer true solange triggerState true ist.

Das wird wohl der Haken sein. Ich erkenne hier nicht die Flanke sondern HIGH.

newarduinosmith:
... und durch die Differenzbildung wird der Einfluss des wechselnden Umgebungslichtes eliminiert.

IR-Lichtschranke?
Kaum Einfluss durch Streulicht.

newarduinosmith:
Die Sendediode soll kurz Zeit haben aufzuleuchten oder zu verlöschen.

Die Schaltzeiten von LEDs werden üblicherweise in ns angegeben.
Solange du wirklich LEDs nimmst und keine Glühlampen ist das unnötig. Schon bevor der "digitalWrite" Befehl beendet ist, hat die LED ihre volle Leuchstärke erreicht.

Ich werde wohl das hier

    triggerState = nextState;
     lastTriggerState = !nextState; //triggerState and lastTriggerState are opposite only if triggerState changed

an den Anfang der Funktion verschieben müssen damit triggerState und lastTriggerState unterschiedlich werden wenn triggerState sich anschließend ändert.

Dann wird auch die Bedeutung "lastTriggerState" - also während des letzten Durchlaufs - sinngemäß. Der letzte boolsche Status ist natürlich immer das Komplement und daher keiner Abfrage wert...

guntherb:
Schon bevor der "digitalWrite" Befehl beendet ist, hat die LED ihre volle Leuchstärke erreicht.

Ich wollte vermeiden, dass die Analogwertauslesung während des "inrush" stattfindet. Falls der "digitalWrite" Befehl jedoch wirklich so schnell fertig ist, dass dann das inlesen noch nicht bgonnen hat, können die delays wegfallen. Wie schnll schalten die Ports? Ich teste das mal...

In der Tat IR. Die Halogen-Schreibtischlampe beinflusst das aber je nach Blickrichtung erheblich.
Ungeachtet dessen ist die Idee abgekupfert und ich fand das Verfahren einfach gut.

guntherb:
IR-Lichtschranke?
Kaum Einfluss durch Streulicht.

Das stimmt meiner Erfahrung nach nicht. Solange die Sonne über dem Horizont steht, ist der Streulichteinfluss erheblich.

Gruß

Gregor

ist der Streulichteinfluss erheblich.

Jede ernst zu nehmende Lichtschranke hat da was gegen eingebaut.
Oder die Ansteuerung muss da was gegen unternehmen. (so wie hier)

Erfolgversprechende Methoden lassen sich reichlich im Internet finden.

gregorss:
Das stimmt meiner Erfahrung nach nicht. Solange die Sonne über dem Horizont steht, ist der Streulichteinfluss erheblich.

Gruß

Gregor

Das stimmt.
Aber da wo mein Gaszähler ist (im Keller), scheint keine Sonne.

Ich habe ein wenig getestet und folgendes herausgefunden:

Ohne die 10ms delay kommt keine brauchbare Messung zustande.

Wenn ich also das Messprinzip beibehalten will, muss die Pause drin bleiben.

Könnte aber auch eine switch case Anwendung werden und als VAR nehme ich die verschiedenen Aufgaben die abgearbeitet werde bmüssen - aber frühestens morgen Nacht... :wink:

Nächster Test ergab, dass bei Schreibtischbeleuchtung und ohne die Differenzmessung Werte um 30, mit Differenzmessung Werte um 5 bei jeweils leerer Lichtschranke (also ohne Spiegel) gemessen werden.

Das Messprinzip ist also durchaus gut und richtig, das Rauschen wird etwa auf 1/6 reduziert.

Die Detektion der steigenden Flanke funktioniert jetzt auch, nachdem ich die Reihenfolge des Wertezuweisens korrigiert habe.

Zuerst wird lastTriggerState = triggerState gesetzt und dann wird triggerState geändert - oder auch nicht. Dadurch können triggerState und lastTriggerState unterschiedlich sein, nämlich bei steigender oder auch bei fallender Flanke.

Nun wird nur bei steigender Flanke aufs LCD geschrieben. ;D

Leider wird nur Müll geschrieben. Werte um 4...5. Bei einer der letzten Versionen waren es irgendwelche unglaubhaften Tausender...

Wenn ich statt duration die millis() aufs LCD schreiben lasse, zählen die korrekt hoch und werden bei jeder steigenden Flanke aktualisiert:

 if ((triggerState != lastTriggerState) && (triggerState == true)) //rising edge detected
    {
    countMillis = millis();
    duration = countMillis - oldCountMillis;
    oldCountMillis = countMillis;  
    lcd.setCursor(6, 1);
    lcd.print("          ");
    lcd.setCursor(6, 1);
    lcd.print(millis());
    Serial.println(duration);   
    }

Mit duration hingegen kommt Müll. Auch das bewusste zeitliche Auslösen der Flanke ( zähle 21...22...23... :wink: ) führt zu keinem brauchbaren Ergebnis:

 if ((triggerState != lastTriggerState) && (triggerState == true)) //rising edge detected
    {
    countMillis = millis();
    duration = countMillis - oldCountMillis;
    oldCountMillis = countMillis;  
    lcd.setCursor(6, 1);
    lcd.print("          ");
    lcd.setCursor(6, 1);
    lcd.print(duration);
    Serial.println(duration);   
    }

Ich finde da keinen Fehler.

-countMillis setzen
-oldCountMillis subtrahieren (die sind älter also kleiner)
-oldCountMillis auf countMillis setzen
-duration ausgeben

Compilermeldungen kommen übrigens keine. Der Code scheint syntaktisch richtig zu sein.

Interessant wirds im Seriellen Monitor:

5 0 1 12765 7 4 5 5 4 5 5 5 5 5 4 5 5 4 0 1 1347 6 5 5 4 5 5 5 5 4 5 5 4 5 5 4 0 1 3166 6 5 5 4 5

Man sieht immer die Flankenwechsel "0" und "1" und dann den offenbar korrekte Wert "duration."

Die 4 und 5 sind offenbar die Programmdauer. Nach "duration" kommt 6 oder 7 weil das LCD Schreiben etwas länger dauert.

Falls "duration" irgendwie ein reserviertes Wort sein sollte, habe ich es durch "murks" ersetzt - ohne Wirkung.

Was zum Geier passiert da?

Es ist ziemlich unsinnig, das Ändern und das Auslesen des triggerState asynchron durchzuführen. Wenn triggerState nur alle 100ms aufdatiert wird, aber das Auslesen bei jedem Durchlauf von loop() erfolgt, dann wird der selbe Status zwangsläufig mehrmals ausgewertet, bis zum nächsten Aufdatieren.

Das Auslesen sollte also direkt anschließend an das Aufdatieren erfolgen, nicht öfter. Das spart auch noch Rechenzeit. Zudem kann man nach dem Auslesen lastTriggerState=triggerState setzen, um eine mehrfache Auswertung zu unterbinden, und das Ändern von lastTriggerState aus detectTrigger() rauswerfen.

Durch das 100ms Intervall sind natürlich die Zeiten für das Erkennen eines Wechsels nur auf 100ms genau, der kann irgendwo innerhalb des letzten Intervalls liegen. Das mag zwar für die Erkennung eines Wechsels ausreichen, wenn sich der Zähler langsam genug dreht, aber nicht für die Berechnung des aktuellen Durchflusses.

Das sind einige der Punkte, deren Sinn ich im ursprünglichen Code überhaupt nicht verstehen kann. Da hat wohl ein Bastler so lange am Programm herumgepfuscht, bis es für seinen Aufbau und seine Anwendung halbwegs funktioniert hat. Mit ordentlicher Programmierung hatte das aber nichts zu tun :-]

Vorsichtshalber könnte man auch noch abfragen, ob der Phototransistor durch Fremdlicht übersteuert wird, und das zusätzliche Licht der LED garnicht mehr feststellen kann.

Hast Recht. Das asynchron zu machen ergibt keinen Sinn. Ich ändere das.

Kurz nachgerechnet:

Wenn der Gasbrenner volle Leistung (20kW) fährt, verbraucht er 0,5L Gas pro Sekunde. Ein Umlauf des Rades ( 1 Liter-Rad) dauert also 20 Sekunden.

Bei zehn Messungen pro Sekunde kommen 200 Messungen pro Umlauf zustande. Da der Umlauf ja die kleinste messbare Einheit des Zählers ist, scheint mir die mit 200 Messungen ausreichend genau erfasst zu sein.

Bei geringerer Kesselleistung steigt automatisch die Präzision bei gleichbleibender Auflösung. Die Auflösung der Kesselleistung oder des Voumenstromes dynamisch zu gestalten hatte ich nocht vor.

DrDiettrich:
Das sind einige der Punkte, deren Sinn ich im ursprünglichen Code überhaupt nicht verstehen kann. Da hat wohl ein Bastler so lange am Programm herumgepfuscht, bis es für seinen Aufbau und seine Anwendung halbwegs funktioniert hat. Mit ordentlicher Programmierung hatte das aber nichts zu tun :-]

Das war ich!

Bin halt kein Programmierer...

Vorsichtshalber könnte man auch noch abfragen, ob der Phototransistor durch Fremdlicht übersteuert wird, und das zusätzliche Licht der LED garnicht mehr feststellen kann.

Habe ich getestet. Die IR-LED ist dominant und das Messverfahren verbessert das (ohnehin gute) Ergebnis.

Hast du noch eine Idee, warum auf dem LCD und Serial so komische Werte ankommen?

Wie lang wird der Impuls, den die Lichtschranke ausspuckt, bei voller Drehzahl? Es reicht ja nicht, mehrmals pro Umdrehung abzutasten, wenn man dabei nicht die Stelle(n) trifft, an denen eine Markierung durchwandert.

DrDiettrich:
Wie lang wird der Impuls, den die Lichtschranke ausspuckt, bei voller Drehzahl? Es reicht ja nicht, mehrmals pro Umdrehung abzutasten, wenn man dabei nicht die Stelle(n) trifft, an denen eine Markierung durchwandert.

Pi Mal Daumen: (20s/10) / 5 = 0,4s.

Wird also mehrfach erfasst werden.

Mein Problem liegt aber aktuell woanders.

Ich verstehe nicht, warum der Code das tut was er tut.

Hat irgendjemand eine Idee wo der Fehler liegt?

12765ms sind fast 13 Sekunden, in denen kein Trigger festgestellt wurde. Ich würde mal anfangen, an dieser Stelle nachzubohren.

Da wäre z.B. interessant, wieviele gültige und ungültige (zwischen triggerLevelLow und High) Werte gemessen wurden. Vermutlich passen die Grenzwerte nicht zu Deinen Meßwerten. Eine Statistik der Verteilung der Differenzen auf den ganzen Bereich wäre da hilfreich.

Das sind genau die dreizehn Sekunden die ich bewusst abgewartet habe bevor ich erneut den Spiegel vor die Lichtschranke schob...

Alles das ist, wie ich schrieb, am Schreibtisch getestet.

5 0 1 12765 7 4 5 5 4 5 5 5 5 5 4 5 5 4 0 1 1347 6 5 5 4 5 5 5 5 4 5 5 4 5 5 4 0 1 3166 6 5 5 4 5

Ich hab extra versucht, unterschiedliche Zeiten zu generieren: 12765ms, 1347ms, 3166ms ...

Das scheint auch glaubhaft.

Es kommt ja auch ordnungsgemäß die "0" und die "1" aus der Funktion detectTrigger.

Für mich sieht das so aus dass "duration" anwächst ab dem Zeitpunkt da "triggerState" FALSE wird.

Die if Schleife im loop unten wird dann nicht durchlaufen.

Sobald "triggerState" HIGH wird geht es in die Schleife und "duration" wird ausgegeben. Soweit korrekt.

Danach aber wird die if Schleife offenbar weiter aufgerufen und duration zeigt nur wenige Millisekunden.

Komisch ist auch, dass immer ~14-15 solcher Werte ausgegeben werden, die in Summe ~68-72ms ergeben.

Die Pausen zwischen meinen Spiegelschiebereien waren definitiv länger als das.

Es wird also diese Werteproduktion "freiwillig" beendet und nicht durch den Trigger.

Kann es sein, dass der Chinese einen defekten Atmel aufs Board gelötet hat?

Bootloader Fehler?

Das sollte jetzt eigentlich endlich klar sein :frowning:

Deine Abfrage prüft effektiv nur, ob triggerState high ist, und das kann sich erst nach der nächsten Messung (100ms später) wieder ändern. Und so lange wird duration neu berechnet und ausgegeben.