3AVR-Controller in C programmieren
Bei der Programmierung der AVR-Controller in C sind einige Besonderheiten gegenüber einem PC-Programm zu beachten. Zum einen gibt es in der Regel keine Standardeingabe und -ausgabe, d.h., Funktionen wie printf()
und scanf()
entfallen. Zum anderen will man die digitalen Ein-/Ausgabesignale (Ports) des Prozessors nutzen, um externe Hardware anzusteuern und auszulesen.
3.1Digitale Ein- und Ausgaben
Die AVR-Controller besitzen verschiedene 8 Bit breite I/O-Register (Ports), auf denen digitale Signale ausgegeben und eingelesen werden können. Die Port-Pins lassen sich unabhängig voneinander benutzen. Dazu muss man zuvor für jeden Pin die Datenrichtung angeben, indem man das zugeordnete Data Direction Register entsprechend setzt.
Direkt nach dem Einschalten der Betriebsspannung sind alle Register des Prozessors gelöscht, d.h., sie enthalten den Wert 0
. Wird ein Bit im Data Direction Register auf 1
gesetzt, ist damit der entsprechende Port-Pin als Ausgang konfiguriert.
Der ATmega8 besitzt die Ports B, C und D, deren Pins meist mehrere Funktionen haben. Im folgenden Beispielprogramm wird ein Taster ausgelesen, der mit Port D, Pin 4 (kurz: pd4
) verbunden ist. Bei gedrücktem Taster wird eine LED eingeschaltet, die an Port D, Pin 5 (pd5
) angeschlossen ist. Die restlichen I/O-Pins bleiben ungenutzt, es ist also nur das Data Direction Register für Port D, Pin 5 auf 1 (DDRD |= (1<<5)
) zu setzen (siehe Funktion _initPortDirections()
).
Listing Anfang
/**************************************************
Beispiel ATmega8 Port I/O:
Taster an pd4 lesen, LED an pd5 ansteuern
***************************************************/
#include <avr/io.h>
#define AN 1
#define AUS 0
#define set_bit(sfr,bit) sfr |= (1<<(bit))
#define clear_bit(sfr,bit) sfr &= ~(1<<(bit))
/*--------------------------------------------------*/
// function prototypes
/*--------------------------------------------------*/
void _initPortDirections(void);
uint8_t _Taster_T3_pressed(void);
void _setLED1(uint8_t val);
/*--------------------------------------------------*/
int main(void)
{ _initPortDirections();
while (1) // Endlosschleife, Atmega kehrt nie zurueck...
{ if(_Taster_T3_pressed())
_setLED1(AN);
else
_setLED1(AUS);
}
return 0;
}
/*!
**************************************************************
* @par Beschreibung:
* PORT-Richtungen einstellen
*************************************************************/
void _initPortDirections(void)
{ DDRB = 0; /* Port B: alle Pins auf INPUT */
DDRC = 0; /* Port C: alle Pins auf INPUT */
DDRD |= (1<<5); /* Port D: Ausgabe: LED1, pd5 */
}
/*!
**************************************************************
* @par Beschreibung:
* Taster T3 lesen, Pin pd4
*************************************************************/
uint8_t _Taster_T3_pressed(void)
{ uint8_t x;
x = PIND & (1<<4);
if(x!=0) return 1; /* gedrueckt */
else return 0;
}
/*!
*************************************************************
* @par Beschreibung:
* LED1 ansteuern, Pin pd5
*************************************************************/
void _setLED1(uint8_t val)
{
if(val!=0) set_bit(PORTD,5);
else clear_bit(PORTD,5);
}
Listing Ende
Sicher kann man diesen Programmcode auch deutlich kürzer halten, aber die Verwendung von Funktionen erhöht die Lesbarkeit und wird in größeren Programmen unverzichtbar.
Zu Beginn des Programms werden die Symbole »AN« und »AUS« sowie die Makros set_bit(sfr,bit)
und Letter clear_bit(sfr,bit)
definiert. Die Makros erleichtern das Setzen bzw. Löschen eines Bits in einem Register (siehe Funktion _setLED1()
).
Das Hauptprogramm main()
eines AVR-Controllers darf sich nicht beenden, sondern enthält eine Endlosschleife, in der mit maximaler Geschwindigkeit der Taster abgefragt und die LED entsprechend geschaltet wird.
Die Funktion _initPortDirections()
setzt die Data Direction Register des ATmega8, hier gibt es nur einen Ausgabe-Pin pd5
.
In der Funktion _Taster_T3_pressed()
wird der Taster abgefragt. Mit dem Zugriff auf PIND
kann man alle Pins am Port D gleichzeitig einlesen. Anschließend wird das interessierende Bit isoliert (»ausmaskiert«), und die Funktion gibt einen entsprechenden Wert zurück.
Ausgaben an Port D erfolgen, indem man auf PORTD
schreibt (siehe Funktion _setLED1()
).
3.2Die serielle Schnittstelle nutzen
Für die Kommunikation zwischen PC und AVR-Controller ist die serielle Schnittstelle (RS232) sehr hilfreich. Wenn der PC keine echte RS232-Schnittstelle mehr besitzt, kann man einen USB-RS232-Konverter benutzen, um diese einfache und praktische Schnittstelle weiterhin zu nutzen.
Die meisten AVR-Controller verfügen über eine USART-Schnittstelle (Universal Synchronous and Asynchronous Receiver and Transmitter). Für die Signalwandlung auf einen Pegel von +/–12 V ist externe Hardware nötig, die auf den Experimentierboards vorhanden ist.
In diesem Abschnitt werden die benötigten AVR-Register und ihre Einstellungen für die Kommunikation mit einem PC-Terminalprogramm vorgestellt. Die verwendeten Funktionen sind in einem C-Modul (avr_ser\avr_ser.h
und avr_ser\avr_ser.c
) zusammengefasst, das in den weiteren Abschnitten für die serielle Kommunikation Verwendung findet.
3.2.1Die serielle Schnittstelle initialisieren
Damit die serielle Kommunikation gelingen kann, ist nicht nur eine korrekte elektrische Verbindung der beiden Partner notwendig, es ist auch das »Protokoll« festzulegen. Dazu gehören die Baudrate, die Anzahl der Datenbits und Stoppbits sowie die Verwendung des Paritätsbits. In der Tabelle sind die relevanten AVR-Register und ihre Funktion dargestellt.
UDR | USART Data Register – eigentlich zwei Register, man schreibt das zu sendende Byte und liest das empfangene Byte |
UCSRA UCSRB UCSRC | USART Control and Status Register A/B/C – Konfiguration und Status des USART |
UBRRL UBRRH | USART Baud Rate Registers – Low-Byte und High-Byte für die Baudrate |
Bild 3.3: USART-Register und ihre Funktion (Schäffer 2008).
Die folgende Funktion initialisiert die serielle Schnittstelle des ATmega8 für eine Kommunikation mit 8 Datenbit, kein Paritätsbit und 1 Stoppbit (8N1).
Listing Anfang
void serInit(uint32_t processorClock_Hz, uint32_t baudRate)
{ uint8_t x;
/* Baudrate Low und High Byte */
UBRRL = (processorClock_Hz / (16*baudRate))-1;
UBRRH = ((processorClock_Hz /...