www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Langer Tastendruck in RB_ISR (PIC CCS-Compiler)


Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe gerade ein kleines Problem für das mir keine Lösung einfällt. 
Ich habe einen 16F873 an dessen PORTB4..7 jeweils 1 Taster angeschlossen 
ist. Beim Tastendruck geht der Pegel auf Low. PORTB4..7 ist als 
PORTB-Interrupt-on-PINChange initialisiert.

die Tastenabfrage sieht folgendermaßen aus
#int_RB
void RB_isr() 
{
 unsigned int new_PortB=0; 
 unsigned int changes=0; 
 new_PortB =input_b(); //erstmal PortB einlesen
 new_PortB = ~new_Portb, // Ergebnis invertieren da LOW-Aktiv
 disable_interrupts(INT_RB); //erstmal PORTB-Interrupts deaktivieren um zu entprellen
 INTF_RB=1; //mal sehen obs das noch braucht
 changes=new_PortB^old_PortB; //Alter Wert mit neuem Wert vergleichen (Exklusiv-Oder)
 changes=changes&new_PortB; //This now reflects every bit that has 
 //gone _high_ (use 'old_PortB', instead of 'new_pins',if you want to find 
 //the ones that have gone low). 
 changes=changes>>4;
 old_PortB=new_PortB; //update your stored value for the future 
 setup_timer_1(T1_INTERNAL | T1_DIV_BY_4); //Timer1_Interrupt aktivieren und Vorteiler durch 4
 SET_TIMER1(0);  //Timer1 mit 0 laden

 if (first_bit(changes)) // Test auf PORTB4
 { 
  Taste1=1;
 } 

 if (second_bit(changes)) // Test auf PORTB5 
 { 
  Taste2=1;
 } 

 if (third_bit(changes)) // Test auf PORTB6
 { 
  Taste3=1;
 } 

 if (fourth_bit(changes)) // Test auf PORTB7
 { 
  Taste4=1;
 }

}//Ende RB_ISR


Timer1 wird zum entprellen verwendet. Das einzige was in der ISR von 
TIMER1 gemacht wird ist folgendes
#int_TIMER1
void timer1interrupt()
{
 setup_timer_1(T1_DISABLED); //wenn Timer1 Interrupt erfolgt, wird er deaktiviert
 enable_interrupts(INT_RB);  //PORTB-Interrupts werden wieder eingeschaltet
} //Ende timer1interrupt

Das ganze funtioniert auch ohne Probleme bisher. Jetzt habe ich 
allerdings das Problem, dass ich eigentlich auch prüfen müsste ob eine 
Taste länger gedrückt wurde. Über die Interrupt-Routine kann ich das ja 
nicht abfangen. Meine Idee die mir gerade beim schreiben kommt ist 
folgende. Ich nehme einen zweiten Timer (Timer0, da Timer2 schon 
anderweitig verwendet wird) und starte diesen nach dem Aufruf der 
RB_ISR, wie ich es schon mit Timer1 mache. Der zweite Timer schaut dann 
nach z.B. 500ms, 1s, 1.5s, 2s ob die Taste noch gedrückt ist und geht 
dann in die Abarbeitung für lange gedrückte Tasten. Kann sowas 
funktionieren oder gibt es vielleicht noch eine einfachere Lösung?

Olaf

Autor: Daniel P. (ppowers)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Naja, also 2 Timer und zusätzlich noch einen Interrupt-on-change für 
eine schnöde Tastenabfrage zu verbraten ist schon etwas übertrieben.

Man kann das auch mit nur einem Timer lösen. Ich hab das z.B. schon mal 
wie folgt gelöst, funktioniert eigentlich ganz gut. Zunächst der 
Keyhandler-Code:
// +----------------------------------------------------------------------+
// |                                                                      |
// |                      Comfort Keyhandling Code                        |
// |                                                                      |
// |           (C) 13/02/2008 - Daniel Porzig - www.base32.de             |
// +----------------------------------------------------------------------+
//
//
//
// How to use:
//   
//   1.) If available, enable internal pull-up resistors, otherwise you 
//      have to use   external pull-up resistors for every key you use.
//  2.) Call InitKeys();
//  3.) Setup Keys by calling "setup_key" for every key.
//  4.) Call "CheckKeys()" about every 50ms
//  5.) Check the key states by calling KeyPress(..) or KeyLongPress(..) 
//      for every key you want to use
//
//
//

// how many keys should be used
#define NUM_KEYS      7

// key states
#define KS_RELEASED    0
#define KS_PRESSED      1
#define KS_LONGPRESSED   2

