Hallo zusammen,
ich habe ein 16x2 Display. Angesteuert beschreiben alles funktioniert.
Kein Thema.
Allerdings habe ich mir was überlegt.
Ich habe ein Poti welcher zum scrollen verschiedener Seiten zuständig
ist.
1. Seite: Namen eingeben
OK druecken
Dieser OK Button soll ein Taster sein.
Wenn ich diesen betätige soll er mir den Text löschen der vorher da
stand
das habe ich mit lcd.clear(); gemacht. Dann will ich einen neuen Text
schreiben. Aber so lange der Taster ja noch den Zustand hat, das er
gedrückt wurde, durchläuft er ja ewig die IF-Abfrage. Und cleart jedes
mal mein Display. Ergo es flimmert dann immer wieder.
Einer eine Idee wie ich das elegant umgehen kann?
Danke
Wenn Ihr den Code wollt sagt einfach bescheid...
Ich arbeite mich grade in das Display ein und möchte eine Art Menü
aufbauen.
Du musst unterscheiden zwischen
* einer Aktion die ausgeführt wird, solange eine Taste gedrückt ist
* eine Aktion die einmalig beim Drücken einer Taste ausgeführt wird
Letzters sind genau die komplexeren Codestücke, die man immer wieder
unter Tastenerkennung une Entprellung findet. Denn die Erkennung ist an
und für sich simpel: Wenn der Taster vom Zustand 'nicht gedrückt' in den
zustand 'gedrückt' wechselt, dann wurde die Taste offenbar
niedergedrückt. Das ist nichts anderes als eine Flankenerkennung, die
aus dem Vergleich von vorher und jetzt lebt. Die Schwierigkeit liegt in
der Entprellung eines Tasters.
Da du Ardunio erwähnt hast. Für einen Arduino hab ich nichts vorrätig.
1
uint8_tvorher;
2
3
4
voidloop()
5
{
6
uint8_tjetzt;
7
8
jetzt=digital_read(...);
9
if(jetzt!=vorher){// Irgendetwas ist an der Taste passiert
10
vorher=jetzt;
11
if(jetzt==LOW){// ... sie wurde offenbar niedergedrückt
Toni K. schrieb:> Einer eine Idee wie ich das elegant umgehen kann?
Flankenerkennung programmieren, d.h. nur der Wechsel nach gedrückt löst
die Aktion aus.
Bei vielen Entprellroutinen ist das schon implementiert.
Toni K. schrieb:> Aber so lange der Taster ja noch den Zustand hat, das er> gedrückt wurde, durchläuft er ja ewig die IF-Abfrage. Und cleart jedes> mal mein Display. Ergo es flimmert dann immer wieder.> Einer eine Idee wie ich das elegant umgehen kann?
Sorge einfach dafür, dass er den Zustand nicht mehr hat.
dann schreib doch den neuen Text erst wenn der Taster losgelassen wurde,
und oder merke dir das der Taster gedrückt wurde, setze ein Flag das
erst wieder zurückgesetzt wird wenn der Taster losgelassen wurde, denn
solange das Flag nicht steht braucht man nur einmal in die Löschroutine
hüpfen und erst dann in den neuen Text wenn der Taster losgelassen wurde
und das Flag nach löschen zurückgesetzt wurde.
Nur eine von vielen Möglichkeiten :
Toni K. schrieb:> das habe ich mit lcd.clear(); gemacht.
Dann kannst du dir eine Flagge setzen a là 'DisplayIsClear'
Toni K. schrieb:> Und cleart jedes> mal mein Display.
Das überspringst du, wenn die Flagge 'DisplayIsClear' gesetzt ist.
Alledings gibt es viele Wege.
Hier ist es evtl. sogar besser, die Tastenabfrage umzugestalten derart,
das du nur Tastenänderungen auswertest und damit Flaggen setzt. Eine
Tastenentprellroutine wie diese hier ist da recht komfortabel:
https://www.mikrocontroller.net/articles/Entprellung
Ok, um das prellen zu vermeiden, müsste ich wahrscheinlich eine Delay
einbauen.
Oder einen Extra Prellfreien Taster kaufen...
Eine kurzes Delay stört mich allerdings nicht.
ich möchte den Taster nur ein einziges mal zur Bestätigung Betätigen ;-)
(hört sich gut an)
Ich habe es mir so gedacht :
1
#include <LiquidCrystal.h>
2
3
// initialize library with the numbers of the interface pins:
4
// (RS, Enable, DB4, DB5, DB6, DB7)
5
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
6
int ok = 2;
7
int seite;
8
int poti;
9
const int sensorMin = 0; // sensor minimum, discovered through experiment
10
const int sensorMax = 1023; // sensor maximum, discovered through experiment
11
12
void setup()
13
{
14
// Das Display initialisieren also wieviele Spalten(16) und Zeilen(2)
15
lcd.begin(16, 2);
16
pinMode(ok, INPUT);
17
18
}
19
20
void loop()
21
{
22
poti = analogRead(0);
23
ok = digitalRead(2);
24
// map the sensor range to a range of four options:
25
int seite = map(poti, sensorMin, sensorMax, 0,5 );
Toni K. schrieb:> if(ok == LOW)> {> lcd.setCursor(0, 0);> lcd.print("Name eingeben!");> lcd.setCursor(0, 1);> lcd.print("OK druecken");> }> else> {> lcd.clear();> lcd.setCursor(0, 1);> lcd.print("neuer Text");> }
Du kannst es drehen und wenden wie du willst, aber so wird das nichts
Du brauchst eine Flankenerkennung. Die lebt aus dem Vergleich von
vorher/jetzt
1
uint8_tvorher;
2
3
4
voidloop()
5
{
6
uint8_tjetzt;
7
8
jetzt=digital_read(...);
9
if(jetzt!=vorher){// Irgendetwas ist an der Taste passiert
10
vorher=jetzt;
11
if(jetzt==LOW){// ... sie wurde offenbar niedergedrückt
12
// (anders rum wäre sie ja losgelassen worden)
13
...
14
}
15
}
Das ist der erste Schritt, um einmalig eine Aktion durchzuführen, die
von einer Änderung des Eingangszustandes abhängt.
Sowas löst man zum Beispiel mit Zustandsautomaten. Lies dich mal zu
diesem Thema ein. Auf den ersten Blick mag es verwirrend oder unnötig
komplex aussehen. Aber du wirst bei praktischer Anwendung dieses
Patterns schnell merken, dann man damit viele reale Aufgaben elegant und
erweiterbar lösen kann.
Zustandsautomaten haben folgende grund-Eigenschaften:
Bei Arduino besteht das Haputprogramm ja stets aus einer Funktion, die
endlos oft wiederholt wird. Das ist schonmal eine durchaus sinnvolle
Vorgabe und passt perfekt zum Zustandsautomaten.
Es gibt (im Idealfall) keine Warteschleifen. Stattdessen wird immer nur
auf Ereignisse reagiert. Wenn "dies", dann mach "das". Wenn kein
interessantes Ereignis aufgetreten ist, endet die Prozedur. Und dank der
Hauptschleife (des Arduino Frameworks) wird sie immer wieder erneut
aufgerufen.
Aber, ein Ereignis soll nicht immer zur gleichen Reaktion führen, wie
dein Beispiel mit dem Taster zeigt. Der Code "Wenn Taster gedrückt, dann
lösche das Display" löscht das Display öfter, als gewünscht.
An dieser Stelle kommt der Zustand in Spiel, der dem Zustandsautomaten
seinen Namen verleiht. Welche Ereignisse von Interesse sind und wie
darauf reagiert wird, hängt vom Zustand der Maschine ab.
Typischerweise wird der Zustand in einer Variablen gespeichert und mit
switch/case verzweigt.
Lies jetzt mal einen Wiki Artikel zum Zustandautomaten, danach lies hier
weiter.
Nun gebe ich Dir eine kleine Hilfe, wie deine Aufgabe mit
Zustandsautomat gelöst werden kann. Lass uns erstmal nur auf den Taster
und das Löschen des Displays konzentrieren. Das Display soll nur
gelöscht werden, wenn es noch nicht gelöscht wurde. Da haben wir zwei
Zustände:
1=Display wurde noch nicht gelöscht
Ereignis: Taste drücken
Führt zu: Display löschen und Wechsel zu Zustand 2
2=Display ist gelöscht aber Taste ist immer noch gedrückt
Ereignis: Taste loslassen
Führt zu: Wechsel zu Zustand 1
Und so sieht das dann in Pseudo-Code aus (die Detailumsetzung in C
bekommst du sicher selbst hin):
1
voidtastenabfrage()
2
{
3
staticuint8_tstatus=1;
4
5
switch(status)
6
{
7
case1:// Das Display wurde noch nicht gelöscht
8
9
if(taste_gedrueckt()==1)
10
{
11
clear_display();
12
status=2;
13
}
14
break;
15
16
case2:// Display ist gelöscht aber Taste ist immer noch gedrückt
17
18
if(taste_gedrueckt()==0)
19
{
20
status=1;
21
}
22
break;
23
}
24
}
25
26
voidloop()
27
{
28
tastenabfrage();
29
}
Das coole dabei ist, dass man ein so gestaltetes Programm ganz simpel
Multitaskingfähig machen kann. Als Beispiel sagen wir mal, wir haben
einen Hardware-Timer (timerCounter) der alle 100ms hochgezählt wird. Und
wir wollen, dass jede Sekunde abwechselnd klick-klack gemacht wird.
Gleichzeitig soll die Taste aber noch wie gehabt funktionieren.
1
voidtastenabfrage()
2
{
3
...// wie gehabt.
4
}
5
6
voidticken()
7
{
8
staticuint8_tstatus=1;
9
staticuint32tnextTime=0;
10
11
switch(status)
12
{
13
case1:// Wir warten auf die nächste Sekunde
14
15
if(timerCounter>nextTime)
16
{
17
nextTime=(timerCounter/100+1)*100;
18
klick();
19
status=2;
20
21
}
22
break;
23
24
case2:// Wir warten auf die nächste Sekunde
25
26
if(timerCounter>nextTime)
27
{
28
nextTime=(timerCounter/100+1)*100;
29
klack();
30
status=1;
31
32
}
33
break;
34
}
35
}
36
37
voidloop()
38
{
39
tastenabfrage();
40
klicken();
41
}
Jetzt macht die Anwendung schon zwei Sachen quasi gleichzeitig. Sie
fragt ständig den Zustand des Tasters ab, reagiert darauf und macht auch
noch abwechselnd immer "klick" und "klack".
Eine kleine Erklärung zu: nextTime = (timerCounter/100+1)*100;
Indem ich zuerst durch hundert dividiere und dann wieder mit 100
multipliziere, sorge ich für regelmäßige Intervalle von exakt 100ms.
Wenn zum Beispiel die Funktion beim Zählerstand 10500 ausgeführt werden
müsste, aber aufgrund irgendeiner Verzögerung erst eine 100stel Sekunde
später dran kommt, haben wir schon den Zählerstand 10501. Bei einer
einfachen Addierung von 100 würden sich nun alle klicks und klacks
verzögern, denn der nächste berechnete Zeitpunkt wäre 10601, dann 10701
und so weiter. Wenn das öfter passiert, summieren sich die Verzögerungen
auf. Die "Uhr" würde immer mehr hinterher hinken.
Durch die obige Berechnung jedoch falle ich immer wieder auf den
richtigen Zeitpuntk zurück. Nach 10501 folgt nämlich 10600.
Verzögerungen werden automatisch heraus gerechnet.
Anmerkung zu static:
Die Zustandsvariablen sind static, damit sie ihre Werte dauerhaft
beibehalten. Wenn die Funktion endet und bei der nächsten Wiederholung
von loop() erneut aufgerufen werden, haben sie immer noch ihre alten
Werte aus dem vorherigen Durchlauf. Alternativ könnte man auch globale
Variablen verwenden.
Hilft das?
Vielen Dank schon einmal für die vielen Antworten.
Da ich auch noch relativ neu auf dem Gebiet des Programmierens bin habe
ich eine Frage zu der Funktion vom entprellen aus dem Link:
https://www.mikrocontroller.net/articles/Entprellung
Das würde ich gerne so in mein Programm einpflegen. Allerdings verstehe
ich die Funktion nicht so ganz.
Und wie ich sie dann bei mir in dem Fall aufrufen muss...
anbei wieder der code:
Da hast du deinen Zustandautomaten.
Die Funktion hat vier Zustände und jeder Zustand reagiert auf ein
Ereignis. Die Funktion liefert genau einmal 1 zurück, wenn der Taster
gerade gedrückt wurde.
Wenn der Taste gehalten wird, oder wenn er losgelassen wird, liefert sie
eine 0 zurück.
Und auf was muss ich dann abfragen? Auf Zustand und/oder rw?
Naja eigentlich nur rw, wenn ich das richtig verstehe.
weil das der einzige wert ist, den die Funktion zurück liefert?!
Naj noch werde ich da nicht so ganz schlau raus. Bin leider noch im
Anfangsstadium.
Ja ich habe die Methode mit dem Delay hinbekommen, aber die halte/finde
ich nicht wirklich schön. Auch wenn es in meinem Anwendungsfall keine
Zeitkritischen Punkte gibt.
Nur möchte ich es eben vernünftig lösen.
Daher würde ich euch nochmals bitten, mit den Code mal genauer zu
erläutern.
1
#define TASTERPORT PINC // Das verstehe ich schon nicht.Warum muss ich #define machen? Ich möchte in meinem Fall, nur einen Taster auf meinem Breadboard auslesen.
2
#define TASTERBIT PINC1 // Das selbe hier
3
4
chartaster(void)// warum char
5
{
6
staticunsignedcharzustand;// auch diese beiden Verstehe ich nicht, weshalb ist das notwendig
7
charrw=0;// siehe oben
8
9
if(zustand==0&&!(TASTERPORT&(1<<TASTERBIT)))//Taster wird gedrueckt (steigende Flanke)
10
// Was müsste ich denn eintragen, wenn es bei mir nur um einen Digitalen Input auf PIN 2 geht. Auch weiß ich nicht, was die Variable "zustand" mir sagen soll.
11
{
12
zustand=1;
13
rw=1;
14
}
15
elseif(zustand==1&&!(TASTERPORT&(1<<TASTERBIT)))//Taster wird gehalten
16
// Wenn ich das bis zu diesem Punkt verstanden haben sollte, denke ich, weiß ich was ich tun soll.
17
{
18
zustand=2;
19
rw=0;
20
}
21
elseif(zustand==2&&(TASTERPORT&(1<<TASTERBIT)))//Taster wird losgelassen (fallende Flanke)
Toni K. schrieb:> static unsigned char zustand; // auch diese beiden Verstehe ich> nicht, weshalb ist das notwendig
zustand ist einfach nur ein Zustand, in dem sich die Tastenerkennung
gerade befindet. Um die musst du dich nicht kümmern.
Das ist so, wie wenn ich dich frage: Was machst du gerade?
Und du antwortest: Ich gehe gerade die Treppe hinunter
Und was machst du jetzt?
Ich drücke die Türklinke hinunter
Und jetzt?
Ich öffne die Tür
usw. usw.
Du warst nacheinander in den Zuständen
* die Treppe hinuntergehend
* Türklinke niederdrückend
* Tür aufmachend
* ....
denk dir anstelle von textuellen Beschreibungen jetzt einfach Zahlen.
Das ist genau das, was hier mit dieser Variablen gemacht wird.
> if(zustand == 0 && !(TASTERPORT & (1<<TASTERBIT))) //Taster wird> gedrueckt (steigende Flanke)> // Was müsste ich denn eintragen, wenn es bei mir nur um einen Digitalen> Input auf PIN 2 geht.
Pin 2 von welchem Port?
Jetztz hast du natürlich ein Problem, weil dir das Arduino Framework mit
seinem
1
digitalRead(2);
schon zuviel wegabstrahiert hat. Aber man kann natürlich die Portpin
Abfrage auch darauf umstellen.
Eine Tastenabfrage sieht in klassischem C für active Low Taster immer
so:
wann immer die originale Tastenabfrage auf 0 getestet hat (erkennbar an
der Negierung mittels !) testest du auf LOW, und wenn im Original kein !
vorkam, dann lautet der Vergleich auf HIGH
> // Wenn ich das bis zu diesem Punkt verstanden haben sollte, denke ich,> weiß ich was ich tun soll.
Der Rest braucht dich alles nicht zu kümmern.
Die Erkennungsfunktion liefert dir den Tastendruck und gut ists.
// Das Display initialisieren also wieviele Spalten(16) und Zeilen(2)
4
lcd.begin(16,2);
5
pinMode(zustand,INPUT);
6
7
}
nein. Die Variable 'zustand' geht dich nichts an. Du willst den Portpin
an dem dein Taster hängt auf Input schalten. Und du willst dort den
Pullup aktivieren, wenn dein Taster active Low verdrahtet ist (was der
Normalfall ist)
1
voidsetup()
2
{
3
// Das Display initialisieren also wieviele Spalten(16) und Zeilen(2)
4
lcd.begin(16,2);
5
pinMode(2,INPUT_PULLUP);
6
}
1
uint8_ttmp=0;
2
3
voidloop()
4
{
5
if(taster()){
6
lcd.setCursor(0,0);
7
lcd.print(tmp);
8
tmp=1-tmp;
9
}
10
}
jedesmal wenn du auf die Taste drückst, ändert die Variable ihren Wert
von 0 auf 1 bzw. umgekehrt. Sie macht das nicht, SOLANGE du die Taste
gedrückt hältst, sondern bei jedem Niederdrücken genau 1 mal. Das heißt:
natürlich nur, wenn du die taster Funktion entsprechend richtig
angepasst hast.
Vielen Dank für die Ausführliche Erklärung.
Jetzt habe ich die Funktion denke ich auch verstanden.
Allerdings würde ich gerne mal meinen Code posten, und euch fragen warum
ich immer noch kein Ergebnis sehe.
1
// include the LCD's library
2
#include<LiquidCrystal.h>
3
4
// initialize library with the numbers of the interface pins:
5
// (RS, Enable, DB4, DB5, DB6, DB7)
6
LiquidCrystallcd(7,8,9,10,11,12);
7
intpoti;
8
intseite;
9
uint8_ttmp=0;
10
constintsensorMin=0;// sensor minimum, discovered through experiment
11
constintsensorMax=1023;// sensor maximum, discovered through experiment
12
13
14
voidsetup()
15
{
16
// Das Display initialisieren also wieviele Spalten(16) und Zeilen(2)
17
lcd.begin(16,2);
18
pinMode(2,INPUT_PULLUP);//Meinen Taster habe ich wie folgt angeschlossen: 5V auf den Eingang des Tasters und der Ausgang geht dann auf mein PIN 2 vom Arduino UNO
19
}
20
21
22
//Funktion für die Taster Entprellung
23
chartaster(void)
24
{
25
staticunsignedcharzustand;
26
charrw=0;
27
28
if(zustand==0&&digitalRead(2)==LOW)
29
//Taster wird gedrueckt (steigende Flanke)
30
31
{
32
zustand=1;
33
rw=1;
34
}
35
elseif(zustand==2&&digitalRead(2)==LOW)//Taster wird gehalten
36
37
{
38
zustand=2;
39
rw=0;
40
}
41
elseif(zustand==2&&digitalRead(2)==HIGH)//Taster wird losgelassen (fallende Flanke)
// map the sensor range to a range of four options:
63
seite=map(poti,sensorMin,sensorMax,0,5);
64
//jetzt = digitalRead(taster);
65
66
67
switch(seite)
68
{
69
case0:
70
71
lcd.setCursor(0,0);
72
lcd.print("Name");
73
lcd.setCursor(0,1);
74
lcd.print("OK");
75
break;
76
case1:
77
lcd.setCursor(0,0);
78
lcd.print("2. Seite");
79
break;
80
case2:
81
lcd.setCursor(0,0);
82
lcd.print("3. Seite");
83
break;
84
case3:
85
lcd.setCursor(0,0);
86
lcd.print("4. Seite");
87
break;
88
case4:
89
lcd.setCursor(0,0);
90
lcd.print("5. Seite");
91
break;
92
93
}
94
95
if(taster()==1)
96
{
97
lcd.setCursor(0,0);
98
lcd.print(tmp);
99
tmp=1-tmp;
100
}
101
else
102
{
103
lcd.setCursor(4,1);
104
lcd.print("blabla");
105
}
106
107
108
}
Ich bekomme natürlich jetzt angezeigt:
Name
OK blabla
Aber beim betätigen, keine Änderung. Wie muss die IF Abfrage genau
lauten?
Oder liegt es an der Hardware?
Ich könnte auch, vom Ausgang des tasters einen 10kOhm Widerstand auf GND
ziehen, damit sollte der Taster ja ein definiertes Signal haben oder?
Aber auch das klappt nicht...
Kann doch nicht so schwer sein
Toni K. schrieb:> Ok, um das prellen zu vermeiden, müsste ich wahrscheinlich eine Delay> einbauen.> Oder einen Extra Prellfreien Taster kaufen...
Oh nein.....
Eigentlich ist das Ganze doch recht einfach:
- beim allerersten Erkennen des Gedrücktseins und Abwesenheit des
Kennzeichens für das Entprellen mache deine Aktion
- direkt nach deiner Aktion setze ein Kennzeichen für das Entprellen
- solange dieses Kennzeichen gesetzt ist, teste, ob die Taste ungedrückt
ist. Das sollte genügend oft passieren, um Prellen beim Loslassen zu
unterdrücken. Wenn sich die Taste als oft genug ungedrückt gezeigt hat,
lösche das Entprell-Kennzeichen.
Da du ohnehin mit nem Delay liebäugelst, wäre es genauso gut, am Ende
deiner Schleife grundsätzlich darauf zu warten, daß die Taste 200x
hintereinander ungedrückt ist.
W.S.
Toni K. schrieb:
Ist zwar schon eine Weile her, aber ich war in der Zwischenzeit
unterwegs.
> > Aber beim betätigen, keine Änderung.> //Funktion für die Taster Entprellung> char taster(void)> {> static unsigned char zustand;> char rw = 0;>> if( zustand == 0 && digitalRead(2) == LOW )> //Taster wird gedrueckt (steigende Flanke)>> {> zustand = 1;> rw = 1;> }> else if (zustand == 2 && digitalRead(2) == LOW ) //Taster wird> gehalten
du musst schon ein wenig genau sein!
Die Abfrage hier lautete im Original nicht ob zustand gleich 2 ist,
sondern ob zustand gleich 1 ist.
> Kann doch nicht so schwer sein
Ist es auch nicht. Aber Programmierung hat auch mit dem achten auf
Details zu tun.