C-Programmierung mit AVR-GCC/ Register

Aus Wikibooks
Wechseln zu: Navigation, Suche


Microcontroller haben außer der Recheneinheit (ALU) und dem Speicher noch weitere Funktionsblöcke, wie Timer/Counter, Schnittstellen, AD-Wandler und Ein-/Ausgabeports. Diese werden alle durch sog. Register konfiguriert und angesprochen. Beim AVR handelt es sich um einen 8-bit Controller, deshalb haben die Register eine breite von 8-bit. Natürlich müssen manchmal auch größere Werte gespeichert werden (16-bit Timer, AD-Wandler), dazu später mehr.

Die AVR-Controller verfügen über eine Vielzahl von Registern. Die meisten davon sind sogenannte Schreib-/Leseregister. Das heißt, das Programm kann die Inhalte der Register sowohl auslesen als auch beschreiben.

Einzelne Register sind bei allen AVRs vorhanden, andere wiederum nur bei bestimmten Typen. So sind beispielsweise die Register, welche für den Zugriff auf den UART notwendig sind, selbstverständlich nur bei denjenigen Modellen vorhanden, welche über einen integrierten Hardware UART bzw. USART verfügen.

Die Namen der Register sind in den Headerdateien zu den entsprechenden AVR-Typen definiert. Dazu muss man den Namen der controllerspezifischen Headerdatei nicht kennen. Es reicht aus, die allgemeine Headerdatei avr/io.h einzubinden:

#include <avr/io.h>

[Bearbeiten] Schreiben in Register

Zum Schreiben kann man Register einfach wie eine Variable setzen. In Quellcodes, die für ältere Versionen des avr-gcc/der avr-libc entwickelt wurden, erfolgt der Schreibzugriff über die Funktion outp(). Aktuelle Versionen des Compilers unterstützen den Zugriff nun direkt und outp() ist nicht mehr erforderlich.

Beispiel:

#include <avr/io.h>
 
...
 
int main(void)
{
    /* Setzt das Richtungsregister des Ports A auf 0xff 
       (alle Pins als Ausgang, vgl. Abschnitt Zugriff auf Ports): */
    DDRA = 0xff;    
 
    /* Setzt PortA auf 0x03, Bit 0 und 1 "high", restliche "low": */
    PORTA = 0x03;   
    ...
 
    // Setzen der Bits 0,1,2,3 und 4
    // Binär 00011111 = Hexadezimal 1F
    DDRB = 0x1F;    /* direkte Zuweisung - unübersichtlich */
 
    /* Ausführliche Schreibweise: identische Funktionalität, mehr Tipparbeit
       aber übersichtlicher und selbsterklärend: */
    DDRB = (1 << DDB0) | (1 << DDB1) | (1 << DDB2) | (1 << DDB3) | (1 << DDB4); 
}

Die ausführliche Schreibweise sollte bevorzugt verwendet werden, da dadurch die Zuweisungen selbsterklärend sind und somit der Code leichter nachvollzogen werden kann. Atmel verwendet sie auch bei Beispielen in Datenblätten und in den allermeisten Quellcodes zu Application-Notes.

Der gcc C-Compiler (genauer der Präprozessor) unterstützt ab Version 4.3.0 Konstanten im Binärformat, z.B. DDRB = 0b00011111 (für WinAVR wurden schon ältere Versionen des gcc entsprechend angepasst). Diese Schreibweise ist jedoch nicht standardkonform und man sollte sie daher insbesondere dann nicht verwenden, wenn Code mit anderen ausgetauscht oder mit anderen Compilern bzw. älteren Versionen des gcc genutzt werden soll.

[Bearbeiten] Verändern von Registerinhalten

Einzelne Bits setzt und löscht man "Standard-C-konform" mittels logischer (Bit-) Operationen.

 x |= (1 << Bitnummer);  // Hiermit wird ein Bit in x gesetzt
 x &= ~(1 << Bitnummer); // Hiermit wird ein Bit in x geloescht

Es wird jeweils nur der Zustand des angegebenen Bits geändert, der vorherige Zustand der anderen Bits bleibt erhalten.

Beispiel:

#include <avr/io.h>
...
#define MEINBIT 2
...
PORTA |= (1 << MEINBIT);    /* setzt Bit 2 an PortA auf 1 */
PORTA &= ~(1 << MEINBIT);   /* loescht Bit 2 an PortA */

Mit dieser Methode lassen sich auch mehrere Bits eines Registers gleichzeitig setzen und löschen.

Beispiel:

#include <avr/io.h>
...
DDRA &= ~( (1<<PA0) | (1<<PA3) );  /* PA0 und PA3 als Eingaenge */
PORTA |= (1<<PA0) | (1<<PA3);      /* Interne Pull-Up fuer beide einschalten */

