Ich komm mal wieder nicht klar mit eigentlich einfachsten Sachen. Ich
habe mir mal eine Funktion geschrieben zum Entprellen von einem GPIO.
Nun wollte ich die für was anderes übernehmen, hat sich aber als
ungeeignet erwiesen.
Ein Problem was mir vorher nie aufgefallen war, es funktioniert nur für
einen einzigen GPIO global, man darf die Funktion nicht mit
verschiedenen GPIO im Code nutzen.
Zweitens sind es diesmal keine GPIO sondern bereits entprellte Software
Buttons. Jetzt hab ich mir gedacht ich jage das durch ChatGPT, soll ja
mittlerweile ganz brauchbar sein. Es hat vorgeschlagen die Buttons zu
tracken, dazu sollte ich für jeden Funktionsaufruf einen Index als
zweites Argument mitgeben. Das wollte ich nicht, stattdessen soll es ein
Zeiger sein, anhand dessen Adresse eine eindeutige Identifizierung
möglich ist.
Nun stehen die Buttons leider in einem Struct. Also erstmal neue globale
Variablen angelegt und somit Redundanz - egal sei's drum.
Herausgekommen ist nun sowas. Es ist leider zu kompliziert und ich
verstehs nicht.
1
intget_keyevent(bool*key){
2
structKeyState{
3
bool*ptr;
4
unsignedlongpressStart;
5
boolactive;
6
boolreportedShort;
7
boolreportedLong;
8
};
9
10
staticKeyStatestates[10];
11
staticintcount=0;
12
13
constunsignedlongtMin=50;// ms
14
constunsignedlongtGap=1000;// ms
15
16
unsignedlongnow=millis();
17
18
// Lookup or register
19
intindex=-1;
20
for(inti=0;i<count;++i){
21
if(states[i].ptr==key){
22
index=i;
23
break;
24
}
25
}
26
if(index==-1&&count<10){
27
states[count]={key,0,false,false,false};
28
index=count++;
29
}
30
if(index==-1)return0;
31
32
KeyState&s=states[index];
33
34
// Rising edge: start tracking
35
if(*key&&!s.active){
36
s.active=true;
37
s.pressStart=now;
38
s.reportedShort=false;
39
s.reportedLong=false;
40
}
41
42
// If still held
43
if(*key&&s.active){
44
unsignedlongtHeld=now-s.pressStart;
45
46
if(tHeld>=tMin&&tHeld<tGap&&!s.reportedShort){
47
s.reportedShort=true;
48
return1;// short press
49
}
50
51
if(tHeld>=tGap&&!s.reportedLong){
52
s.reportedLong=true;
53
return2;// long press
54
}
55
}
56
57
// Reset after release
58
if(!*key&&s.active){
59
s.active=false;
60
}
61
62
return0;
63
}
64
// usage
65
voidloop(){
66
intevent=get_keyevent(&softKey);
67
if(event==1){
68
// short press
69
}elseif(event==2){
70
// long press
71
}
72
}
Es kommen aber noch Anforderungen hinzu. Einerseits soll sich der
Software-Button wie jede Taste einer PC-Tastatur anfühlen, also sofort
anschlagen als Single und nach kurzer Verzögerung als Longpress.
Zusätzlich soll aber eine "feststeckende" Taste erkannt und automatisch
ignoriert werden. Nach Ablauf der Totzeit soll sie wieder erlaubt sein.
Was den Singlepress angeht, so soll dieser erst nach 50 ms gültig sein.
Schnelles tippen soll aber ebenfalls erlaubt sein. Keine Ahnung wie das
mit dem Longpress vereinbar ist. Ich möchte später ein Tastenpaar
auswerten; eine Taste wird gedrückt gehalten, währendessen die andere
mehrmals getippt.
Einige Anforderungen widersprechen sich. Ich habe nun so viel gelesen
und einen Knoten im Kopf.
Michael B. schrieb:> Aber die Fähigkeit der Leser sinkt, heute muss man wohl Kindersprech in> Teletubbisvideos für ein nicht weiter erklärtes 'shield', dass die> Internas versteckt, anbieten damit man die Leser auf Legoniveau nicht> überfordert.
Gibt's eine Arduino Library die das alles kann? Muss aber für
Software-Buttons sein, nicht für GPIO
Also brauche ich mehrere Funktionen für verschiedene Tastenabfragen.
Mich beschlich schon so ein Verdacht.
Hab mir beides kurz angesehen und bin noch mehr verwirrt. Es hilft wohl
nichts, werde wohl wieder bei Null anfangen und das Rad neu erfinden.
Alexander schrieb:> Also brauche ich mehrere Funktionen für verschiedene Tastenabfragen.
Nein, Du brauchst mehrere Zustandsvariablen für mehrere Tastenabfrage.
Der Code bleibt der gleiche - vorausgesetzt, er kann mit mehreren
Zustandsvariablen umgehen.
Peter Daneggers Code ist hier ziemlich elegant, denn er kommt für jede
Taste nur mit wenigen zusätzlichen Bits aus. Da Du keinen Timer benutzt
und immer den Timstamp pollst, benötigst Du mindestens 3 x 32 bit pro
Taste. Das ist nicht nur Speicherplatzverschwendung, sondern macht den
Code für mehrere Tasten dann auch wesentlich ineffizienter - jedenfalls
für 8-Bit-AVRs.
Siehe auch Entprellung.
Mit dem pollen hab ich auch meine Probleme. Momentan verpasse ich manche
Tastendrücke. Ganz blöd wird es wenn ich das Loslassen verpasse. Der AVR
Code mag elegant sein, aber die Sprache ist mir zu low. Ich brauch was
fertiges für Arduino. Es läuft auf einem ESP32 Ressourcen sind
zweitrangig.
Frank M. schrieb:> Peter Daneggers Code ist hier ziemlich elegant
+1
Frank M. schrieb:> Da Du keinen Timer benutzt
außerdem
"Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang"
Alexander schrieb:> Mit dem pollen hab ich auch meine Probleme.
Falls jemand den thread zu lange blockiert, wird pollen natürlich nie
robust funktionieren. Auf Arduino wo die Leute irgendwie dazu tendieren
sleep 100ms zu machen, da kann ja vieles dann nicht mehr richtig laufen.
Vermutlich hast du irgendwo in einer library ein paar funktionen die zu
lange blockieren.
Alexander schrieb:> Der AVR> Code mag elegant sein, aber die Sprache ist mir zu low.
Viele Codezeilen kann man damit natürlich nicht schinden, so daß es
möglichst unübersichtlich und sophisticated ausschaut.
Der Thread ist recht lang geworden und es sind auch ne Menge Anwender
darunter, die ihn problemlos auf 32Bit Boliden portiert haben.
Definiert man die Variablen als uint64_t, kann man bis zu 64 Tasten
bearbeiten (eine Taste je Bit). Die jeweilige Taste wird bequem per
Bitmaske adressiert, aufwendige Pointerarithmetic entfällt. Die
entsprechenden Eventfunktionen gelten immer für alle Tasten. Man kann
somit auch ein Event für mehrere Tasten zusammen in einem Aufruf
löschen.
Alexander schrieb:> Was hat's mit den ALL_KEYS auf sich, ist das ne Maske oder ne> Abfrage?
Steht doch in debounce.h. Das ist eine Maske für alle abzufragenden
Tasten. Im Beispiel sind es drei Tasten, die parallel abgefragt werden:
Peter D. schrieb:> Gute Frage, ich sehe nirgends eine Benutzung.
Stimmt auch wieder. Letztendlich sind die benutzten Taster (hier 3)
durch die defines erklärt:
1
#define KEY0_IN 8
2
#define KEY1_IN 9
3
#define KEY2_IN 10
Offenbar wollte Falk hier ursprünglich den Weg über die Maske
beschreiten. Egal, der Code ist genau das, was der TO will.
Hallo,
@ TO:
Wenn du wirklich etwas sauberes programmieren möchtest, dann schreibe
dir eine Klasse. Hast ja schließlich Arduino und kannst C++ verwenden.
Damit haste dann saubere Datenkapselung, saubere Methodenaufrufe usw..
Die ganzen Unsicherheiten fallen weg. Verwende millis() für zeitlich
gesteuerte Abfragen und lass die Timer in Ruhe. Du hast keinerlei
Begrenzung für I/Os. Am Ende erstellst du für jeden Pin eine Instanz
usw.
Ich würde das jedoch noch trennen. Ein Klasse für die Pin Steuerung.
Eingang oder Ausgang und was man damit machen kann. Und eine Klasse zum
Entprellen. In der Entprellklasse verwendest du die Pin Klasse. Alles
ist und bleibt gekapselt, nichts kommt sich in die Quere. Es sind alle
Funktionen/Methoden die Arduino bietet ausreichend.
gehören nicht in die loop.
Das gehört alles in die Klasse und wird intern abgehandelt.
Und übergebe der Instanz die Pinnummer als Parameter.
Stichwort Konstruktor.
Wozu soll man eine Pinnummer woanders angeben wenn diese fest zur
Instanz gehört. Einmal angeben und dann vergessen.
Am Ende sieht so aus
1
keyA.update();
2
keyB.update();
Die update Methode kümmert sich um alles was nötig ist intern.
Datenkapselung!
Und du fragst nur noch ab ob ein Ereignis Pressed, Released usw.
stattgefunden hat.
Wenn es noch mehr Instanzen werden sollte, kannste das mittels Array und
for Schleife erschlagen.
Desweiteren fehlt noch eine
1
init()
Methode in der bspw. für Eingänge Pullups aktiv werden.
Das alles ist weiter ausbaufähig damit man Methoden für Ausgänge
betreffend nicht ausversehen für Eingänge verwenden kann. Das ist eine
schöne Spielweise um sich das Verständnis für Klassen anzueignen. Wenn
du das dann noch in eine eigene Bibliothek auslagerst, kannste das immer
wieder verwenden.
Achte darauf das es keine Multikulti Klasse wird.
Alexander schrieb:> Vermutlich hab ich nur ein polling Problem.
Nö, das ist doch der gleiche Schrunz, wie oben schon.
Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen.
Dadurch geht kein Tastendruck verloren. Der 1ms Systick ist doch wie
dazu gemacht, dort den Interrupttask mit aufzurufen.
Und das Main fragt einfach nur die Erreignisse ab und muß sich um keine
Zeitbedingungen mehr kümmern.
Das kann man auch schön CPP-style in eine Klasse schreiben:
getkey.press
getkey.rpt
getkey.state
getkey.short
getkey.long
Danke Veit,
jetzt hab ich auch mal den Nutzen von Klassen verstanden. Nutze die zwar
aus allen möglichen Libs aber denke selbst nur in C Funktionen, von
allein wäre ich da nicht drauf gekommen. Ich könnte das jetzt noch
weiter ausbauen, aber ich brauche es gar nicht.
Der Knoten im Kopf hat sich gelöst. Ich war die ganze Zeit auf das
Entprellen von einzelnen Tasten aus, aber was ich wirklich brauche ist
das Entprellen von Tastenkombinationen! Ich habe immer die Rising Edge
als Start für die Entprellung genommen, und hatte immer Probleme die
Tasten zu erwischen. Aber die Tasten interessieren mich ja gar nicht.
Ich werde die Tasten nun verUNDen und die Tastenkombination dann wie
eine einzelne Taste behandeln und entprellen. Mal sehen ob ChatGPT da
eine Klasse dafür zaubern kann, ansonsten mach ich es mit der Funktion
aus dem OP.
Peter D. schrieb:> Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen.> Dadurch geht kein Tastendruck verloren. Der 1ms Systick ist doch wie> dazu gemacht, dort den Interrupttask mit aufzurufen.
Ein Hinweis. Mit Arduino, was der TO verwendet, muss man sich keinen
zusätzlichen 1ms Systemtick bauen. Tasterabfragen benötigen auch kein
1ms Intervall. Wenn man sich mittels millis() ein 20 bis 40ms
"Abfragetimer" programmiert, reicht das für Taster aus. Meinetwegen noch
als 2. Parameter für die Instanzen.
> Und das Main fragt einfach nur die Ereignisse ab und muß sich um keine> Zeitbedingungen mehr kümmern.> Das kann man auch schön CPP-style in eine Klasse schreiben:> getkey.press> getkey.rpt> getkey.state> getkey.short> getkey.long
Hier stimme ich zu. Das hat er schon angefangen so zu machen.
Peter D. schrieb:> Ich habe mir schon was dabei gedacht, einen Interrupt zu benutzen.> Dadurch geht kein Tastendruck verloren.
Den Wrapper von Falk könnte ich problemlos übernehmen, ich hab schon
einen xTaskCreatePinnedToCore wo ich es dazu packen könnte. Aber meine
Tasten sind im Prinzip schon entprellt und kommen mit Interrupt rein.
Alexander schrieb:> Ich war die ganze Zeit auf das> Entprellen von einzelnen Tasten aus, aber was ich wirklich brauche ist> das Entprellen von Tastenkombinationen!
Was meinst Du damit, ein Tresorschloß?
Aber auch da muß man die Tasten jede für sich erkennen und dann eben mit
den Code-Array vergleichen.
Nein das wäre eine Zahlenkombination 1234. Eine Tastenkombination ist
z.B. Strg + Alt + Entf. Wenn man immer nur die steigende Flanke der
einzelnen Tasten auswertet ist es schwer die gleichzeitig zu treffen.
Das ist recht einfach. Die Steuertaste[n] müssen gedrückt gehalten
werden und werden erst ausgewertet, sobald die Flanke der Bedientaste
geliefert wird.
Dafür sind in dem langen Thread auch Beispiele. Und auch Beispiele für
"gleichzeitiges" Drücken.
Alexander schrieb:> Ich werde die Tasten nun verUNDen und die Tastenkombination dann wie> eine einzelne Taste behandeln und entprellen.
Funktioniert jetzt.