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


von Olaf (Gast)


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
1
#int_RB
2
void RB_isr() 
3
{
4
 unsigned int new_PortB=0; 
5
 unsigned int changes=0; 
6
 new_PortB =input_b(); //erstmal PortB einlesen
7
 new_PortB = ~new_Portb, // Ergebnis invertieren da LOW-Aktiv
8
 disable_interrupts(INT_RB); //erstmal PORTB-Interrupts deaktivieren um zu entprellen
9
 INTF_RB=1; //mal sehen obs das noch braucht
10
 changes=new_PortB^old_PortB; //Alter Wert mit neuem Wert vergleichen (Exklusiv-Oder)
11
 changes=changes&new_PortB; //This now reflects every bit that has 
12
 //gone _high_ (use 'old_PortB', instead of 'new_pins',if you want to find 
13
 //the ones that have gone low). 
14
 changes=changes>>4;
15
 old_PortB=new_PortB; //update your stored value for the future 
16
 setup_timer_1(T1_INTERNAL | T1_DIV_BY_4); //Timer1_Interrupt aktivieren und Vorteiler durch 4
17
 SET_TIMER1(0);  //Timer1 mit 0 laden
18
19
 if (first_bit(changes)) // Test auf PORTB4
20
 { 
21
  Taste1=1;
22
 } 
23
24
 if (second_bit(changes)) // Test auf PORTB5 
25
 { 
26
  Taste2=1;
27
 } 
28
29
 if (third_bit(changes)) // Test auf PORTB6
30
 { 
31
  Taste3=1;
32
 } 
33
34
 if (fourth_bit(changes)) // Test auf PORTB7
35
 { 
36
  Taste4=1;
37
 }
38
39
}//Ende RB_ISR

Timer1 wird zum entprellen verwendet. Das einzige was in der ISR von 
TIMER1 gemacht wird ist folgendes
1
#int_TIMER1
2
void timer1interrupt()
3
{
4
 setup_timer_1(T1_DISABLED); //wenn Timer1 Interrupt erfolgt, wird er deaktiviert
5
 enable_interrupts(INT_RB);  //PORTB-Interrupts werden wieder eingeschaltet
6
} //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

