Forum: Mikrocontroller und Digitale Elektronik lichtschrankenauswertung, zeitweise sperrung Eingang


von T1waz T. (t1waz_t)


Lesenswert?

Hallo,
es geht um eine vermeintlich simple Angelegenheit,
jedoch klappt es bei mir nach vielen Versuchen immer noch nicht.

Aufgabe:
Vier Lichtschranken (eine pro Bahn), detektieren fallende Teile als 
positive Flanke und geben bei jeder Auslösung ein digitales Signal aus.

Durch die Geometrie der Teile ist eine doppelt Auslösung der 
Lichtschranke möglich, muss aber für eine einstellbare Zeit, z.B 1s 
deaktiviert sein.

--> Alles über eine SoftwareAuswertung, aber meine Versuche mit millis() 
scheitern bisher

Klingt einfach, ich bekomm es aber leider nicht gebacken.
Ich freue mich über jede Hilfe!


Vielen Dank.

Gruß

: Verschoben durch User
von Magnus M. (magnetus) Benutzerseite


Lesenswert?

Monoflop

von T1waz T. (t1waz_t)


Lesenswert?

Magnus M. schrieb:
> Monoflop

bin mir nicht sicher, was ich damit anfangen soll

von Max D. (max_d)


Lesenswert?

Code hier hochladen.

Sperre würde ich bei Erkennung setzten und per timer runterzählen.

Nachtrag wenn du eh schon eine Zeitbasis (millis() klingt danach) hast, 
dann merk dir einfach die letzte Zeit und vergleich die.

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

T1waz T. schrieb:
> Durch die Geometrie der Teile ist eine doppelt Auslösung der
> Lichtschranke möglich, muss aber für eine einstellbare Zeit, z.B 1s
> deaktiviert sein.

Der Lichtschranke wirst du nur schwer verbieten können, ein zweites Mal 
auszulösen, sofern du nicht nach der ersten Auslösung den Sender 
abschaltest.

Am einfachsten wird es sein, wenn du das zweite Signal in deiner 
Software ignorierst, z.B. indem du einen neuen Unterbrechungspuls erst 
nach einer gewissen Mindestzeit akzeptierst.

von T1waz T. (t1waz_t)


Lesenswert?

Wolfgang schrieb:
> T1waz T. schrieb:
>> Durch die Geometrie der Teile ist eine doppelt Auslösung der
>> Lichtschranke möglich, muss aber für eine einstellbare Zeit, z.B 1s
>> deaktiviert sein.
>
> Der Lichtschranke wirst du nur schwer verbieten können, ein zweites Mal
> auszulösen, sofern du nicht nach der ersten Auslösung den Sender
> abschaltest.
>
> Am einfachsten wird es sein, wenn du das zweite Signal in deiner
> Software ignorierst, z.B. indem du einen neuen Unterbrechungspuls erst
> nach einer gewissen Mindestzeit akzeptierst.

Das war wohl schlecht ausgedrückt, ich will nicht die Lichtschranke 
pausieren,
sondern wie du sagst, erst nach einer Mindest Zeit das nächste Signal 
akzeptieren.

Genau daran scheitere ich.

Ich poste später mal den Code

von T1waz T. (t1waz_t)


Lesenswert?

Hier ist mein Code.
1
const int counterPin = D2;
2
const int sensorPin[4] = {D3,D4,D5,D6};
3
4
const long interval = 2000;
5
unsigned long previousMillis[4] = {0,0,0,0};
6
unsigned long currentMillis[4];
7
int sensorPinBlocked[4] = {0,0,0,0};
8
int buttonState[4];
9
int lastButtonState[4];
10
unsigned long myCounter = 0;
11
unsigned long myCounterOld = 0;
12
13
void setup() {
14
  // put your setup code here, to run once:
15
  
16
  Serial.println("Hau rein....");
17
  pinMode(counterPin, OUTPUT);
18
19
  for (int i = 0; i < 4; i++) {
20
     pinMode(sensorPin[i], INPUT);
21
  }
22
   Serial.begin(9600);
23
}
24
25
void loop() {
26
    for (int i = 0; i < 4; i++) {
27
      currentMillis[i] = millis() + interval;
28
      buttonState[i] = digitalRead(sensorPin[i]);
29
      
30
      if (myCounter == HIGH) {
31
        if (currentMillis[i] - previousMillis[i] >= interval){
32
           previousMillis[i] = currentMillis[i];
33
           sensorPinBlocked[i] = HIGH;   
34
        } else {
35
           sensorPinBlocked[i] = LOW;
36
        }
37
      }
38
      
39
      if (sensorPinBlocked[i] == LOW) {
40
        if (buttonState[i] != lastButtonState[i]) {
41
          myCounter = HIGH;
42
        }else {
43
          myCounter = LOW;
44
        }
45
      }
46
47
      delay(299);
48
      lastButtonState[i] = buttonState[i];
49
    }
50
}

