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