Forum: Projekte & Code Universelle Tastenabfrage


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Rolf N. (rolfn)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Danke. Es funktioniert.

...Rolf

von AnBo (Gast)


Bewertung
0 lesenswert
nicht 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!
#ifndef F_CPU
#define F_CPU 1000000UL
#endif

#define KEY_DDR         DDRA
#define KEY_PORT        PORTA
#define KEY_PIN         PINA
#define KEY0            0
#define KEY1            1
#define KEY2            2
#define KEY3      3
#define ALL_KEYS        (1<<KEY0 | 1<<KEY1 | 1<<KEY2 | 1<<KEY3)

#define REPEAT_MASK     (1<<KEY1)       // repeat: key0, key2
#define REPEAT_START    50                        // after 500ms
#define REPEAT_NEXT     20                        // every 200ms

#define LED_DDR         DDRA
#define LED_PORT        PORTA
#define LEDBLUE         4
#define LEDYELLOW       5
#define LEDRED          6
#define LEDGREEN    7

#include <stdint.h>
#include <avr/io.h>
#include <stdio.h>
#include <util/delay.h>                // for _delay_ms()
#include <avr/interrupt.h>
#include "lcd.h"

volatile uint8_t key_state;                                // debounced and inverted key state:
                          // bit = 1: key pressed
volatile uint8_t key_press;                                // key press detect

volatile uint8_t key_rpt;                                  // key long press and repeat


ISR( TIMER0_OVF_vect )                            // every 10ms
{
  static uint8_t ct0, ct1, rpt;
  uint8_t i;
  
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
  
  i = key_state ^ ~KEY_PIN;                       // key changed ?
  ct0 = ~( ct0 & i );                             // reset or count ct0
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
  i &= ct0 & ct1;                                 // count until roll over ?
  key_state ^= i;                                 // then toggle debounced state
  key_press |= key_state & i;                     // 0->1: key press detect
  
  if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
  rpt = REPEAT_START;                          // start delay
  if( --rpt == 0 ){
    rpt = REPEAT_NEXT;                            // repeat delay
    key_rpt |= key_state & REPEAT_MASK;
  }
}

///////////////////////////////////////////////////////////////////
//
// check if a key has been pressed. Each pressed key is reported
// only once
//
uint8_t get_key_press( uint8_t key_mask )
{
  cli();                                          // read and clear atomic !
  key_mask &= key_press;                          // read key(s)
  key_press ^= key_mask;                          // clear key(s)
  sei();
  return key_mask;
}

///////////////////////////////////////////////////////////////////
//
// check if a key has been pressed long enough such that the
// key repeat functionality kicks in. After a small setup delay
// the key is reported being pressed in subsequent calls
// to this function. This simulates the user repeatedly
// pressing and releasing the key.
//
uint8_t get_key_rpt( uint8_t key_mask )
{
  cli();                                          // read and clear atomic !
  key_mask &= key_rpt;                            // read key(s)
  key_rpt ^= key_mask;                            // clear key(s)
  sei();
  return key_mask;
}

///////////////////////////////////////////////////////////////////
//
// check if a key is pressed right now
//
uint8_t get_key_state( uint8_t key_mask )

{
  key_mask &= key_state;
  return key_mask;
}

///////////////////////////////////////////////////////////////////
//
uint8_t get_key_short( uint8_t key_mask )
{
  cli();                                          // read key state and key press atomic !
  return get_key_press( ~key_state & key_mask );
}

///////////////////////////////////////////////////////////////////
//
uint8_t get_key_long( uint8_t key_mask )
{
  return get_key_press( get_key_rpt( key_mask ));
}



