Forum: Projekte & Code Universelle Tastenabfrage


von Rolf N. (rolfn)


Lesenswert?

Kann mir jemand einen Hinweis geben, wie man Peters Code so erweitern
kann, dass zusätzlich zum langen Drücken einer Taste auch noch das sehr
lange Drücken erkannt wird? (Soll als eine Art "reset" verwendet werden,
also nicht für die normale Bedienung.) Vielen Dank im voraus,

...Rolf

von Peter D. (peda)


Lesenswert?

Rolf Niepraschk schrieb:
> Kann mir jemand einen Hinweis geben

Wo ist Dein Problem?
Niemand hindert Dich daran, das entsprechende Bit in key_state zu testen 
und damit einen Zähler im Timerinterrupt hochlaufen zu lassen.

von Rolf N. (rolfn)


Lesenswert?

Danke. Es funktioniert.

...Rolf

von AnBo (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Sowas macht man am besten als Statemaschine.
> Für 3s Wartezeit muß der Timerinterrupt >12ms sein oder man nimmt ein
> uint16_t als Zähler.
> enum { OFF, ON, BLINK };
>
> int main()
> {
>   uint8_t state = OFF;
>   for(;;){
>     switch( state ){
>       case OFF:
>                   led_off();
>                   if( get_key_short( 1<<KEY1 ))
>                     state = ON;
>                   if( get_key_long( 1<<KEY1 ))
>                     state = ON;
>                   break;
>       case ON:
>                   led_on();
>                   if( get_key_short( 1<<KEY1 ))
>                     state = OFF;
>                   if( get_key_long( 1<<KEY1 ))
>                     state = BLINK;
>                   break;
>       case BLINK:
>                   led_blink();
>                   if( get_key_short( 1<<KEY1 ))
>                     state = OFF;
>                   if( get_key_long( 1<<KEY1 ))
>                     state = OFF;
>                   break;
>     }
>   }
> }

Hallo,
doofe Frage, aber bist du sicher dass das so funktioniert?
Ich habe das meiner Meinung nach genauso, allerdings passiert auf 
Tastendruck nichts. Außerhalb der SM funktioniert es tadellos.
Jemand eine Idee?
Danke!
1
#ifndef F_CPU
2
#define F_CPU 1000000UL
3
#endif
4
5
#define KEY_DDR         DDRA
6
#define KEY_PORT        PORTA
7
#define KEY_PIN         PINA
8
#define KEY0            0
9
#define KEY1            1
10
#define KEY2            2
11
#define KEY3      3
12
#define ALL_KEYS        (1<<KEY0 | 1<<KEY1 | 1<<KEY2 | 1<<KEY3)
13
14
#define REPEAT_MASK     (1<<KEY1)       // repeat: key0, key2
15
#define REPEAT_START    50                        // after 500ms
16
#define REPEAT_NEXT     20                        // every 200ms
17
18
#define LED_DDR         DDRA
19
#define LED_PORT        PORTA
20
#define LEDBLUE         4
21
#define LEDYELLOW       5
22
#define LEDRED          6
23
#define LEDGREEN    7
24
25
#include <stdint.h>
26
#include <avr/io.h>
27
#include <stdio.h>
28
#include <util/delay.h>                // for _delay_ms()
29
#include <avr/interrupt.h>
30
#include "lcd.h"
31
32
volatile uint8_t key_state;                                // debounced and inverted key state:
33
                          // bit = 1: key pressed
34
volatile uint8_t key_press;                                // key press detect
35
36
volatile uint8_t key_rpt;                                  // key long press and repeat
37
38
39
ISR( TIMER0_OVF_vect )                            // every 10ms
40
{
41
  static uint8_t ct0, ct1, rpt;
42
  uint8_t i;
43
  
44
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
45
  
46
  i = key_state ^ ~KEY_PIN;                       // key changed ?
47
  ct0 = ~( ct0 & i );                             // reset or count ct0
48
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
49
  i &= ct0 & ct1;                                 // count until roll over ?
50
  key_state ^= i;                                 // then toggle debounced state
51
  key_press |= key_state & i;                     // 0->1: key press detect
52
  
53
  if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
54
  rpt = REPEAT_START;                          // start delay
55
  if( --rpt == 0 ){
56
    rpt = REPEAT_NEXT;                            // repeat delay
57
    key_rpt |= key_state & REPEAT_MASK;
58
  }
59
}
60
61
///////////////////////////////////////////////////////////////////
62
//
63
// check if a key has been pressed. Each pressed key is reported
64
// only once
65
//
66
uint8_t get_key_press( uint8_t key_mask )
67
{
68
  cli();                                          // read and clear atomic !
69
  key_mask &= key_press;                          // read key(s)
70
  key_press ^= key_mask;                          // clear key(s)
71
  sei();
72
  return key_mask;
73
}
74
75
///////////////////////////////////////////////////////////////////
76
//
77
// check if a key has been pressed long enough such that the
78
// key repeat functionality kicks in. After a small setup delay
79
// the key is reported being pressed in subsequent calls
80
// to this function. This simulates the user repeatedly
81
// pressing and releasing the key.
82
//
83
uint8_t get_key_rpt( uint8_t key_mask )
84
{
85
  cli();                                          // read and clear atomic !
86
  key_mask &= key_rpt;                            // read key(s)
87
  key_rpt ^= key_mask;                            // clear key(s)
88
  sei();
89
  return key_mask;
90
}
91
92
///////////////////////////////////////////////////////////////////
93
//
94
// check if a key is pressed right now
95
//
96
uint8_t get_key_state( uint8_t key_mask )
97
98
{
99
  key_mask &= key_state;
100
  return key_mask;
101
}
102
103
///////////////////////////////////////////////////////////////////
104
//
105
uint8_t get_key_short( uint8_t key_mask )
106
{
107
  cli();                                          // read key state and key press atomic !
108
  return get_key_press( ~key_state & key_mask );
109
}
110
111
///////////////////////////////////////////////////////////////////
112
//
113
uint8_t get_key_long( uint8_t key_mask )
114
{
115
  return get_key_press( get_key_rpt( key_mask ));
116
}
117
118
119
120
int main(void)
121
{  
122
  int displaystate=0;
123
  typedef enum
124
  {
125
    READY=0,
126
    STARTMEASUREMENT
127
  } current_state;
128
129
  current_state state = READY;
130
131
  LED_DDR = 0xFF;
132
133
  // Configure debouncing routines
134
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
135
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
136
  
137
  TCCR0B = (1<<CS02)|(1<<CS00);         // divide by 1024
138
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
139
  TIMSK0 |= 1<<TOIE0;                   // enable timer interrupt
140
  
141
  sei();
142
  
143
  lcd_init(LCD_DISP_ON);        // Initialize LCD
144
  
145
  while(1){
146
    switch (state)
147
    {
148
      case READY:
149
          if(displaystate==0){
150
            lcd_clrscr();
151
            lcd_puts("Bereit");
152
            displaystate=1;
153
          }
154
          LED_PORT = (1<<LEDGREEN);
155
          if( get_key_short( 1<<KEY3 )){
156
            displaystate=0;
157
            state=STARTMEASUREMENT;
158
          }
159
          if( get_key_long( 1<<KEY3 )){  
160
            displaystate=0;
161
            state=STARTMEASUREMENT;
162
          }
163
          break;
164
      
165
      case STARTMEASUREMENT:
166
          if(displaystate==0){
167
            lcd_clrscr();
168
            lcd_puts("State1");
169
            displaystate=1;
170
          }
171
          LED_PORT = (1<<LEDYELLOW);
172
          if( get_key_short( 1<<KEY2 )){
173
            displaystate=0;
174
            state=READY;
175
          }
176
          if( get_key_long( 1<<KEY2 )){
177
            displaystate=0;
178
            state=READY;
179
          }
180
          break;
181
        }
182
  }
183
}

von Peter D. (peda)


Lesenswert?

AnBo schrieb:
> aber bist du sicher dass das so funktioniert?
> Ich habe das meiner Meinung nach genauso, allerdings passiert auf
> Tastendruck nichts.

Dein Code ist dermaßen verschieden zu meinem Beispiel, es wäre ein 
Wunder, wenn er genauso funktionierte.
Probier doch meinen einfach mal aus.

von AnBo (Gast)


Lesenswert?

Hallo,
ich bin noch mal alles Schritt für Schritt durchgegangen und habe es mit 
deinem Code verglichen. Da ist mir dann letztendlich aufgefallen, dass 
ich die Pullups in einem Schritt wieder ausgeschaltet hatte.

Danke für deine ganzen Mühen und die viele Hilfestellung!

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Peter Dannegger schrieb:
> Probier doch meinen einfach mal aus.

 LOL.

von Mario S. (dachstuhlklaus)


Angehängte Dateien:

Lesenswert?

Guten Tag zusammen.

Ich habe mich die letzen Tage mit dem hier vorgestellten Entprellcode 
auseinandergesetzt und möchte als erstes sagen:

"Danke an Peter Dannegger für diesen tollen code und an alle die ihn 
weiter verbessert haben!"

Zugegeben, ich habe nicht jede Zeile verstanden, aber weiss jetzt wie 
ich ihn anwenden kann. Vielleicht würde ja Zettel und Stift zu besserem 
Verständnis führen ;-)

Als kleines Dankeschön habe ich mir mal die Mühe gemacht alles hier 
zusammengetragene in eine Datei zu packen. Die Nutzung von mehreren 
Eingangsports, für mehr als acht Tasten, habe ich allerdings nicht 
eingebaut.

Da ich programmieren nur mit PICs gelernt habe, kann ich nur die 
Funktion dieses angehängten codes bestätigen (Simuliert mit Proteus). 
Wenn also mal jemand die Version für AVR kontrollieren könnte wäre das 
super. Ich habe es aber nach bestem Gewissen versucht umzusetzen.

Den Simulationsschaltkreis für Proteus habe ich auch angefügt. Also viel 
Spass damit :-)

Schöne Grüsse

Mario

@Peter Dannegger:
Darf ich den Code im CCS Forum posten? Unter Angabe der Quelle 
natürlich.

: Bearbeitet durch User
von Pumba (Gast)


Lesenswert?

Hallo,

bei mir reagieren die Taster nicht ;/
Musste ein paar Anpassungen der Timer für den Attiny841 vornehmen. Die 
ISR wird alle 10ms aufgerufen, dass soweit schon richtig.

Die Leds hab ich direk an PINA 0 - 2 angeschlossen und auf der anderen 
Seite mit einem 470Ohm Widerstand nach GND verbunden.