von Daniel P. (ppowers)


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:
1
// +----------------------------------------------------------------------+
2
// |                                                                      |
3
// |                      Comfort Keyhandling Code                        |
4
// |                                                                      |
5
// |           (C) 13/02/2008 - Daniel Porzig - www.base32.de             |
6
// +----------------------------------------------------------------------+
7
//
8
//
9
//
10
// How to use:
11
//   
12
//   1.) If available, enable internal pull-up resistors, otherwise you 
13
//      have to use   external pull-up resistors for every key you use.
14
//  2.) Call InitKeys();
15
//  3.) Setup Keys by calling "setup_key" for every key.
16
//  4.) Call "CheckKeys()" about every 50ms
17
//  5.) Check the key states by calling KeyPress(..) or KeyLongPress(..) 
18
//      for every key you want to use
19
//
20
//
21
//
22
23
// how many keys should be used
24
#define NUM_KEYS      7
25
26
// key states
27
#define KS_RELEASED    0
28
#define KS_PRESSED      1
29
#define KS_LONGPRESSED   2
30
31
// key modes
32
#define KM_ACT_ON_PRESS         0x01
33
#define KM_ACT_ON_RELEASE      0x02
34
#define KM_ACT_ON_LONGPRESS      0x03
35
36
// Key Data Structure (for each key)
37
struct key_struct
38
{
39
   int8 _mode;               // key mode (const)
40
   int8 _repeat_delay;       // delay for repeatedly pressed keys (const)
41
   int8 _longpress_delay;      // how long the key has to be pressed before a longpress gets activated
42
   int8 state;
43
   int8 counter;
44
   int8 longpress_cnt;
45
};
46
struct key_struct keys[NUM_KEYS];
47
48
// key state variables
49
int8 last_keystate = 0xFF;
50
int8 key_actions = 0x00;
51
int8 key_longpress_actions = 0x00;
52
53
54
// Configure a single button
55
//   num            - which key to configure
56
//   mode         - configure how the key reacts (KM_ACT_ON_PRESS, KM_ACT_ON_RELEASE or KM_ACT_ON_LONGPRESS)
57
//   repeat_delay   - when a button is (long)pressed continuously, this value sets the delay between 
58
//                 repeatedly triggered actions
59
//   longpress_delay   - configures how long a key (with key-mode == KM_ACT_ON_LONGPRESS) has to be hold down before
60
//                 a longpress action is triggered
61
void setup_key(int8 num, int8 mode, int8 repeat_delay, int8 longpress_delay)
62
{
63
   keys[num]._mode = mode;
64
   keys[num]._repeat_delay = repeat_delay;
65
   keys[num]._longpress_delay = longpress_delay;
66
}
67
68
// initializes Key data for all keys
69
//     call this function before first call of "CheckKeys()"
70
void InitKeys()
71
{
72
   int8 a;
73
   for(a=0; a<NUM_KEYS; a++)
74
   {
75
      keys[a].state = KS_RELEASED;
76
      keys[a].counter = 0;
77
      keys[a].longpress_cnt = 0;
78
   }
79
   last_keystate = 0xFF;
80
}
81
82
// Returns true if the standard action for key(num) has to be executed
83
int1 KeyPress(int8 num)
84
{
85
   return bit_test(key_actions, num);
86
}
87
88
// Returns true if the key(num) is currently longpressed
89
int1 KeyLongpress(int8 num)
90
{
91
   return bit_test(key_longpress_actions, num);
92
}
93
94
// Returns true if one or more keys are currently pressed or have changed state since last CheckKeys()
95
int1 KeyActivity()
96
{
97
   return (key_longpress_actions || key_actions)||(last_keystate!=0xFF);
98
}
99
100
// This function does all the keyhandling. Call this function about every 50ms.
101
void CheckKeys()
102
{
103
   int8 i, diff;
104
   int8 keystate = 0xFF;
105
106
   // get current state of keys
107
   keystate = input_b();
108
109
   // reset action flags
110
   key_actions = 0x00;
111
   key_longpress_actions = 0x00;
112
113
   // determin the difference between current and last keystate
114
   diff = last_keystate ^ keystate;
115
116
   // loop through all keys
117
   for(i=0; i<NUM_KEYS; i++)
118
   {
119
      // key has changed?
120
      if(bit_test(diff,i))
121
      {
122
         switch(keys[i].state)
123
         {
124
         case KS_PRESSED:   // key was in pressed state before
125
            keys[i].state = KS_RELEASED;   // change keystate
126
            keys[i].counter = 0;          // reset longpress counter
127
128
            if(keys[i]._mode == KM_ACT_ON_RELEASE)
129
            {
130
               // +---------------------+
131
               // | DO STANDARD ACTION! |
132
               // +---------------------+
133
               bit_set(key_actions,i);
134
            }
135
            else
136
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
137
            {
138
               // key was released, but key was not in Longpress state yet.
139
               // +---------------------+
140
               // | DO STANDARD ACTION! |
141
               // +---------------------+
142
               bit_set(key_actions,i);
143
            }
144
            else
145
            {
146
               // KM_ACT_ON_PRESS:     DO NOTHING
147
            }
148
         break;
149
         case KS_RELEASED:      // key was in released state before
150
            keys[i].state = KS_PRESSED;   // change keystate
151
            if(keys[i]._mode == KM_ACT_ON_PRESS)
152
            {
153
               keys[i].counter = 0;   // reset keypress counter
154
               // +---------------------+
155
               // | DO STANDARD ACTION! |
156
               // +---------------------+
157
               bit_set(key_actions,i);
158
            }
159
            else if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
160
            {
161
               // action is triggered later
162
               keys[i].longpress_cnt = 0;          // reset longpress counter            
163
            }
164
            else
165
            {
166
               // KM_ACT_ON_RELEASE:     DO NOTHING
167
            }
168
         break;
169
         case KS_LONGPRESSED:   // key was released after a Longpress. Special handling!
170
            keys[i].state = KS_RELEASED;   // change keystate
171
172
            // does this key have lonpress mode enabled?
173
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
174
            {
175
               // When a key was released after a longpress, nothing should happen!
176
            }
177
            else
178
            {
179
               // KM_ACT_ON_RELEASE:     DO NOTHING
180
               // KM_ACT_ON_PRESS:     DO NOTHING
181
            }
182
         break;
183
         }
184
      }
185
      else
186
      {
187
         // check keys whose states DID NOT change here!
188
         // (autofire, longpress repeat)
189
         
190
         if(keys[i].state == KS_PRESSED)
191
         {
192
            if(keys[i]._mode == KM_ACT_ON_RELEASE)
193
            {
194
               // key is either still pressed or still released so
195
               // no action needed here
196
            }
197
            else
198
            if(keys[i]._mode == KM_ACT_ON_PRESS)      // check "autofire" function
199
            {
200
               // key is still pressed
201
               keys[i].counter++;      // increase keypress counter
202
               if(keys[i].counter >= keys[i]._repeat_delay)
203
               {
204
                  keys[i].counter = 0;
205
                  // +---------------------+
206
                  // | DO STANDARD ACTION! |
207
                  // +---------------------+
208
                  bit_set(key_actions,i);
209
               }
210
            }
211
            else
212
            if(keys[i]._mode == KM_ACT_ON_LONGPRESS)
213
            {
214
               // key is still pressed
215
               keys[i].longpress_cnt++;      // increase keypress counter
216
217
               // do we have to trigger the longpress action?
218
               if(keys[i].longpress_cnt >= keys[i]._longpress_delay)
219
               {
220
                  keys[i].counter = 0;
221
                  // +----------------------+
222
                  // | DO LONGPRESS ACTION! |
223
                  // +----------------------+
224
                  keys[i].state = KS_LONGPRESSED;      // set key into longpressed state
225
                  bit_set(key_longpress_actions,i);
226
               }
227
            }
228
         }
229
         else
230
         if(keys[i].state == KS_LONGPRESSED)      // check longpressed button "autofire" function
231
         {
232
            // key is still (long)pressed
233
            // (we don't have to check the key's mode here since only a KM_ACT_ON_LONGPRESS-
234
            // button can get into the KS_LONGPRESSED state
235
            keys[i].counter++;      // increase keypress counter
236
            
237
            // do we have to trigger the longpress action again?
238
            if(keys[i].counter >= keys[i]._repeat_delay)
239
            {
240
               keys[i].counter = 0;
241
               // +----------------------+
242
               // | DO LONGPRESS ACTION! |
243
               // +----------------------+
244
               bit_set(key_longpress_actions,i);
245
            }
246
         }
247
      }
248
   }
249
   // save current keystate
250
   last_keystate = keystate;
251
}