int main(void)
{  
  int displaystate=0;
  typedef enum
  {
    READY=0,
    STARTMEASUREMENT
  } current_state;

  current_state state = READY;

  LED_DDR = 0xFF;

  // Configure debouncing routines
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
  
  TCCR0B = (1<<CS02)|(1<<CS00);         // divide by 1024
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
  TIMSK0 |= 1<<TOIE0;                   // enable timer interrupt
  
  sei();
  
  lcd_init(LCD_DISP_ON);        // Initialize LCD
  
  while(1){
    switch (state)
    {
      case READY:
          if(displaystate==0){
            lcd_clrscr();
            lcd_puts("Bereit");
            displaystate=1;
          }
          LED_PORT = (1<<LEDGREEN);
          if( get_key_short( 1<<KEY3 )){
            displaystate=0;
            state=STARTMEASUREMENT;
          }
          if( get_key_long( 1<<KEY3 )){  
            displaystate=0;
            state=STARTMEASUREMENT;
          }
          break;
      
      case STARTMEASUREMENT:
          if(displaystate==0){
            lcd_clrscr();
            lcd_puts("State1");
            displaystate=1;
          }
          LED_PORT = (1<<LEDYELLOW);
          if( get_key_short( 1<<KEY2 )){
            displaystate=0;
            state=READY;
          }
          if( get_key_long( 1<<KEY2 )){
            displaystate=0;
            state=READY;
          }
          break;
        }
  }
}
        

von Peter D. (peda)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Probier doch meinen einfach mal aus.

 LOL.

von Mario S. (dachstuhlklaus)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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.
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 8E6

#define KEY_DDR         DDRB
#define KEY_PORT        PORTB
#define KEY_PIN         PINB
#define KEY0            0
#define KEY1            1
#define KEY2            2
#define ALL_KEYS        (1<<KEY0 | 1<<KEY1 | 1<<KEY2)

#define REPEAT_MASK     (1<<KEY1 | 1<<KEY2)       // repeat: key1, key2
#define REPEAT_START    50                        // after 500ms
#define REPEAT_NEXT     20                        // every 200ms

#define LED_DDR         DDRA
#define LED_PORT        PORTA
#define LED0            0
#define LED1            1
#define LED2            2

volatile uint8_t key_state;                                // debounced and inverted key state:
// bit = 1: key pressed
volatile uint8_t key_press;                                // key press detect

volatile uint8_t key_rpt;                                  // key long press and repeat


ISR( TIMER0_OVF_vect )                         // every 10ms
{
  static uint8_t ct0, ct1, rpt;
  uint8_t i;
  
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);

  i = key_state ^ ~KEY_PIN;                       // key changed ?
  ct0 = ~( ct0 & i );                             // reset or count ct0
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
  i &= ct0 & ct1;                                 // count until roll over ?
  key_state ^= i;                                 // then toggle debounced state
  key_press |= key_state & i;                     // 0->1: key press detect
  
  if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
  rpt = REPEAT_START;                          // start delay
  if( --rpt == 0 ){
    rpt = REPEAT_NEXT;                            // repeat delay
    key_rpt |= key_state & REPEAT_MASK;
  }

  static uint8_t ct;
  if(++ct == 50) { LED_PORT ^= 1<<LED2; ct= 0;}
}


uint8_t get_key_press( uint8_t key_mask )
{
  cli();                                          // read and clear atomic !
  key_mask &= key_press;                          // read key(s)
  key_press ^= key_mask;                          // clear key(s)
  sei();
  return key_mask;
}

uint8_t get_key_rpt( uint8_t key_mask )
{
  cli();                                          // read and clear atomic !
  key_mask &= key_rpt;                            // read key(s)
  key_rpt ^= key_mask;                            // clear key(s)
  sei();
  return key_mask;
}

uint8_t get_key_state( uint8_t key_mask )
{
  key_mask &= key_state;
  return key_mask;
}

uint8_t get_key_short( uint8_t key_mask )
{
  cli();                                          // read key state and key press atomic !
  return get_key_press( ~key_state & key_mask );
}

uint8_t get_key_long( uint8_t key_mask )
{
  return get_key_press( get_key_rpt( key_mask ));
}

int main( void )
{
  LED_DDR |= (1<<LED0) | (1<<LED1) | (1<<LED2);

  // Configure debouncing routines
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
  KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors
  
  TCCR0B = (1<<CS02)|(1<<CS00);         // divide by 1024
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
  TIMSK0 |= 1<<TOIE0;                   // enable timer interrupt
  
  sei();
  
  while(1){
    if(get_key_short( 1<<KEY0 ))  // Led2 togglen
    LED_PORT ^= 1<<LED0;
    
    if( get_key_short( 1<<KEY1 ))  // Led1 togglen
    LED_PORT ^= 1<<LED1;

    if( get_key_short( 1<<KEY2 ))  // Leds loeschen
    LED_PORT &= ~((1<<LED0) | (1<<LED1));
  }
}

von Uwe S. (de0508)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


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