Die Taster sind direkt mit PINB 0 - 2 verbunden und auf der anderen 
Seite mit GND verbunden.
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
#define F_CPU 8E6
6
7
#define KEY_DDR         DDRB
8
#define KEY_PORT        PORTB
9
#define KEY_PIN         PINB
10
#define KEY0            0
11
#define KEY1            1
12
#define KEY2            2
13
#define ALL_KEYS        (1<<KEY0 | 1<<KEY1 | 1<<KEY2)
14
15
#define REPEAT_MASK     (1<<KEY1 | 1<<KEY2)       // repeat: key1, key2
16
#define REPEAT_START    50                        // after 500ms
17
#define REPEAT_NEXT     20                        // every 200ms
18
19
#define LED_DDR         DDRA
20
#define LED_PORT        PORTA
21
#define LED0            0
22
#define LED1            1
23
#define LED2            2
24
25
volatile uint8_t key_state;                                // debounced and inverted key state:
26
// bit = 1: key pressed
27
volatile uint8_t key_press;                                // key press detect
28
29
volatile uint8_t key_rpt;                                  // key long press and repeat
30
31
32
ISR( TIMER0_OVF_vect )                         // every 10ms
33
{
34
  static uint8_t ct0, ct1, rpt;
35
  uint8_t i;
36
  
37
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);
38
39
  i = key_state ^ ~KEY_PIN;                       // key changed ?
40
  ct0 = ~( ct0 & i );                             // reset or count ct0
41
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
42
  i &= ct0 & ct1;                                 // count until roll over ?
43
  key_state ^= i;                                 // then toggle debounced state
44
  key_press |= key_state & i;                     // 0->1: key press detect
45
  
46
  if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
47
  rpt = REPEAT_START;                          // start delay
48
  if( --rpt == 0 ){
49
    rpt = REPEAT_NEXT;                            // repeat delay
50
    key_rpt |= key_state & REPEAT_MASK;
51
  }
52
53
  static uint8_t ct;
54
  if(++ct == 50) { LED_PORT ^= 1<<LED2; ct= 0;}
55
}
56
57
58
uint8_t get_key_press( uint8_t key_mask )
59
{
60
  cli();                                          // read and clear atomic !
61
  key_mask &= key_press;                          // read key(s)
62
  key_press ^= key_mask;                          // clear key(s)
63
  sei();
64
  return key_mask;
65
}
66
67
uint8_t get_key_rpt( uint8_t key_mask )
68
{
69
  cli();                                          // read and clear atomic !
70
  key_mask &= key_rpt;                            // read key(s)
71
  key_rpt ^= key_mask;                            // clear key(s)
72
  sei();
73
  return key_mask;
74
}
75
76
uint8_t get_key_state( uint8_t key_mask )
77
{
78
  key_mask &= key_state;
79
  return key_mask;
80
}
81
82
uint8_t get_key_short( uint8_t key_mask )
83
{
84
  cli();                                          // read key state and key press atomic !
85
  return get_key_press( ~key_state & key_mask );
86
}
87
88
uint8_t get_key_long( uint8_t key_mask )
89
{
90
  return get_key_press( get_key_rpt( key_mask ));
91
}
92
93
int main( void )
94
{
95
  LED_DDR |= (1<<LED0) | (1<<LED1) | (1<<LED2);
96
97
  // Configure debouncing routines
98
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
99
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
100
  
101
  TCCR0B = (1<<CS02)|(1<<CS00);         // divide by 1024
102
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
103
  TIMSK0 |= 1<<TOIE0;                   // enable timer interrupt
104
  
105
  sei();
106
  
107
  while(1){
108
    if(get_key_short( 1<<KEY0 ))  // Led2 togglen
109
    LED_PORT ^= 1<<LED0;
110
    
111
    if( get_key_short( 1<<KEY1 ))  // Led1 togglen
112
    LED_PORT ^= 1<<LED1;
113
114
    if( get_key_short( 1<<KEY2 ))  // Leds loeschen
115
    LED_PORT &= ~((1<<LED0) | (1<<LED1));
116
  }
117
}

von Uwe S. (de0508)


Lesenswert?

Nun Pumba?

Der KEY0 fehlt in der Definition.

Vielleicht solltest Du erstmal nur mit get_key_press() arbeiten.

Wenn Du das verstanden hast, kannst Du die zeitabhängigen 
Taster-Funktionen einbauen, konfigurieren und auch nutzen.

Ok ?

von Mathias O. (m-obi)


Lesenswert?

Die Pullups sind beim 841 bei PUE. Steht auch im Datenblatt. Bei mir 
hats damals funktioniert mit dem 841.

: Bearbeitet durch User
von Pumba (Gast)


Lesenswert?

Die key_press Variante macht auch nichts, ausser die Leds direkt auf 
HIGH zu setzen.

von Mathias O. (m-obi)


Lesenswert?

Versuch mal anstatt so
1
key_state ^ ~KEY_PIN
das so zu machen
1
key_state ^ KEY_PIN

von Uwe S. (de0508)


Lesenswert?

Mathias

dein Vorlag ist unsinning, wenn Du nicht weist wie der Code von PeDa 
funktioniert, dann rate bitte nicht.


Mathias O. schrieb:
> key_state ^ ~KEY_PINdas so zu machenkey_state ^ KEY_PIN

von Mathias O. (m-obi)


Lesenswert?

Jo er hat ja die Taster active low. Dachte die sind an VCC 
angeschlossen.

von Pumba (Gast)


Lesenswert?

Das mit der neuen Ansteuerung mit PUEx hatte ich bislang nicht gewusst.

Laut dem Datenblatt werden die Pullups wie folgt gesetzt.

PUEx |= (1<<PUEx1) | (1<<PUEx0);
die einzelnen Bits PUEx0 sind im Atmel Studio nicht per defines gesetzt. 
Spielt aber keine Rolle. Da ich die auch mit 0, 1  etc machen kann. 
Jedoch geht es so wie ich mir gedacht habe nicht.
1
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
2
  PUEB |= ALL_KEYS;
3
  //KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors

von Mathias O. (m-obi)


Lesenswert?

Mit welchem Wert hast du F_CPU definiert? 2278?

von Pumba (Gast)


Lesenswert?

F_CPU ist 8MHz

von Mathias O. (m-obi)


Lesenswert?

Wo hängt dein Quarz dran? PB0 und PB1 sind doch XTAL.

von Mathias O. (m-obi)


Lesenswert?

Dann schreib mal 8000000UL bei F_CPU

von Pumba (Gast)


Lesenswert?

Der Takt ist hier NICHT das Problem! Das geht. Ob nun "8E6" oder 
"8000000UL" spielt keine Rolle.

Quarz ist keiner im Einsatz. Ich arbeite mit der internen Oscillator. 
Der läuft mit 8MHz

von MikeD (Gast)


Lesenswert?

Das Atomisieren per cli(); sei(); finde ich ein wenig heftig. In meiner 
App läuft parallel ein anderer Interrupt, der alle 256 CPU cycles für 
ein paar Takte  zuschlägt (soft PWM). Der würde regelmäßig und oft 
gekillt.

Es geht ja nur darum, den Tasten - Debouncer für die Dauer der 
Tastenstatus Abfragen lahm zu legen. Andere Interrupts, die die Tastatur 
- Variablen nicht verändern, dürfen ja schadlos laufen ... od'r?

Ich lösche / setze daher selektiv nur das Timer - Interrupt Enable Bit 
des 10ms Timers. Sollte - was sehr unwahrscheinlich, aber immerhin 
möglich ist - genau während dieser Sperre ein Timer Overflow eintreten, 
gibt's in der Tastatur (und nur dort) einen Glitch von 10ms ... das ist 
meiner Meinung nach vertretbar.

von Uwe S. (de0508)


Lesenswert?

Servus,

Wiso ein "Glitch von 10ms" ?

Ich verstehe das Interruptsystem etwas anders:
Wenn der Timerinterrupt wieder aktiviert wurde, was ja nur ein paar µs 
dauert, springt man sofort in Timer Interrupt Routine, wenn das Timer 
Interrupt Flag gesetzt ist.

Klar können auch noch andere Interrupts mit höhere Priorität anstehen, 
die will ich jetzt nicht betrachten.

von MikeD (Gast)


Lesenswert?

@Uwe S

Servus,

wieder mal nachgelesen ... hast recht ... für den Fall, dass der 10ms 
key-Timer während der Atomexplosion überläuft (und das entsprechende 
Timer Interrupt Flag 0 ist) wird der Interrupt gespeichert und nach dem 
erneuten Setzen des Flags ausgeführt (in meinem Fall TIFR2.TOV2)

Liebe Grüsse aus +/- Wien

von Joachim B. (jar)


Lesenswert?

Mario Sachse schrieb:
> "Danke an Peter Dannegger für diesen tollen code und an alle die ihn
> weiter verbessert haben!"

auch ich möchte noch mal Danke sagen für diese Entprellroutine.

Ich nutze sie im AVR, im Arduino

mal zum Tasten entprellen, klappt sogar an einer I2C LCD Schnitte mit 
PCF8574 die ich für 8 Tasten nutze.

Beschreibung,

Timer Interrupt 64µs für IRMP
wenn ein Counter auf 10ms hochgezählt hat wird in die I2C 
Tastaturabfrage gesprungen (100-200µs) dort wird ein Flag gesetzt das es 
ein langer IRQ wird, der Interrupt für IRMP wieder freigegeben und 
solange das Flag gesetzt ist am I2C Tastenabfragen vorbei gesprungen. 
Das Flag wird nur zurückgenommen wenn I2C wieder frei ist, der TWI 
Auftrag erledigt ist.

So entgeht mir kein IRMP Befehl, kein Tastendruck.

Für eine Laser Steuerung wo ausser den regulären 20Hz Impulsen mit 80µs 
Breite zur Lasertriggerung auch leider noch Fremdimpulse kommen unter 
10µs und fremd triggern habe ich nun die IRQ auf 10µs gesetzt wie eine 
Tastaturabfrage und trigger den Laser nur noch wenn ein solider Trigger 
größer 40µs erfolgt. Getestet mit einem Arduino als Pulsgenerator, ich 
kann F und Trigerbreite schön einstellen und sehen wie PeDas 
Entprellroutine wirkt.

: Bearbeitet durch User
von Dave (Gast)


Lesenswert?

Moin,

ich nutze die Entprellroutine auf einem STM32F1 und habe bei einem 
Taster festgestellt, dass er häufiger nicht als gedrückt erkannt wurde.
Die Funktion im IRQ wird alle 10ms aufgerufen und es gab keine atomic 
Blöcke in den Tastenfunktionen.
Nun dachte ich mir, alle IRQs sperren ist etwas zu viel des Guten, es 
geht ja nur um das Auslesen der Tastenpins.
Spricht etwas gegen folgenden Ansatz?
In der debounce.c
1
static volatile uint8_t IRQdoNotReadFlag;
2
3
uint16_t get_button_press(uint16_t button_mask) {
4
  IRQdoNotReadFlag = 1;
5
  button_mask &= button_press;
6
  button_press ^= button_mask;
7
  IRQdoNotReadFlag = 0;
8
  return button_mask;
9
}