// key modes
#define KM_ACT_ON_PRESS         0x01
#define KM_ACT_ON_RELEASE      0x02
#define KM_ACT_ON_LONGPRESS      0x03

// Key Data Structure (for each key)
struct key_struct
{
   int8 _mode;               // key mode (const)
   int8 _repeat_delay;       // delay for repeatedly pressed keys (const)
   int8 _longpress_delay;      // how long the key has to be pressed before a longpress gets activated
   int8 state;
   int8 counter;
   int8 longpress_cnt;
};
struct key_struct keys[NUM_KEYS];

// key state variables
int8 last_keystate = 0xFF;
int8 key_actions = 0x00;
int8 key_longpress_actions = 0x00;


// Configure a single button
//   num            - which key to configure
//   mode         - configure how the key reacts (KM_ACT_ON_PRESS, KM_ACT_ON_RELEASE or KM_ACT_ON_LONGPRESS)
//   repeat_delay   - when a button is (long)pressed continuously, this value sets the delay between 
//                 repeatedly triggered actions
//   longpress_delay   - configures how long a key (with key-mode == KM_ACT_ON_LONGPRESS) has to be hold down before
//                 a longpress action is triggered
void setup_key(int8 num, int8 mode, int8 repeat_delay, int8 longpress_delay)
{
   keys[num]._mode = mode;
   keys[num]._repeat_delay = repeat_delay;
   keys[num]._longpress_delay = longpress_delay;
}

// initializes Key data for all keys
//     call this function before first call of "CheckKeys()"
void InitKeys()
{
   int8 a;
   for(a=0; a<NUM_KEYS; a++)
   {
      keys[a].state = KS_RELEASED;
      keys[a].counter = 0;
      keys[a].longpress_cnt = 0;
   }
   last_keystate = 0xFF;
}

// Returns true if the standard action for key(num) has to be executed
int1 KeyPress(int8 num)
{
   return bit_test(key_actions, num);
}

// Returns true if the key(num) is currently longpressed
int1 KeyLongpress(int8 num)
{
   return bit_test(key_longpress_actions, num);
}

// Returns true if one or more keys are currently pressed or have changed state since last CheckKeys()
int1 KeyActivity()
{
   return (key_longpress_actions || key_actions)||(last_keystate!=0xFF);
}

// This function does all the keyhandling. Call this function about every 50ms.
void CheckKeys()
{
   int8 i, diff;
   int8 keystate = 0xFF;

   // get current state of keys
   keystate = input_b();

   // reset action flags
   key_actions = 0x00;
   key_longpress_actions = 0x00;

   // determin the difference between current and last keystate
   diff = last_keystate ^ keystate;

   // loop through all keys
   for(i=0; i<NUM_KEYS; i++)
   {
      // key has changed?
      if(bit_test(diff,i))
      {
         switch(keys[i].state)
         {
         case KS_PRESSED:   // key was in pressed state before
            keys[i].state = KS_RELEASED;   // change keystate
            keys[i].counter = 0;          // reset longpress counter

            if(keys[i]._mode == KM_ACT_ON_RELEASE)
            {
               // +---------------------+
               // | DO STANDARD ACTION! |
               // +---------------------+
               bit_set(key_actions,i);
            }
            else
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
            {
               // key was released, but key was not in Longpress state yet.
               // +---------------------+
               // | DO STANDARD ACTION! |
               // +---------------------+
               bit_set(key_actions,i);
            }
            else
            {
               // KM_ACT_ON_PRESS:     DO NOTHING
            }
         break;
         case KS_RELEASED:      // key was in released state before
            keys[i].state = KS_PRESSED;   // change keystate
            if(keys[i]._mode == KM_ACT_ON_PRESS)
            {
               keys[i].counter = 0;   // reset keypress counter
               // +---------------------+
               // | DO STANDARD ACTION! |
               // +---------------------+
               bit_set(key_actions,i);
            }
            else if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
            {
               // action is triggered later
               keys[i].longpress_cnt = 0;          // reset longpress counter            
            }
            else
            {
               // KM_ACT_ON_RELEASE:     DO NOTHING
            }
         break;
         case KS_LONGPRESSED:   // key was released after a Longpress. Special handling!
            keys[i].state = KS_RELEASED;   // change keystate

            // does this key have lonpress mode enabled?
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
            {
               // When a key was released after a longpress, nothing should happen!
            }
            else
            {
               // KM_ACT_ON_RELEASE:     DO NOTHING
               // KM_ACT_ON_PRESS:     DO NOTHING
            }
         break;
         }
      }
      else
      {
         // check keys whose states DID NOT change here!
         // (autofire, longpress repeat)
         
         if(keys[i].state == KS_PRESSED)
         {
            if(keys[i]._mode == KM_ACT_ON_RELEASE)
            {
               // key is either still pressed or still released so
               // no action needed here
            }
            else
            if(keys[i]._mode == KM_ACT_ON_PRESS)      // check "autofire" function
            {
               // key is still pressed
               keys[i].counter++;      // increase keypress counter
               if(keys[i].counter >= keys[i]._repeat_delay)
               {
                  keys[i].counter = 0;
                  // +---------------------+
                  // | DO STANDARD ACTION! |
                  // +---------------------+
                  bit_set(key_actions,i);
               }
            }
            else
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
            {
               // key is still pressed
               keys[i].longpress_cnt++;      // increase keypress counter

               // do we have to trigger the longpress action?
               if(keys[i].longpress_cnt >= keys[i]._longpress_delay)
               {
                  keys[i].counter = 0;
                  // +----------------------+
                  // | DO LONGPRESS ACTION! |
                  // +----------------------+
                  keys[i].state = KS_LONGPRESSED;      // set key into longpressed state
                  bit_set(key_longpress_actions,i);
               }
            }
         }
         else
         if(keys[i].state == KS_LONGPRESSED)      // check longpressed button "autofire" function
         {
            // key is still (long)pressed
            // (we don't have to check the key's mode here since only a KM_ACT_ON_LONGPRESS-
            // button can get into the KS_LONGPRESSED state
            keys[i].counter++;      // increase keypress counter
            
            // do we have to trigger the longpress action again?
            if(keys[i].counter >= keys[i]._repeat_delay)
            {
               keys[i].counter = 0;
               // +----------------------+
               // | DO LONGPRESS ACTION! |
               // +----------------------+
               bit_set(key_longpress_actions,i);
            }
         }
      }
   }
   // save current keystate
   last_keystate = keystate;
}
   