von Mathias O. (m-obi)


Bewertung
0 lesenswert
nicht lesenswert
Versuch mal anstatt so
key_state ^ ~KEY_PIN
das so zu machen
key_state ^ KEY_PIN

von Uwe S. (de0508)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Jo er hat ja die Taster active low. Dachte die sind an VCC 
angeschlossen.

von Pumba (Gast)


Bewertung
0 lesenswert
nicht 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.
  KEY_DDR &= ~ALL_KEYS;                // configure key port for input
  PUEB |= ALL_KEYS;
  //KEY_PORT |= ALL_KEYS;                // and turn on pull up resistors

von Mathias O. (m-obi)


Bewertung
0 lesenswert
nicht lesenswert
Mit welchem Wert hast du F_CPU definiert? 2278?

von Pumba (Gast)


Bewertung
0 lesenswert
nicht lesenswert
F_CPU ist 8MHz

von Mathias O. (m-obi)


Bewertung
0 lesenswert
nicht lesenswert
Wo hängt dein Quarz dran? PB0 und PB1 sind doch XTAL.

von Mathias O. (m-obi)


Bewertung
0 lesenswert
nicht lesenswert
Dann schreib mal 8000000UL bei F_CPU

von Pumba (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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
static volatile uint8_t IRQdoNotReadFlag;

uint16_t get_button_press(uint16_t button_mask) {
  IRQdoNotReadFlag = 1;
  button_mask &= button_press;
  button_press ^= button_mask;
  IRQdoNotReadFlag = 0;
  return button_mask;
}

in der Funktion die dann im IRQ aufgerufen wird:
void call_button_in_IRQ(void)                  // Call this in SysTick-Handler
{                                              // Should be called every 10ms
  static uint16_t ct0, ct1;
  #ifdef USE_KEY_REPEAT
    static uint16_t rpt;
  #endif
  uint16_t i;
  uint16_t BUTTON_MASK_NOW = BUTTON_PORT;    // get Stade of the InputDataRegister for the used Port
  if (!IRQdoNotReadFlag) {

    i = button_state ^ BUTTON_MASK_NOW;        // key changed ?
    ct0 = ~(ct0 & i);                          // reset or count ct0
    ct1 = ct0 ^ (ct1 & i);                     // reset or count ct1
    i &= ct0 & ct1;                            // count until roll over ?
    button_state ^= i;                         // then toggle debounced state
    button_press |= button_state & i;          // 0->1: key press detect
    
  #ifdef USE_KEY_REPEAT
    if ((button_state & REPEAT_MASK) == 0)     // check repeat function
      rpt = REPEAT_START;                    // start delay
    if (--rpt == 0) {
      rpt = REPEAT_NEXT;
      button_rpt |= button_state & REPEAT_MASK;
    }
  #endif
  }
}

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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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
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)


Bewertung
-1 lesenswert
nicht 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.
if ( get_key_short( 1<<KEY1))
     i++;

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

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

Gruß
Martin

von Peter D. (peda)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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:
#ifndef UNBOUNCE_H
#define UNBOUNCE_H

#include "sw_spi.h"

#define KEY_PORT        PORTA               // Input register
#define KEY0            /*PORTAbits.RA*/1   // KEY 0...3
#define KEY1            /*PORTAbits.RA*/0   //
#define KEY2            /*PORTAbits.RA*/6   //
#define KEY3            /*PORTAbits.RA*/7   //

                        // Type all keys for repeat and long press function */
#define REPEAT_MASK     (1<<KEY0|1<<KEY1|1<<KEY2|1<<KEY3)
#define REPEAT_START    50   // After 500ms (start time long press)
#define REPEAT_NEXT     50   // Every 200ms (repeat time)
#define TIMEOUT              // Define when timout is needed for two
                             // keys pressed together

void interrupt service_interrupt(void);

uint8_t get_key_press( uint8_t key_mask );

uint8_t get_key_release(uint8_t key_mask);

uint8_t get_key_rpt( uint8_t key_mask );

uint8_t get_key_short( uint8_t key_mask );

uint8_t get_key_long( uint8_t key_mask );

uint8_t get_key_long_r( uint8_t key_mask );

uint8_t get_key_rpt_l( uint8_t key_mask );

uint8_t get_key_common( uint8_t key_mask );

#endif

Und hier nachfolgend die Funktionen:
#include "unbounce.h"

