Forum: Mikrocontroller und Digitale Elektronik [C] GCC Atmega32, Menü über Funktionspointer, PeDa Entprellung, unterscheidet key_short/long nicht


von Natulo (Gast)


Lesenswert?

Hallo und einen schönen guten Abend. (inzwischen Nacht)
Als kleine Vorwarnung, ich bin noch recht unerfahren in der C und µC 
Programmierung, entsprechend kann der Code etwas abenteuerlich aussehen. 
(Vorallem das Inkludieren..)

Programmiert wird in C mit dem GCC (4.8.1) im AVR Studio 6.2.
Der µC ist ein Atmega32 16PU, die Taster sind lowaktiv.

Mit dem Programm möchte ich Analogwerte aufnehmen, auf dem Display 
anzeigen und später an einen PC/SD Karte schicken.

Aktuell bin ich dabei, die Menüstruktur aufzubauen und benute für jedes 
Menü einen Zustand (Funktion) in einer FSM.
Dort sollen dann statische Texte (liegen noch im RAM) und dynamische 
Werte angezeigt werden.

Die FSM basiert auf dem Code:
http://stackoverflow.com/questions/133214/is-there-a-typical-state-machine-implementation-pattern/9322528#9322528

Zum Entprellen der Tasten benutze ich den Code von PeDa.


Hier mal der grobe Aufbau des Menüs:
1
Optionsmenü <-----> Option 1 <--> Option 1 einstellen
2
^                      ^
3
|                      |
4
v                      v
5
Startmenü           Option 2 <--> Option 2 einstellen
6
^                      .
7
|                      ect..
8
v
9
Menü Ch1 <-----> Anzeige 1
10
^                   ^
11
|                   |
12
v                   v
13
Menü Ch2         Anzeige 2  <-->   Details
Das Menü beginnt mit der Funktion für das Startmenü.

Mit den Tasten Hoch(PC5)/Runter(PC4) wird vertikal navigiert, mit 
Links(PC2)/Rechts(PC6) "kurz" wird horizontal navigiert, mit PC2/PC6 
"lang" übernimmt man die in "Option x einstellen" gesetzten Werte bzw. 
geht wieder eine Menüebene hoch.


Jetzt zum eigentlichen Problem:
Mit den Tasten Hoch(PC5)/Runter(PC4) kann ich mich problemlos 
navigieren.
Wenn ich im "Startmenü" bin, kann ich zum Testen mit der Taste PC3 
"kurz" die LED PB3 und mit PC3 "lang" die LED PB4 togglen.
Wenn ich jedoch in einem der anderen Menüs bin, reagieren die Ausgänge 
PB3 und PB4 willkürlich auf einen kurzen Tastendruck von PC3. Ein 
"langer" Tastendruck wird nicht mehr erkannt, sondern er geht als 
"kurzer" durch.

Selbiges passiert auch mit den Tasten, die für die horizontale 
Navigation zuständig sind.

Zum Eingrenzen des Problems:
Das Startmenü funktioniert einwandfrei, in allen anderen Menüs wird 
nicht mehr richtig zwischen "get_key_shot" und "get_key_long" 
unterschieden.
Leider sehe ich zurzeit nicht, woran das liegt.

[Mod: Quelltext auf Wunsch des TE entfernt, ist unter
Beitrag "Re: [C] GCC Atmega32, Menü über Funktionspointer, PeDa Entprellung, unterscheidet key_short/long nic"
zu finden.]

Hoffentlich habe ich nichts wichtiges vergessen. Wäre schön, wenn da 
jemand einen Blick drauf werfen könnte und mir zeigen kann, was da 
schief läuft.

Schöne Grüße

: Bearbeitet durch Moderator
von Regelcop (Gast)


Lesenswert?

Natulo schrieb:
> Hoffentlich habe ich nichts wichtiges vergessen.

Doch.

Du hast vergessen die "Wichtige Regeln - erst lesen, dann posten!"
zu lesen, zu verstehen und danach zu handeln.

Im Speziellen interpretiere ich deine Source als "längeren Sourcecode"
der "nicht im Text einzufügen ist, sondern als Dateianhang"

von Daniel N. (natulo)


Angehängte Dateien:

Lesenswert?

Das war natürlich blöd meinerseits. Ich habe meinen Beitrag mal 
gemeldet, vielleicht kann ein Moderator bitte den Quelltext entfernen.

