C für PICs: Grundlagen von C
zurück
zu C für PICs , C-Compiler , PIC-Prozessoren , Elektronik , Homepage
C für PICs
Grundlagen
von
C
Variablen
Funktionen
Operationen
Programmablaufsteuerung
Arrays und Strings
Pointer
Strukturierte Typen
PIC-spezifisches
zurück zu C für PICs
Grundlagen
von
C
Programmaufbau
Ein C-Programm besteht aus
- Preprozessor Direktiven
- Deklarationen
- Definitionen
- Ausdrücken und
- Anweisungen
Preprozessor Direktiven
Bevor der C-Compiler sich an die Arbeit macht, den Quelltext in Assembler zu
übersetzen, bereitet der Preprozessor den Quelltext auf. Er
erstellt aus den einzelnen *.c und *.h Files eines Projektes einen
großen Quelltextblock und ersetzt einzelne Textschnipsel durch
alternativen Text.
Was der Preprozessor genau machen soll, wird durch die Preprozessor Direktiven
festgelegt.
Dabei handelt es sich vor allem um die #define
Direktive, die #include
Direktive und die #ifdef
Direktive.
Die #include-Direktive
dient
dazu, an einer bestimmten Stelle im Quelltext den kompletten Inhalt
eines anderen Quelltext-Files einzufügen.
Die #define-Direktive weist
den Preprozessor an, einen bestimmten Text (z.B. ein Wort) im
gesamten
folgenden Quelltext gegen einen anderen Text zu ersetzen.
Mit #ifdef ... #endif
lässt sich festlegen, ob ein Textblock genutzt werden soll oder
nicht.
Deklarationen
Die Variablen sind in einem
Microcontroller nichts anderes als Speicherzellen, die durch Ihre
Adressen unterschieden werden können. Da es aber sehr
umständlich wäre, immer mit den Speicher-Adressen hantieren
zu müssen, legt man für die Variablen sinnvolle Namen fest.
Globale Variablen werden außerhalb von Funktionen deklariert. Sie
sind (ab der Deklaration) im gesamten Programm bekannt, und können
folglich überall verwendet werden.
Wird eine Variable innerhalb einer Funktion deklariert, so ist sie
nur
von der Deklaration bis zum Ende dieser Funktion bekannt. So eine
Variable ist eine lokale Variable. Mit dem Ende der Funktion wird
sie vergessen und der von ihr belegte Speicherplatz wieder frei
gegeben.
Ähnliches gilt auch für Funktionen (also Programmschnipsel)
und Typen. Eine Funktion ist ein Unterprogramm, mit einer bestimmten
Startadresse im Programmspeicher. Anstelle der korrekten
Programmspeicheradresse verwendet man einen Funktionsnamen, der mit
der
Funktionsdeklaration festgelegt wird.
Definitionen
Mit einer Definition legt man den (numerischen) Wert einer Variable
fest. Damit wird auch gleichzeitig der Typ und damit der
Speicherverbrauch einer Variable festgelegt.
Ausdrücke
Ein Ausdruck ist etwas, was einen bestimmten Zahlenwert hat. So ist
"428" zum Beispiel ein Ausdruck. Ein Ausdruck kann aber auch aus
mehreren Operatoren und
Operanden zusammengesetzt sein. So ist "152 + 6 * (12+8)" ebenfalls
ein
Ausdruck, da es einen bestimmten Zahlenwert besitzt, in diesem Fall
272.
Anstelle nackter Zahlen kann man auch Variablen als Operanden
einsetzen. "Startzeit + Weg / Speed". Der Zahlenwert des Ausdrucks
hängt dann vom momentanen Wert der Variablen ab.
Anweisungen
Die Anweisungen steuern den
Programmablauf.
#pragma
Pragmaanweisungen sind kein Programmcode, sondern eine Anweisung an
den
Compiler/Preprozessor, während der Compilierung etwas Spezielles
zu tun. Die Pragmas sind kein Bestandteil der C-Sprache,
ermöglichen aber auf bestimmte Eigenschaften von
Prozessoren/Mikrocontrollern einzugehen, die C allein nicht abdeckt.
Es gibt eine ganze Reihe unterschiedlicher Pragmaanweisungen.
#pragma code
#pragma
romdata
#pragma
udata
#pragma
idata
#pragma
config
#pragma
interrupt fname
#pragma
interruptlow fname
#pragma
sectiontype
#pragma
tmpdata [section-name]
#pragma
varlocate bank variable-name
#pragma
varlocate "section-name" variable-name
Ich kann hier nicht auf alle Pragmas eingehen. Einige wichtige
werden
aber im weiteren Text erläutert.
#include
Bevor ein C-Quelltext zum Compiler gelangt, wird er vom Preprozessor
bearbeitet. Die include-Direktive
weist
den
Preprozessor
an,
an dieser Stelle in den Quelltext den Inhalt eines weiteren
Textfile einzufügen. Der Name dieses Files wird in spitzen Klammer
oder in Anführungszeichen eingeschlossen.
#include
<math.h>
#include
"user.h"
Spitze Klammer bezeichnen eine Standard-Include-Datei (z.B.
Bibliothek
des Compilers). Sie wird in allen Include-Verzeichnissen gesucht.
Anführungszeichen dagegen sind für Include-Dateien gedacht,
die man selbst geschrieben hat. Sie wird zunächst im aktuellen
Verzeichnis des Projektes gesucht. Erst wenn Sie dort nicht gefunden
wird, sucht der Compiler in den Include-Verzeichnissen weiter.
Main
Der ausführbare Programmcode jedes C-Programms besteht aus
Programmblöcken, die Funktionen genannt
werden.
So eine Funktion kann man aufrufen, dann wird der Code in
dieser Funktion vom Prozessor abgearbeitet, und danach kehrt der
Prozessor zum rufenden Programm zurück.
Die Hauptfunktion eines jeden C-Programms heißt main. Ein C-Programm wird
gestartet,
indem main aufgerufen
wird. main kann dann
weitere Funktionen
aufrufen. Ist die main -Funktion
fertig
abgearbeitet, dann kehrt der Prozessor zum Betriebssystem
zurück. In einem PIC gibt es normalerweise kein Betriebssystem.
Deshalb macht der Prozessor beim Beenden von main das naheliegendste -
ein
Reset.
In der Regel enthält main in
einem
PIC-C-Programm
deshalb
eine
Endlosschleife, so dass das Programm endlos läuft, bis die
Betriebsspannung abgeschaltet wird, oder der Resettaster gedrückt
wird. Nach Reset oder power-on wird im PIC automatisch main gestartet.
Variable
Eine Variable ist ein Stück Speicher (RAM, GPR) des Prozessors, in
dem man Daten ablegen kann. Je nachdem, ob es sich um einfache ganze
Zahlen, große Fließkommazahlen, einzelne Zeichen oder Texte
handelt, benötigt man für eine Variable einen kleineren oder
größeren Speicherbereich.
Bevor man im C-Programm eine Variable benutzen kann, muss man sie
deklarieren. Dabei legt man den Namen der Variable fest, und welche
Datensorte man in ihr speichern will. Letzteres bezeichnet man als
den
Typ der Variable. C kennt von hause aus fünf Grundtypen: char, int, float,
double und void. Weitere Typen kann man
aber je
nach Bedarf definieren.
Bei der Variabeldeklaration teilt man dem C-Compiler den
gewünschten Typ und Namen der Variablen mit. Der Compiler
reserviert daraufhin einen ausreichend großen RAM-Bereich, und
merkt sich den Variablen-Namen, unter dem er zukünftig diesen
RAM-Bereich adressieren wird.
Variablen werden in einem
späteren Abschnitt detailliert behandelt.
Konstanten
(#define)
Während der Wert einer Variablen im Programm beliebig
geändert werden kann (im Rahmen ihres Typs), hat eine Konstante
einen festen Wert. So ist z.B. "25" ein fester Wert. Man kann einem
festen Wert einen Namen geben, der sich dann im Programm anstelle
seines Wertes verwenden lässt. Ein uns allen geläufiges
Beispiel ist die Kreiszahl Pi. Anstelle von 3,1415..... schreiben
wir
in Formeln lieber Pi, das spart Tinte.
Auch in C kann man mit so einer Konstanten arbeiten, dass setzt aber
voraus, dass man dem Compiler den Wert der Konstanten mitteilt. Das
erfolgt durch das #define
Statement
#define
Pi 3.1415926
#define LED LATBbits.LATB0
So, von nun an kann man in C-Ausdrücken einfach Pi schreiben, wenn man die
Kreiszahl
meint. Der Preprozessor wird dann automatisch vor der Compilierung
alle Pi's mit dem Zahlenwert 3,1415926 ersetzen. (Ich weiß, dass
das Apostroph dort nicht hingehört.)
Das zweite obige #define
Statement legt fest, dass nun "LED"
anstelle von "LATBbits.LATB0"
geschrieben werden kann. Dieser lange merkwürdige
Bezeichner ist in C18 der Name für das IO-Pin RB0. In meinem Beispiel
sei
an diesem Pin eine Leuchtdiode angeschlossen. Da ist es doch
praktisch,
dieses Pin von nun an einfach als LED
bezeichnen zu können.
Mit dem #define
Statement lassen sich auch kleine Makros definieren.
#define
mLED_On() LED =
1;
#define
mLED_Off() LED = 0;
#define
mLED_Toggle() LED = !LED;
Nun kann ich meine LED am Pin RB0 mit Hilfe dieser Makros ein-, aus-
oder umschalten. Schreibe ich irgentwo im Programm:
mLED_Toggle()
dann ersetzt das der Preprozessor automatisch mit
LED = !LED;
und merkt sofort, das er noch nicht fertig ist, sondern macht weiter
und erhält:
LATBbits.LATB0 = !LATBbits.LATB0;
#ifdef
#ifndef
#endif
Mit #ifdef und #endif lassen sich
Programmabschnitte einschließen, die nur in den Quelltext
aufgenommen werden sollen falls ein bestimmter Bezeichner vorab
definiert wurde. Dadurch kann man z.B. ein Programm während der
Entwicklung mit zusätzlichen Testfunktionen ausstatten, die dann
in der Endversion wieder herausgenommen werden, ohne sie aus dem am
Quelltext herauslöschen zu müssen.
Das folgende Beispiel zeigt, wie das funktioniert. Irgendwo am
Programmanfang steht die Definition eines Bezeichners mit
möglichst eingängigem Namen.
#define
TestFunktionen
Später im Quelltext steht die Testfunktion, die nur während
der Entwicklung benötigt wird. Beim Erreichen der #ifdef-Zeile
prüft der Preprozessor, ob er einen Bezeichner mit dem Namen
"TestFunktionen" kennt. Falls das nicht der Fall ist, überspringt
er den folgenden Quelltext bis zur nächsten #endif-Statement. Da
in unserem Beispiel aber TestFunktionen definiert wurde, wird auch
der
betroffene Quelltextabschnitt ins Programm aufgenommen.
#ifdef
TestFunktionen
// Alles
hier stehende
// wird vom Preprozessor aus dem Quelltext entfernt
// falls "TestFunktionen" unbekannt ist.
// Damit dieser Abschnitt im Quelltext enthalten ist, muss
also
//"TestFunktionen" mit einer #define-Anweisung definiert
werden,
// bevor der
Preprozessor
diesen Quelltextabschnitt hier erreicht.
void function TestRoutine(void)
{
....
}
#endif
Um später eine Programmversion ohne die Testfunktion zu erstellen,
genügt es, vor dem Compilieren die Define-Zeile durch Vorstellen
von "//" auszukommentieren (siehe nachfolgender
Abschnitt) oder zu löschen.
Das Gegenteil von #ifdef
(wenn-definiert) ist #ifndef
(wenn-nicht-definiert). Stößt der Preprozessor auf
eine Zeile "#ifndef Bezeichner", dann überspringt er alles bis zum
nächsten #endif, falls Bezeichner vorher definiert wurde.
Eine wichtige Funktion kommt #ifndef
und #endif im Zusammenhang
mit
#include in größeren Projekten zu. Es kommt leicht vor, dass
man ein File mit Definitionen (z.B. ein Header-File für eine
Bibliothek) in mehrere Quelltextfiles eines C-Programms einbindet.
Damit würden die Definitionen mehrfach auftauchen, was
natürlich zu Redifinitionen von Bezeichnern führen
würde. Der Compiler würde gewaltig schimpfen. Aus diesem
Grunde sorgen die einzubindenden Files selbst dafür, dass sie nur
ein mal eingebunden werden. Das sieht dann im einzubindenden File so
aus:
#ifndef
TollerBezeichner
#define TollerBezeichner
....
#endif
Der eigentliche Quelltext ist zwischen #define und #endif
eingeschlossen. Wird das File zum ersten mal eingebunden, dann ist
TollerBezeichner (hier wählt man am Besten einen Bezeichner, der
zum Filenamen passt) noch unbekannt, und der Quelltext wird
eingebunden. Dabei wird auch der Bezeichner definiert.
Wird nun das File zum wiederholten Male eingebunden, dann ist der
Bezeichner inzwischen ja bekannt, und der Quelltext des Files wird
nun
übersprungen.
Kommentare
Das Wichtigste an einem Programm sind die Kommentare. Das ist
beschreibender Text, den der Compiler ignorieren muss, denn er dient
ausschließlich dazu, dem menschlichen Quelltextleser zu
erläutern, was der Code machen soll. Für den Compiler ist der
Code also schlichtweg überflüssig, das Programm läuft
auch ohne Kommentare genauso gut. Will man aber auch nach einigen
Wochen
seinen eigenen C-Code verstehen, so sollte man als Mensch tunlichst
mit
Kommentaren beschreiben, was die Aufgabe des C-Codes ist, oder man
wird
es schwer haben, seine eigenen Gedankengänge wieder
nachzuvollziehen.
Kommentare müssen speziell gekennzeichnet werden, damit der
Preprozessor sie aus dem Code entfernen kann, bevor der Compiler
den Code übersetzt. Ein kurzer (einzeiliger) Kommentar wird mit
Doppelschrägstrich eingeleitet:
// das hier ist ein
Kommentar
Solch ein Kurzkommentar reicht bis zum Zeilenende. Die nächste
Zeile ist dann wieder Code. Soll ein mehrzeiliger Kommentar
geschrieben
werden, dann ist er mit /*
zu beginnen und mit */ zu
beenden.
/*
Alles
zwischen
diesen
Zeichen
ist
Kommentar.
*/
Funktionen
Der ausführbare Programmcode jedes C-Programms besteht aus
Programmblöcken, die Funktionen genannt werden. So eine Funktion
kann man aufrufen, dann wird der Code in dieser Funktion vom
Prozessor
abgearbeitet, und danach kehrt der Prozessor zum rufenden Programm
zurück.
Funktionen werden in einem
späteren Abschnitt detailliert behandelt.
zurück
zu C für PICs , C-Compiler , PIC-Prozessoren , Elektronik , Homepage
Autor: sprut
erstellt: 01.10.2007
letzte Änderung: 23.10.2012