in der Funktion die dann im IRQ aufgerufen wird:
1
void call_button_in_IRQ(void)                  // Call this in SysTick-Handler
2
{                                              // Should be called every 10ms
3
  static uint16_t ct0, ct1;
4
  #ifdef USE_KEY_REPEAT
5
    static uint16_t rpt;
6
  #endif
7
  uint16_t i;
8
  uint16_t BUTTON_MASK_NOW = BUTTON_PORT;    // get Stade of the InputDataRegister for the used Port
9
  if (!IRQdoNotReadFlag) {
10
11
    i = button_state ^ BUTTON_MASK_NOW;        // key changed ?
12
    ct0 = ~(ct0 & i);                          // reset or count ct0
13
    ct1 = ct0 ^ (ct1 & i);                     // reset or count ct1
14
    i &= ct0 & ct1;                            // count until roll over ?
15
    button_state ^= i;                         // then toggle debounced state
16
    button_press |= button_state & i;          // 0->1: key press detect
17
    
18
  #ifdef USE_KEY_REPEAT
19
    if ((button_state & REPEAT_MASK) == 0)     // check repeat function
20
      rpt = REPEAT_START;                    // start delay
21
    if (--rpt == 0) {
22
      rpt = REPEAT_NEXT;
23
      button_rpt |= button_state & REPEAT_MASK;
24
    }
25
  #endif
26
  }
27
}

Das scheint schon deutlich besser zu funktionieren, ich habe aber den 
Eindruck, dass gelegentlich "kurze" Tastendrücke doch noch unerkannt 
bleiben. Bin mir aber nicht ganz sicher, ob es nicht evtl. die 
Entprellung ist. Eigentlich müsste aber ein gedrückter Taster deutlich 
länger dauern, als die Entprellzeit.

von Uwe S. (de0508)


Lesenswert?

Hallo,

bei den AVR µC kann man alle Interrupts Sperren und dann wieder 
freigeben, oder nur den zugehörigen Timer-Interrupt.
Die Abarbeitung des Timer-Interrupts wird dann nach seiner Freigabe 
"sofort" nachgeholt. Und geht nicht 'verloren'.

Bei deiner Realisierung in call_button_in_IRQ() geht aber dieser 
Interrupt verloren !
Also hier würde ich mal ansetzen.

von Dave (Gast)


Lesenswert?

Hallo Uwe,

danke für die Antwort. Interessanter Gedanke, das ergibt durchaus Sinn.
Wie macht man das denn bei den STM32? Bei mir sorgt der SysTick für die 
10ms Zeitbasis, aber das wird ja auch noch für andere Dinge benutzt und 
daher möchte ich das nicht aus dem Takt bringen.
Je häufiger ich also Tastenabfragefunktionen benutze, desto häufiger 
würde der SysTick IRQ kurzzeitig gesperrt werden...

Wenn ich über
1
SysTick->CTRL  &= ~SysTick_CTRL_TICKINT_Msk; /* Disable SysTick IRQ */
den SysTick IRQ in den Tastenabfragefunktionen ausschalte und kurz 
danach wieder anschalte, wird er vermutlich nicht nachgeholt.

von Martin L. (schrau_baer)


Lesenswert?

Hallo Forumsmitglieder,

ich habe mich schon versucht an den Codebeispielen für Microchip PIC 
Mikrocontroller aber ich scheitere leider beim Aufruf der Funktionen.

Ich nutze 4 von 8 Pins des PORT A als Eingänge, der Rest sind Ausgänge. 
Compiler ist XC8 in Verbindung mit einem PIC16F628.

Beim Aufruf der Funktion in meinem Main Programm gibt mir der Compiler 
zu verstehen dass er "KEY1" nicht kennt.
1
if ( get_key_short( 1<<KEY1))
2
     i++;

Die Fehlercodes lauten:
1
(361) function declared implicit int
1
(192) undefined identifier "KEY1"

Gerne füge ich noch den Quellcode der "unbounce.c" diesem Post hinzu.

Gruß
Martin

von Peter D. (peda)


Lesenswert?

Dave schrieb:
> Nun dachte ich mir, alle IRQs sperren ist etwas zu viel des Guten, es
> geht ja nur um das Auslesen der Tastenpins.

Wenn Dein IRQ so extrem zeitkritisch ist, daß er nicht mal eine 
Verzögerung um 2 CPU-Befehle verträgt, dann hast Du weitaus größere 
Probleme.

von Martin L. (schrau_baer)


Lesenswert?

So ich nochmal, nachdem ich den Fehler der richtigen Deklaration beheben 
konnte nun eine neue Herausforderung:

Es sind 4 Taster am PIC angeschlossen an RA0, RA1, RA6 und RA7 (alle als 
Input in den TRISA Regsiter gesetzt, der Rest des PortsA ist als Output 
deklariert)). Die Entprellroutine funktioniert auch zu 50% denn nur die 
letzten beiden Taster RB6&7 werden ausgewertet. Ich bin leider etwas 
verunsichert ob ich etwas falsch gemachte habe daher hier dier Code in 
voller Schönheit. Nicht wunder, es handelt sich um einen Microchip 
PIC16F628.

Hier der Inhalt der Headerdatei:
1
#ifndef UNBOUNCE_H
2
#define UNBOUNCE_H
3
4
#include "sw_spi.h"
5
6
#define KEY_PORT        PORTA               // Input register
7
#define KEY0            /*PORTAbits.RA*/1   // KEY 0...3
8
#define KEY1            /*PORTAbits.RA*/0   //
9
#define KEY2            /*PORTAbits.RA*/6   //
10
#define KEY3            /*PORTAbits.RA*/7   //
11
12
                        // Type all keys for repeat and long press function */
13
#define REPEAT_MASK     (1<<KEY0|1<<KEY1|1<<KEY2|1<<KEY3)
14
#define REPEAT_START    50   // After 500ms (start time long press)
15
#define REPEAT_NEXT     50   // Every 200ms (repeat time)
16
#define TIMEOUT              // Define when timout is needed for two
17
                             // keys pressed together
18
19
void interrupt service_interrupt(void);
20
21
uint8_t get_key_press( uint8_t key_mask );
22
23
uint8_t get_key_release(uint8_t key_mask);
24
25
uint8_t get_key_rpt( uint8_t key_mask );
26
27
uint8_t get_key_short( uint8_t key_mask );
28
29
uint8_t get_key_long( uint8_t key_mask );
30
31
uint8_t get_key_long_r( uint8_t key_mask );
32
33
uint8_t get_key_rpt_l( uint8_t key_mask );
34
35
uint8_t get_key_common( uint8_t key_mask );
36
37
#endif

Und hier nachfolgend die Funktionen:
1
#include "unbounce.h"
2
3
volatile uint8_t key_state;                // Debounced and inverted key state:
4
                                           // Bit = 1: key pressed
5
volatile uint8_t key_press;                // Key press detect
6
volatile uint8_t key_release;              // Key release detected
7
volatile uint8_t key_rpt;                  // Key long press and repeat
8
9
//******************************************************************************
10
//                              INTERRUPTS
11
//******************************************************************************
12
//#INT_TIMER0                                     // Timer 0 interrupt
13
void  interrupt service_interrupt(void)
14
{
15
    TMR0=0x0064;                         // 156 x 64µs = 9.984ms (~10ms)
16
                                         // Preset 100 = 256 - 156
17
                                         //Setzt den Zählerstand auf x von 255
18
    static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt;
19
    uint8_t i;
20
21
    i = key_state ^ ~KEY_PORT;                  // Key changed ? active low key
22
    //i = key_state ^ KEY_PORT;                   // Active high key
23
    ct0 = ~( ct0 & i );                         // Reset or count ct0
24
    ct1 = ct0 ^ (ct1 & i);                      // Reset or count ct1
25
    i &= ct0 & ct1;                             // Count until roll over ?
26
    key_state ^= i;                             // Then toggle debounced state
27
    key_press |= key_state & i;                 // 0->1: key press detect
28
    key_release |= ~key_state & i;              // 1->0: key release detect
29
30
    if( (key_state & REPEAT_MASK) == 0 )        // Check repeat function
31
        rpt = REPEAT_START;                     // Start delay
32
33
    if( --rpt == 0 ){
34
        rpt = REPEAT_NEXT;                      // Repeat delay
35
        key_rpt |= key_state & REPEAT_MASK;     // Active low keys
36
    }
37
    INTCONbits.T0IF=0; //Setzt das Interruptflag nach Zählerüberlauf zurück
38
}
39
40
//******************************************************************************
41
//                              FUNCTIONS
42
//******************************************************************************
43
//______________________________________________________________________________
44
// Button debounce routines
45
uint8_t get_key_press( uint8_t key_mask ){
46
    //disable_interrupts(GLOBAL);
47
    INTCONbits.GIE = 0;
48
    key_mask &= key_press;                      // Read key(s)
49
    key_press ^= key_mask;                      // Clear key(s)
50
    //enable_interrupts(GLOBAL);
51
    INTCONbits.GIE = 1;
52
    return key_mask;
53
}
54
55
uint8_t get_key_release(uint8_t key_mask){
56
    //disable_interrupts(GLOBAL);
57
    INTCONbits.GIE = 0;
58
    key_mask &= key_release;                    // Read key(s)
59
    key_release ^= key_mask;                    // Clear key(s)
60
    //enable_interrupts(GLOBAL);
61
    INTCONbits.GIE = 1;
62
    return key_mask;
63
}
64
65
uint8_t get_key_rpt( uint8_t key_mask ){
66
    //disable_interrupts(GLOBAL);
67
    INTCONbits.GIE = 0;
68
    key_mask &= key_rpt;                        // Read key(s)
69
    key_rpt ^= key_mask;                        // Clear key(s)
70
    //enable_interrupts(GLOBAL);
71
    INTCONbits.GIE = 1;
72
    return key_mask;
73
}
74
75
uint8_t get_key_short( uint8_t key_mask ){
76
    uint8_t i;
77
    //disable_interrupts(GLOBAL);
78
    INTCONbits.GIE = 0;
79
    i = get_key_press( ~key_state & key_mask );
80
    INTCONbits.GIE = 1;
81
    return i;
82
}
83
84
uint8_t get_key_long( uint8_t key_mask ){
85
    return get_key_press( get_key_rpt( key_mask ));
86
}
87
88
uint8_t get_key_long_r( uint8_t key_mask ){           // If repeat function needed
89
    return get_key_press( get_key_rpt( key_press & key_mask ));
90
}
91
92
uint8_t get_key_rpt_l( uint8_t key_mask ){            // If long function needed
93
    return get_key_rpt( ~key_press & key_mask );
94
}
95
96
uint8_t get_key_common( uint8_t key_mask ){
97
  return get_key_press((key_press & key_mask) == key_mask ? key_mask : 0);
98
}
99
;