Ich habe die Dateien jetzt als Anhang angehängt.
Der betroffene Code steht in Main.c und Menu.c.

Schöne Grüße

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael K. schrieb:
> vielleicht kann ein Moderator bitte den Quelltext entfernen.

Erledigt, habe einen Link auf deinen neuen Beitrag stattdessen 
hinterlegt.

von Daniel N. (natulo)


Angehängte Dateien:

Lesenswert?

Nochmal einen schönen guten Abend.

Ich habe vorhin die Funktion "menu_option_set_date" in der Menu.c mit 
Leben gefüllt.
Beim Ausprobieren ist mir dann aufgefallen, dass innerhalb dieses Menüs, 
ähnlich wie im "Startmenü", die Tastenerkennung problemlos funktioniert.

Mit der Ursprungsversion der Funktion, die ich im Beitrag weiter oben 
angehängt hatte, funktioniert sie hingegen wie beschrieben nicht. (Es 
wird nicht zwischen key_press_short und _long unterschieden, ein Druck 
auf PC3 löst willkürlich mal PB3, mal PB4 aus.)

Ich habe mal die funktionsfähige und die "fehlerhafte" Funktion als .txt 
angehängt.

Da mich das Verhalten stutzig gemacht hat, habe ich noch ein wenig 
herumprobiert.

Im dem Code unten ist eine Stelle markiert. Wenn ich die sechs Zeilen 
auskommentiere, tritt das Verhalten bei fast jedem Tastendruck von PC3 
auf.
Wenn ich sie jetzt wieder aktiviere, tritt das Verhalten wesentlich 
seltener auf. (nur noch alle >>100 Tastendrücke)

Und wie ich jetzt festgestellt habe, tritt das auch im "Startmenü" auf. 
Nur nicht ganz so häufig wie in den anderen Menüs, bei denen noch recht 
wenig Code drin steht.

Der Fehler wird also durch den Umfang des zu bearbeitenden Codes in den 
einzelnen Programmpunkten beeinflusst.
1
void menu_option_set_date( state_t* currentState ){
2
  #define DAY       0
3
  #define MONTH     1
4
  #define YEAR     2
5
  
6
  static uint8_t tempDate[3]; 
7
  
8
  static uint8_t mode=DAY;
9
  
10
  static char writeOnce = 1;
11
  if(writeOnce){
12
    lcd_clearf();
13
    //lcd_clear();
14
    lcd_setcursor(1,1);
15
    lcd_string("option_set_date");
16
    
17
    tempDate[DAY] = rtc_data[4];
18
    tempDate[MONTH] = rtc_data[5];
19
    tempDate[YEAR] = rtc_data[6];
20
    
21
    /*lcd_setcursor(2,1);
22
    lcd_zahl(tempDate[DAY],2,'m');
23
    lcd_data('.');
24
    lcd_zahl(tempDate[MONTH],2,'m');
25
    lcd_data('.');
26
    lcd_zahl(tempDate[YEAR],2,'m');*/
27
    
28
    writeOnce--;
29
  }
30
  
31
    
32
    
33
  //  vvv   Das hier ist die besagte Stelle vvv
34
  lcd_setcursor(2,1);
35
  lcd_zahl(tempDate[DAY],2,'m');
36
  lcd_data('.');
37
  lcd_zahl(tempDate[MONTH],2,'m');
38
  lcd_data('.');
39
  lcd_zahl(tempDate[YEAR],2,'m');
40
  //  ^^^ Je mehr auskommentiert wird, umso häufiger tritt der Fehler auf ^^^ 
41
42
  // Abspeichern der temporären Werte in RTC, bestätigt mit OK
43
  
44
  if(currentState->input == KEY_RIGHT_L){
45
    lcd_setcursor(2,12);
46
    lcd_data('O');
47
    lcd_data('K');
48
    } else if (currentState->input != KEY_NULL){
49
    lcd_setcursor(2,12);
50
    lcd_data(' ');
51
    lcd_data(' ');
52
  }
53
  
54
  if(currentState->input == KEY_LEFT_L){
55
    writeOnce = 1;
56
    currentState->function = menu_option_date;
57
  }
58
};

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Die long/short Erkennung benötigt, daß sie zur Unterscheidung 
entsprechend häufig aufgerufen wird. Kann das durch Programmlaufzeiten 
zu lange dauern, muß man es mit im Interrupt tun.