volatile uint8_t key_state;                // Debounced and inverted key state:
                                           // Bit = 1: key pressed
volatile uint8_t key_press;                // Key press detect
volatile uint8_t key_release;              // Key release detected
volatile uint8_t key_rpt;                  // Key long press and repeat

//******************************************************************************
//                              INTERRUPTS
//******************************************************************************
//#INT_TIMER0                                     // Timer 0 interrupt
void  interrupt service_interrupt(void)
{
    TMR0=0x0064;                         // 156 x 64µs = 9.984ms (~10ms)
                                         // Preset 100 = 256 - 156
                                         //Setzt den Zählerstand auf x von 255
    static uint8_t ct0 = 0xFF, ct1 = 0xFF, rpt;
    uint8_t i;

    i = key_state ^ ~KEY_PORT;                  // Key changed ? active low key
    //i = key_state ^ KEY_PORT;                   // Active high key
    ct0 = ~( ct0 & i );                         // Reset or count ct0
    ct1 = ct0 ^ (ct1 & i);                      // Reset or count ct1
    i &= ct0 & ct1;                             // Count until roll over ?
    key_state ^= i;                             // Then toggle debounced state
    key_press |= key_state & i;                 // 0->1: key press detect
    key_release |= ~key_state & i;              // 1->0: key release detect

    if( (key_state & REPEAT_MASK) == 0 )        // Check repeat function
        rpt = REPEAT_START;                     // Start delay

    if( --rpt == 0 ){
        rpt = REPEAT_NEXT;                      // Repeat delay
        key_rpt |= key_state & REPEAT_MASK;     // Active low keys
    }
    INTCONbits.T0IF=0; //Setzt das Interruptflag nach Zählerüberlauf zurück
}

//******************************************************************************
//                              FUNCTIONS
//******************************************************************************
//______________________________________________________________________________
// Button debounce routines
uint8_t get_key_press( uint8_t key_mask ){
    //disable_interrupts(GLOBAL);
    INTCONbits.GIE = 0;
    key_mask &= key_press;                      // Read key(s)
    key_press ^= key_mask;                      // Clear key(s)
    //enable_interrupts(GLOBAL);
    INTCONbits.GIE = 1;
    return key_mask;
}

uint8_t get_key_release(uint8_t key_mask){
    //disable_interrupts(GLOBAL);
    INTCONbits.GIE = 0;
    key_mask &= key_release;                    // Read key(s)
    key_release ^= key_mask;                    // Clear key(s)
    //enable_interrupts(GLOBAL);
    INTCONbits.GIE = 1;
    return key_mask;
}

uint8_t get_key_rpt( uint8_t key_mask ){
    //disable_interrupts(GLOBAL);
    INTCONbits.GIE = 0;
    key_mask &= key_rpt;                        // Read key(s)
    key_rpt ^= key_mask;                        // Clear key(s)
    //enable_interrupts(GLOBAL);
    INTCONbits.GIE = 1;
    return key_mask;
}

uint8_t get_key_short( uint8_t key_mask ){
    uint8_t i;
    //disable_interrupts(GLOBAL);
    INTCONbits.GIE = 0;
    i = get_key_press( ~key_state & key_mask );
    INTCONbits.GIE = 1;
    return i;
}

uint8_t get_key_long( uint8_t key_mask ){
    return get_key_press( get_key_rpt( key_mask ));
}

uint8_t get_key_long_r( uint8_t key_mask ){           // If repeat function needed
    return get_key_press( get_key_rpt( key_press & key_mask ));
}

uint8_t get_key_rpt_l( uint8_t key_mask ){            // If long function needed
    return get_key_rpt( ~key_press & key_mask );
}

uint8_t get_key_common( uint8_t key_mask ){
  return get_key_press((key_press & key_mask) == key_mask ? key_mask : 0);
}
;

Irgendwie will der Fehler sich mir nicht offenbaren.

Gruß

: Bearbeitet durch User
von Moritz A. (moritz_a)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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?
// key.h -----------------------

#ifndef KEY_H
#define KEY_H

uint8_t get_key_press( uint8_t );
uint8_t get_key_rpt( uint8_t );
uint8_t get_key_short( uint8_t );
uint8_t get_key_long( uint8_t );
uint8_t test_i2c_key(void);
//void timer2_init(void);