Irgendwie will der Fehler sich mir nicht offenbaren.

Gruß

: Bearbeitet durch User
von Moritz A. (moritz_a)


Angehängte Dateien:

Lesenswert?

Um das Verhalten der "inneren" Variablen sowie der verschiedenen 
Abfrageroutinen mal genauer zu beobachten sowie damit zu experimentieren 
habe ich die ganze Routine mal in ein PC-taugliches C-Programm 
gewandelt.

Vielleicht kann damit ja noch jemand was anfangen.

von Wuhu W. (wuhuuu2)


Lesenswert?

Hallo,

ich verwendet die original Routine von Peter Dannegger und diese 
funktioniert perfekt.

Bevor ich es für mein Projekt versuche, funktioniert die Routine auch, 
wenn man z.B. 3 PINs als Ausgang schaltet.
Das bedeutet man wertet einen gesamten Port aus, der sowohl Ein als auch 
Ausgänge hat.

Können(und wenn ja welche?) Probleme auftreten?

Liebe Grüße

von Joachim B. (jar)


Lesenswert?

Wuhu W. schrieb:
> funktioniert die Routine auch,
> wenn man z.B. 3 PINs als Ausgang schaltet.

ja

Wuhu W. schrieb:
> Das bedeutet man wertet einen gesamten Port aus, der sowohl Ein als auch
> Ausgänge hat.

aber das Ergebnis wird mit den definierten KEYs (Eingänge) oder bei der 
Abfrage *) verundiert, je nach dem ob high- oder low- aktiv, bei Abfrage 
auf high ver"unden", bei Abfrage auf low negiert ver"unden"

*) ret_key=get_key_press( ALL_KEYS ); // welche Taste(n) will ich 
wissen?
1
// key.h -----------------------
2
3
#ifndef KEY_H
4
#define KEY_H
5
6
uint8_t get_key_press( uint8_t );
7
uint8_t get_key_rpt( uint8_t );
8
uint8_t get_key_short( uint8_t );
9
uint8_t get_key_long( uint8_t );
10
uint8_t test_i2c_key(void);
11
//void timer2_init(void);
12
13
extern volatile uint8_t key_state;  // debounced and inverted key state:
14
                                    // bit = 1: key pressed
15
extern volatile uint8_t key_press;  // key press detect
16
extern volatile uint8_t key_rpt;    // key long press and repeat
17
extern volatile uint8_t ct0, ct1, rpt;
18
extern volatile uint8_t key;
19
20
21
// --------------- KEY --------------- 
22
#if defined(__AVR_ATmega328P__)
23
  // D5 (T1)        PD5
24
  #define KEY_DDR     DDRD
25
  #define KEY_PORT    PORTD
26
  #define KEY_PIN     PIND
27
  #define KEY_UP         5
28
  // D6 (AIN0)      PD6
29
  #define KEY_DOWN       6
30
  // D7 (AIN1)      PD7
31
  #define SET_TASTE      4
32
33
  // D8 (ICP1)      PB0
34
  // D9 (OC1A)      PB1 (PWM)
35
#endif
36
37
#define ALL_KEYS         (1<<KEY_UP | 1<<KEY_DOWN | 1<<SET_TASTE)
38
 
39
#define REPEAT_MASK     (1<<KEY_UP | 1<<KEY_DOWN)
40
#define REPEAT_START    50                        // after 500ms
41
#define REPEAT_NEXT     15                        // every 200ms
42
43
extern volatile uint8_t key_state;  // debounced and inverted key state:
44
                                    // bit = 1: key pressed
45
extern volatile uint8_t key_press;  // key press detect
46
extern volatile uint8_t key_rpt;    // key long press and repeat
47
extern volatile uint8_t ct0, ct1, rpt;
48
49
#endif // #ifndef KEY_H
50
// key.h -----------------------
51
52
// isr -----------------------
53
    ii = key_state ^ ~KEY_PIN;
54
    ct0 = ~( ct0 & ii );                             // reset or count ct0
55
    ct1 = ct0 ^ (ct1 & ii);                          // reset or count ct1
56
    ii &= ct0 & ct1;                                 // count until roll over ?
57
    key_state ^= ii;                                 // then toggle debounced state
58
    key_press |= key_state & ii;                     // 0->1: key press detect
59
               
60
    if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
61
      rpt = REPEAT_START;                          // start delay
62
    if( --rpt == 0 )
63
    {  rpt = REPEAT_NEXT;                            // repeat delay
64
       key_rpt |= key_state & REPEAT_MASK;
65
    }
66
// isr -----------------------
67
68
// loop -----------------------
69
  ret_key=get_key_press( ALL_KEYS );
70
71
  if(ret_key&(1<<SET_TASTE))
72
  { ret_key&=~(1<<SET_TASTE); // ~ Port IN negiert weil low Abfrage
73
// loop Auschnitt -----------------------

von Peter D. (peda)


Lesenswert?

Wuhu W. schrieb:
> Können(und wenn ja welche?) Probleme auftreten?

Die Entprellung ist je Pin unabhängig.
D.h. auch 7 Out-Pins sind dem einen In-Pin völlig wurscht.
Nur die Maske für Repeat-Funktion will nur die aktiven Inputs sehen.

von Christian F (Gast)


Lesenswert?

http://www.compuphase.com/electronics/debouncing.htm

Falls ihr immer noch Schwierigkeiten oder ein Brett vorm Kopf habt, lest 
euch die Erklärung im Link auch mal durch. Etwas kleinschrittiger.
Ich habe etwas gebraucht (also nen guten halben Bleistift) an Hand des 
obigen Codes das Konzept nachzugehen. Man hat, glaub ich, einfach zuerst 
Probleme sich klar zu machen, was Peter wann als logisch '1' und '0' 
speichert (die internen Zustände und aktuellen Ports) und dass das Ding 
halt rückwärts zählt.

von Simpel (Gast)


Lesenswert?

Wenn es nur um das Entprellen geht, reicht es, wenn der Eingang in 
definierten Zeitintervallen (Timer-Int.) abgefragt wird, die größer sind 
als die max. Prellzeit.

Beispiel:
Der Taster wurde gedrückt und prellt noch während der 1. Abfrage.

Möglichkeit 1:
Er wird während des Prellens zufällig als "nicht gedrückt" erfasst. 
Nicht weiter schlimm: Er war vorher nicht gedrückt und wird nun eben für 
eine Intervall-Länge weiterhin als "nicht gedrückt" angesehen. Danach 
hat er ab der 2. Abfrage ausgeprellt und wird fortan statisch als 
"gedrückt" erfasst, solange er gedrückt bleibt.

Möglichkeit 2:
Er wird schon während des Prellens zufällig als "gedrückt" erfasst. 
Prima! Das war ja auch die Absicht beim Drücken. Bei den folgenden 
Abfragen hat er ausgeprellt und wird weiterhin statisch als "gedrückt" 
ausgewertet.

Mehr Aufwand zum reinen Entprellen braucht's nicht.

Alles darüber Hinausgehende dient nicht mehr dem Entprellen des Tasters, 
sondern der prophylaktischen Störunterdrückung von extern eingestreuten 
Spikes oder Bursts auf die mitunter langen Taster-Zuleitungen. Wo diese 
Störquellen ausgeschlossen werden können, reicht die Timer-Entprellung.

von Bastler (Gast)


Lesenswert?

Spart wieviele FLASH-/RAM-Bytes?
Ganz abgesehen von der Implementierungs-/Fehlersuch-/Umbau(weils doch 
mehr Funktion braucht)-Zeit.
Es gibt was, das tut was es soll, wozu also neues erfinden.

von Simpel (Gast)


Lesenswert?

@Bastler

Es ging mir nur um die simple Betrachtung des reinen Entprellens, das 
bzgl. der Komplexität meist 'überkandidelt' interpretiert und "gelöst" 
wird.

Dazu braucht es nicht eine einzige zusätzliche Codezeile. Die 
Eingangsabfrage muss ohnehin gemacht werden. Indem man diese in einen 
zeitlich passenden Timerinterrupt verfrachtet, ist das reine 
"Entprellungsproblem" gelöst, wie ich im vorigen Beitrag gezeigt habe.

Dort wo diese reine Entprell-Funktionalität ausreicht, braucht man keine 
All-inclusive-Routine.

von Karl M. (Gast)


Lesenswert?

Hallo Simpel,

im rein mathematischen Sinn, hast Du leider nichts gezeigt.

Anforderung, ich habe 8 Tastereingänge und möchte verschiedene Events 
von der Entprellroutine erhalten.

1) Wurde eine Taste gedrückt ?
2) Wurde eine Taste losgelassen ?
3) Ist eine Taste immer noch gedrückt (repeat pressed) ?
4) Wurde eine Taste nur kurz gedrückt ?

Bitte schreibe, als Beweis, das Vorgegebene als Code deine bevorzugten 
Programmiersprache nieder.

von Moby A. (moby-project) Benutzerseite


Lesenswert?

Karl M. schrieb:
> Bitte schreibe, als Beweis

Was soll er beweisen? Denn

Simpel schrieb:
> Es ging mir nur um die simple Betrachtung des reinen Entprellens

Mit seiner entsprechenden (simplen) Problemlösung hat er ganz recht, da 
brauchts doch nicht mehr (erwähnte fehlende Störeinstreuungen 
vorausgesetzt).

: Bearbeitet durch User
von T.G. (Gast)


Lesenswert?

Hallo,
ich habe die Entprellung bei mir mit einem PIC implementiert, und sie 
funktioniert auch einwandfrei.

Jetzt habe ich aber ein Problem weil ich einen zweiten Timer habe der 
alle 148 us aufgerufen wird.
Mein Timer für den Entpreller ist auf 10ms gestellt.

der zweite Timer hat eine höhere Priorität, aber selbst ohne die bringt 
er die Routine anscheinend so durcheinander das Tasten als gedrückt 
erkannt werden obwohl diese nicht gedrückt werden. Oder erst nach 
mehrmaligem schnellen drücken erkannt werden.

Kann das an dem schnellen Timer liegen oder habe ich doch irgendwo in 
meinem Code mist gebaut? Sobald ich den Timer deaktiviere oder dessen 
Interrupt funktioniert alles.

von T.G. (Gast)


Lesenswert?

Ich kann meine Frage selber beantworten...