...und hier ein kleines Beispielprogramm zur Veranschaulichung der 
Benutzung:
// aus Performancegründen sollte die TRIS-Registereinstellung von Hand erfolgen
#use fast_io(b)

// als Beispiel sind an 4 Pins von Port A LEDs angeschlossen
#define LED_RED    PIN_A0
#define LED_GREEN  PIN_A1
#define LED_YELL   PIN_A2
#define LED_BLUE   PIN_A3

// Keyhandler ins Projekt einbinden
#include "KeyHandler.c"
.
.
.
void main()
{
   int1 red_ledstate = false;   // Statusvariable für die rote Demo-LED
   int1 yell_ledstate = false;  // Statusvariable für die gelbe Demo-LED
   int1 blue_ledstate = false;  // Statusvariable für die blaue Demo-LED
   .
   .
   .
   // die 4 Demo-LEDs zunächst abschalten
   output_low(LED_GREEN);
   output_low(LED_RED);
   output_low(LED_YELL);
   output_low(LED_BLUE);

   set_tris_b(0xFF);   // alle Pins von Port B als Eingänge

   // Der Handler benötigt Pull-up-Widerstände an den Tasten. 
   // Im idealfall können die internen pullups genutzt werden
   port_b_pullups(true);
   
   // Variablen des Keyhandlers initialisieren
   InitKeys();
   
   // Tasten konfigurieren (Tastennummer, Tastenmodus, Wiederholverzögerung, Zweitfunktionverzögerung)
   setup_key(0, KM_ACT_ON_PRESS, 3, 0);          // Taste 0 löst bei einfachem Tastendruck aus,
                                                // wiederholte Auslösung nach kurzer Verzögerung
   setup_key(1, KM_ACT_ON_RELEASE, 0, 0);  // Taste 1 löst nur beim Loslassen der Taste aus
   setup_key(2, KM_ACT_ON_LONGPRESS, 255, 10);  // Taste 2 löst beim Loslassen aus (Funktion 1),
                                                // wird sie länger gedrückt gehalten, löst die
                                                // Zweitfunktion aus. (wird die Taste weiterhin 
                                                // gedrückt gehalten, löst nach langer Verzögerung
                                                // die Zweitfunktion wiederholt erneut aus.
   .
   .
   .
   setup_key(7, KM_ACT_ON_LONGPRESS, 2, 5);  // Taste 7 löst beim Loslassen aus (Funktion 1),
                                                // wird sie länger gedrückt gehalten, löst die
                                                // Zweitfunktion aus. (wird die Taste weiterhin 
                                                // gedrückt gehalten, löst nach kurzer Verzögerung
                                                // die Zweitfunktion wiederholt erneut aus.

   // Hauptschleife
   while(1)
   {
       // KeyHandler aufrufen
       CheckKeys();

       // Hier als Beispiel ein paar Reaktionen auf Tastennutzung:
       if(KeyPress(0)) // Taste 0 gedrückt?
          output_high(LED_GREEN);   // Wenn Taste gedrückt LED einschalten
       else
          output_low(LED_GREEN);    // ...sonst LED ausschalten

      if(KeyPress(1)) // Taste 1 losgelassen?
      {
          // wenn ja, gelbe LED umschalten
          yell_ledstate = !yell_ledstate;
          output_bit(LED_YELL,yell_ledstate);   // LED-Status am Portpin ausgeben
      }

      // Taste 2 hat zwei Funktionen. Zunächst Funktion 1
      if(KeyPress(2)) // Taste 2 losgelassen?
      {
          // wenn ja, rote LED umschalten
          red_ledstate = !red_ledstate;
          output_bit(LED_RED,red_ledstate);   // LED-Status am Portpin ausgeben
      }
      // nun die Zweitfunktion von Taste 2 prüfen:
      if(KeyLongpress(2)) // Taste 2 lange gedrückt gehalten?
      {
          // wenn ja, blaue LED umschalten
          blue_ledstate = !blue_ledstate;
          output_bit(LED_BLUE,blue_ledstate);   // LED-Status am Portpin ausgeben
      }
 
      // ein paar Millisekunden warten...
      delay_ms(50);
   }
}