Dein Programm ist ziemlich verzwickt.
Ob das überhaupt so funktionieren kann, die Tastenabfragen alle zusammen 
in einer Funktion zu machen und nur einer Variable zuzuweisen, kann ich 
nicht überblicken. Zumindest können die if/else-Ketten bewirken, daß 
Tasten längere Zeit nicht ausgewertet werden, weil sie weiter hinten in 
der Kette stehen.

Ich habe in Menüs die Tasten immer direkt ausgewertet. Dann ist es auch 
möglich, daß die selbe Taste in einem Kontext long/short bewirkt, in 
einem anderen Kontext aber press/repeat.

Und generell sollten bei einem Kontextwechsel alle Tastenereignisse 
gelöscht werden, damit nicht uralte Ereignisse etwas bewirken, die im 
vorherigen Kontext nicht benutzt wurden:
1
  get_key_press(0xFF);
2
  get_key_rpt(0xFF);

von Daniel N. (natulo)


Angehängte Dateien:

Lesenswert?

Hallo peda und danke für die Antwort.

Peter D. schrieb:
> Die long/short Erkennung benötigt, daß sie zur Unterscheidung
> entsprechend häufig aufgerufen wird. Kann das durch Programmlaufzeiten
> zu lange dauern, muß man es mit im Interrupt tun.
Meinst du damit, die Tasten innerhalb des Interrupts mit den 
get_key_short/long abzufragen die Ergebnis der Abfrage dann im 
restlichen Programm zu benutzen?

Und könntest du das mit dem löschen der Tastenereignisse bitte näher 
ausführen?

Ich habe testweise die Tastenabfrage über die Funktion rausgenommen.
Also in der Main.c die Stelle
1
207 //state.input = keyevent();
und in der Menu.c die komplette Funktion "keyevent".

Bis auf "Startmenü", "Optionen" und "Option Date/Set Date" habe ich die 
anderen Menüfunktionen rausgenommen.

In den verbliebenen Menüs frage ich die Tasten jetzt nicht mehr über das 
Struct ab, sondern direkt über deine Funktionen. (Siehe Anhang)

Am Tastenverhalten hat das jedoch nichts verändert, das Problem bleibt.

Lediglich ein neuer Nebeneffekt ist aufgetreten, den ich bisher so noch 
nicht gesehen hatte. Das Menü besteht jetzt erstmal nur noch aus:
1
[ Optionen ] <-> [Option Datum] <-> [Setze Datum]
2
    ^
3
    |
4
    v 
5
[ Start ]
Wenn ich jetzt z.B. in Start bin und die Taste runter drücke, bei der 
eigentlich nichts passieren sollte, und danach die Taste rauf drücke, 
sieht man wie sich das "Start" Menü neu aufbaut. Als ob nach dem Menü 
noch ein "Schattenmenü" wäre, in etwa so:
1
^ Option < Taste runter führt jetzt wieder zu "Option", nicht zu "Start"
2
| Option < eigentliches Menü, Taste rauf führt zu "Schattenmenü", runter zu Start
3
| Start
4
v Start

von Peter D. (peda)


Lesenswert?

get_key_short und get_key_long müssen in jedem Kontext aufgerufen 
werden, sonst funktionieren sie nicht.
Man muß sie ja nicht auswerten. Allerdings stellt sich dann die Frage, 
warum man überhaupt unterscheidet.

Generell würde ich sie nur sparsam einsetzen, da get_key_short erst auf 
Loslassen reagiert, was nicht intuitiv ist.
Versuch mal Dein Menü nur mit press und repeat zu bauen.

von Daniel N. (natulo)


Lesenswert?

So, nochmal einen schönen guten Abend.
Ich habe nochmal ein wenig rumgewühlt und ein wenig rumprobiert.

Dabei bin ich auf einen alten Beitrag von dir gestoßen:
Beitrag "Re: Universelle Tastenabfrage"

>Die Repeat-Maske darf daher nur die Pins beinhalten, die auch repeatet
>werden sollen!

Also in einem kleinen Testprogramm etwas herumgespielt:
1
if(get_key_short(1<<PC0)){
2
  PORTB ^= (1<<PB6);    
3
  } else if (get_key_long(1<<PC0)){
4
  PORTB ^= (1<<PB7);
5
}
6
      