soweit meine Idee.
Wo die Flankenauswertung zurückgesetzt wird, ist mir nicht ganz klar.

Zusätzliche Frage: Gibt es eine gute Möglihckeit für Debuggen oder 
Simulieren, um das Programm zeilenweise mit Werten zu sehen?

von Peter D. (peda)


Lesenswert?

T1waz T. schrieb:
> soweit meine Idee.
> Wo die Flankenauswertung zurückgesetzt wird, ist mir nicht ganz klar.

Wenn das Deine Idee ist, solltest Du eigentlich wissen, wo Du was zurück 
setzt.
Ich finde es recht kompliziert und schwer zu verstehen.
Schreib das Programm einfach erstmal nur für eine Lichtschranke. Und 
erst, wenn es funktioniert, erweitere es.

von Peter D. (peda)


Lesenswert?


von Thomas W. (goaty)


Lesenswert?

Die Logik ist verkehrt,
sensorPinBlocked ist nur für eine Schleifeniteration HIGH, sonst immer 
LOW.
Daher wird wohl dann immer myCounter=HIGH.
Wobei ich den Code für etwas unübersichtlich halte.
1
      currentMillis[i] = millis() + interval;
2
      buttonState[i] = digitalRead(sensorPin[i]);
3
      
4
      if (myCounter == HIGH) {
5
        if (currentMillis[i] - previousMillis[i] >= interval){
6
           previousMillis[i] = currentMillis[i];
7
           sensorPinBlocked[i] = LOW;   
8
        } else {
9
           sensorPinBlocked[i] = HIGH;
10
        }
11
      }
12
      
13
      if (sensorPinBlocked[i] == LOW) {
14
        if (buttonState[i] != lastButtonState[i]) {
15
          myCounter = HIGH;
16
          lastButtonState[i] = buttonState[i];
17
        }else {
18
          myCounter = LOW;
19
        }
20
      }
21
22
      delay(299);
23
      //lastButtonState[i] = buttonState[i];

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

T1waz T. schrieb:
> Genau daran scheitere ich.
>
> Ich poste später mal den Code

Nee, laß das mal mit dem Posten von nicht funktionierendem Code.

Aber warum scheiterst du an so etwas einfachem? Ich komme mir schon vor 
wie eine tibetanische Gebetsmühle, aber anstatt mal zuzuhören und zu 
lernen, halten sich die Leute ihre Augen fest zu.

Also lerne aus der Lernbetty oder irgend einem anderen Zeugs, was ich 
hier mal gepostet hatte und arbeite mit Events. Das löst dein Problem im 
Handumdrehen.

Wie geht's?
Anstatt auf irgendwas zu warten, wird das Eintreffen irgendeines 
Ereignisses in eine Event-Warteschlange geschrieben und dann im 
Grundprogramm ausgewertet. Und mit einer Systemuhr (so alle 1..10 ms 
oder so) kannst du dann auch verzögerte Events haben. Das sind welche, 
die erst nach einer von dir wählbaren Zeit von der Systemuhr in die 
Event-Warteschlange geschrieben werden.

mit sowas kannst du "Entprell"-Totzeiten machen, ohne deinen µC 
irgendwie mit Warten auf Entprellung zu blockieren.

W.S.

von Jens M. (schuchkleisser)


Lesenswert?

1
if input1 {
2
  if age1 > x {
3
    count1;
4
    age1 = millis();
5
  }
6
}

Das ganze einfach 4mal untereinander in die Loop, fertich.
Vermutlich.
Ist natürlich pseudo und schnell mal hingelegt.

von T1waz T. (t1waz_t)


Lesenswert?

W.S. schrieb:
> Aber warum scheiterst du an so etwas einfachem?

Gute Frage, darum habe ich hier gepostet.

Was Event Warteschlangen sind, bzw. wie ich sowas implementiere in meine 
Anforderung, weiß ich leider nicht.

