Forum: Mikrocontroller und Digitale Elektronik Zuverlässig auf Inputs (Tastendruck) reagieren


von Christoph K. (christophkind)


Lesenswert?

Halo Zusammen,

ich hoffe ich verfasse diesen Beitrag im richtigen Forum.

Mein Problem ist folgendes:
Ich habe in meiner Arduino-Loop-Routine sehr viel Code, der zur 
Ausführung mehrere Sekunden brauchen kann (weil er auf Ergebnisse von 
externen Komponenten wartet). Zur Zeit löse ich das so, dass ich mit 
delay(n) warte, bis das Ergebnis da ist (ich gucke z.B. alle 100ms nach) 
bzw. nach einer bestimmten Zeit (timeout) einen Fehler zurück gebe.

Das hat für sich allein relativ gut funktioniert, nun habe ich jedoch 
das Problem, dass ich auf Inputs reagieren möchte (in meinem Fall auf 
das Klicken eines Tasters. Dieser Taster soll, je nach dem, wie er 
geklickt wird (Klick, Doppelklick, Dreifachklick) verschiedene Aktionen 
aufrufen.

Meine Frage ist nun, wie ich während mein langer Loop ausgeführt wird 
zuverlässig auf den Schalter reagieren kann?

Mein erster Ansatz ist, dass ich delay(n) durch delayAndHandleInputs(n) 
wrappe und die Wartezeit stückle und ab und an nach den Status des 
Schalter gucke. Diesen Weg finde ich jedoch sehr "gebastelt", daher 
wollte ich euch fragen, ob es zu solchen Dingen eine "Best practise" 
gibt.

Vielen Dank

von Till (Gast)


Lesenswert?

In der Tat hast Du bei Deinem Programmablauf wohl sehr geschludert.
Ein Redisign wäre wohl nötig und du solltest dich einmal unbedingt mit 
dem Thema "Interrupts" auseinandersetzen.
Und zwar erst einmal auf dem Level der Hardware , also Atmel Manual 
lesen!
Arduino kann zu schnellen Ergebnissen führen obgleich man nicht viel 
Anhnung von dem haben muss was wirklich unter der Haube schlummert, 
jetzt rächt sich das.

Wenn du den sourcecode hier nicht offen legst wird dir auch niemand 
helfen wollen und können.

Ggf. kann man da noch was retten, über Interrupts ;-)

Till

von Christoph K. (christophkind)


Lesenswert?

Also ich hab sehr lange auch mit ATMEL auf unterer Ebene gearbeitet, 
Interrupts sind mir durchaus ein Begriff :-D , aber mit Interrupts auf 
Tastendrücke zu reagieren scheint mir hier nicht das richtige Rezept.

Ich sehe hierbei auch nicht den Knackpunkt am Arduino, sondern eher 
darin, dass es kein wirkliches Multi-Threading auf den Mikrocontrollern 
gibt.


Hier mal ein Pseudo-Code:


void loop()
{
   int answer = PowerExternalDeviceOn(); // Dauert ca. 3 Sekunden
   int value = GetValueFromExternalDevice(); // Dauert ca. 2 Sekunden
}


int PowerExternalDeviceOn()
{
    int start = millis();
    while(GetCurrentStateOfExternalDevice() == LOW)
    {
        delay(500);

        if(millis() > start + 3000)
           return -1;
    }

    return 1;
}

von Rowland (Gast)


Lesenswert?

Mit Pin-Interrupts auf Tastendrücke zu reagieren ist sowieso nicht 
empfehlenswert. Die Lösung heißt Timerinterrupt. Mittels diesen einfach 
den Taster zyklisch (z.B. 10ms, 20ms) abfragen. So lässt sich dieser 
auch recht unkomlpiziert entprellen.

von Till (Gast)


Lesenswert?

Multithreading, mein Gott.... baust Du ne Raketensteuerung?

Ob Du über Pininterrupts oder Timerinterrupts gehst ist völlig egal.
Auf alle Fälle Interrupts.

Dein Code-Konzept ist schlicht und ergreifend Kappes.
Du bist überfordert.

