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
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
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; }
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.
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.
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 | }
|
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.
@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 ?
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)
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
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 !
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.
@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
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.
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.
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
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.
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
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
Ok, ich verstehe es nun langsam.... Danke, man lernt nie aus !
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.