7
if(get_key_long(1<<PC1)){
8
  PORTB ^= (1<<PB1);
9
  zahl1++;
10
}
11
      
12
if(get_key_long(1<<PC2)){
13
  PORTB ^= (1<<PB2);
14
  zahl2++;
15
}
16
      
17
if(get_key_short(1<<PC3)){
18
        
19
} else if (get_key_long(1<<PC3)){
20
  PORTB ^= (1<<PB3);
21
  zahl3++;
22
}
23
24
if(get_key_short(1<<PC4)){
25
        
26
} else if (get_key_long(1<<PC4)){
27
  PORTB ^= (1<<PB4);
28
  zahl4++;
29
}
Und dann zahl1 bis zahl4 auf dem Display ausgeben lassen.
In der debounce.h habe ich dann die Repeat_Mask geändert:
1
#define REPEAT_MASK     ( 1<<PC0 | 1<<PC2 | 1<<PC4 )

Wie ich konsequenter Weise feststellen konnte:
Bei den Tasten, bei denen die Repeat_Mask nicht gesetzt war, tat sich 
nichts. Da wo die Repeat_Mask gesetzt war, funktionierte die Erkennung 
problemlos. Ich konnte jetzt auch keinen Unterschied zwischen der 
alleinigen Abfrage von  if(get_key_long) und der Kombination aus 
if(get_key_short) else if (get_key_long) feststellen.

Dann habe ich nochmal in das andere Programm geguckt um zu sehen, was 
ich da anders gemacht habe. Da ich mir nicht sicher war, für welche 
Tasten ich die Repeat Funktion brauchen würde, hatte ich einfach 
geschrieben:
1
#define REPEAT_MASK     0xff


Nach etwas Grübeln dämmerte es mir dann:
An Port C hängen von PC0 bis PC7 Taster, für die ich die internen 
Pullups benutze. An PC0 und PC1 hängt aber auch das TWI, an dem die RTC 
hängt, die ich jedoch erst später integriert habe.
Darum habe ich das DDRC und PORTC auch so gesetzt:
1
KEY_DDR=0x02; // PINC als Eingang, bis auf PC0 + PC1
2
KEY_PORT=0xfc; // Int. Pull-Ups am Eingang aktiviert, bis auf PC0 + PC1

Da ich aber nicht daran gedacht habe, die Repeat_Mask anzupassen, stand 
die nach wie vor auf 0xff, hat sie also die ganze Zeit die Takt- und 
Datenleitung des TWI mit abgefragt..
Das könnte dann auch erklären, wieso "volle" Menüfunktionen etwas besser 
liefen, er hat schlicht seltener in der main mit RTC_read() die Daten 
von der RTC abgefragt und somit seltener auf das TWI zugegriffen.

Da gibt es dann auch noch Verbesserungspotenzial in meinem Programm, die 
RTC muss schließlich nicht mit Maximalgeschwindigkeit in der 
Hauptschleife abgefragt werden.

Zur Problemlösung: Ich habe in der Debounce.h jetzt nur noch die Tasten 
in die Repeat_Mask eingetragen, die ich für die Menünavigation / 
Einstellungen auch mit solch einer Funktion verwenden werde.


Dann hätte ich noch eine Frage zu deinem Vorschlag, das Menü nur mit 
press und repeat aufzubauen:
Wäre es möglich, mit dem repeat ein ähnliches Verhalten wie mit dem 
key_long hinzubekommen?
Die Idee war eigentlich, z.B. beim Stellen einer Uhrzeit:
press up/down zählt die aktuelle Ziffer rauf/runter, rpt macht das dann 
bei gedrückt halten automatisch, mit short left/right wollte ich die 
aktuelle Stelle verändern und mit long left/right dann die Auswahl 
bestätigen ('OK') bzw. in dem Schritt dann eine Menüebene nach oben 
gehen.
Mit dem rpt könnte man auch eine kleine Verzögerung hinbekommen, 
allerdings wird der Rückgabewert ja permanent geändert, was im 
darüberliegenden Menüpunkt dann ebenfalls direkt ausgewertet werden 
würde, so lange wie man den Knopf gedrückt hält.

Schöne Grüße

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.