Könnte daran liegen, dass ich mich noch nicht ewig damit beschäftige


Zu meiner Frage mit Simulation /Debugging, gibts da irgendwas?
#
Gruß

von K. S. (the_yrr)


Lesenswert?

W.S. schrieb:
> arbeite mit Events

seid wann hat Arduino events?

> mit sowas kannst du "Entprell"-Totzeiten machen, ohne deinen µC
> irgendwie mit Warten auf Entprellung zu blockieren.
das ist doch garnicht das Problem hier.

Thomas W. schrieb:
> sensorPinBlocked ist nur für eine Schleifeniteration HIGH, sonst immer
> LOW.
stimmt nicht ganz.
nach einer Flanke am Pin außerhalb der "Totzeit" wird myCounter high, 
dadurch wird sensorPinBLocked high bis Ende der "Totzeit" und myCounter 
bleibt gezwungenermaßen high. Am Ende der "Totzeit" wird 
sensorPinBlocked low, erst dann kann myCounter low werden, aber dann ist 
sensorPinBlocked auf low festgesetzt.



Probleme bei deinem Code:

Wenn der Pin sich innerhalb der Blockierzeit ändert, wird die Erkennung 
bis zum Ende der Blockierzeit aufgeschoben.

myCounter ist sowohl nach einer steigenden als auch einer fallenden 
Flanke high, und zwar für die "Totzeit" *(stimmt so nicht ganz, siehe 
nächster Absatz). beide Flanken sind gleichwertig, wenn der Pin high 
wird wird genau gleich behandelt udn gibt intern das gleiche Signal als 
ob der Pin low wird.

das Echte Problem ist, dass PreviousMillis nur am Ende der letzten 
Totzeit gesetzt wird. das heißt deine "Totzeit" beginnt nicht bei einer 
Flanke zu zählen, sondern bei Ende der letzten "Totzeit". daher ist die 
Dauer der echten Totzeit auch nicht 2000ms sondern (2000ms - Zeit seit 
Ende der letzten "Totzeit"). Der beginn der Totzeit muss beim Ändern des 
Pins gesetzt werden.

Beispiel
- wenn vor 10s die letzte Flanke kam, ist previousmillis die Zeit von 
vor 8s
- bei der nächsten Flanke wird myCounter high
- im nächsten durchlauf ist die Totzeit bereits >8s, daher bleibt 
sensorpinblocked low
- nun wird sofort myCounter wieder low, da die "Totzeit" schon lange 
abgelaufen ist.
-> das funktioniert bei steigender und fallender Flanke gleich

