Taster an µC anschließen
Typischerweise werden Taster mit einem PullUp Widerstand an den Eingang eines µC angeschlossen. Dabei fällt besonders bei Anwendungen, die Tastendrücke zählen sollen auf, dass bei einem Tastendruck oft mehr als ein Impuls erzeugt wird. Die Ursache ist das Tastenprellen, Kontaktprellen. Beim Schließen des Kontaktes wird die Verbindung noch mehrmals unterbrochen durch das Nachfedern der Kontake.
Nebenstehend ein Timingdiagramm für das Prellen eines Drehwellen-Schnappschalters der Firma Marquardt der im Münzprüfer eines Kunstautomaten verbaut wurde. Bei diesem Schalter ist das Prellen nach 1,2 ms vorbei und ein konstanter Pegel erreicht. Es gibt mehrere Lösungsansätze dem Prellen zu begegnen.
Hier sollen softwarebasierte Lösungen mit Warteschleifen vorgestellt werden.
Zähler für Tastendrücke
Ein Zähler soll die Tastendrücke auf PD0 zählen und binär auf PORTB ausgeben.
#include <avr/io.h> // Definitionen laden
#include <util/delay.h> // Delay-Bibliothek laden
int main(){ // Hauptprogramm
PORTB=0xff; // alle LED aus
DDRB=0xff; // PB als Ausgang
unsigned char zaehler = 0;
while(1){ // Endlosschleife
while(PIND&1); // solange Taste nicht gedrueckt
_delay_ms(10); // Prellen abwarten
zaehler++;
PORTB=~zaehler; // Ausgeben
while(!(PIND&1)); // solange Taste gedrueckt
}
return 0;
}
Um die Impulse durch das Prellen nicht mit zu zählen wird nach dem ersten Impuls so lange wartet, bis das Prellen vorbei ist. Dazu muss die Wartezeit warte1 länger als die Prellzeit des Tasters sein. Sollte der Taster beim Loslassen auch prellen muss dafür eine Wartezeit warte2 eingebaut werden. Versuchsweise können die Unterprogrammaufrufe _delay_ms() auskommentiert werden um die Auswirkung des Prellens zu studieren.
Abfrage, Maskierung und Entprellen mehrerer Tasten
#include <avr/io.h> // Definitionen laden #include <util/delay.h> // Delay-Bibliothek laden #define KEYREADER ~PIND & 0b01111111 // Einlesen, invertieren, maskieren zurecht schieben unsigned char keyOld = 0; // alter Tasten-Zustand unsigned char keyEnter,keyExit; // gedrueckte und losgelassene Tasten void keyCheck(){ // Tastaturabfrage mit Flankendedektion unsigned char keyTest,tmp; keyEnter = 0, keyExit = 0; keyTest = KEYREADER; // Einlesen if (keyOld != keyTest){ // hat sich was getan _delay_ms(10); // Prellen abwarten tmp = KEYREADER; // noch mal Einlesen if (tmp == keyTest){ // ist es stabil? keyEnter = (~keyOld) & keyTest; // steigende Flanke !alt und neu keyExit = keyOld & (~keyTest); // fallende Flanke alt und !neu keyOld = keyTest; } } } int main(){ // Hauptprogramm PORTB=0xff; // alle LED aus DDRB=0xff; // PB als Ausgang unsigned char zaehler = 0; while(1){ // Endlosschleife keyCheck(); // Tastatur abfragen if(keyEnter&1){ // wenn Taste0 gedrueckt zaehler++; PORTB=~zaehler; // Ausgeben } } return 0; }
Ein Unterprogramm für eine recht störresistente entprellte Tastaturabfrage. Neu gedürchte Tasten werden in keyenter und losgelassene Tasten in keyexit erfasst. Das Invertieren der Eingänge, Maskieren und Zurechtschieben kann mit KEYREADER eingestellt werden.
Gedanken zur Größe des PullUp-Widerstandes
Sei der µC in einem Fahrradtacho eingesetzt und der Schalter sei der Reedkontakt an der Gabel, der durch einen Magneten an einer Speiche geschlossen wird. Die Batterie sei eine LR2032 3V mit 40mAh Kapazität:
- Bei einem kleinen Widerstand z.B. R=100Ω fließt während der Schalter geschlossen ist ein nicht unbedeutender Querstrom: 3V/100Ω=30mA . Befindet sich der Magnet an der Speiche dauerhaft über dem Reedkontakt, ist mir schon beim Abstellen des Fahrrads schon passiert, wäre die Batterie in etwas über einer Stunde leer!
- Bei einem großen Widerstand z.B. R=1MΩ können elektrische Einstreuungen (z.B. durch Dynamo) in die Sensorleitung bei offenem Reedkontakt bereits zu Fehlsignalen führen.
- Typische Werte für PullUp Widerstände bei µC liegen in der Praxis zwischen 4,7kΩ-10kΩ.
Einbauen von keyCheck() mittels #include in Quelltext
Oft benötigte Hilfsfunktionen werden gerne in externe Dateien ausgelagert und dann mittels #include in den Quelltext eingebaut. Dies macht den Quelltext übersichtlicher.
unsigned char keyOld =0; // alter Tasten-Zustand unsigned char keyEnter,keyExit; // gedrueckte und losgelassene Tasten void keyCheck(){ // Tastaturabfrage mit Flankendedektion unsigned char keyTest,tmp; keyEnter = 0, keyExit = 0; keyTest = KEYREADER; // Einlesen if (keyOld != keyTest){ // hat sich was getan _delay_ms(10); // Prellen abwarten tmp = KEYREADER; // noch mal Einlesen if (tmp == keyTest){ // ist es stabil? keyEnter = (~keyOld) & keyTest; // steigende Flanke !alt und neu keyExit = keyOld & (~keyTest); // fallende Flanke alt und !neu keyOld = keyTest; } } }
Einfacher Ansatz
Die Hilfsfunktion wird einfach in eine externe Datei keycheck.inc ausgelagert und im Hauptprogramm eingefügt.
Die ausgelagerte Datei befindet sich hier im selben Verzeichnis wie der c-Quellcode, darf jedoch nicht dem Projekt als Source-File hinzugefügt werden, sonst meckert der Compiler.
#include <avr/io.h> // Definitionen laden #include <util/delay.h> // CPU Frequenz einstellen! #define KEYREADER ~PIND & 0b01111111 // Einlesen, invertieren, maskieren zurecht schieben #include "keycheck.inc" // Codetext einbinden int main(){ // Hauptprogramm PORTB=0xff; // alle LED aus DDRB=0xff; // PB als Ausgang while(1){ // Endlosschleife keyCheck(); if(keyEnter&1){ // Taste gedrueckt? PINB=1; // LED an PB0 invertieren } } return 0; }
Tyischer Einbau mit Header-Datei
[Include-Files (C)] Der C-konforme Ansatz mit Einbindung in das Projekt ist wesentlich komplizierter. In einer Header-Datei werden die im Hauptprogramm benötigten Variablen und Schnittstellen deklariert. In der .c-Datei wird der Quellcode definiert. Die Headerdatei wird ins Hauptprogramm und in die gleichnahmige Definitionsdatei eingebunden (mit #inlcude). Eine doppelte Deklaration (Fehlermeldung!) wird mit #ifndef vermieden.
#ifndef CHECKKEYS_H_INCLUDETD // notwendig um Doppeldeklarationen zu vermeiden #define CHECKKEYS_H_INCLUDETD unsigned char keyEnter,keyExit; // gedrueckte und losgelassene Tasten void checkKeys(unsigned char keyMask,unsigned char keyNlogik); // Maske, neg.Logik #endif
#include <avr/io.h> // Definitionen laden #include <util/delay.h> // CPU Frequenz einstellen! #include "checkKeys.h" void checkKeys(unsigned char keyMask,unsigned char keyNlogik){// Maske, neg.Logik static unsigned char keyOld = 0; unsigned char keyTest,tmp; keyEnter = 0, keyExit = 0; keyTest = (PIND^keyNlogik)&keyMask; // Einlesen if (keyOld != keyTest){ // hat sich was getan _delay_ms(20); // Prellen abwarten tmp = (PIND^keyNlogik)&keyMask; // noch mal Einlesen if (tmp == keyTest){ // ist es stabil? keyEnter = (~keyOld) & keyTest; // steigende Flanke !alt und neu keyExit = keyOld & (~keyTest); // fallende Flanke alt und !neu keyOld = keyTest; } } }
#include <avr/io.h> // Definitionen laden #include <util/delay.h> // CPU Frequenz einstellen! #include "checkKeys.h" // Header einbinden int main(){ // Hauptprogramm PORTB=0xff; // alle LED aus DDRB=0xff; // PB als Ausgang while(1){ // Endlosschleife checkKeys(0b11,0b01); // Maske,NLogik if(keyEnter&1){ // Taste gedrueckt? PORTB^=1; // LED an PB0 invertieren } if(keyEnter&2){ // Taste gedrueckt? PORTB^=2; // LED an PB1 invertieren } } return 0; }
OOP Lösung
Der Compiler kann auch C++, leider funktioniert _delay_ms() nicht mehr –Fehlermeldung, muss selber gebastelt werden. Zunächst wird die Klasse deklariert. In der .cpp-Datei wird der Quellcode definiert. Im Hauptprogramm wird die Header-Datei eingebunden und ein KeyChecker-Objekt erzeugt.
class KeyChecker{ public: KeyChecker(unsigned char keyM,unsigned char keyNl,unsigned char hz); void checkKeys(); unsigned char getEnter(); unsigned char getExit(); void delay(unsigned int d); // eingenes Delay, da delay.h Fehler ergibt private: unsigned char keyEnter, keyExit; unsigned char keyMask,keyNlogik,keyOld,mhz; };
#include <avr/io.h> // Definitionen laden #include "KeyChecker.h" KeyChecker::KeyChecker(unsigned char keyM,unsigned char keyNl,unsigned char hz){ keyMask=keyM; keyNlogik=keyNl; keyOld=keyNl; mhz=hz; } void KeyChecker::delay(unsigned int d){ // Zeit muss noch kalibriert werden volatile unsigned int i,k; for (i=0;i<d;i++) for(k=0;k<100*mhz;k++); } void KeyChecker::checkKeys(){ // Tastaturabfrage mit Flankendedektion unsigned char keyTest,tmp; keyEnter = 0, keyExit = 0; keyTest = (PIND^keyNlogik)&keyMask; // Einlesen if (keyOld != keyTest){ // hat sich was getan delay(20); // Prellen abwarten tmp = (PIND^keyNlogik)&keyMask; // noch mal Einlesen if (tmp == keyTest){ // ist es stabil? keyEnter = (~keyOld) & keyTest; // steigende Flanke !alt und neu keyExit = keyOld & (~keyTest); // fallende Flanke alt und !neu keyOld = keyTest; } } } unsigned char KeyChecker::getEnter(){ return keyEnter; } unsigned char KeyChecker::getExit(){ return keyExit; }
#include <avr/io.h> // Definitionen laden #include "KeyChecker.h" // Header einbinden int main(){ // Hauptprogramm PORTB=0xff; // alle LED aus DDRB=0xff; // PB als Ausgang KeyChecker kCheck(0b11,0b01,1); // Maske,neg.Logik, MHz while(1){ // Endlosschleife kCheck.checkKeys(); // Tasten abfragen if(kCheck.getEnter()&1){ // Taste 0 gedrueckt? PINB=1; // LED an PB0 invertieren } if(kCheck.getEnter()&2){ // Taste 1 gedrueckt? PINB=2; // LED an PB0 invertieren } //kCheck.delay(500); //PINB=4; } return 0; }
Balkenanzeige [Demo1] [Demo2]
Entwickeln Sie ein Programm für eine Balkenanzeige.