extern volatile uint8_t key_state;  // debounced and inverted key state:
                                    // bit = 1: key pressed
extern volatile uint8_t key_press;  // key press detect
extern volatile uint8_t key_rpt;    // key long press and repeat
extern volatile uint8_t ct0, ct1, rpt;
extern volatile uint8_t key;


// --------------- KEY --------------- 
#if defined(__AVR_ATmega328P__)
  // D5 (T1)        PD5
  #define KEY_DDR     DDRD
  #define KEY_PORT    PORTD
  #define KEY_PIN     PIND
  #define KEY_UP         5
  // D6 (AIN0)      PD6
  #define KEY_DOWN       6
  // D7 (AIN1)      PD7
  #define SET_TASTE      4

  // D8 (ICP1)      PB0
  // D9 (OC1A)      PB1 (PWM)
#endif

#define ALL_KEYS         (1<<KEY_UP | 1<<KEY_DOWN | 1<<SET_TASTE)
 
#define REPEAT_MASK     (1<<KEY_UP | 1<<KEY_DOWN)
#define REPEAT_START    50                        // after 500ms
#define REPEAT_NEXT     15                        // every 200ms

extern volatile uint8_t key_state;  // debounced and inverted key state:
                                    // bit = 1: key pressed
extern volatile uint8_t key_press;  // key press detect
extern volatile uint8_t key_rpt;    // key long press and repeat
extern volatile uint8_t ct0, ct1, rpt;

#endif // #ifndef KEY_H
// key.h -----------------------

// isr -----------------------
    ii = key_state ^ ~KEY_PIN;
    ct0 = ~( ct0 & ii );                             // reset or count ct0
    ct1 = ct0 ^ (ct1 & ii);                          // reset or count ct1
    ii &= ct0 & ct1;                                 // count until roll over ?
    key_state ^= ii;                                 // then toggle debounced state
    key_press |= key_state & ii;                     // 0->1: key press detect
               
    if( (key_state & REPEAT_MASK) == 0 )            // check repeat function
      rpt = REPEAT_START;                          // start delay
    if( --rpt == 0 )
    {  rpt = REPEAT_NEXT;                            // repeat delay
       key_rpt |= key_state & REPEAT_MASK;
    }
// isr -----------------------

// loop -----------------------
  ret_key=get_key_press( ALL_KEYS );

  if(ret_key&(1<<SET_TASTE))
  { ret_key&=~(1<<SET_TASTE); // ~ Port IN negiert weil low Abfrage
// loop Auschnitt -----------------------


von Peter D. (peda)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
-1 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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
#pragma code
#pragma interrupt high_prior_InterruptHandler

void high_prior_InterruptHandler(void)
{}

#pragma code
#pragma interrupt low_prior_InterruptHandler
void low_prior_InterruptHandler(void)
{}

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.
#pragma code
#pragma interruptlow low_prior_InterruptHandler
void low_prior_InterruptHandler(void)
{}
Das ist also korrekt. Und jetzt funktioniert es auch :)

von AVR-Bastler (Gast)


Bewertung
0 lesenswert
nicht 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:
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
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)


Bewertung
0 lesenswert
nicht lesenswert
Folgender Code funktioniert bei mir einwandfrei:


static uint8_t lcd_update = 0;
static char buffer[20];
static uint32_t counter = 0;

      // decrement
      if (get_key_press(1<<KEY_DEC))
      {
        if (counter > 0)
        {
          counter--;
          lcd_update = 1;
        }
      }
      if (get_key_rpt(1<<KEY_DEC))
      {
        if (counter >= 10)
        {
          counter -= 10;
        }
        else
        {
          counter = 0;
        }
        
        lcd_update = 1;
      }


      if (lcd_update)
      {
        lcd_update = 0;
        lcd_setcursor(0,1);
        sprintf(buffer, "%16" PRIu32 "", counter);
        lcd_string(buffer);
      }


lg
Chris

von AVR-Bastler (Gast)