In Quellcodes, die für ältere Version den des avr-gcc/der avr-libc entwickelt wurden, werden einzelne Bits mittels der Funktionen sbi und cbi gesetzt bzw. gelöscht. Beide Funktionen sind nicht mehr erforderlich.

Siehe auch:

[Bearbeiten] Lesen aus Registern

Zum Lesen kann man auf Register einfach wie auf eine Variable zugreifen. In Quellcodes, die für ältere Versionen des avr-gcc/der avr-libc entwickelt wurden, erfolgt der Lesezugriff über die Funktion inp(). Aktuelle Versionen des Compilers unterstützen den Zugriff nun direkt und inp() ist nicht mehr erforderlich.


Die Zustände von einzelnen Bits erhält man durch Einlesen des gesamten Registerinhalts und ausblenden der Bits deren Zustand nicht von Interesse ist. Einige Beispiele zum Prüfen ob Bits gesetzt oder gelöscht sind:


Die AVR-Bibliothek (avr-libc) stellt auch Funktionen (Makros) zur Abfrage eines einzelnen Bits eines Registers zur Verfügung, diese sind bei anderen Compilern meist nicht verfügbar (können aber dann einfach durch Macros "nachgerüstet" werden).

bit_is_set (<Register>,<Bitnummer>)
Die Funktion bit_is_set prüft, ob ein Bit gesetzt ist. Wenn das Bit gesetzt ist, wird ein Wert ungleich 0 zurückgegeben. Genau genommen ist es die Wertigkeit des abgefragten Bits, also 1 für Bit0, 2 für Bit1, 4 für Bit2 etc.
bit_is_clear (<Register>,<Bitnummer>)
Die Funktion bit_is_clear prüft, ob ein Bit gelöscht ist. Wenn das Bit gelöscht ist, also auf 0 ist, wird ein Wert ungleich 0 zurückgegeben.

Die Funktionen (eigentlich Makros) bit_is_clear bzw. bit_is_set sind nicht erforderlich, man kann und sollte C-Syntax verwenden, die universell verwendbar und portabel ist. Siehe auch Bitmanipulation.

[Bearbeiten] Warten auf einen bestimmten Zustand

Es gibt in der Bibliothek avr-libc Funktionen, die warten, bis ein bestimmter Zustand eines Bits erreicht ist. Es ist allerdings normalerweise eine eher unschöne Programmiertechnik, da in diesen Funktionen "blockierend" gewartet wird. Der Programmablauf bleibt also an dieser Stelle stehen, bis das maskierte Ereignis erfolgt ist. Setzt man den Watchdog ein, muss man darauf achten, dass dieser auch noch getriggert wird (Zurücksetzen des Watchdogtimers).

Die Funktion loop_until_bit_is_set wartet in einer Schleife, bis das definierte Bit gesetzt ist. Wenn das Bit beim Aufruf der Funktion bereits gesetzt ist, wird die Funktion sofort wieder verlassen. Das niederwertigste Bit hat die Bitnummer 0.

#include <avr/io.h>
...
 
/* Warten bis Bit Nr. 2 (das dritte Bit) in Register PINA gesetzt (1) ist */
 
#define WARTEPIN PINA
#define WARTEBIT PA2
 
// mit der avr-libc Funktion:
loop_until_bit_is_set(WARTEPIN, WARTEBIT);
 
// dito in "C-Standard":
// Durchlaufe die (leere) Schleife solange das WARTEBIT in Register WARTEPIN
// _nicht_ ungleich 0 (also 0) ist.
while ( !(WARTEPIN & (1 << WARTEBIT)) ) ;
...


Die Funktion loop_until_bit_is_clear wartet in einer Schleife, bis das definierte Bit gelöscht ist. Wenn das Bit beim Aufruf der Funktion bereits gelöscht ist, wird die Funktion sofort wieder verlassen.

#include <avr/io.h>
...
 
/* Warten bis Bit Nr. 4 (das fuenfte Bit) in Register PINB geloescht (0) ist */
#define WARTEPIN PINB
#define WARTEBIT PB4
 
// avr-libc-Funktion:
loop_until_bit_is_clear(WARTEPIN, WARTEBIT);
 
// dito in "C-Standard":
// Durchlaufe die (leere) Schleife solange das WARTEBIT in Register WARTEPIN
// gesetzt (1) ist 
while ( WARTEPIN & (1<<WARTEBIT) ) ;
...

Universeller und auch auf andere Plattformen besser übertragbar ist die Verwendung von C-Standardoperationen.

Siehe auch:

Wikibooks buchseite.svg Zurück zu Warteschleifen | One wikibook.svg Hoch zu Inhaltsverzeichnis | Wikibooks buchseite.svg Vor zu
Meine Werkzeuge
Namensräume

Varianten
Aktionen
Navigation
Mitmachen
Werkzeuge
Drucken/exportieren