Es gibt für den Arduino (bzw. ganz generell) ein Keyboard aus 4 x 4 Tasten, welches es erlaubt, eine gedrückte Taste über eine analoge Spannung zu identifizieren. Das Keyboard beruht auf einer Widerstandsmatrix. Die Versorgungsspannung (3.3 Volt, nicht 5 Volt) wird je nach gedrückter Taste in einem bestimmten Verhältnis geteilt, so dass man beim Abfragen an einem analogen GPIO Werte zwischen 0 (keine Taste gedrückt) und ca. 600 .. 4096 erhält, je nachdem, welche Taste gedrückt wurde.
AUFGABE: Miss diese Spannungen mit einem Voltmeter und erstelle eine entsprechende Tabelle! Markiere die Mitte zwischen den jeweiligen Intervallen und benutze dieser Werte zur Entscheidung darüber, welche Taste gedrückt ist.
Wenn man über ein Case-Statement oder über einen Array diese Wert zur Entscheidung benutzt, sollte es eigentlich ein Leichtes sein, die jeweilige Taste zu identifizieren.
In der Praxis zeigt es sich jedoch, dass dies nur für bestimmte Tasten robust funktioniert, die relativ große Spannungen liefern.
Bei manchen Tasten liegen hingegen die Spannungswerte, die man ermittelt, so nahe beieinander, dass durch die Überlagerung mit Netzbrumm-Anteilen auch schon mal eine falsche Taste ermittelt werden kann. Außerdem könne fremde Elektrogeräte (z.B. Staubsauger) störende Spannungsimpulse in das Netz abgeben, die zu Messfehlern führen.
Man sollte daher einige Stichproben hintereinander durchführen und dann den Mittelwert bilden.
Im Idealfall könnte man z.B. 10 Stichproben im Abstand von 2 msec verwenden, dann würden sich die Wechselspannungsanteile mathematisch bei einer Durchschnittsbildung kompensieren.
Der Nachteil einer solchen Lösung ist jedoch, dass man dazu den Prozessor für 20 msec blockieren würde, nur um die Tastatur abzufragen. Für einen Regelungsprozess kann das schon eine zu große Einschränkung sein.
Besser ist es, innerhalb der Haupt-Verarbeitungsschleife („loop()“) immer nur ganz kurz den jeweilige Analogwert auszulesen und dann entweder sofort oder nach einigen Aufrufen zu entscheiden, ob bzw. wann ein gültiger Tastendruck vorliegt. Zu klären ist auch, wie mit einem anhaltenden Auftreten sehr ähnlicher Werte umgegangen werden soll. Der Prozessor würde sonst möglicherweise sehr viele Tastendrücke derselben Taste pro Sekunde liefern! Ein gewisser statistische Ausgleich wird auch bei diesem Verfahren stattfinden, wenn auch nicht so perfekt wie bei dem obigen Beispiel mit 10 Messungen exakt alle 2 msec. Abgesehen von der Mittelwertbildung muss man auch ein Verfahren implementieren, um Ausreißer zu erkennen und zu eliminieren.
Ein mögliches Vorgehen dazu:
- Liest man einen ziemlich großen Wert, so akzeptiert man ihn sofort und betrachtet die entsprechende Taste als gedrückt.
- Liest man eine Null, so ist definitiv keine Taste gedrückt. Eine eventuell zuvor begonnene Erkennung wird abgebrochen und beim ersten Auftreten eines Wertes, der über 0 liegt, neu gestartet.
- Liest man einen mittleren oder kleinen Wert, so sammelt man mehrere solche Werte nacheinander (die Keyboard-Erkennungsroutine wird bei jedem Loop-Durchgang aufgerufen, also in der Regel sehr, sehr oft). Hat man genügend Werte (z.B. 3, 10 oder 20), so bildet man den Durchschnitt und zieht diesen zur Ermittlung der gedrückten Taste heran.
- Verfeinern läßt sich das Verfahren noch, indem man „Ausreißer“ erkennt (mitunter treten spontane Spannungen auf, die stark abweichen, man nennt sie „Spikes“) und für die Mittelwertbildung ignoriert. Man könnte aber auch nach dem Auftreten eines Spike mit der Durchschnittsbildung von vorn beginnen. Als Spike könnte man z.B. Werte ansehen, die um mehr als 30% des Abstands bis zur nächsten Taste von den bisher empfangenen Werten abweichen.
Beispiel: Drei Tasten (A,B,C) mögen (im Mittel) die Werte 1000, 1100 und 1200 Millivolt liefern. Man empfängt nacheinander die Werte
0, 0, 975, 995, 1010, 800, 1110, 1120, 1144, 0, 0
Die Software verwendet 1050 und 1150 als Grenzen, um zwischen den drei Tasten zu unterscheiden. Außerdem möge sie so programmiert sein, dass sie mindestens drei konsistente Werte (= mit einer Abweichung <=30 mV) benötigt, bevor sie entscheidet, dass ein gültiger Tastendruck vorliegt.
Je nach den Details des Algorithmus können unterschiedliche Ergebnisse entstehen:
- Vergleicht man immer gegenüber dem ERSTEN Wert, der von Null verschieden ist, so wird keine einzige Taste erkannt, weil nur der zweite Wert nahe genug bei 975 liegt. Alle anderen Werte werden ignoriert.
- Vergleicht man mit dem Durchschnitt der bislang passenden Werte, so wird 1010 mit 985 verglichen und weil diese Differenz kleiner als 30 ist, wird Taste A erkannt.
- Allerdings gibt es hier bei stetig steigenden oder fallenden Werten die Gefahr einer „Drift“ des Durchschnitts (wenn man z.B. 10 konsistente Werte erwartet). Es werden dann Werte als konsistent akzeptiert, die für sich alleine betrachtet besser zu einer anderen Taste passen würden.
- Noch stärker wäre der Effekt des „Hineinrutschens in eine andere Taste“, wenn man immer gegenüber dem letzten Wert vergleicht. Verlangt man 5 Werte, so könnte eine Folge wie 975, 1000, 1025, 1050, 1075 als konsistent betrachtet werden, wobei die ersten dei Werte besser zu Taste A passen, der 4. Wert auf der Grenzlinie liegt und der 5. Wert eindeutig besser zu B passen würde. Zieht man den Anfangswert oder den Gesamtdurchschnitt zur Entscheidung heran, dann erhält man A. Verläßt man sich auf den letzten, quasi aktuellsten Wert, erkennt man B. Man kann Beispiele für 10 konsistente Werte konstruieren, bei denen sogar A, B oder C herauskommen können, je nach Algorithmus.
Was soll eigentlich passieren, nachdem man sich entschieden hat, eine Taste als „erkannt“ zu bezeichnen?
- Nehmen wir an, die ersten drei Signalwerte haben zur Erkennung von A geführt und danach wir die 800 gelesen. Soll dieser Wert ab jetzt als neuer Referenzwert verwendet werden? Oder will man warten, bis eine 0 auftritt (= der Benutzer hat die zuvor erkannte Taste losgelassen). Es könnte immerhin sein, dass der Benutzer die erste Taste losgelassen und inzwischen eine andere Taste gedrückt hat, OHNE dass wir dies mitbekommen haben, weil er sehr schnell war und wir in genau dem Moment zwischen den beiden Tastendrücken das Signal nicht abgefragt haben. Oder er ist auf die zweite Taste mit geringfügiger zeitlicher Überlappung gewechselt. Dann würden wir selbst bei extrem schneller Abfrage KEINE 0 sehen, dennoch wäre der Finger aber auf einer anderen Taste!
- Es spricht also einiges dafür, die 800 als den möglichen Beginn einer neuen Taste anzusehen – oder als einen Spike. Interesanterweise passen die folgenden Wert nicht zu der 800. Wir könnten sie natürlich alle ignorieren. Oder wir könnten die 1110 als den möglichen Beginn einer neuen Folge ansehen, insbesondere, weil die folgenden Wert so gut dazu passen. Allerdings kann der Algorithmus nicht vorausschauen. Er müsste sich – wenn er entsprechend schlau sein soll – die 800 und die 1110 merken und dann beim Eintreffen der 1120 entscheiden, dass 1110 oder der Mittelwert aus 1110 und 1120 ab jetzt als Referenzwert für Konsistenzentscheidungen benutzt werden soll. Im letzteren Fall würde beim Eintreffen der 1144 die Taste C erkannt werden, weil 1144 nahe genug bei 1115 liegt.
- Oder der Algorithmus akzeptiert jeden Spike als möglichen Neubeginn. Dann würde aber (bei 5 konsistenten Werten) eine Folge wie 900,900,1000,900,900,900 keinen gültigen Tastendruck liefern. Ob das wirklich ideal ist?
Ganz generell stellt sich außerdem noch die Frage, wie man mit einer länger gedrückten Taste umgehen soll. Man kann den Tastendruck als beendet erklären, nachdem drei passende Signale erkannt wurden. Was soll man tun, wenn direkt danach wieder die selbe Taste erkannt wird? Ein Benutzer kann durchaus 200 msec auf einer Taste mit der Hand liegen bleiben. Bei entsprechend hoher Abfragefrequenz durch die loop() würde man dann möglicherweise 30 oder mehr einzelne Tastendrücke „erkennen“.
Soll man identische Wert grundsätzlich immer ignorieren, egal wier lange sie anhalten? Das wäre auch schade, denn der Benuztzer erwartet möglicherweise ein AUTO-REPEAT Verhalten, wenn er die Taste besonders lang gedrückt hält. Außerdem können wir – wie zuvor erklärt – nicht sicher sein, dass wir eine 0 sehen, wenn der Benutzer gleitend von einer Taste zur anderen wechselt.
Man wird also eine Sperrdauer („Totzeit“) nach dem Erkennen einer Taste vorsehen müssen – die aber nur gilt, solange Werte gemessen werden, die zu der zuletzt erkannten Taste passen. Ist diese initiale Sperrzeit abgelaufen, sollte immer wieder ein Tastendruck „erkannt“ werden und eine zweite, kürzere Sperrzeit immer wieder neu gestartet werden.
Wenn man beispielsweise eine Taste hat, die einzelne Motorschritte auslöst, dann wäre ein solches Verhalten extrem wünschenswert. Man würde vielleicht sogar erwarten, dass ein Art Beschleuinigung eintritt, je länger man die Taste gedrückt hält, d.h. die zweite Sperrzeit müsste (bis zu einer Untergrenze) immer weiter verkürzt werden mit jedem weiteren Tastenereignis, das man erkennt.
AUFGABE
Schreibe ein Programm, das möglichst robust, mit Tastaturmesswerten umgeht und daraus „Tastendrücke“ erzeugt.
Verwende eine nicht abgeschirmte Leitung, um die Tastatur anzuschließen und platziere die Leitung neben einem Netzteil o.ä., um Störungen einzufangen.
Verwende in der loop variierende Delays (z.b. Zufallswerte zwischen 1 und 10 msec) um zu simulieren, dass andere Aufgaben den Prozessor unterschiedlich lange beschäftigen, bevor er wieder dazu kommt, die Tastatur abzufragen. Schreibe alle Spannungswerte des Tastatur GPIO Pins auf die serielle Schnittstelle und gib dort auch aus, wann und wie oft du einen (welchen) Tastendruck identifizierst.
Benutze zwei Tasten, um Einzelschritte des Motors auszulösen.
Setze die Verzögerung in der loop() auf konstant 1 msec und programmiere nun die Tastatur so, dass eine Repeat-Funktion mit Beschleunigung ausgeführt wird, wenn Tasten auf dem Keypad länger gedrückt gehalten werden.
Zeige, dass du auf diese Weise den Motor einigermaßen „ergonomisch“ mit den beiden Tasten zu einer bestimmten Wunschposition hin bewegen kannst. Passe die Zeitkonstanten so an, dass es sich „gut anfühlt“.