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
übersetze, 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
vergesen 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
#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 man main aufruft. 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
addressieren 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 Präprozessor 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. Irrgentwo 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. Fals das nicht der Fall ist, überspringt
er den folgenden Quelltext bis zur nächsten #endif-Statement. Da
in unserem Beispiel aber TestFunktion definiert wurde, wird auch der
betroffene Quelltextabschnitt in's 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 Copmpilieren 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 schipfen. Aus diesem
Grunde sorgen die einzubindenden Files selbst dafür, dass sie nur
ein mal eingebunden werden. Das sieht dann 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 genausogut. 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
Präprozessor sie aus dem Code entfernen kann, bevor der Compiler
den
Code übersetzt. Ein kurzer 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: 01.10.2007