Bewertung
0 lesenswert
nicht 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
 if (!(msec % 10))
  {
  debounce();
... 
die Abfrage der Taster vorgenommen.
Der Tiny13A zum Testen taktet geringfügig höher (9,6/8MHz).

von chris (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht lesenswert
Ja ertappt :)

Also es geht um diese Zeile:
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


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Johannes H.,

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

von Johannes H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Da ist der Code:
#include "includes/config.h"
#include "includes/debounce.h"
#include "includes/light_ws2812.h"

#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

struct cRGB LED_WHEEL_16[16];
struct cRGB COLORS [8];

#define LEDS 16

uint8_t value_max = 10;
int8_t led_index = 0;
int8_t led_dark_step = 2;

float brightness_red=0.1, brightness_green=0.1, brightness_blue=0.1;

void dark(uint8_t led_index, uint8_t led_dark_step){
  if (led_index - led_dark_step < 0) led_index = LEDS - abs(led_index - led_dark_step);
  else led_index -= led_dark_step;
  
  LED_WHEEL_16[led_index].r=0;
  LED_WHEEL_16[led_index].g=0;
  LED_WHEEL_16[led_index].b=0;  
}

int main(void){
  LED_INIT();  // Set LED pins 
  
  /**** Set Timer for Debounce Functionality ****/
  TCCR0 = (1<<CS02)|(1<<CS00);         // divide by 1024
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1600 * 10e-3 + 0.5);  // preload for 10ms
  TIMSK |= 1<<TOIE0;                   // enable timer interrupt
  
  sei();    // Enable global interrupts

  while(1){  
        
    if( get_key_short( 1<<KEY2 ))
      LED_EVENT(LED2,1);  
    if (get_key_long(1<<KEY2))  
      LED_EVENT(LED2,5);  
      
    LED_WHEEL_16[led_index].r=value_max;LED_WHEEL_16[led_index].g=00;LED_WHEEL_16[led_index].b=value_max;// blue
    ws2812_setleds(LED_WHEEL_16,LEDS);
    _delay_ms(200);
    dark(led_index, led_dark_step);
    ws2812_setleds(LED_WHEEL_16,LEDS);
    
    led_index++;
    if(led_index == 16){
      led_index = 0;
    }    
  }
}

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)


Bewertung
0 lesenswert
nicht 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:

Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Michael !

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

Dieser Wert ist direkt abgängig von dem Pollintervall !