von Frank M. (frank_m35)


Lesenswert?

Hier mal ein alternativer Ansatz, der 'Multithreading' erlaubt :-D, da 
der Code nicht blockierend arbeitet.
1
typedef enum
2
{
3
 STATE_POWERON = 0,
4
 STATE_GETVALUE,
5
 STATE_ERROR
6
} PROGRAM_STATES;
7
8
PROGRAM_STATES currentState = STATE_POWERON;
9
10
volatile DWORD elapsedTime = 0;
11
12
void loop()
13
{
14
  switch currentState
15
  {
16
  case STATE_POWERON:
17
    // check for 1 second max.
18
    if (elapsedTime > 1000) {
19
      currentState = STATE_ERROR;
20
      break;
21
    }
22
23
    // if not ready, don't advance
24
    if (GetCurrentStateOfExternalDevice() == LOW) 
25
      break;
26
27
    // initiate next state
28
    elapsedTime = 0;
29
    currentState = STATE_GETVALUE;
30
    break;
31
32
  case STATE_GETVALUE:
33
    if (elapsedTime > 500) {
34
      currentState = STATE_ERROR;
35
      break;
36
    }
37
38
    if (!GetValueFromExternalDevice(&bValue)
39
      break;
40
41
    elapsedTime = 0;
42
    currentState = STATE_POWERON;
43
    break;
44
45
  case STATE_ERROR:
46
    while (1) {
47
      Nop();
48
    }
49
  }
50
51
  // Sonstige Auswertungen, bspw. Taster etc.
52
  ...
53
}
54
55
void TimerInterrupt1ms()
56
{
57
 elapsedTime++;  
58
59
 // Tasterabfrage etc
60
}

von Peter D. (peda)


Lesenswert?

Christoph Kind schrieb:
> aber mit Interrupts auf
> Tastendrücke zu reagieren scheint mir hier nicht das richtige Rezept.

Die eierlegende Wollmilchsau ist schon ein Interrupt, nämlich der 
Timerinterrupt.
Damit bist Du die Delays in der Mainloop los und der Interrupt speichert 
die Drücke, bis die Mainloop sie abholt.

Ein bewährte Routine steht in der Codesammlung und im Tutorial.

von Till (Gast)


Lesenswert?

@Peter,

wie eng willst Du denn die Timerinterrupts folgen lassen um Tastendrücke 
zu detektieren?
Wie verhält sich das dann mit Entprellung, wird die dann überflüssig ?

von MaWin (Gast)


Lesenswert?

Till schrieb:
> Ob Du über Pininterrupts oder Timerinterrupts gehst ist völlig egal.

Na ja, die eine Methode funktioniert (Entprellung per Timer-Interrupt), 
die andere nicht (Pininterrupt) also macht das schon einen Unterschied.
Und wenn man 2 Aufgaben (Reaktion auf Tastendrücke während einer langen 
Berechnung) quasi simultan bearbeitet ist das Multitasking, egal wie es 
realisiert ist (preemtive mit Interrupts oder kooperierend)

von Peter D. (peda)


Lesenswert?

Till schrieb:
> wie eng willst Du denn die Timerinterrupts folgen lassen um Tastendrücke
> zu detektieren?

In meiner Praxis haben sich 10ms als optimal erwiesen.
Das ergibt eine CPU-Last von etwa:
1 / (16MHz * 10ms / 50) =  0,03%
Dem Interupthandler sollten 50 Zyklen reichen.

Der genaue Wert ist aber unkritisch, 2 .. 50ms sind o.k.

Till schrieb:
> Wie verhält sich das dann mit Entprellung, wird die dann überflüssig ?

Der Witz daran, der Timerinterrupt macht das ja mit.
Er macht insgesamt folgendes (für einen Port):
- Entprellung
- Flankenerkennung
- Speicherung
- optional Auto-Repeat oder kurz/lang Erkennung

von Till (Gast)


Lesenswert?

Peter Dannegger schrieb:

> Er macht insgesamt folgendes (für einen Port):
> - Entprellung
> - Flankenerkennung
> - Speicherung
> - optional Auto-Repeat oder kurz/lang Erkennung

Peter,
das verstehe ich nicht so recht, oder besser gar nicht ;-)