Zitat datasheet:
If both low-priority and high-priority interrupts are
enabled, the Stack registers cannot be used reliably to
return from low-priority interrupts. If a high-priority
interrupt occurs while servicing a low-priority interrupt,
the Stack register values stored by the low-priority
interrupt will be overwritten. In these cases, users must
save the key registers in software during a low-priority
interrupt.

Normalerweise erledigt das ja der Compiler.
Natürlich unter der Voraussetzung das man richtig programmiert.
Auszug aus dem C Compiler Handbuch:
A high-priority interrupt uses the shadow registers to save and restore 
the minimal context, while a low-priority interrupt uses the software 
stack to save and restore the minimal context
1
#pragma code
2
#pragma interrupt high_prior_InterruptHandler
3
4
void high_prior_InterruptHandler(void)
5
{}
6
7
#pragma code
8
#pragma interrupt low_prior_InterruptHandler
9
void low_prior_InterruptHandler(void)
10
{}

Keine Ahnung ob das aus einem Beispiel kopiert wurde oder wo ich das her 
hatte, das Projekt liegt schon etwas länger in der Ecke.
Wenn aber für beide Interrupts das gleiche Register zum speichern 
verwendet wird, tritt denke ich genau das ein was oben beschrieben 
wurde.
1
#pragma code
2
#pragma interruptlow low_prior_InterruptHandler
3
void low_prior_InterruptHandler(void)
4
{}
Das ist also korrekt. Und jetzt funktioniert es auch :)

von AVR-Bastler (Gast)


Lesenswert?

Hallo Peter!

Deine Entprell-Routine nutze ich schon seit ein paar Jahren - vielen 
Dank dafür!

Nun wollte ich die get_key_long() Funktion anwenden (zur Einstellung 
eines Zahlenwertes: kurzer Druck = +1, langer Druck = +10).

Ergebnis: Ein langer Tastendruck wird zuverlässig erkannt. Bei einem 
kurzen gibt es leider nur eine Erfolgsquote von ca. 90%, d.h. etwa jeder 
10te wird falsch als langer Tastendruck erkannt.
Etwas verwirrt habe ich einen Attiny13A nur mit der Entprellroutine 
gefüttert, ein Knopf, zwei LEDs, 9,6MHz/8, verschiedene Werte für 
REPEAT_START und  REPEAT_NEXT, aber dasselbe Problem.
Hat jemand schon einmal Åhnliches beobachtet?


Außerdem ist mir folgendes aufgefallen:
Ich hatte den Sinn von REPEAT_NEXT so verstanden, dass ein weiterer 
langer Druck gezählt wird, wenn der Knopf nach Ablauf dieses 
Zeitintervalls immer noch gedrückt ist.

Nun wird in der get_key_long() aber hier:
1
return get_key_press( get_key_rpt( key_mask ));
key_press durch den Aufruf von get_key_press() gelöscht, und jeder 
folgende Aufruf von get_key_long() liefert 0, auch wenn in key_rpt das 
entsprechende Bit gesetzt ist.
Mit
1
return (key_press & get_key_rpt( key_mask ));
wird es etwas besser, aber dann gibt's nach jedem langen Tastendruck 
einen kurzen "gratis", was wohl auch nicht der "Spezifikation" 
entspricht...
Mir fällt aber nix ein, wie man dies ohne Einführung einer weiteren 
Variable lösen könnte.

von chris (Gast)


Lesenswert?

Folgender Code funktioniert bei mir einwandfrei:


1
static uint8_t lcd_update = 0;
2
static char buffer[20];
3
static uint32_t counter = 0;
4
5
      // decrement
6
      if (get_key_press(1<<KEY_DEC))
7
      {
8
        if (counter > 0)
9
        {
10
          counter--;
11
          lcd_update = 1;
12
        }
13
      }
14
      if (get_key_rpt(1<<KEY_DEC))
15
      {
16
        if (counter >= 10)
17
        {
18
          counter -= 10;
19
        }
20
        else
21
        {
22
          counter = 0;
23
        }
24
        
25
        lcd_update = 1;
26
      }
27
28
29
      if (lcd_update)
30
      {
31
        lcd_update = 0;
32
        lcd_setcursor(0,1);
33
        sprintf(buffer, "%16" PRIu32 "", counter);
34
        lcd_string(buffer);
35
      }


lg
Chris

von AVR-Bastler (Gast)


Lesenswert?

Hallo Chris,

OK, auch da wird natürlich ein langer Druck zusätzlich als kurzer 
gezählt - allerdings gleich am Anfang und nicht erst beim Loslassen.
Das dürfte für den User besser einzuschätzen und komfortabler sein.

Hast Du schon einmal das Problem mit "falsch-langen" gehabt (also einem 
kurzen Druck, der als langer erkannt wurde?)

Mit welchem Takt arbeitest Du? Wieviele Interrupts generierst Du?

In meiner "richtigen" Anwendung habe ich F_CPU = 1MHz. Ich brauche einen 
Millisekunden-Takt, den ich mit TIM0_COMPA generiere.
Bei jedem 10. wird dann per
1
 if (!(msec % 10))
2
  {
3
  debounce();
4
...
die Abfrage der Taster vorgenommen.
Der Tiny13A zum Testen taktet geringfügig höher (9,6/8MHz).

von chris (Gast)


Lesenswert?

Hi,

genau, der erste Tastendruck erhöht um 1 (bzw. erniedrigt in meinem 
Beispiel). Hält man weiter gedrückt, wird in 10er Schritten gezählt. 
Beim Loslassen passiert nichts.

Ich führe die Tastenentprellung im Timerinterrupt alle 10 ms durch.
Wenn ich auch einen ms-Takt brauche, stelle ich den Interrupt auf 1ms 
und zähle im Interrupt eine Variable bis 10 hoch. Die Entprellfunktion 
rufe ich direkt im Interrupt (als inline Funktion) auf.

von Peter D. (peda)


Lesenswert?

AVR-Bastler schrieb im Beitrag #4471676:
> Ergebnis: Ein langer Tastendruck wird zuverlässig erkannt. Bei einem
> kurzen gibt es leider nur eine Erfolgsquote von ca. 90%, d.h. etwa jeder
> 10te wird falsch als langer Tastendruck erkannt.

Dann ist die Zeit für lang zu lurz.

AVR-Bastler schrieb im Beitrag #4471676:
> Ich hatte den Sinn von REPEAT_NEXT so verstanden, dass ein weiterer
> langer Druck gezählt wird, wenn der Knopf nach Ablauf dieses
> Zeitintervalls immer noch gedrückt ist.

Das wird kompliziert, wenn man alle 3 Sachen machen will (kurz, lang, 
repepeat), nicht nur für den Bediener.

Beitrag "Re: Universelle Tastenabfrage"

Beitrag "Re: Universelle Tastenabfrage"

von chris (Gast)


Lesenswert?

AVR-Bastler schrieb im Beitrag #4472189:
> if (!(msec % 10))
>   {
>   debounce();
> ...


Da fällt mir gerade auf:
Wenn dieser Code bei dir in der Hauptschleife steht, kann er innerhalb 
einer Millisekunde öfter aufgerufen werden, wenn deine Schleifenlaufzeit 
kleiner als 1 ms ist. Weil die geprüfte Bedingung ja die ganze Zeit 
erfüllt ist, bis msec erhöht wird.
Das würde das falsch-erkennen der langen/kurzen Tastendrücke erklären.
Mach vielleicht besser einen Zähler, der bis 10 zählt und wieder auf 0 
gesetzt wird. Dann wird wirklich nur jedes 10. mal die Entprellung 
durchgeführt.

von AVR-Bastler (Gast)


Lesenswert?

chris schrieb:
> Da fällt mir gerade auf:
> Wenn dieser Code bei dir in der Hauptschleife steht, kann er innerhalb
> einer Millisekunde öfter aufgerufen werden,

Das ist ein valider Punkt. - Danke!
Ich dachte schon, das war's. Leider nein.

Aber ich habe nun mit den Zeiten noch etwas gespielt.
Wenn ich die debounce() alle 50ms aufrufe, funktioniert es. Dafür fallen 
dann sehr schnelle Tastendrücke hinten runter.

Also kann ich mir aussuchen, ob ich am langen oder am kurzen (Zeit-) 
Ende Fehler habe.
Ich denke, der Taster ist einfach schrottig.

von chris (Gast)


Lesenswert?

AVR-Bastler schrieb im Beitrag #4473359:
> Das ist ein valider Punkt. - Danke!
> Ich dachte schon, das war's. Leider nein.

Wie sieht denn dein aktueller (vollständiger) Code aus?

von AVR-Bastler (Gast)


Lesenswert?

Hallo Peter,

Peter D. schrieb:
> Dann ist die Zeit für lang zu lurz.

Ich habe die REPEAT_START schon auf 255.

Aber danke für die get_key_long_r() und get_key_rpt_l(). Ich werde die 
morgen mal simulieren.

von Peter D. (peda)


Lesenswert?

AVR-Bastler schrieb im Beitrag #4473359:
> Wenn ich die debounce() alle 50ms aufrufe, funktioniert es.

Das glaub ich Dir nicht.

50ms * 255 = 12,75s.
Da mußt Du ja auf der Taste einschlafen für Langdruck.

Also entweder Deine Zeitbasis stimmt nicht, Deine 50ms stimmen nicht 
oder Du hast in der Repeat-Maske Pins drin, die garnicht für Langdruck 
vorgesehen sind.

von AVR-Bastler (Gast)


Lesenswert?

Sorry - hab gerade "Land unter", da bin ich noch nicht wieder dazu 
gekommen.

Deine Rechnung ist natürlich richtig, Peter.
Deine Annahme, ich würde bei der Fehlersuche wahllos alle Parameter 
gleichzeitig ändern, natürlich nicht.

Aber auch 255*10ms = 2,55s ist für einen Anwender zu lang. Trotzdem habe 
ich es damit mal versucht, einfach um herauszufinden, ob dieser 
Parameter das Problem löst. Hat er nicht.

von Johannes (menschenskind)


Lesenswert?

Hi Peter,

Ich habe Deinen Code jetzt integriert.
Woran ich aber gerade grüble ist, dass das "get_key_long" bereits nach 
sehr kurzem "Lang-Drücken" des Tasters registriert wird.

Ich habe in den Defines diesen Wert von 50 auf 100 geändert "#define 
REPEAT_START    100 "
Da sich das Verhalten aber nicht ändert, nehme ich also an, dass ich an 
der falschen Stelle gedreht habe.
Mein Ziel ist es, ein Lang-Drücken von etwa 2 Sekunden zu detektieren.

Danke im Voraus
Hannes

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johannes H. schrieb:
> Da sich das Verhalten aber nicht ändert, nehme ich also an, dass ich an
> der falschen Stelle gedreht habe.

Du hast hier verschwiegen, dass Du noch woanders dran geschraubt hast, 
wie Du in folgendem Thread schreibst.

Johannes H. schrieb:
> Ebenso habe ich den Vorteilquotienten von 1024 auf 1600 gesetzt da ich
> mit einem höheren Takt als in Deinem Beispiel arbeite.

1600? Zeig mal den Code, wie Du das gemacht hast.

von Johannes (menschenskind)


Lesenswert?

Ja ertappt :)

Also es geht um diese Zeile:
1
TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
Hab mir ausgerechnet, welcher Wert für 16MHz da ungefähr stehen muss, um 
10ms zu erhalten-->1600

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johannes H. schrieb:
> Hab mir ausgerechnet, welcher Wert für 16MHz da ungefähr stehen muss, um
> 10ms zu erhalten-->1600

Dann schau Dir mal TCCR0 im Datenblatt und im Code an! Das wird auf den 
Teiler 1024 gesetzt. Diesen setzt Peter dann für TCNT0 wieder ein.

Du kannst da nicht einfach 1600 hinschreiben. Den Teiler 1600 kann man 
(naturgemäß) gar nicht konfigurieren in TCCR0. Im Datenblatt stehen die 
gültigen Teiler. Wenn, dann solltest Du besser den Faktor 10e-3 
korrigieren. Mit welcher Frequenz läuft Peters Code? 1MHz oder 8MHz? 
Dann überlege, wie Du ihn für 16MHz modifizieren musst.

Im anderen Thread hatte Dich auch schon jemand darauf hingewiesen... 
aber offenbar hast Du das nicht verstanden ;-)