Das gezeigte Beispiel kommt völlig ohne Timer aus.
Effizienter wird die Geschichte natürlich, wenn man die Funktion 
"CheckKeys" mittels eines Timer-Interrupts alle x ms aufruft.

Gruß
daniel

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier ein Beispielcode zwar für den AVR, aber da in C sollte eine 
Anpassung an PIC leicht gehen:

Beitrag "Universelle Tastenabfrage"

Pro Taste werden nur 5 Bitvariablen benötigt, d.h. es wird wesentlich 
weniger SRAM belegt.
Durch die parallele Abarbeitung ist auch der Flash- und Rechenzeitbedarf 
wesentlich geringer.


Peter

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

@Daniel: Die Tastenabfrage mit Interrupt on Change hat ihre Gründe. Ein 
zyklisches nachschauen ob sich was am PORT getan hat kommt in diesem 
Fall nicht in Frage, ebensowenig das Entprellen mit einer delay. 
Zugegeben, 2 Timer ist nicht gerade wenig ;-)
Mal sehen, mir ist da heute bei der Arbeit eine Idee gekommen das ganze 
mit dem bereits verwendeten Timer1 zu machen.

@Peter: So wie ich das sehe, gehst Du auch hier mit einer zyklischen 
Abfrage auf die Tasten. Dem steht das gleiche entgegen was ich Daniel 
schon geschrieben habe. Ich habe zwar nicht ganz bei Deiner 
interrupt-Routine durchgeblickt, aber ich denke Du machst das so wie ich 
mir es heute überlegt habe. Indem ich den Timer starte, und dann nach 
dem Tastendruck zyklisch (für eine maximlae Zeitdauer von z.B. 3s) 
abfrage ob die Taste noch gedrückt ist. Damit könnte ich einen langen 
Tastendruck abfragen. Soviel RAM vergeude ich jetzt auch nicht (nachdem 
ich die Portabarbeitung auf int8 gesetzt habe ;-) ). Sind nur 4Byte 
wobei da noch der Overhead dabei ist für die restlichen 4 Tasten die ich 
theoretisch (ohne PIN-Change_Interrupt) noch frei hätte ;-)

Grüße

Olaf

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Olaf wrote:
> Ein
> zyklisches nachschauen ob sich was am PORT getan hat kommt in diesem
> Fall nicht in Frage

Kannst Du dafür auch einen Grund nennen?

Oftmals hat man ja eh schon nen Timerinterrupt laufen und dann kann man 
den gleich mitbenutzen.

Der Code ist sehr kurz, d.h die CPU-Belastung ist <0,1%. Ein 
CPU-Leistungsverlust entsteht also praktisch nicht.
Durch die Kürze werden auch andere Interrupts kaum behindert.

Auch Stromsparen wird nicht verhindert. Man läßt die CPU durch den 
Pin-Change-Interrupt aufwecken, dann den Timerinterrupt entprellen und 
geht dann wieder schlafen.