...und hier ein kleines Beispielprogramm zur Veranschaulichung der 
Benutzung:
1
// aus Performancegründen sollte die TRIS-Registereinstellung von Hand erfolgen
2
#use fast_io(b)
3
4
// als Beispiel sind an 4 Pins von Port A LEDs angeschlossen
5
#define LED_RED    PIN_A0
6
#define LED_GREEN  PIN_A1
7
#define LED_YELL   PIN_A2
8
#define LED_BLUE   PIN_A3
9
10
// Keyhandler ins Projekt einbinden
11
#include "KeyHandler.c"
12
.
13
.
14
.
15
void main()
16
{
17
   int1 red_ledstate = false;   // Statusvariable für die rote Demo-LED
18
   int1 yell_ledstate = false;  // Statusvariable für die gelbe Demo-LED
19
   int1 blue_ledstate = false;  // Statusvariable für die blaue Demo-LED
20
   .
21
   .
22
   .
23
   // die 4 Demo-LEDs zunächst abschalten
24
   output_low(LED_GREEN);
25
   output_low(LED_RED);
26
   output_low(LED_YELL);
27
   output_low(LED_BLUE);
28
29
   set_tris_b(0xFF);   // alle Pins von Port B als Eingänge
30
31
   // Der Handler benötigt Pull-up-Widerstände an den Tasten. 
32
   // Im idealfall können die internen pullups genutzt werden
33
   port_b_pullups(true);
34
   
35
   // Variablen des Keyhandlers initialisieren
36
   InitKeys();
37
   
38
   // Tasten konfigurieren (Tastennummer, Tastenmodus, Wiederholverzögerung, Zweitfunktionverzögerung)
39
   setup_key(0, KM_ACT_ON_PRESS, 3, 0);          // Taste 0 löst bei einfachem Tastendruck aus,
40
                                                // wiederholte Auslösung nach kurzer Verzögerung
41
   setup_key(1, KM_ACT_ON_RELEASE, 0, 0);  // Taste 1 löst nur beim Loslassen der Taste aus
42
   setup_key(2, KM_ACT_ON_LONGPRESS, 255, 10);  // Taste 2 löst beim Loslassen aus (Funktion 1),
43
                                                // wird sie länger gedrückt gehalten, löst die
44
                                                // Zweitfunktion aus. (wird die Taste weiterhin 
45
                                                // gedrückt gehalten, löst nach langer Verzögerung
46
                                                // die Zweitfunktion wiederholt erneut aus.
47
   .
48
   .
49
   .
50
   setup_key(7, KM_ACT_ON_LONGPRESS, 2, 5);  // Taste 7 löst beim Loslassen aus (Funktion 1),
51
                                                // wird sie länger gedrückt gehalten, löst die
52
                                                // Zweitfunktion aus. (wird die Taste weiterhin 
53
                                                // gedrückt gehalten, löst nach kurzer Verzögerung
54
                                                // die Zweitfunktion wiederholt erneut aus.
55
56
   // Hauptschleife
57
   while(1)
58
   {
59
       // KeyHandler aufrufen
60
       CheckKeys();
61
62
       // Hier als Beispiel ein paar Reaktionen auf Tastennutzung:
63
       if(KeyPress(0)) // Taste 0 gedrückt?
64
          output_high(LED_GREEN);   // Wenn Taste gedrückt LED einschalten
65
       else
66
          output_low(LED_GREEN);    // ...sonst LED ausschalten
67
68
      if(KeyPress(1)) // Taste 1 losgelassen?
69
      {
70
          // wenn ja, gelbe LED umschalten
71
          yell_ledstate = !yell_ledstate;
72
          output_bit(LED_YELL,yell_ledstate);   // LED-Status am Portpin ausgeben
73
      }
74
75
      // Taste 2 hat zwei Funktionen. Zunächst Funktion 1
76
      if(KeyPress(2)) // Taste 2 losgelassen?
77
      {
78
          // wenn ja, rote LED umschalten
79
          red_ledstate = !red_ledstate;
80
          output_bit(LED_RED,red_ledstate);   // LED-Status am Portpin ausgeben
81
      }
82
      // nun die Zweitfunktion von Taste 2 prüfen:
83
      if(KeyLongpress(2)) // Taste 2 lange gedrückt gehalten?
84
      {
85
          // wenn ja, blaue LED umschalten
86
          blue_ledstate = !blue_ledstate;
87
          output_bit(LED_BLUE,blue_ledstate);   // LED-Status am Portpin ausgeben
88
      }
89
 
90
      // ein paar Millisekunden warten...
91
      delay_ms(50);
92
   }
93
}

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

von Peter D. (peda)


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

von Olaf (Gast)


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

von Peter D. (peda)


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

von Olaf (Gast)


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

von Peter D. (peda)


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.

von Olaf (Gast)


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

von Peter D. (peda)


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

von Olaf (Gast)


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

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.