Es kommt bei Peters Code gar nicht so groß auf die Zeiten an. Sie 
sollten jedoch zumindest größenordnungsmäßig passen. Das reicht in den 
meisten Fällen.

: Bearbeitet durch Moderator
von Johannes (menschenskind)


Lesenswert?

Ja den Vorteiler nicht, aber den Zählwert im TCNT-Register kann man doch 
beliebig setzen.

Peters Code nutzt 1MHz, da habe ich mich um Faktor 10 verguckt.
Also wird bei meinen 16MHz der Interrupt 16 Mal häufiger aufgerufen 
(wenn ich den Code so lasse), oder?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johannes H. schrieb:
> Peters Code nutzt 1MHz, da habe ich mich um Faktor 10 verguckt.
> Also wird bei meinen 16MHz der Interrupt 16 Mal häufiger aufgerufen
> (wenn ich den Code so lasse), oder?

Peter hat das portabel formuliert für TCNT0: Er benutzt F_CPU. Wenn 
dieser bei Dir auf 16000000UL steht, dann ist alles bereits richtig. Der 
Wert

  (F_CPU / 1024 * 10e-3 + 0.5)

wird dann automatisch 16 mal größer.

Du brauchst also in der Regel nichts mehr anzupassen.

: Bearbeitet durch Moderator
von Johannes (menschenskind)


Lesenswert?

Ok, ja seh ich ein, da bin ich einem Denkfehler aufgesessen...

Aber das ursprüngliche Problem dass "get_key_long" nach einer viel zu 
kurzen Zeit auslöst, besteht immer noch.
Hast Du da noch eine Idee?

von Karl M. (Gast)


Lesenswert?

Johannes H.,

bei mir funktioniert das alles, es liegt an deinem Programm.
Wo ist das ?

von Johannes (menschenskind)


Lesenswert?

Hallo,

Da ist der Code:
1
#include "includes/config.h"
2
#include "includes/debounce.h"
3
#include "includes/light_ws2812.h"
4
5
#include <util/delay.h>
6
#include <avr/io.h>
7
#include <avr/interrupt.h>
8
#include <avr/sleep.h>
9
10
struct cRGB LED_WHEEL_16[16];
11
struct cRGB COLORS [8];
12
13
#define LEDS 16
14
15
uint8_t value_max = 10;
16
int8_t led_index = 0;
17
int8_t led_dark_step = 2;
18
19
float brightness_red=0.1, brightness_green=0.1, brightness_blue=0.1;
20
21
void dark(uint8_t led_index, uint8_t led_dark_step){
22
  if (led_index - led_dark_step < 0) led_index = LEDS - abs(led_index - led_dark_step);
23
  else led_index -= led_dark_step;
24
  
25
  LED_WHEEL_16[led_index].r=0;
26
  LED_WHEEL_16[led_index].g=0;
27
  LED_WHEEL_16[led_index].b=0;  
28
}
29
30
int main(void){
31
  LED_INIT();  // Set LED pins 
32
  
33
  /**** Set Timer for Debounce Functionality ****/
34
  TCCR0 = (1<<CS02)|(1<<CS00);         // divide by 1024
35
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1600 * 10e-3 + 0.5);  // preload for 10ms
36
  TIMSK |= 1<<TOIE0;                   // enable timer interrupt
37
  
38
  sei();    // Enable global interrupts
39
40
  while(1){  
41
        
42
    if( get_key_short( 1<<KEY2 ))
43
      LED_EVENT(LED2,1);  
44
    if (get_key_long(1<<KEY2))  
45
      LED_EVENT(LED2,5);  
46
      
47
    LED_WHEEL_16[led_index].r=value_max;LED_WHEEL_16[led_index].g=00;LED_WHEEL_16[led_index].b=value_max;// blue
48
    ws2812_setleds(LED_WHEEL_16,LEDS);
49
    _delay_ms(200);
50
    dark(led_index, led_dark_step);
51
    ws2812_setleds(LED_WHEEL_16,LEDS);
52
    
53
    led_index++;
54
    if(led_index == 16){
55
      led_index = 0;
56
    }    
57
  }
58
}

Den ganzen Debounce-Code habe ich eine separate c-Datei ausgelagert.

Karl, wie stellst Du denn ein, nach welcher Zeit "get_key_long" auslöst?

von Jörg (Gast)


Lesenswert?

Johannes H. schrieb:
> wie stellst Du denn ein, nach welcher Zeit "get_key_long" auslöst?

Dort wo Du Repeat_Start definiert hast.

von M. K. (kichi)


Angehängte Dateien:

Lesenswert?

Ich habe mal versucht alle Funktionalitäten aus dem Thread zusammen zu 
tragen und in ein möglichst wiederverwertbares Modul zu packen. 
Vielleicht kann es jemand brauchen.

Die verwendete cm_atomic.h ist unter folgendem Link zu finden: 
Beitrag "Re: atomic-lib für stm32"

von Karl M. (Gast)


Lesenswert?

Hallo Michael !

und hier ist schon der erste Fehler !
1
#define  REPEAT_START         50
2
#define  REPEAT_NEXT          20

Dieser Wert ist direkt abgängig von dem Pollintervall !

von M. K. (kichi)


Lesenswert?

Dass die Werte vom Pollintervall abhängen ist klar, aber warum soll das 
dann gleich falsch sein?

Mag ja sein, dass du recht hast, verstehen tue ich es aber nicht. 
Zugegebenermaßen habe ich die Repeat-Geschichten aber auch noch nie 
genutzt, sondern einfach so übernommen. Daher ist es sehr 
wahrscheinlich, dass ich den entsprechenden Beitrag nicht gelesen habe.

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