von M. K. (kichi)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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:
void knopf_abfrage(void){

  static unsigned char ct0 = 0xFF, ct1 = 0xFF, rpt;
    unsigned char i, key_pin;

  key_pin = 0xff;
  if(!T_U) key_pin &= ~(1<<KEY_U);
  if(!T_O) key_pin &= ~(1<<KEY_O);
  if(!T_M) key_pin &= ~(1<<KEY_M);
  if(!T_SP) key_pin &= ~(1<<KEY_SP);
  if(!T_START) key_pin &= ~(1<<KEY_START);

  i = key_state ^ ~key_pin;     // key changed ? 
usw.

mit entsprechenden
#define PIN_KN_U PINB
#define KN_U PB3
#define T_U (PIN_KN_U & (1<<KN_U))
#define KEY_U 4 

#define PIN_KN_O PINB 
#define KN_O PB4
#define T_O (PIN_KN_O & (1<<KN_O))
#define KEY_O 3  

#define PIN_KN_M PINC
#define KN_M PC0
#define T_M (PIN_KN_M & (1<<KN_M))
#define KEY_M 2

#define PIN_KN_SP PINC
#define KN_SP PC1
#define T_SP (PIN_KN_SP & (1<<KN_SP))
#define KEY_SP 1 

#define PIN_KN_START PINB
#define KN_START PB2
#define T_START (PIN_KN_START & (1<<KN_START))
#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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Guten Mittag,

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

von Henning (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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 H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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
    if( stop ){
      try_sleep();
      get_key_short( 1<<KEY0 );      // ignore short press during stop

von Johannes H. (Firma: Kreatronik) (menschenskind)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Peter,

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

von Mathias W. (mathias_w)


Bewertung
0 lesenswert
nicht 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:
volatile uint8_t key_state;                                // debounced and inverted key state:
                                                  // bit = 1: key pressed
volatile uint8_t key_press;                                // key press detect
 
volatile uint8_t key_rpt;                                  // key long press and repeat

ISR( TIMER0_OVF_vect )                            // every 10ms
{
  static uint8_t ct0 = 0xFF, ct1 = 0xFF;
  static uint8_t rpt_ct0 = 0xFF, rpt_ct1 = 0xFF, rpt_ct2 = 0xFF;
  uint8_t i;
 
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);  // preload for 10ms
 
  i = key_state ^ ~KEY_PIN;                       // key changed ?
  ct0 = ~( ct0 & i );                             // reset or count ct0
  ct1 = ct0 ^ (ct1 & i);                          // reset or count ct1
  i &= ct0 & ct1;                                 // count until roll over ?
  key_state ^= i;                                 // then toggle debounced state
  key_press |= key_state & i;                     // 0->1: key press detect
  key_release |= ~key_state & i;                  // 1->0: key release detect

  i = key_state
  rpt_ct0 = ~( rpt_ct0 & i );                     // reset or count rpt_ct0
  rpt_ct1 = rpt_ct0 ^ (rpt_ct1 & i);              // reset or count rpt_ct1
  rpt_ct2 = (rpt_ct0 & rpt_ct1) ^ (rpt_ct2 & i);  // reset or count rpt_ct2
  i &= rpt_ct0 & rpt_ct1 & rpt_ct2;               // count until roll over ?
  key_rpt |= key_state & i;                       // then repeat key

}

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)


Bewertung
0 lesenswert
nicht 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:
  i = key_state
  rpt_ct0 = ~( rpt_ct0 & i );                               // reset or count rpt_ct0
  rpt_ct1 = rpt_ct0 ^ (rpt_ct1 & i);                        // reset or count rpt_ct1
  rpt_ct2 = (rpt_ct0 & rpt_ct1) ^ (rpt_ct2 & i);            // reset or count rpt_ct2
  rpt_ct3 = (rpt_ct0 & rpt_ct1 & rpt_ct2) ^ (rpt_ct3 & i);  // reset or count rpt_ct3
  i &= rpt_ct0 & rpt_ct1 & rpt_ct2 & rpt_ct3;               // count until roll over ?
  key_rpt |= key_state & i;                                 // then repeat key

von Andreas B. (bitverdreher)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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.
// -> in Header vertical_counter.h
#include <cstdint>
#include <utility>
#include <array>

#if __has_include(<etl/type_traits.h>)
# include <etl/type_traits.h>
# define HAVE_ETL
#endif

template<auto N> using NumberOfCounters = std::integral_constant<size_t, N>;
template<auto N> using NumberOfBits     = std::integral_constant<size_t, N>;

template<typename Counters, typename CounterSize>
struct VCounter {
    inline static constexpr auto nCounters = Counters::value;
    inline static constexpr auto nDigits   = CounterSize::value;
    static_assert(nCounters <= (sizeof(uint64_t) * 8));
    
#ifdef HAVE_ETL
    using value_type = etl::typeForBits_t<nCounters>;
#else
    template<auto Bits>
    struct typeForBits {
        using type = typename std::conditional<(Bits <= 8), uint8_t, 
                         typename std::conditional<(Bits <= 16), uint16_t, 
                             typename std::conditional<(Bits <= 32), uint32_t,
                                  typename std::conditional<(Bits <= 64), uint64_t, void>::type>::type>::type>::type;
    };
    using value_type = typename typeForBits<nCounters>::type;
#endif
    inline constexpr VCounter& operator<<(const value_type which) {
        digits[0] = ~(digits[0] & which);
        update(std::make_index_sequence<nDigits-1>{}, which);
        return *this;        
    }
    inline constexpr value_type maxReached() {
        return [&]<auto... II>(std::index_sequence<II...>){
            return (digits[II] & ...);
        }(std::make_index_sequence<nDigits>{});
    }
private:
    using digits_t = std::array<value_type, nDigits>;
    template<auto... II>
    inline constexpr void update(std::index_sequence<II...>, const value_type which) {
        value_type r{digits[0]};
        (((digits[II + 1] = (r ^ (digits[II + 1] & which))), r &= digits[II + 1]), ...);
    }
    digits_t digits = [&]{
         digits_t d;
         for(auto& i : d) {
             i = static_cast<value_type>(-1);
         }
         return d;
     }();
};
// <-- Ende Header

using VC = VCounter<NumberOfCounters<9>, NumberOfBits<3>>; // hier ggf. ändern

int main() {
    VC c;
    
    c << VC::value_type{0x01}; // count-up bit 1
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};
    c << VC::value_type{0x01};

//    c << VC::value_type{0x00}; // reset
    
    return c.maxReached(); // check
}

von EliasH (Gast)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.