Wieso entprellt der ?
Wie kann der Timerinterrupts die Flanke an einem Port erkennen?
Speicherung?

Ich würde das gern verstehen, weil es ja so viele Probleme löst.

Wieso hast Du dann so viel Zeit in Deine Entprellroutinen gesteckt ?

Bin auf die Antworten gespannt !

von m.n. (Gast)


Lesenswert?

MaWin schrieb:
> Na ja, die eine Methode funktioniert (Entprellung per Timer-Interrupt),
> die andere nicht (Pininterrupt)

Pin-Interrupt funktioniert auch. Man muß es nur begreifen und machen.
Ignoranz ist dabei kein guter Lehrmeister.

von Christoph K. (christophkind)


Lesenswert?

@Frank M
Deine Methode gefällt mir sehr gut, ist nur etwas umständlich zu 
Programmieren und erinnert mich sehr an SpagettiCode :-D

Mir gefällt dieser Ansatz für mein kleines überschaubares Projekte 
jedoch sehr gut. Hat dieses Pattern einen Namen?

Vielen Dank für eure Hilfe

von Frank M. (frank_m35)


Lesenswert?

Christoph Kind schrieb:
> @Frank M
> Deine Methode gefällt mir sehr gut, ist nur etwas umständlich zu
> Programmieren und erinnert mich sehr an SpagettiCode :-D
>
> Mir gefällt dieser Ansatz für mein kleines überschaubares Projekte
> jedoch sehr gut. Hat dieses Pattern einen Namen?
>
> Vielen Dank für eure Hilfe

Statemachine:
http://www.mikrocontroller.net/articles/Statemachine

Etwas ungünstig ist, dass im vorherigen Zustand der nächste Zustand 
definiert werden muss und die Variablen initialisiert werden müssen. 
Ansonsten hält so eine Statemachine den Code, wenn man sich an die Idee 
gewöhnt hat, extrem übersichtlich, da man leicht den Programmablauf 
nachvollziehen kann, der ja meist nicht linear ist. Und sie blockiert 
eben nicht, was eine Vorraussetzung für viele zukünftige Funktionen und 
ein flüssiges Programm ist.


Im Code habe ich einen Timer mit 1ms exemplarisch angegeben, empfehlen 
würde ich aber, wie auch von Peter schon gesagt, 10ms, da es den 
Overhead reduziert, vor allem wenn du noch mehr Funktionen später 
hinzufügst, und sehr gut ausreicht.


Welches Gerät liest du denn aus?
Viele ICs haben einen Interrupt pin, den sie high setzen, wenn die Daten 
bereit sind. Den könntest du noch hinzu verwenden um genau dann zu 
lesen, wenn die Daten bereit sind, erspart dir das Pollen.

von MaWin (Gast)


Lesenswert?

m.n. schrieb:
> Pin-Interrupt funktioniert auch.

Nein.

> Man muß es nur begreifen und machen.

Begriffen habe ich, dass nach einem Pin-Interrupt mindestens die 
Prellzeit lang, also 5ms, keine Interrupts von diesem Pin sangenommen 
werden dürfen, man also diese Zeit aktiv warten muss oder per 
Timer-Interrupt abwarten muss. Wenn man eh einen Timer braucht kann man 
sich den Pin Interrupt gleich sparen, der Code wird kürzer und braucht 
weniger Resourcen.

> Ignoranz ist dabei kein guter Lehrmeister.

Du darfst davon ausgehen, daß meine Beiträge ein guter Lehrmeister sind 
und du sie nicht ignorieren solltest.

Auch wenn du erst mal nichts begreifst weil du in deinem Leben zu wenig 
gemacht hast.

von B. S. (bestucki)


Lesenswert?