Lieber Herr Dannegger, Vielen Dank!
Der Code arbeitet sofort und perfekt!
Ich sollte nur am Anfang für mehr Freiheit mit Pins für die Tasten etwas 
anpassen:
1
void knopf_abfrage(void){
2
3
  static unsigned char ct0 = 0xFF, ct1 = 0xFF, rpt;
4
    unsigned char i, key_pin;
5
6
  key_pin = 0xff;
7
  if(!T_U) key_pin &= ~(1<<KEY_U);
8
  if(!T_O) key_pin &= ~(1<<KEY_O);
9
  if(!T_M) key_pin &= ~(1<<KEY_M);
10
  if(!T_SP) key_pin &= ~(1<<KEY_SP);
11
  if(!T_START) key_pin &= ~(1<<KEY_START);
12
13
  i = key_state ^ ~key_pin;     // key changed ?
usw.

mit entsprechenden
1
#define PIN_KN_U PINB
2
#define KN_U PB3
3
#define T_U (PIN_KN_U & (1<<KN_U))
4
#define KEY_U 4 
5
6
#define PIN_KN_O PINB 
7
#define KN_O PB4
8
#define T_O (PIN_KN_O & (1<<KN_O))
9
#define KEY_O 3  
10
11
#define PIN_KN_M PINC
12
#define KN_M PC0
13
#define T_M (PIN_KN_M & (1<<KN_M))
14
#define KEY_M 2
15
16
#define PIN_KN_SP PINC
17
#define KN_SP PC1
18
#define T_SP (PIN_KN_SP & (1<<KN_SP))
19
#define KEY_SP 1 
20
21
#define PIN_KN_START PINB
22
#define KN_START PB2
23
#define T_START (PIN_KN_START & (1<<KN_START))
24
#define KEY_START 0

Auch REPEAT_NEXT habe ich auf 10 gesetzt, aber das ist wohl eine 
Geschmackssache.

Vielen Dank für diese Möglichkeit!

: Bearbeitet durch User
von Henning (Gast)


Lesenswert?

Hallo,

ich würde meine C Kenntnisse als sehr bescheiden bezeichnen. Dennoch 
versuche Ich gerade den Code aus 
Beitrag "Universelle Tastenabfrage" zu verstehen.

Ich verstehe den Sinn folgender Anweisung nicht ...

ct0 = ~( ct0 & i );

Unmittelbar zuvor wird der Wert von ct0 initialisiert.

static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt;

Wenn nun ct0 mit 0xFF initialisiert wurde und ein bitweises und mit i 
erfolgt, dann kann das Ergebnis doch immer nur gleich dem Wert von i 
sein. Oder?

Demzufolge wäre doch ct0 ~= i; doch viel kürzer und einfacher zu 
verstehen.


Wo liegt mein Denkfehler?

Gruß
Henning

von Karl M. (Gast)


Lesenswert?

Guten Mittag,

in der Deklaration der Variable und ihre "Reichweite".
1
static uint8_t ct0;

von Henning (Gast)


Lesenswert?

Hallo,

was möchtest du mir damit sagen?
Input please ...


Im Beitrag zur Entprellung steht nichts von
static uint8_t ct0;

sondern
static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt;

Bedeutet das, das der Wiki Beitrag, der den Anfängern ja erklären soll 
wie es funktioniert, fehlerhaft ist?

Dann sollte das umgehend von einem "Wissenden" korrgiert werden...

Gruß
Henning

von STK500-Besitzer (Gast)


Lesenswert?

Henning schrieb:
> Hallo,
>
> was möchtest du mir damit sagen?
> Input please ...
>
> Im Beitrag zur Entprellung steht nichts von
> static uint8_t ct0;
>
> sondern
> static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt;
>
> Bedeutet das, das der Wiki Beitrag, der den Anfängern ja erklären soll
> wie es funktioniert, fehlerhaft ist?

Grundlagen!
Das "static" sorgt dafür, dass die Variable nur ein Mal initialisiert 
wird, und nach dem Verlassen der Funktion aber immer noch besteht 
(ähnlich einer globalen Variable, nur dass der Zugriff auf diese nur 
innerhalb dieser Funktion erfolgen kann.
Würde es fehlen, würde die Variable bei jedem Funktionsaufruf neu 
initialisiert werden.
Ob da nur eine defklariert wird, oder fünf, ist dabei egal.

von Bernd N (Gast)


Lesenswert?

Die Antwort von
Karl war schon richtig aber du hast nicht darüber nachgedacht.

https://de.wikibooks.org/wiki/C-Programmierung:_static_%26_Co.

>> Unmittelbar zuvor wird der Wert von ct0 initialisiert.
Deine Annahme stimmt nur für das erste Mal.

von Henning (Gast)


Lesenswert?

Ahhh,

jetzt hab ich's.

Habe aus Karl's erster Antwort einfach nicht entnehmen können, das es um 
das Wörtchen "static" in der Variablendeklaration geht...

Jetzt ist mir natürlich klar, das meine Schlussfolgerungen Unsinn waren.

Vielen Dank für die Erleuchtung.
Henning

von Johannes (menschenskind)


Lesenswert?

Hallo Peter & Andere,

Ich bin bei der Anwendung Der Entprellroutine in meiner Anwendung auf 
ein Problem gestoßen:
Mittels "Kurz Drücken" eines Tasters werden verschiedene Modi 
durchgeschaltet, sprich ein index inkrementiert.
Mittels "Lang Drücken" desselben Tasters wird der µC mit Deiner 
"try_sleep"-Funktion schlafen gelegt und mittels Level Change Interrupt 
wieder aufgeweckt.

Jetzt habe ich das Problem, dass beim Aufwecken auch gleichzeitig ein 
"Kurz Drücken" detektiert wird und damit der nächste Modus ausgewählt 
wird, was nicht erwünscht ist.

Leider habe keine Möglichkeit gefunden, während der Aufwachphase, das 
"get_key_short" temporär zu deaktivieren. Siehst Du/ihr eine 
Möglichkeit?

Danke & Gruß

von Edson (Gast)


Lesenswert?

Johannes H. schrieb im Beitrag #4892788
> Leider habe keine Möglichkeit gefunden, während der Aufwachphase, das
> "get_key_short" temporär zu deaktivieren. Siehst Du/ihr eine
> Möglichkeit?

Dann mach das doch in der Einschlaf-Phase. Also deinen Index um 1 
verringern, so dass er nach dem Aufwecken und dem "get_key_short" wieder 
stimmt.

von Johannes (menschenskind)


Lesenswert?

Hi Edson,
Ja das funktioniert, dass ich darauf nicht gekommen bin :)
Aber ich suche noch nach der eleganten Lösung. Den Index zu verringern 
ist ja nur ne Art Workaround.

Hast Du schon mit Peters Debounce Algorithmus gearbeitet?

von Johannes (menschenskind)


Lesenswert?

Hi, bin jetzt mit dem Workaround an eine weitere Grenze gestoßen und 
muss nun eine andere Lösung finden.
Ich hatte gerade probiert, beim Einschlafvorgang einfach den Timer 0 
Interrupt zu deaktivieren und eine kurze Zeit nach dem Aufwachen wieder 
zu aktivieren, da hängt sich das Programm aber irgendwo auf :(

Peter/Edson, was für Optionen fallen euch denn noch ein?

von Peter D. (peda)


Lesenswert?

Johannes H. schrieb:
> Leider habe keine Möglichkeit gefunden, während der Aufwachphase, das
> "get_key_short" temporär zu deaktivieren. Siehst Du/ihr eine
> Möglichkeit?

https://www.mikrocontroller.net/attachment/115266/Knightrider2.zip
1
    if( stop ){
2
      try_sleep();
3
      get_key_short( 1<<KEY0 );      // ignore short press during stop

von Johannes (menschenskind)


Lesenswert?

Hallo Peter,

Nein das funktioniert leider nicht :(
Es wird weiterhin nach dem Aufwachen das kurze Drücken detektiert.

von Mathias W. (mathias_w)


Lesenswert?

Hallo!

Horst hatte ja schon mal in seinem Beitrag 
Beitrag "Re: Universelle Tastenabfrage" angefragt, wie man 
die Repeat-Funktion nicht nur für eine Taste, sondern für alle Tasten 
unabhängig hinbekommt. Ein Szenario dafür ist z. B. ein Tastmodul mit 
acht Eingängen, an die Taster in acht verschiedenen Räumen angeschlossen 
sind.

Ich dachte mir, dass man dafür einfach einen weiteren vertikalen Zähler 
verwenden könnte, dann allerdings z. B. 3-bit oder sogar noch breiter.

Den Code stelle ich mir so vor:
1
volatile uint8_t key_state;                                // debounced and inverted key state:
2
                                                  // bit = 1: key pressed
3
volatile uint8_t key_press;                                // key press detect
4
 
5
volatile uint8_t key_rpt;                                  // key long press and repeat
6
7
ISR( TIMER0_OVF_vect )                            // every 10ms
8
{
9
  static uint8_t ct0 = 0xFF, ct1 = 0xFF;
10
  static uint8_t rpt_ct0 = 0xFF, rpt_ct1 = 0xFF, rpt_ct2 = 0xFF;
11
  uint8_t i;
12
 
13
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
14
 
15
  i = key_state ^ ~KEY_PIN;                       // key changed ?
16
  ct0 = ~( ct0 & i );                             // reset or count ct0
17
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
18
  i &= ct0 & ct1;                                 // count until roll over ?
19
  key_state ^= i;                                 // then toggle debounced state
20
  key_press |= key_state & i;                     // 0->1: key press detect
21
  key_release |= ~key_state & i;                  // 1->0: key release detect
22
23
  i = key_state
24
  rpt_ct0 = ~( rpt_ct0 & i );                     // reset or count rpt_ct0
25
  rpt_ct1 = rpt_ct0 ^ (rpt_ct1 & i);              // reset or count rpt_ct1
26
  rpt_ct2 = (rpt_ct0 & rpt_ct1) ^ (rpt_ct2 & i);  // reset or count rpt_ct2
27
  i &= rpt_ct0 & rpt_ct1 & rpt_ct2;               // count until roll over ?
28
  key_rpt |= key_state & i;                       // then repeat key
29
30
}

Die restlichen Funktionen sind identisch wie von Peter geposted.

Nur leider funktioniert das bei mir so nicht. Einige Tasten werden 
sofort bei get_key_short erkannt, andere sofort bei get_key_long. 
Zwischendurch ändert sich das mal. Keine der Tasten ändert beim länger 
drücken ihren Repeat-Status.

Wie müsste die Timer-Routine richtig aussehen und wie wäre die korrekte 
Erweiterung für einen 4- bzw. 5-bit vertikalen Counter?

von Mathias W. (mathias_w)


Lesenswert?

Ich muss mich korrigieren. Der von mir gepostete Code funktioniert 
genauso, wie gewünscht. Problem war nur, dass ich ihn gleich in 
Assembler implementiert und sich da ein Fehler eingeschlichen hatte. Im 
Beitrag hatte ich für mich als Kontrolle die C-Version geschrieben.

Ein weiteres Problem war das Timing. Ein 3-bit vertical counter ist doch 
bisl arg wenig. Nun schwanke ich zwischen eine 4-bit oder 5-bit vertical 
counter. Der 4-bit gefällt mir momentan am besten.

Das Schema zur Erweiterung um weitere Bits ist übrigens recht simpel und 
offensichtlich:
1
  i = key_state
2
  rpt_ct0 = ~( rpt_ct0 & i );                               // reset or count rpt_ct0
3
  rpt_ct1 = rpt_ct0 ^ (rpt_ct1 & i);                        // reset or count rpt_ct1
4
  rpt_ct2 = (rpt_ct0 & rpt_ct1) ^ (rpt_ct2 & i);            // reset or count rpt_ct2
5
  rpt_ct3 = (rpt_ct0 & rpt_ct1 & rpt_ct2) ^ (rpt_ct3 & i);  // reset or count rpt_ct3
6
  i &= rpt_ct0 & rpt_ct1 & rpt_ct2 & rpt_ct3;               // count until roll over ?
7
  key_rpt |= key_state & i;                                 // then repeat key

von Andreas B. (bitverdreher)


Lesenswert?

Mathias W. schrieb:
> Problem war nur, dass ich ihn gleich in
> Assembler implementiert und sich da ein Fehler eingeschlichen hatte.

Magst Du diesen Code auch mal veröffentlichen? Dann bräuchte ich mir 
nicht die Mühe machen, die Komfortroutine in Assembler zu übertragen. 
Die Kurz-Lang Erkennung wäre für mich wichtig.

von Wilhelm M. (wimalopaan)


Lesenswert?

Falls man bis zu 64 vertikale Zähler beliebiger Breite (besser: Höhe) 
braucht, kann man das folgende template VCounter nehmen. Es ist genau 
der gleiche vertikale Zähler mit impliziter Rücksetzung.


Vorteile:
- vollkommen generisch
- gekapselt

Nachteile:
- C++ mit TMP
- ein paar Zeilen Code in Header-Datei

Ist also der direkte Ersatz für ct0, ct1, ct2, ... im originalen Code.
1
// -> in Header vertical_counter.h
2
#include <cstdint>
3
#include <utility>
4
#include <array>
5
6
#if __has_include(<etl/type_traits.h>)
7
# include <etl/type_traits.h>
8
# define HAVE_ETL
9
#endif
10
11
template<auto N> using NumberOfCounters = std::integral_constant<size_t, N>;
12
template<auto N> using NumberOfBits     = std::integral_constant<size_t, N>;
13
14
template<typename Counters, typename CounterSize>
15
struct VCounter {
16
    inline static constexpr auto nCounters = Counters::value;
17
    inline static constexpr auto nDigits   = CounterSize::value;
18
    static_assert(nCounters <= (sizeof(uint64_t) * 8));
19
    
20
#ifdef HAVE_ETL
21
    using value_type = etl::typeForBits_t<nCounters>;
22
#else
23
    template<auto Bits>
24
    struct typeForBits {
25
        using type = typename std::conditional<(Bits <= 8), uint8_t, 
26
                         typename std::conditional<(Bits <= 16), uint16_t, 
27
                             typename std::conditional<(Bits <= 32), uint32_t,
28
                                  typename std::conditional<(Bits <= 64), uint64_t, void>::type>::type>::type>::type;
29
    };
30
    using value_type = typename typeForBits<nCounters>::type;
31
#endif
32
    inline constexpr VCounter& operator<<(const value_type which) {
33
        digits[0] = ~(digits[0] & which);
34
        update(std::make_index_sequence<nDigits-1>{}, which);
35
        return *this;        
36
    }
37
    inline constexpr value_type maxReached() {
38
        return [&]<auto... II>(std::index_sequence<II...>){
39
            return (digits[II] & ...);
40
        }(std::make_index_sequence<nDigits>{});
41
    }
42
private:
43
    using digits_t = std::array<value_type, nDigits>;
44
    template<auto... II>
45
    inline constexpr void update(std::index_sequence<II...>, const value_type which) {
46
        value_type r{digits[0]};
47
        (((digits[II + 1] = (r ^ (digits[II + 1] & which))), r &= digits[II + 1]), ...);
48
    }
49
    digits_t digits = [&]{
50
         digits_t d;
51
         for(auto& i : d) {
52
             i = static_cast<value_type>(-1);
53
         }
54
         return d;
55
     }();
56
};
57
// <-- Ende Header
58
59
using VC = VCounter<NumberOfCounters<9>, NumberOfBits<3>>; // hier ggf. ändern
60
61
int main() {
62
    VC c;
63
    
64
    c << VC::value_type{0x01}; // count-up bit 1
65
    c << VC::value_type{0x01};
66
    c << VC::value_type{0x01};
67
    c << VC::value_type{0x01};
68
    c << VC::value_type{0x01};
69
    c << VC::value_type{0x01};
70
    c << VC::value_type{0x01};
71
    c << VC::value_type{0x01};
72
73
//    c << VC::value_type{0x00}; // reset
74
    
75
    return c.maxReached(); // check
76
}

von EliasH (Gast)


Lesenswert?

Im Original Code von Peter Danneger wird die Globale Variable:
volatile uint8_t key_state;
deklariert aber im Code finde ich keine Startinitialisierung.
in der ISR finden dann auch gleich eine Bitoperation damit statt.
Da die Variable keine Initialisierung hat, nehme ich an das der 
undefinierte Zustand für den Start bewusst in kauf genommen wird? Stimmt 
das?
MfG Elias

von Wilhelm M. (wimalopaan)


Lesenswert?

EliasH schrieb:
> Im Original Code von Peter Danneger wird die Globale Variable:
> volatile uint8_t key_state;

globale variable -> zero-initialized

C-Buch oder hier:

https://en.cppreference.com/w/c/language/initialization

: Bearbeitet durch User
von Steffen (Gast)


Lesenswert?

Hallo
Versuche die Tasterabfrage in meinen Atmega 8 zum laufen zu bekommen. Es 
geht so weit alles, komme nur mit der Nutzung der Tasten durcheinander.
Es sthet drin:

- get_key_press()
- get_key_rpt()
- get_key_press() mit get_key_rpt()
- get_key_short() mit get_key_long()
- get_key_short() mit get_key_long_r() und get_key_rpt_l()

Leider komme ich mit der Bezeichnung und der Nutzung nicht klar. Die 
Beschreibungen sind alle relativ kurz geraten.
Welche Taste macht was wann und mit welcher?

von Karl M. (Gast)


Lesenswert?

Hallo,

Sollte man dann ich das Programm "befragen" und den Code der 
Basisfunktionen verstehen?

Als Tipp, alle genannten Funktionen bekommen ein Bitmaske der Keys 
übergeben.

von Steffen (Gast)


Lesenswert?

Das habe ich getan. Kann mit einer Taste einschalten mit einer anderen 
aus, kurz einschalten mit lang ausschalten das geht alles und klar.
Was mach ich aber damit:
get_key_short() mit get_key_long_r() und get_key_rpt_l()
Es werden 3 Tasten angegeben. Das ist mir unklar.

von Karl M. (Gast)


Lesenswert?

Hallo,

ich habe noch ein paar Links für dich:

Beitrag "Re: Universelle Tastenabfrage"

Über dieses Bild / Diagramm sollte sich einiges Aufklären.
Beitrag "Problem mit Tasterentprellung"

https://www.mikrocontroller.net/articles/Entprellung

Dann hängt es immer noch davon ab, wie also durch wen das Programm 
implementiert wurde.
Stichwort: Fehler im eigenen Code und Codetests.

von Karl M. (Gast)


Lesenswert?

Von meiner Seite aus,

hatte ich die Basiscode von Peda (Peter Dannegger), Codeteile und die 
Funktionsnamen in Assembler unter LunaAVR implementiert.

Dann auf beliebige Ports noch erweitert, so dass man über die 
Hochsprache LunaAVR auch einfach zwei oder drei 8 Bit Ports entprellen 
kann.

von Steffen (Gast)


Lesenswert?

Hallo Karl M.
Habe deinen Rat befolgt und die Artikel von Peter durchgelesen. Muss 
ganz ehrlich sein, das ist für mich ein ganzes Stück zu hoch. Da reichen 
meine Verständnisse in C nicht aus.
Habe parallel dazu auf meinem Atmega 8 das Programm installiert und 
verschiedene Versionen geteste. Mit  if(get_key_press(1<<KEY_1)) kann 
ich die LED ohne Probleme ein und mit  if(get_key_press(1<<KEY_2)) 
ausschalten. Das geht. Leider erschliesst sich die Nutzung, wie Peter 
sie beschreibt gar nicht.
Such jetzt nach der Funktion, mit Taster kurz drücken einschalten, mit 
Taster lang drücken ausschalten. Dachte das diese Funktion ist 
get_key_short() mit get_key_long(), geht aber nicht. Da ist wohl noch 
ein grosse Denkfehler von mir drin.

von Steffen (Gast)


Lesenswert?

Habe das nächste in den Griff bekommen. Es funktioniert jetzt auch 
get_key_short und get_key_long Bedienung mit einem Taster ein und aus. 
Hatte den Timer falsch eingestellt.

von Hochspannung (Gast)


Lesenswert?

Ich entprelle hardwaremäßig

von Steffen (Gast)


Lesenswert?

Was macht das get_key_release

von Heiner (Gast)


Lesenswert?

Steffen schrieb:
> Was macht das get_key_release

Guck mal in den C-Code 😉

von Steffen (Gast)


Lesenswert?

Heiner schrieb:
> Guck mal in den C-Code 😉

Das habe ich gemacht. Leider reichen meine Kenntnisse von C dazu nicht 
aus. Den Ablauf verstehe ich nicht (muss ich das?). Möchte den Code 
anwenden und dazu zählt für mich die Nutzung der Tasten.

von Heiner (Gast)


Lesenswert?

Steffen schrieb:
> Heiner schrieb:
>> Guck mal in den C-Code 😉
>
> Das habe ich gemacht. Leider reichen meine Kenntnisse von C dazu nicht
> aus. Den Ablauf verstehe ich nicht (muss ich das?). Möchte den Code
> anwenden und dazu zählt für mich die Nutzung der Tasten.

Damit wirst Du nicht weit kommen - was wirst Du machen, wenn etwas nicht 
funktioniert ( und dieser Fall wird so sicher eintreten wie das Amen in 
der Kirche 😉) - wieder hier laut um Hilfe rufen?
Allein die Frage “muss ich das?” zeigt Deine Unfähigkeit und die 
Ignoranz.

von C-lover (Gast)


Lesenswert?

Steffen schrieb:
> Leider reichen meine Kenntnisse von C dazu nicht
> aus. Den Ablauf verstehe ich nicht (muss ich das?).
Der Ablauf ist unter C etwas kreativer als außerhalb. Wenn du nur 
Hochsprachen kennst dann wäre ein Konzept:
Tasten (Fernbedienung/Kabel) senden Ereignisse: A_down, A_repeat, 
A_up_kurz, A_up_lang
an die Anwendung wesentlich einfacher (Tastatur-Treiber u.U. eher in 
einfachen assembler).

von Micha (Gast)


Lesenswert?

Steffen schrieb:
> Was macht das get_key_release
Signalisiert das Loslassen der Taste, wie der Name schon suggeriert. 
Siehe die Grafiken unter 
Beitrag "Re: Universelle Tastenabfrage"

von Daniel N. (Gast)


Lesenswert?

Guten Abend,
ich möchte die Entprellroutine als Teil einer nicht kommerziellen 
studentischen Projektarbeit verwenden.
Die Funktionen habe ich in eine Source Datei und die Defines, Variablen 
und Prototypen in eine Header Datei geschrieben. Mit den Funktionen 
frage ich in meinem Programm ein paar Taster ab.
Als Kommentar habe ich am Anfang beider Dateien geschrieben:
1
/*
2
 * Entprellroutine von Peter Dannegger
3
 * Von https://www.mikrocontroller.net/articles/Entprellung#Timer-Verfahren_(nach_Peter_Dannegger)
4
*/

Wäre das so ausreichend gekennzeichnet oder gibt es für (diesen) 
Programmcode auch sowas wie eine GNU Lizenz?
Hat da jemand Informationen zu oder von Herrn Dannegger diesbezüglich 
etwas gelesen?
Ansonsten muss ich mal meinen Betreuer fragen ob sowas in Ordnung ist 
und wie das gekennzeichnet werden muss.


Mit freundlichen Grüßen

von A. S. (Gast)


Lesenswert?

Daniel N. schrieb:
> Hat da jemand Informationen zu oder von Herrn Dannegger diesbezüglich
> etwas gelesen?
Frag ihn einfach, er ist aktiv hier
https://www.mikrocontroller.net/user/show/peda

von Peter D. (peda)


Lesenswert?

@Daniel N.

Ja, kannst Du so machen.

von Flöte (nsolo)


Lesenswert?

Falls man long und repeat parallel auf Tastern nutezen möchte hier meine 
erweiterung im Timer Interrupt
1
static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt[8];
2
static uint8_t long_press[8];
3
uint8_t i, in, nBtn=3;
4
5
...
6
7
8
for (int ii = 0; ii<nBtn; ii++){
9
    if( (key_state & (1 << ii)) == 0 )            // check repeat function
10
    {
11
      long_press[ii] = LONG_START;                // start long press delay
12
      rpt[ii] = REPEAT_START;                     // start repeat delay
13
    }
14
  
15
    if( --rpt[ii] == 0 )
16
    {
17
      rpt[ii] = REPEAT_NEXT;                      // repeat delay
18
      key_rpt |= key_state & (1 << ii);
19
    }
20
21
    if( --long_press[ii] == 0 ){
22
      key_long |= key_state & (1 << ii);
23
    }
24
}

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.