Der Code merkt sich auch die Tastendrücke.
Andere Routinen verlieren oft den Tastendruck, wenn die Mainroutine 
gerade beschäftigt ist.
Das kann hier nicht passieren. Das Taste-gedrückt-Bit bleibt solange 
gesetzt, bis die Auswertung es zurücksetzt.

Man kann außerdem durch die Trennung von Entprellen/Drückdauererkennung 
und Auswertung in Untermenüs den Tasten sehr leicht unterschiedliche 
Funktionen zuweisen.


Peter

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Peter,

das Programm das in bestimmten Abständen oder per Anforderung aufgerufen 
wird, muss kontinuierlich abgearbeitet werden und sollte durch keine 
zusätzlichen Aufgaben unterbrochen werden. Ausser, es kommt etwas 
wichtiges dazwischen wie z.B. eine Abbruchbedingung durch einen 
Tastendruck (deshalb auch der lange Tastendruck). Das Programm wird 
durch einen Tastendruck (zunächst, später mit einem Triggersignal) 
gestartet, läuft ab, und geht dann, wenn es fertig ist, in den 
Sleepmode. Die Arbeit die der Controller zu verrichten hat ist gemittelt 
etwa max 1min. pro Tag und das ganze mit Batterieversorgung.

Olaf

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Olaf wrote:
> das Programm das in bestimmten Abständen oder per Anforderung aufgerufen
> wird, muss kontinuierlich abgearbeitet werden und sollte durch keine
> zusätzlichen Aufgaben unterbrochen werden.

Das ist ja gerade der Witz eines Interrupts, daß die unterbrochene Task 
überhaupt nichts davon mitbekommt. Sie wird also aus ihrer Sicht 
kontinuierlich abgearbeitet.
Und der Compiler sorgt dafür, daß alle verwendeten Register gesichert 
werden, es werden also auch keine Daten zerstört.
Die Task wird nur etwas verzögert, wenn man den Interrupt kurz hält.


Es gibt nur sehr wenige Anwendungen, die wirklich keine Interrupts (d.h. 
keinen Jitter) vertragen.
Z.B. wenn man ein Videosignal erzeugen will (stimmt aber auch nicht, man 
kann ja den Interrupt in der Austastlücke freigeben).

Man kann einfach die Interrupts in kritischen Abschnitten sperren.
Man kann sogar die Interrupts nur an einer ganz bestimmten Stelle für 
wenige Zyklen freigeben, damit alle bis dahin aufgelaufenen Ereignisse 
ihren Handler ausführen.

Dem Entprellinterrupt ist es völlig wurscht, wenn er mal einige 10000 
Zyklen später behandelt wird.


Peter


P.S.:
Ich will Dich nicht zu Interrupts überreden, ich wollte nur klarmachen, 
daß es keinen Grund gibt, auf Interrupts zu verzichten. Mir fällt 
zumindest keine derartige Anwendung ein.

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Peter,

-> (weiß nicht wie das mit dem zietieren geht)
P.S.:
Ich will Dich nicht zu Interrupts überreden, ich wollte nur klarmachen,
daß es keinen Grund gibt, auf Interrupts zu verzichten. Mir fällt
zumindest keine derartige Anwendung ein.


verstehe ich jetzt nicht. Überreden brauchst Du mich nicht, da ich in 
meiner Routine eh schon alles mit Interrupts mache. PortB wird mit 
Interrupt abgefragt und die Entprellroutine auch mit Interrupt gemacht. 
Das sind beim CCS-Compiler die kryptischen #int_RB void RB_isr() z.B. 
als PIN-Change- Interrupt. Das sieht beim AVR etwas anders aus, macht 
aber im Prinzip das gleiche.

Olaf

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Olaf wrote:
> -> (weiß nicht wie das mit dem zietieren geht)

geht wohl nur angemeldet.


> verstehe ich jetzt nicht. Überreden brauchst Du mich nicht, da ich in
> meiner Routine eh schon alles mit Interrupts mache.

Das bezog sich darauf, daß Du behauptet hast, Du könntest nicht mit dem 
Timerinterrupt entprellen (zyklische Abfrage).


Peter

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Peter,

da haben wir wohl aneinander vorbei geschrieben. Ich meinte das mit dem 
zyklischen Abfragen so, dass ich den µC nicht jedesmal aufwecken will 
wenn ich die Tasten abfrage. Das entprellen mache ich ja auch mit einem 
Timerinterrupt.

Olaf

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.