Eingänge (Wie kommen Signale in den µC)
Die digitalen Eingangssignale können auf verschiedene Arten zu unserer Logik gelangen.
Am einfachsten ist es, wenn die Signale direkt aus einer anderen digitalen Schaltung übernommen werden können. Hat der Ausgang der entsprechenden Schaltung TTL-Pegel dann können wir sogar direkt den Ausgang der Schaltung mit einem Eingangspin von unserem Controller verbinden.
Hat der Ausgang der anderen Schaltung keinen TTL-Pegel so müssen wir den Pegel über entsprechende Hardware (z. B. Optokoppler, Spannungsteiler, "Levelshifter" aka Pegelwandler) anpassen.
Die Masse der beiden Schaltungen muss selbstverständlich miteinander verbunden werden. Der Software selber ist es natürlich letztendlich egal, wie das Signal eingespeist wird. Wir können ja ohnehin lediglich prüfen, ob an einem Pin unseres Controllers eine logische 1 (Spannung größer ca. 0,7*Vcc) oder eine logische 0 (Spannung kleiner ca. 0,2*Vcc) anliegt. Detaillierte Informationen darüber, ab welcher Spannung ein Eingang als 0 ("low") bzw. 1 ("high") erkannt wird, liefert die Tabelle DC Characteristics im Datenblatt des genutzten Controllers.
Spannungstabelle
(ca. Grenzwerte)
|
Low |
High |
bei 5 V |
1 V |
3,5 V |
bei 3,3 V |
0,66 V |
2,31 V |
bei 1,8 V |
0,36 V |
1,26 V |
Die Abfrage der Zustände der Portpins erfolgt direkt über den Registernamen.
Dabei ist wichtig, zur Abfrage der Eingänge nicht etwa Portregister PORTx zu verwenden, sondern Eingangsregister PINx. Ansonsten liest man nicht den Zustand der Eingänge, sondern den Status der internen Pull-Up-Widerstände. Die Abfrage der Pinzustände über PORTx statt PINx ist ein häufiger Fehler beim AVR-"Erstkontakt".
Will man also die aktuellen Signalzustände von Port D abfragen und in eine Variable namens bPortD abspeichern, schreibt man folgende Befehlszeilen:
#include <avr/io.h>
#include <stdint.h>
...
uint8_t bPortD;
...
bPortD = PIND;
...
|
Mit den C-Bitoperationen kann man den Status der Bits abfragen.
#include <avr/io.h>
...
/* Fuehre Aktion aus, wenn Bit Nr. 1 (das "zweite" Bit) in PINC gesetzt (1) ist */
if ( PINC & (1<<PINC1) ) {
/* Aktion */
}
/* Fuehre Aktion aus, wenn Bit Nr. 2 (das "dritte" Bit) in PINB geloescht (0) ist */
if ( !(PINB & (1<<PINB2)) ) {
/* Aktion */
}
...
|
Siehe auch Bitmanipulation#Bits_prüfen
[Bearbeiten] Interne Pull-Up Widerstände
Portpins für Ein- und Ausgänge (GPIO) eines AVR verfügen über zuschaltbare interne Pull-Up Widerstände (nominal mehrere 10kOhm, z. B. ATmega16 20-50kOhm). Diese können in vielen Fällen statt externer Widerstände genutzt werden.
Die internen Pull-Up Widerstände von Vcc zu den einzelnen Portpins werden über das Register PORTx aktiviert bzw. deaktiviert, wenn ein Pin als Eingang geschaltet ist.
Wird der Wert des entsprechenden Portpins auf 1 gesetzt, so ist der Pull-Up Widerstand aktiviert. Bei einem Wert von 0 ist der Pull-Up Widerstand nicht aktiv. Man sollte jeweils entweder den internen oder einen externen Pull-Up Widerstand verwenden, aber nicht beide zusammen.
Im Beispiel werden alle Pins des Ports D als Eingänge geschaltet und alle Pull-Up Widerstände aktiviert. Weiterhin wird Pin PC7 als Eingang geschaltet und dessen interner Pull-Up Widerstand aktiviert, ohne die Einstellungen für die anderen Portpins (PC0-PC6) zu verändern.
#include <avr/io.h>
...
DDRD = 0x00; /* alle Pins von Port D als Eingang */
PORTD = 0xff; /* interne Pull-Ups an allen Port-Pins aktivieren */
...
DDRC &= ~(1<<DDC7); /* Pin PC7 als Eingang */
PORTC |= (1<<PC7); /* internen Pull-Up an PC7 aktivieren */
|
[Bearbeiten] Tasten und Schalter
Der Anschluss mechanischer Kontakte an den Mikrocontroller gestaltet sich ebenfalls ganz einfach, wobei wir zwei unterschiedliche Methoden unterscheiden müssen: Active Low und Active High.
Anschluss mechanischer Kontakte an einen µC
Active Low: Bei dieser Methode wird der Kontakt zwischen den Eingangspin des Controllers und Masse geschaltet. Damit bei offenem Schalter der Controller kein undefiniertes Signal bekommt wird zwischen die Versorgungsspannung und den Eingangspin ein sogenannter Pull-Up Widerstand geschaltet. Dieser dient dazu, den Pegel bei geöffnetem Schalter auf logisch 1 zu ziehen.
|
Active High: Hier wird der Kontakt zwischen die Versorgungsspannung und den Eingangspin geschaltet. Damit bei offener Schalterstellung kein undefiniertes Signal am Controller ansteht, wird zwischen den Eingangspin und die Masse ein Pull-Down Widerstand geschaltet. Dieser dient dazu, den Pegel bei geöffneter Schalterstellung auf logisch 0 zu halten. Der Pull-Down-Widerstand muß extern geschaltet werden.
|
Der Widerstandswert von Pull-Up- und Pull-Down-Widerständen ist an sich nicht kritisch. Wird er allerdings zu hoch gewählt, ist die Wirkung eventuell nicht gegeben. Als üblicher Wert haben sich 10 kOhm eingebürgert. Die AVRs verfügen an den meisten Pins softwaremäßig über zuschaltbare interne Pull-Up Widerstände (vgl. Abschnitt Interne Pull-Up Widerstände), welche insbesondere wie hier bei Tastern und ähnlichen Bauteilen (z. B. Drehgebern) statt externer Bauteile verwendet werden können.
[Bearbeiten] (Tasten-)Entprellung
Nun haben alle mechanischen Kontakte, sei es von Schaltern, Tastern oder auch von Relais, die unangenehme Eigenschaft zu prellen. Dies bedeutet, dass beim Schließen des Kontaktes derselbe nicht direkt Kontakt herstellt, sondern mehrfach ein- und ausschaltet bis zum endgültigen Herstellen des Kontaktes.
Soll nun mit einem schnellen Mikrocontroller gezählt werden, wie oft ein solcher Kontakt geschaltet wird, dann haben wir ein Problem, weil das Prellen als mehrfache Impulse gezählt wird. Diesem Phänomen muss beim Schreiben des Programms unbedingt Rechnung getragen werden.
Beim folgenden einfachen Beispiel für eine Entprellung ist zu beachten, dass der AVR im Falle eines Tastendrucks 200ms wartet, also brach liegt. Bei zeitkritische Anwendungen sollte man ein anderes Verfahren nutzen (z. B. Abfrage der Tastenzustände in einer Timer-Interrupt-Service-Routine).
#include <avr/io.h>
#include <inttypes.h>
#ifndef F_CPU
#warning "F_CPU war noch nicht definiert, wird nun mit 3686400 definiert"
#define F_CPU 3686400UL /* Quarz mit 3.6864 Mhz */
#endif
#include <util/delay.h> /* bei alter avr-libc: #include <avr/delay.h> */
/* Einfache Funktion zum Entprellen eines Tasters */
inline uint8_t debounce(volatile uint8_t *port, uint8_t pin)
{
if ( ! (*port & (1 << pin)) )
{
/* Pin wurde auf Masse gezogen, 100ms warten */
_delay_ms(50); // max. 262.1 ms / F_CPU in MHz
_delay_ms(50);
if ( *port & (1 << pin) )
{
/* Anwender Zeit zum Loslassen des Tasters geben */
_delay_ms(50);
_delay_ms(50);
return 1;
}
}
return 0;
}
int main(void)
{
DDRB &= ~( 1 << PB0 ); /* PIN PB0 auf Eingang (Taster) */
PORTB |= ( 1 << PB0 ); /* Pullup-Widerstand aktivieren */
...
if (debounce(&PINB, PB0)) /* Falls Taster an PIN PB0 gedrueckt.. */
PORTD = PIND ^ ( 1 << PD7 ); /* ..LED an Port PD7 an-
bzw. ausschalten */
...
}
|
Die obige Routine hat leider mehrere Nachteile:
- sie detektiert nur das Loslassen (unergonomisch)
- sie verzögert die Mainloop immer um 100ms bei gedrückter Taste
- sie verliert Tastendrücke, je mehr die Mainloop zu tun hat.
Eine ähnlich einfach zu benutzende Routine, aber ohne all diese Nachteile findest Du hier: Entprellung für Anfänger
Zum Thema Entprellen siehe auch: