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
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.

nach oben


#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.
nach oben


#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.

nach oben


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.

nach oben


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.

nach oben


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;

nach oben


#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.

nach oben


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.
*/
nach oben


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.
nach oben

--> weiter zu Variablen

zurück zu C für PICs , C-Compiler , PIC-Prozessoren , Elektronik , Homepage



Autor: sprut
erstellt: 01.10.2007
letzte Änderung: 23.10.2012