das hier dürfte besser funktionieren:
1
 if (currentMillis[i] - previousMillis[i] >= interval){
2
           sensorPinBlocked[i] = LOW;   
3
        } else {
4
...
5
6
 if (buttonState[i] != lastButtonState[i]) {
7
          previousMillis[i] = currentMillis[i];
8
          myCounter = HIGH;
9
          lastButtonState[i] = buttonState[i];
10
        }else {

: Bearbeitet durch User
von Thomas W. (goaty)


Lesenswert?

Ich hab noch einen Vorschlag (weil mir grad langweilig war):
1
    unsigned long lastEventTime[4] = {0,0,0,0};
2
    bool lastInput[4] = {digitalRead(sensorPin[0],
3
                         digitalRead(sensorPin[1],
4
                         digitalRead(sensorPin[2],
5
                         digitalRead(sensorPin[3]};
6
7
    for (int i = 0; i < 4; i++) {
8
      unsigned long now = millis();
9
      bool input = digitalRead(sensorPin[i]);
10
11
      if (input != lastInput[i]) {
12
        lastInput[i] = input;
13
        if (now < lastEventTime[i] + interval) {
14
          // ignore
15
        }
16
        else {
17
          lastEventTime[i] = now;
18
          // do something here
19
        }
20
      }
21
      delay(299);
22
    }

: Bearbeitet durch User
von MinMax (Gast)


Lesenswert?

Thomas W. schrieb:
1
>         if (now < lastEventTime[i] + interval) {
2
>           // ignore
3
>         }
4
>         else {
5
>           lastEventTime[i] = now;
6
>           // do something here
7
>         }
8
>       }
9
>       delay(299);
10
>     }

Dein Vorschlag hat aber ein inherentes Problem, das nach etwa 49 Tagen 
ununterbrochenen Betriebs zum Vorschein kommt. Spielt hier vielleicht 
keine Rolle, sollte aber von vornherein vermieden werden.

Wenn "interval = 2000" ist, und wenn "lastEventTime[i] = now" bei einem 
millis() Stand von z.B. (2^32 - 1000) gesetzt wird, dann wird beim 
nächsten Schleifendurchlauf in der Abfrage "if (now < lastEventTime + 
interval)" die Summe von "lastEventTime[i] + interval" =1000 ergeben. Da 
aber millis() noch keinen Überlauf hatte, wird die Abfrage gleich wieder 
nach "do something here" reinlaufen, obwohl die Zeit noch nicht 
abgelaufen ist.

Also Zeitspannen immer über eine Differenz abprüfen:

if (now - lastEventTime[i] >= interval)

von Peter D. (peda)


Lesenswert?

T1waz T. schrieb:
> W.S. schrieb:
>> Aber warum scheiterst du an so etwas einfachem?
>
> Gute Frage, darum habe ich hier gepostet.

Also keine gute Frage von W.S.

T1waz T. schrieb:
> Was Event Warteschlangen sind, bzw. wie ich sowas implementiere in meine
> Anforderung, weiß ich leider nicht.

Brauchst Du auch nicht zu wissen. Man muß nicht alles unnötig 
verkomplizieren.
Warteschlangen braucht man erst, wenn temporär mehr Ereignisse 
eintreffen, als gerade behandelt werden können. Z.B. ne GUI ist gerade 
damit ausgelastet, nervtötende Animationen auf das GLCD zu malen.

von T1waz T. (t1waz_t)


Lesenswert?

ich danke euch für die Mühen.

Sobald ich Zeit habe, werde ich mich dem Problem weiter widmen,
anschließend hoffe ich, ich kann meine Lösung präsentieren.

Nochmals zu der Frage mit Simulation /Debugging, gibts da irgendwas?

von Thomas W. (goaty)


Lesenswert?

Kommt auf die Hardware drauf an. Beim Arduino wohl nur printf...

von Jens M. (schuchkleisser)


Lesenswert?

Arduino ist für easy use, da gibt es sowas nicht.
Unter gewissen Umständen kannst du einfach hier und da via print Werte 
ausgeben, aber das kann z.B. timings versauen und braucht RAM.

von T1waz T. (t1waz_t)


Lesenswert?

Hab jetzt eine Kombi aus dem von MinMax und Thomas W.,
vielen Dank, scheint genau zu machen, was ich will.
1
if (now - lastEventTime[i] <= interval)
hier nur in kleiner Zeichen umgedreht.

1
void loop() {
2
  for (int i=0; i<4; i++){
3
    currentMillis = millis() + interval;
4
    input =  digitalRead(sensorPin[i]);
5
   
6
    if (input != lastInput[i]) {
7
        lastInput[i] = input;
8
        
9
        if (currentMillis - previousMillis[i] <= interval){
10
         Serial.println(" blocked:" );
11
         Serial.println(input);
12
         digitalWrite(counterPin, LOW);
13
         delay(1);
14
        }
15
        else {
16
          previousMillis[i] = currentMillis;
17
          // do something here
18
          Serial.println("counting:" );
19
          Serial.println(input);
20
          digitalWrite(counterPin, HIGH);
21
        }
22
      }
23
24
  }   
25
26
}

das
1
currentMillis = millis() + interval
habe ich gesetzt, dass nach dem einschalten sofort einmal gezählt werden 
kann,somit ist das quasi als Startwert definiert.

Bei irgendwelchen Anmerkungen oder Kritik gerne nochmals posten.

Ansonsten bedanke ich mich für die fast durchgehende sinnvolle Kritik.

Gruß
Roman

von T1waz T. (t1waz_t)


Lesenswert?

war leider etwas vorschnell, da mein externer Zähler das nicht packt.
Möglicherweise das Signal zu kurz oder die Zeit dazwischen zu klein.

daher folgende Lösung
1
void loop() {
2
  for (int i = 0; i < 4; i++) {
3
    currentMillis[i] = millis() + interval;
4
    input[i] =  digitalRead(sensorPin[i]);
5
    pulseDuration = millis();
6
7
    //Zeit zwischen zwei Signalen, bevor Pin auf High gesetzt werden kann
8
    currentMillisCounter = millis();
9
    if (counter != counterOld) {
10
      if (currentMillisCounter - previousMillisCounter <= intervalCounter + pulseInterval) {
11
        //do nothing
12
      } else {
13
        previousMillisCounter = currentMillisCounter;
14
        counterOld = counterOld + 1;
15
        digitalWrite(counterPin, HIGH);
16
        pulseDurationOld = pulseDuration;
17
      }
18
    }
19
    
20
   //Länge des Signals, danach der Pin auf Low gesetzt
21
  if (pulseDuration - pulseDurationOld <= pulseInterval) {
22
    //do Nothing
23
  }else{
24
    digitalWrite(counterPin, LOW);
25
  }
26
27
  //Flankenauswertung der Lichtschranken, erhöht einen internen Zähler 
28
    if (input[i] != lastInput[i]) {
29
      lastInput[i] = input[i];
30
31
      if ((currentMillis[i] - previousMillis[i] <= interval)) {
32
        //do nonthing
33
      }
34
      else {
35
        if (input[i] == HIGH){
36
          previousMillis[i] = currentMillis[i];
37
          // do something here
38
          counter = counter + 1;
39
        }
40
      }
41
    }
42
43
  }
44
45
}

: Bearbeitet durch User
von T1waz T. (t1waz_t)


Lesenswert?

ich denke, Thread kann zu

von W.S. (Gast)


Angehängte Dateien:

Lesenswert?

K. S. schrieb:
> seid wann hat Arduino events?

"seit".

Und:
Wenn man es will, hat es jeder µC.

Das Prinzip ist ausgesprochen einfach: Es gibt ne Event-Warteschlange 
(ein simper FIFO bzw. Ringpuffer) und einige Funktionen zum Füllen, 
Leeren, Abfragen und für Sonstiges. Gefüllt wird diese Warteschlange von 
diversen Quellen: Interrupts und sonstigen Routinen. Geleert wird sie in 
der Grundschleife, die ja zumeist in main() angesiedelt ist. Und für 
verzögerte Events (eben das, was hier gebraucht wird) braucht man noch 
irgend ein Uhrprogramm im µC, also nen Interrupt, der so alle 1..10 ms 
eine ISR aktiviert.

Siehe angehängtes Zeug. Ich hatte das wohl schon mal gepostet und 
entsinne mich, daß sich da Leute drüber aufgeregt hatten, daß ich beim 
SysTick auch eine simple Wartefunktion vorgehalten habe. Sowas ist 
nützlich für den System-Anlauf, wenn wesentliche Teile der Firmware noch 
nicht laufen, weil sie ja erstmal aufgesetzt werden müssen. Für später 
baucht man sowas dann nichr mehr.

So, mal ein Beispiel:
1
#define Totzeit  1000  // Millisekunden
2
3
#define Schranke_1  1  
4
#define Tot_1       101
5
6
// und für weitere Schranken:
7
#define Schranke_2  2
8
#define Tot_2       102
9
10
#define Schranke_3  3
11
#define Tot_3       103
12
// usw.
13
14
// Interrupt-Handler für Lichtschranke 1:
15
 if (!Exists_Delayed_Event(Tot_1))
16
 { Add_Event(Schranke_1);
17
   Add_Delayed_Event(Totzeit,Tot_1);
18
 }

So, das reicht. Ist doch ein recht kurzes Stück Quelle.
Jedesmal, wenn die Lichtschranke anspricht, wird getestet, ob die 
Totzeit schon vorüber ist. Wenn ja, dann wird ein Event "Schranke_1" in 
die Queue geworfen. Der wird dann in main() gelesen und eine 
entsprechende Aktion ausgeführt (was auch immer). Zusätzlich wird auch 
ein Event "Tot_1" mit einer Verzögerung von 1000 ms erzeugt. Solange der 
noch in der zeitabhängigen Warteschlange ist, wird die schranke 
ignoriert. Auch diesen Event kriegt main() dann ab, wenn seine Zeit um 
ist und kann dann auch darauf reagieren.

Ach ja, das angehängte Zeugs ist für 32 Bit µC's, weswegen die Events 
als 32 Bit Worte definiert sind. Für kleiner µC's kann das problemlos 
zusammengekürzt werden wenn man nur wenige Event-Typen braucht.

Ebenso kann man das Zeitraster von Millisekunden und Zeiten in long auf 
ein gröberes Zeitraster von vielleicht 10 oder 20 ms und Zeiten in int 
zusammenkürzen, was auf 8 Bittern dann weniger Ressourcen braucht.

W.S.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.