Till schrieb:
> Wieso entprellt der ?
Zeichne dir ein beliebiges Eingangssignal inkl. Prellen von einigen 
wenigen ms auf ein Stück Papier. Nun legst du ein Raster von z.B. 10ms 
über dein Signal. Das sind genau die Punkte, die du abtastet, alles 
dazwischen interessiert dich nicht. Aus deinen Punkten kannst du nun ein 
neues Signal zeichnen und wie du feststellen wirst, ist das Prellen weg 
(hängt von deinem Raster ab). Vergrössere oder verkleinere das Raster 
nun, schiebe es nach links oder rechts und beobachte was passiert.

EDIT:
Damit tust du nichts anderes, als ein zeitkontinuierliches, 
wertdiskretes Signal in ein zeit- und wertdiskretes Signal umzuwandeln. 
Siehe:
http://de.wikipedia.org/wiki/Digitalsignal

> Wie kann der Timerinterrupts die Flanke an einem Port erkennen?
Dazu benötigst du den aktuellen Zustand des Signals und den Zustand der 
letzten Abtastung. Nun vergleichst du die beiden Zustände miteinander, 
sind diese nicht gleich, hat wohl wer den Taster gedrückt. Nun musst du 
nur noch zwischen einer negativen und positiven Flanke unterscheiden.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Till schrieb:
> das verstehe ich nicht so recht, oder besser gar nicht ;-)

Hast Du Dir den Code mal angesehen?

Beitrag "Universelle Tastenabfrage"

Till schrieb:
> Wieso hast Du dann so viel Zeit in Deine Entprellroutinen gesteckt ?

Habe ich nicht.
Die Hauptidee ist eigentlich nur das parallele Entprellen.
Der Rest ist Standard:
Sind alter Zustand und Taste ungleich, wird gezählt, sonst wird der 
Zähler rückgesetzt.
Nach Zählen bis 4 (d.h. nach 40ms) wird der neue Zustand übernommen.
Gleichzeitig wird geprüft, ob es die Drücken-Flanke war und dann das 
Drück-Bit gesetzt.
Die Mainloop kann nur das Drück-Bit abholen und rücksetzen.

Der Code selbst ist simpel, ausschließlich nur logische Verknüpfungen 
(AND, OR, XOR, NOT) und keine umständlichen Arrayzugriffe und bedingte 
Sprünge.
Den Code dann hinzuschreiben hat vielleicht 10min gedauert.

Der Hauptunterschied zu vermeintlich einfachen Lösungen mit Delay ist, 
daß eine Änderung 4-mal hintereinander anliegen muß, um übernommen zu 
werden. Dadurch hat man eine erheblich sichere Entprellung und nebenbei 
auch eine perfekte Störunterdrückung (bessere EMV).

Die Delaylösung ist leider nicht nur eine Anfängerlösung, sondern wird 
von Profis auch häufig benutzt. Die Folge sind fehlende oder doppelte 
Tastendrücke, mit denen sich die Benutzer vieler Geräte rumärgern 
müssen.

von Peter D. (peda)


Lesenswert?

Hier noch ein Link zu Erklärungen der Funktion.
Um den Anhang zu sehen, muß man angemeldet sein.

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=128650

von Peter D. (peda)


Lesenswert?

Hier noch ein fertiger Entprell-IC, MAX6816:

http://www.maximintegrated.com/datasheet/index.mvp/id/1896

Im Datenblatt, Figure 1. Block Diagram, sieht man genau das gleiche 
Prinzip:
Taktgeber, EXOR, Zähler, Latch.
Sogar die Entprellzeit (typ 40ms) ist die gleiche.
Nur die Flankenauswertung fehlt.

Die ICs sind allerdings sehr teuer. Daher sind wenige Zeilen Code für 
die gleiche Funktion effizienter.

: Bearbeitet durch User
von Till (Gast)


Lesenswert?

Ok, ich verstehe es nun langsam....

Danke, man lernt nie aus !

von Till (Gast)


Lesenswert?

Also das ist wirklich lesenswert.
Erschreckend was so ein Taster alles tut und notwendig dann eine 
entsprechend intelligente Vorgehensweise um dem Herr zu werden :

http://www.ganssle.com/debouncing.htm

Peter, danke für den Link.

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.