www.PIC-Projekte.de

Willkommen auf meiner privaten Elektrotechnik Homepage

Kleiner C Kurs für PIC Microcontroller

 Gliederung


» 1.0 Vorwort
» 1.1 Vorbereitungen
   › 1.2 Erstellen eines neuen Projekts mit C18 unter MPLAB
› 2.0 Erstes Beispielprogramm
» 3.0 C Grundlagen
   › 3.1 Variablen
   › 3.2 Variablen Deklaration
   » 3.3 Funktionen
         › Uebergabeparameter
         › Rueckgabeparameter
   » 3.4 Mathematische Operation
         › Arithmetische Operationen
         › Logische Operationen
         › Relationale Operationen
         › Bitweise Operationen
         › Increment und Decrement
   › 3.5 Arrays und Strings
   › 3.6 Bedingungen
   › 3.7 Switch Case
   » 3.8 Schleifen
         › While
         › For
         › Do While
   » 3.9 Weitere Steuerungen
         › Break
         › Continue
         › Return
» 4.0 Speziell für PIC
         › Konfiguration
         › Ein/Ausgänge
         › Wann PORT und wann LAT?
         › Interrupt
         »  Häufig gestellte Fragen und gern gemachte Fehler
                  › Gleich (=) ist nicht gleich Gleich (==)
                  › Funktion aufrufen
                  › Funktion anmelden
                  › Warteschleifen
         › C18 Libraries
› 5.0 Codesnipsel
         › Zwei 8-Bit Variablen in eine 16 Bit umwandeln
› 6.0 Schlusswort

 1.0 Vorwort


Tipp: Programmierung in Assembler und C

Ich möchte Ihnen hier in diesem kleinen C Kurs etwas die Hochsprache C näher bringen und vor allem wie man damit PIC Microcontroller programmieren kann. Ich habe damals mit Assembler angefangen was mir beim Einstieg in C ganz gut geholfen hat. Während ich Assembler nur 1 Jahr lang in der Berufsschule gelernt habe, musste ich mir C komplett selber beibringen aber Sie werden schnell feststellen, es ist nicht unmöglich. Und vorallem ist es schön, wenn man ein Programm schreibt, welches 1. nicht einmal halb so lang ist wie ein Assembler Programm und die selbe Aufgabe erledigt und 2. das Programm am Ende auch funktioniert.

 1.1 Vorbereitungen

Bevor wir loslegen können bedarf es ein paar Vorbereitungen. Zunächst einmal brauchen Sie die entsprechende Entwicklungsumgebung um das Programm für den PIC zu schreiben. Ich empfehle Ihnen die kostenlose Software  MPLAB IDE direkt von Microchip. Sie ist zum einen kostenlos und lässt sich mit dem Compiler  C18 verbinden, welchen wir embenfalls für das Schreiben unserer C-Programme benötigen. Wenn Sie dem Link zu dem C18 Compiler folgen, können Sie sich unten auf der Seite unter "Special free versions for academic" den Compiler kostenfrei herunter laden. Nun installieren Sie als erstes MPLAB IDE und im Anschluss den C18. Nun ist die Entwicklungsumgeung bereits komplett fertig eingerichtet für das Programmieren.

 1.2 Erstellen eines neuen Projekts mit C18 unter MPLAB

Hier möchte ich kurz erklären, wie sie mit dem C18 Compiler und der Entwicklungsumgebung ein neues Projekt erstellen. Nachdem Sie die Installationen erfolgreich abgeschlossen haben starten sie das Programm MPLAB IDE. Sie werden nun erstmal ein leeres Fenster vorfinden, das liegt daran, dass noch kein Projekt offen ist. Wählen Sie nun in dem Reiter: Project das Menü Project Wizard aus:



Jetzt können Sie sich Ihr Projekt erstellen. Als erstes suchen Sie sich den PIC heraus mitdem Sie arbeiten wollen. An dieser Stelle möchte ich nochmal darauf hinweisen, dass der C18 nur PIC18F unterstützt! Nachdem Sie sich ihren jeweiligen PIC18 ausgesucht haben, klicken Sie auf weiter. Jetzt verlangt das Programm von Ihnen eine Auswahl des Compilers. In der Auswahlliste entscheiden Sie sich für: Microchip C18 Toolsuite und suchen in dem unteren Fenster alle verlangten Dateien raus (wenn nicht schon gefunden):



Im Anschluss verlangt das Programm von Ihnen einen Pfad und Namen des neuen Projektes. Hier geben Sie am besten einen sinnvollen Namen ein, damit Sie später nachvollziehen können, was dies Projekt beinhaltet. Jetzt haben Sie eine neues Projekt angelegt, welches i.M. noch leer ist. Damit Sie jetzt Ihren Programmcode schreiben können wählen Sie File - new. Jetzt hat sich ein leeres Blatt geöffnet dieses Speichern Sie wiederum ab in Ihrem Projektordner unter Main.c Also gehen Sie unter File auf Speichern unter und tragen als Dateinamen main.c ein. Jetzt hat MPLAB erkannt, dass Sie ein C Dokument schreiben und die entsprechende Syntaxhervorhebung aktiviert. Nun müssen Sie nur noch die einelnen Dateien Ihrem Projekt hinzufügen. Gehen Sie dazu auf den Reiter View und setzen einen Haken bei Project. Jetzt haben Sie Ihren Projektordner vorliegen, welcher zum Schluss so aussehen muss:



Also Sie gehen mit einem Rechtsklick auf den Ordner Source File und wählen Add files.. aus und suchen Ihre abgespeicherte Main.c Datei. Unter Other Files fügen Sie die Dateien IHR_PROJEKT.mcw und IHR_PROJEKT.mcp ein. Dann brauchen Sie noch die Linker Datei für Ihren PIC, diese finden Sie im Ordner lkr im C18 Verzeichnes. Zum Schluss fügen Sie alle *.h Dateien hinzu, die Sie in Ihren Projekt verwenden (Bibliotheken), diese finden Sie ebenfalls im C18 Verzeichnis im h Ordner.

 2.0 Erstes Beispielprogramm
Hinweis: Ich habe ganz bewusst die Codebeispiele in diesem Kurs als Bilder eingefügt, denn es hilft niemandem, wenn Sie die Beispielprogramme nur per Copy & Paste übernehmen. Sie lernen am besten durch learning by doing! Ich empfehle Ihnen daher also immer direkt Versuche zu starten, mit dem was Sie gelesen haben!

Wenn Sie gleich das erste Beispielprogramm sehen, werden Sie sich warscheinlich erstmal wundern, da Sie lediglich irgendwelche Buchstaben und Zeichen auf einem Haufen sehen werden und Sie erstmal wenig bis nichts verstehen. Aber lassen Sie sich davon nicht abschrecken! Es verhällt sich ganz ähnlich wie Ihre ersten Englisch Unterrichtsstunden, da haben Sie auf erstmal "kein" Wort verstanden. Und genauso ist es auch beim Programmieren, denn es ist auch eine Fremdsprache.

 Hier das erste Programm:



Wie ich oben bereits erwähnt habe wird Sie das eventuell erstmal abschrecken. Aber lassen Sie sich nicht einschüchtern, es sieht schwieriger aus als es ist. Die Grundlagen der Programmiersprache C sind relativ einfach zu erlernen. Es ist allgemein üblich, dass Programmcodes in Englisch kommentiert werden, ich werde aber Grundsätzlich auf Deutsch zurück greifen, da ich finde man als Anfänger schon genug damit zu tun hat die Sprache C zu begreifen, da braucht man nicht noch eine Erklärung in einer weiteren Fremdsprache! Wenn Sie sich dann irgendwann etwas geübter in der Sprache C bewegen, kann ruhig mal ein Englischer Kommentar Platz finden, aber bis dahin nicht. Nun zurück zum Beispielprogramm. Nehmen wir das Programm einmal auseinander und schauen uns die einzelnen Befehle an:

//Unser erstes Beispiel-Programm

Das ist ein Kommentar und wird somit nicht vom Compiler1 berücksichtigt. Durch die einleitenden zwei / wird ein Kommentar angezeigt. Alles was sich nach diesen beiden Slashs in der Zeile befindet hat keinen Einfluss auf das Programm. Sobald aber die Zeile zu Ende ist und Sie in der nächsten Zeile schreiben, ist der Kommentar zu Ende! Wenn Sie Kommentare verfassen, welche über die Länge von einer Zeile hinaus gehen, dann sollten Sie sich einer anderen Schreibweise bedienen:

1 Übersetzt das Programm in die Maschinensprache

/*So können Sie
ruhig in mehreren Zeilen schreiben*/

Durch deinen Schrägstrich mit einem folgenden Sternchen * leiten Sie ein Kommentar ein und alles was nun bis zu dem beendenden */ geschrieben wird ist in dem Kommentar und wird nicht den Programmcode beeinflussen. Nun schauen wir uns den nächsten Code an:

#include <p18cxxx.h>

Durch den #include Befehl können wir Funktions Bibliotheken in unser Programm einbinden und sonit benutzen. Die Bibliothek des verwendenden PIC ist natürlich gezungenerma&szlig;en erforderlich! Durch den include Befehl werden Dateien in das Programm eingebunden, welche die Dateiendung *.h haben. Also es sind "header" Dateiein. Mehr Information über den include Befehl wird im Kapitel "Präprozessor" beschrieben! Also zusammen gefasst, Sie müssen immer die Bibliothek des PIC, den Sie verwenden einbinden.

void main(void)

Durch den Befehl wird die Main Funktion eröffnet. Alles was nun in den beiden folgenden geschweiften Klammern { } steht, gehört zu der Main Funktion. Dies ist ganz ähnlich wie in Assembler wo das Main Programm durch die Marke Main begonnen wird und durch ein Goto Main abgeschlossen wird. Das void vor dem main ist der so genannte Rückgabeparameter, man kann damit einer Funktion einen Wert zurück geben werden (Dies wird aber später unter Funktionen erklärt). Das void nach dem main in Klammern ist der Übergabeparameter (wird ebenfalls unter Funktionen noch erklärt und da wir in diesem Beispiel-Programm auf Über/Rückgabe-Parameter verzichten, wird an die stellen void geschrieben was soviel bedeutet wie, wird nicht verwendet, leer.

Wichtig: Die Funtkion Main, muss immer vorhanden sein!

 3.0 C Grundlagen


Vorab der Hinweis, wenn Sie ein C Programm erstellen müssen Sie beachten, dass zwischen Gro&szlig;- und Kleinschreibung unterschieden wird. Wenn Sie also eine Funktion (Unterprogramm) schreiben und dieses test nennen und dann mit dem Befehl TEST() aufrufen wollen, wird das einen Fehler erzeugen. Sie müssen die Gro&szlig;- und Kleinschreibung beachten! Wie Sie ja bei dem ersten Beispiel-Programm schon gesehen haben, wird pro Zeile i.d.R. nur ein Befehl geschrieben, denn nehmen Sie einmal das Programm von oben und schreiben es so auf:


Das ist schlicht und einfach unübersichtlich. Es mag bei diesem Beispiel noch nicht so dramatisch sein, doch wenn das Programm umfangreicher wird, dann verliert man sehr schnell den Überblick. Rein programmtechnisch würde die Schreibweise aber so auch arbeiten. Es ist aber einfach nicht Zweckdienlich. Und au&szlig;erdem wird Ihnen jeder Mensch dieser Erde bei einer solchen Programmierung seine Hilfe verweigern, mit Sicherheit! Daher gewöhnen wir uns ein paar Schreibregeln an. Wir schreiben Grundsätzlich nur einen Befehl pro Zeile gefolgt von einem Kommentar. Und wenn eine Funktion eingeleitet wird, dann wird der Code zwischen den geschweiften Klammern eingerückt geschrieben, so erkennt man sofort wo eine Funktion beginnt und wo sie endet. Was wir uns nicht aussuchen können ist, dass eine Anweisung (Befehl) immer mit einem Semikolon (;) beendet werden müssen. Ein PIC beginnt seine Arbeit immer bei der Main-Funktion, deshalb muss dies auch immer vorhanden sein! In der Main Funktion arbeitet ein PIC dann Schritt für Schritt alle Befehle von oben nach unten ab. Ein PIC kann nicht mehrere Operationen gleichzeitig ausführen, sondern er arbeitet eine Operation nach der anderen sequenziell ab. Das ist aber nicht weiter schlimm, weil er dies einerseits sehr schnell macht und anderseits kann er zwischendurch durch Interrups an eine andere Programmstelle springen, dort etwas abarbeiten und dann wieder zurück springen.

 3.1 Variablen

Um überhaupt mit dem Arbeitsspeicher des PIC zu arbeiten brauchen wir Variablen in denen wir Daten schreiben, speichern und bei Bedarf wieder lesen können. Bei Variablen unterscheidet man in C zwischen verschiedenen Typen. Grundsätzlich gilt aber, dass man mit dem Speicherplatz sparsam umgehen soll. Braucht man z.B. nur eine Speicherzelle, welche eine maximale Speicherkapazität von 256 haben muss, macht es keinen Sinn eine Variable mit dem Fassungsvermögen von 65536 zu verwenden! Bitte denken Sie daran, die Speicherkapazität in einem Microcontroller ist begrenzt.

Folgend die verschiedenen Typen (C18) aufgeführt:

Typ Anzahl der Bits Wertebereich
bit 1 0 bis 1
char 8 -128 bis 127
unsigned char 8 0 bis 255
signed char 8 -128 bis 127
int 16 -32768 bis 32767
unsigned int 16 0 bis 65535
short int 16 -32768 bis 32767
unsigned short int 16 0 bis 65535
short long 24 -8 388 608 bis 8 388 607
unsigned short long 24 0 bis 16 777 215
long int 32 -2 147 483 648 bis 2 147 483 647
unsigned long int 32 0 bis 4 294 967 295
float 32 1.17E-38 bis 6.80E+38
double 32 1.17E-38 bis 6.80E+38

 3.2 Variablen Deklaration


Jetzt haben wir schon die verschiedenen Variablen Typen kennen gelernt aber wie wendet man diese an? Nun das sehen Sie hier. Bei der "Variablen Deklaration" wird der Variablen Typ, der Name und eventuell ein Anfangswert festgelegt. Schauen wir uns das einmal anhand eines Beispiels an:

Typ Variablenname;

Folglich schreibt man:

unsigned char Speicher;

Somit hätten wir eine Variable des Typens unsigned char mit dem Namen Speicher deklariert. In diese Variable können wir nun Daten speichern. Um eine Variable zu beschreiben schreiben wir folgendes:

Speicher = 100; oder Speicher = 100+100; oder Speicher = Speicher2 + Speicher3;

Es lassen sich Variablen natürlich auch über Operationen beschreiben wie in dem Beispiel oben, dabei spielt es keine Rolle ob die Operation mit Zahlen oder anderen Variablen durchgeführt wird. Man muss natürich darauf aufpassen, dass man immer die entsprechenden Variablen-Typen benutzt. Denn eine Addition von 200+200 ergäbe 400 und wäre somit nicht geeignet für eine unsigned char!

Weitere Beispiele:
Speicher1 = 100;
Speicher2 = 50;
Speicher3 = Speicher1/Speicher2;
Speicher4 = 2*Speicher3;

Der Inhalt von Speicher4 wäre dann 4.

 3.3 Funktionen


Jetzt kommen wir zu den Funktionen. Sie werden beim Programm-Schreiben immer wieder benötigt und sind uns eine große Hilfe beim strukturieren des Ablaufs. Und wie benutzt man nun diese Funktionen? Schauen wir uns zunächst einmal wieder ein Beispiel an:

Beispielcode - Funktionen:



Was wir ja bereits wissen ist, dass ein PIC immer bei der Funktion Main beginnt. Nun schaun wir uns an was da als erstes auf den PIC wartet. In der Zeile 14 wartet die erste Anweisung auf den PIC, welche ihm mitteilt, dass er die Funktion Funktion_A aufrufen soll. Also wird er in die Funktion springen und landet dann bei der Anweisung (Zeile 4):

LATAbits.LATA5 = 1;

Dies ist ein neuer Befehl, welchen ich hier nur ganz kurz ansprechen möchte (wird später noch genauer erklärt). Durch diesen Befehl wird das Bit 5 des PORTA auf High (=5V) gesetzt und wir nehmen mal an, dass dort evtl. eine Transistorschaltung mit iner LED sitzt. Also würde die LED in der Funktion_A eingeschaltet werden. Nachdem der PIC die Funktion_A komplett abgearbeitet hat (in diesem Fall lediglich eine Anweisung) kehrt er zurück von wo aus er die Funktion aufgerufen hat und geht einen Schritt vorwärts. Also würde er nun Zeile 15 aufrufen, was zur Folge hätte, dass die LED ausgeschaltet wird. Und so weiter... Und so fort... Fazit: Die LED würde also blinken. Jein, denn die LED würde so schnell blinken (an/aus), dass wir es gar nicht wahrnehmen würden. Aber ohnehin ist das Programm sinnlos, es dient ja nur dem Zweck, dass wir es als Lernbeispiel verwenden können.

Ein weiteres Beispiel:



Nun schauen Sie sich das Programm einmal genau an, dann werden Sie feststellen, dass sich eigentlich zum vorherigen Beispiel nicht viel verändert hat. Es steht jetzt lediglich die Main Funktion über den anderen Funktionen. Also das selbe. Falsch! Dieses Beispiel würde so nicht arbeiten und der Compiler würde eine Fehlermeldung ausgeben, welche ungefähr hei&szlig;en würde: "Unbekannte Funktion: Funktion_A" und das liegt daran, dass eine Funktion (au&szlig;enommen Main) immer vor der Main Funktion stehen muss oder die Funktion, wenn sie unter der Main Funktion steht einmal vor Main angemeldet werden muss. Folgendes Beispiel würde wieder funktionieren:

Ein weiteres Beispiel:



Dieses Programm würde funktionieren, da wir die Funktionen vor Main einmal angemeldet haben, dies geschieht einfach indem man die Funktion vor Main einmal aufführt:

Rückgabewert Funktions_Name(Übergabewert);  void Funktion_A(void); oder  void Bla(void);

 Übergabeparameter:

Als nächstes geht es um die "void", die wir bisher nicht angesprochen haben. Funktionen haben in C noch einen kleinen aber feinen Vorteil, denn man kann ihnen Parameter übergeben. Das bedeutet, dass wenn man eine Funktion aufruft ihr einen Wert mitgeben kann und wenn die Funktion zu Ende ist, kann somit ein Wert zurück gegeben werden. Sehen wir uns dazu mal ein Beispiel an:



Hier würde der PIC in der Zeile 7 die Funktion Funktion_A aufrufen und würde den Wert "5" mit übergeben. Da nun in der Funktion_A der Übergabeparameter als "unsigned char A" deklariert wurden ist, steht die "5" nun in "A" ("A" ist nun auch eine normale Variable und kann innerhalb der Funktion als solche genutzt werden). Jetzt wird die Funktion Funktion_A abgearbeitet und beginnt also in der Zeile 12, wo etwas steht, was wir noch nicht kennen (Erklärung folgt) aber uns evtl. erschlie&szlig;en können. Es steht "IF(Wenn) (A==5)", dass bedeutet, wenn die Speicherzelle oder Variable "A" den Wert "5" beinhaltet, dann führe aus, was in den folgenden geschweiften Klammern {} steht... Else(Ansonsten), wenn nicht (oder Ansonsten) führe aus was in den Nachfolgenden geschweiften Klammern {} steht. Da "A" ja nun den Wert "5" tatsächlich beinhaltet würde die LED bei diesem Programm immer eingeschaltet sein. Würde die Variable "A" einen von "5" unterschiedlichen Wert annehmen (im Beispielprogramm nicht möglich), würde die Else Routine durchlaufen werden und die LED wäre aus. Das (A==5) ist eine so genannte Bedingung, wenn diese Bedingung wahr ist, wird die IF-Routine ausgeführt.

Zusammengefasst: (5==5) ist eine wahre Bedingung während (5==4) eine falsche Bedingung ist!

 Rückgabeparameter:

Nun kann man natürlich nicht nur einen Wert Übergeben, sondern kann auch Werte zurückgeben. Das sind dann die so genannten Rückgabeparameter und sind im Prinzip nichts gro&szlig; anderes. Wobei ich sie seltener verwende und dafür lieber globale Variablen benutze. Aber hier nun ein Beispiel:



Dieses Programm würde der Variablen "c" die Addition der Zahlen 13 und 17 zu ordnen (30). Dies würde folgenderma&szlig;en ablaufen: Das Programm will der Variablen "c" einen Wert zuordnen finden dort einen Aufruf zu einer Funktion vor. In dieser Funktion werden nun die ihr übergebenden Werte (13 und 17) in die Variablen (A und B) geschrieben. Dann wird die Funktion durch den Befehl Return wieder verlassen mit dem Rückgabewert (A+B). Also wird die "30" zurück gegeben (Die "30" steht nun in de Variablen "c"!

Zusammengefasst

Das void in der Klammer:
Die Klammer nach dem Funktionsnamen, dient dazu beim Aufruf dieser Funktion Werte mitgegeben zu können, die die Funktion benötigt. "Void" heisst, dass keine Werte mitgegeben werden, anderfalls wird wie bei der Variablen Deklaration (siehe oben) hier der mitgegebene Wert deklariert.

Das void vor der Funktion:
Wie bei dem "void" in der Klammer, nur dass dieses "void" dafür da ist, falls die aufgerufene Funktion einen Wert zurückgeben möchte. "Void" vor der Funktion bedeutet, dass die Funktion keinen wert zurück gibt. Wenn die Möglichkeit genutzt werden soll, muss dies wieder mit eine entsprechenden Vaiablen Möglichkeit (Integer,...) deklariert werden!

 3.4 Mathematische Operationen


Wir haben ja im Laufe dieses Lehrganges bereits Variablen kennen gelernt und im Umgang mit Microcontrollern dreht sich im Endeffekt ja alles um Zahlen daher bietet uns die Sprache C eine Reihe an Rechenoperationen an, die wir benutzen können um einen Ausdruck zu bilden. Ein Ausdruck ist eine Operanden (also die eigentlichen Zahlen) und Operatoren (Rechenzeichen; Minus, Plus,...). Es stehen folgende Operationen zur Verfügung:

 Arithmetische Operationen:

Symbol Beschreibung
+ Addition
- Subtraktion
* Multiplikation
/ Division
% Modulo

Bis auf die Rechenart "Modulo" sollten eigentlich alle anderen Arten klar sein. Die Rechenoperation Modulo teilt eine Zahl durch eine andere Zahl und gibt des Rest aus. Ein Beispiel:

A = 11%5  11/5=2 (Rest=1) A würde den Wert "1" annehmen
B = 5%3  5/3=1 (Rest=2) B würde den Wert "2" annehmen

 Logische Operationen:

Symbol Funktion
&& UND
|| ODER
! NICHT

 Diese Operationen beziehen sich auf die ganze Variable! Nicht Bitweise!

A = 10 (Binär: '00001010'), B = 20 (Binär: '00010100')   A&&B=0
A = 10 (Binär: '00001010'), B = 10 (Binär: '00001010')   A&&B=2

 Relationale Operationen:

Symbol Beschreibung
> grö&szlig;er
>= grö&szlig;er oder gleich
< kleiner
<= kleiner oder gleich
== gleich
!= ungleich

 Bitweise Logische Funktionen:

Symbol Operation
& bitweises UND
| bitweises ODER
^ bitweises Exklusiv-ODER
~ Einerkomplement
>> bitweises Verschieben nach rechts
<< bitweises Verschieben nach links

 Diese Operationen beziehen sich auf jeden einzelnen Bit einer Variable!

A='10101010' , B='01010101'  A&B='00000000'
A='10101010' , B='10101010'  A&B='10101010'

 Inkrement / Dekrement:

Selbstverständlich sind auch die gängigen Operationen wie das Inkrementieren (+1) oder Dekrementieren (-1) einer Variablen möglich. Man unterscheidet verschiedene Schreibweisen:

A = A+1; oder A++; oder ++A;   A wird bei allen Anweisungen um 1 erhöht
B = B-1; oder B--; oder --B;   B wird bei allen Anweisungen um 1 vermindert
Reihenfolge der Rechenoperationen beachten! Punkt vor Strich (Klammer) vor beidem! Mehr

 3.5 Arrays und Strings


Arrays:

Wir errinern uns, dass wir Variablen deklarieren können und in diesen genau einen Wert speichern können. Doch was macht man, wenn man mal mehrere Werte automatisiert speichern will? Dafür benutzen wir sogenannte Arrays. Ein Array ist eine bestimmte Anzahl von Variablen mit dem selben Typ. Sehen wir uns einmal an wie ein Array deklariert werden kann:

Typ Arrayname [Anzahl];  Int Array_01[10];

Somit hätten wir unsere erstes Array erstellt. Dieses besteht nun aus zehn Int Variablen (0-9) mit dem Namen Array_01. Beschreiben können wir das Array wie folgt:

Array_01[0] = 50;  Somit steht eine "50" in der ersten Variable;
Array_01[1] = 50+50;  Somit steht eine "100" in der zweiten Variable;

Sie sehen schon man kann mit dieser Schreibweise Platz sparen, doch bis hier hin würde das auch nicht viel bringen, doch wenn wir uns den nächsten Schritt anschauen, werden wir schnell den Vorteil eines Arrays erkennen:



In diesem Beispielprogramm wird in der Zeile 2 erst einmal eine Variable mit dem Namen i deklariert. Gefolgt von einer Deklaration für ein Array Namens Array_01 mit 5 Speicherplätzen2. Dann kommt in der Zeile 5 etwas, das wir bisher noch nicht kennen. Das soll uns aber ersteinmal nicht weiter stören (wird später erklärt). Man spricht von einer For-Schleife, diese wird solange wiederholt, bis der Ausdruck i<5 nicht mehr wahr ist! Duch die Initialisierungsanweisung i=0 wird die Variable i zu Beginn auf 0 gesetzt. Die Schleifenanweisung i++ wird nach jeder durchlaufenden Schleife durchgeführt. Wir errinern uns, dass i++; gleich der Erhöhung von i um 1 ist. Somit wird also die Variable von i von 0..5 hoch gezählt. Also würde diese Schleife genau 5 mal durchlaufen werden. Die For-Schleife wird wie erwähnt noch einmal näher erklärt. Wir gehen also davon aus, dass i seinen Wert nach jeder Schleife um 1 erhöht. So würde sich folgender Inhalt in dem Array ergeben:

2 Wir zählen die "0" immer mit, daher hat ein Array mit [5] auch 5 Speicherplätze (0-4)!

Durchgang 0:  Array_01[0] Inhalt: 0
Durchgang 1:  Array_01[1] Inhalt: 5
Durchgang 2:  Array_01[2] Inhalt: 10
Durchgang 3:  Array_01[3] Inhalt: 15
Durchgang 4:  Array_01[4] Inhalt: 20

Und genau dadurch lassen sich später Datenbanken leichter und vor allem mit weniger Schreibarbeit erstellen, als wenn wir jeder Variablen einen Wert einzelnd zuordnen müssen und somit auch für jede Variable eine eigene Rechnung haben müssten. Ein Array wird zwar ähnlich wie eine Variable deklariert muss aber immer mit einem Index angesprochen werden. Wir haben diesen Index bereits verwendet. In unserem Beispiel war es die Variable "i". Folglich gilt:

Array_A = Array_B;  Diese Operation kann nicht funktionieren!
For (i=0; i<5; i++) {Array_A[i] = Array_B[i];}  So würde es funktionieren!

Mehrdimensionale Arrays:

Bisher haben wir nur über die "Eindimensionalen" Arrays besprochen es gibt aber auch noch "Mehrdimensionale" Arrays. Sie können sich mehrdimensionale Arrays wie eine Tabelle vorstellen.

unsigned char Array_A[5]; //Eindimesional = 5 Zeilen
unsigned char Array_A[5][5]; //Zweidimensional = 5 Zeilen und 5 Spalte

Man kann Arrays auch direkt bei der Deklaration Werte zu weisen. Dies würde wie folgt aussehen:

unsigned char Array_ED [5] = {0,1,2,3,4};
unsigned char Array_MD [4][4] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

Strings:

Strings sind im Grunde genommen nichts neues. Es sind eindimensionale Arrays, welche aus dem Typ "char" gebildet werden und werden dafür benutzt Text zu speichern. Wenn wir einfolgenden String deklarieren:

char Text[5];

Können wir in diesem String 6 Elemente im ASCII Code speichern (z.B. Buchstaben/Zeichen für die Ausgabe auf einem LCD). Man muss hier aber eines beachten: Man könnte ja nun in den String auch nur 2 ASCII Elemente speichern. Würde man diese nun ausgeben wollen, würde der PIC nicht wissen wo der Text denn in dem String steht. Aus diesem Grund wird ein Text immer mit dem Code "0" abgeschlossen. Also hätten wir in diesem String nur noch 5 Speicherplätze frei! Hinweis: Die "0" am Ende bedeutet nicht, dass auf dem Display eine "0" ausgegeben würde. Dies ist lediglich ein Steuercode und muss IMMER vorhanden sein. Sollten wir also nun wirklich 6 ASCII Zeichen brauchen, müssten wir ein String mit [6] deklarieren (1 Platz mehr für die "0").

 3.6 Bedingungen

Bedingungen sind sehr wichtig für unsere Programmsteuerung. Mit ihnen entsteht eigentlich erst ein logischer Zusammenhang. Wir können mit den If,Else Bedingungen sehr genaue Steurerungen erarbeiten. Wir können mit den Bedingungen Werte überprüfen und in Abhängigkeit Operationen durchführen, Texte ausgeben oder es ggf. nicht machen. Schauen wir uns einmal ein Beispiel an:



Bei dieser IF Bedingung würd in Zeile 5 geprüft werden ob der Wert in der Variable Batterie_Volt kleiner ist als "1000". Wenn diese Aussage wahr ist, also Batterie_Volt kleiner ist als 1000, würde der If Anweisungsblock {} ausgeführt werden. Nun ist dieses Beispiel sinnlos, da wir die Variable Batterie_Volt ja oben immer mit "500" beschreiben. Es würde also immer die IF-Funktion ausgeführt werden. (Es ist ja nur ein Beispiel!) Angenommen die Variable nimmt nun einen Wert an, der grö&szlig;er ist als "1000", dann würde die Warnung nicht gelöscht werden und die IF-Funktion würde nicht mehr ausgeführt. Also müssen wir das Programm bearbeiten:



Nun würde, wenn sich der Wert von Batterie_Volt ändert und grö&szlig;er 1000 wird, die Else-Funktion einschalten und durcharbeitet werden. Dadurch hätten wie eine zweiseitige Bedingung, wo wir mit nur der IF Abfrage eine einseitige Abfrage hatten. Wir wissen, dass die Variable Batterie_Volt in diesem Programm immer den Wert "500" behalten wird, es dient ja nur um die Funktionen If, Else zu verstehen. Später in richtigen Programmen könnte die Variable zum Beispiel mit dem gemessenen Analogwert (umgewandelt in digital durch den Analog-Digital-Umsetzter des PIC) gefüllt werden und somit wäre das Programm eine Art Batterie-Wächter.

Eine if-Bedingung hat also immer mindestens folgende Ausführung: (einseitig)

if (Ausdruck)
{
    Anweisungen;
}

Eine if-Bedingung kann folgende Ausführung haben: (zweiseitig)

if (Ausdruck/Bedingung X)
{
    Anweisungen Y;
}
else
{
    Anweisungen Z;
}

Vereinfacht gesagt: Wenn X, dann Y ansonsten Z

Folgende Ausdrücke können für die Bedingung benutzt werden:

x == y logischer Vergleich auf Gleichheit
x != y logischer Vergleich auf Ungleichheit
x < y logischer Vergleich auf "kleiner"
x <= y logischer Vergleich auf "kleiner oder gleich"
x > y logischer Vergleich auf "grö&szlig;er"
x >= y logischer Vergleich auf "grö&szlig;er oder gleich"

Es können natürlich auch mehrere Bedingungen verknüpfen lassen

x && y wahr, wenn x wahr und y wahr sind
x || y wahr, wenn x wahr und/oder y wahr
!x wahr, wenn x nicht wahr ist

Ich denke die einzelnen Vergleichsoperatoren dürften klar sein. Für die Bedingungs-Verknüpfung möchte ich hier nochmal ein Beispiel zeiegn, damit das Prinzip klar wird:



Diese If-Funktion würde nur ausgeführt werden, wenn A kleiner ist als B und A grö&szlig;er ist als 80 oder A gleich 11 ist. So lassen sich mehrere Bedingungen in einer zusammenfassen.

 3.7 Switch Case

Es wird Ihnen öfters mal passieren, dass Variablen auf viele verschiedene Zahlenwerte überprüft werden müssen. Nun können Sie dieses natürlich mit diversen If-Funktionen erledigen, wesentlich eleganter geht dies allerdings mit der Switch-Case-Funktion. Sehen wir uns auch dazu ein Beispiel an:



Stellen Sie sich vor, Ihr PIC bewegt sich in der Main Funktion und bekommt nun die Anweisung in die Funktion Read_ADC zu springen. Bei dieser Anweisung würde der PIC einen Übergabeparameter mitgeben, welcher sich bei diesem Beispiel zwischen 0 und 2 befinden müsste. Wir gehen davon aus, dass der Übergabewert eine "1" ist. Beim erreichen der Funktion wird diese "1" zunächst einmal in die Variable "Channel" geschrieben. Jetzt startet der Anweisungsblock wo direkt die Switch-Case Anweisung wartet. Hier steht nun switch(chanel), dass heisst, der PIC würde nun (1 in Chanel) zu dem Case "1" springen und da arbeiten. Wichtig ist an dieser Stelle auch das break;, denn wenn diese Anweisung am Ende einer case Anweisung nicht stehen würde, dann würden alle anderen Case Anweisungen da drunter auch noch abgearbeitet werden (es sei denn das ist gewollt). Das break kann bei der letzten Case-Anweisung natürlich entfallen, denn da drunter kommt ja ohnehin nichts mehr.

 3.8 Schleifen

Jetzt haben wir ja schon eine ganze Menge angeschaut aber es gibt selbstverständlich noch mehr. Schleifen! Sie werden immer dann benötigt, wenn ein Anweisungsblock {} mehrfach wiederholt werden soll/muss. Hier mal direkt ein Beispiel:

 While-Schleife



Dieses Beispiel zeigt die Endlos-Schleife sie muss in jedem C Programm (für µC) vorhanden sein, denn ein PIC arbeitet seinen Programmcode immer durch, wieder und wieder. Eine while Schleife wird solange durchlaufen, wie ihr Ausdruck "wahr" (=unterschiedlich zu 0) ist. Da in der Klammer eine 1 steht, ist der Ausdruck immer 1 (=wahr) und die Schleife wird nie verlassen. Es sei denn, die Betriebsspannung wird entfernt. Nachfolgend die Grundschreibweise für eine While-Schleife:

while (Ausdruck)
{
    Anweisungen;
}

Man kann die Anweisung natürlich auch anders gestalten (nicht bei der Funktion Main!!), sodass sich bedingte Schleifen ergeben. Zum Beispiel:



Diese While Schleife würde solange wiederholt werden, wie der Wert von "i" kleiner 10 und ungleich 10 ist! Anders ausgedrückt, die While Schleife würde beendet werden, wenn "i" den Wert 10 hat oder grö&szlig;er 10 ist! Man kann die Bedingung im While Kopf, genau wie bei den If-Funktionen, beliebig erweitern.

 For-Schleife

Die For-Schleife haben wir oben in Beispielen schon angesprochen. Ich möchte sie hier aber dennoch noch einmal genau erklären. Eine For-Schleife ist ähnlich wie eine While-Schleife nur dass man im Schleifen-Kopf mehr einbringen kann. Dazu schauen wir uns wieder ein Beispielcode an:



In einer For-Schleife gibt es immer eine Variablen Initialisierung eine Abbruchbedingung und eine Variablen Veränderung, welche immer angegeben werden müssen. In dem obigen Beispiel ist die Variablen Initialisierung das: i=0; Somit ist festgelegt, dass die Variable "i" zu Beginn den Inhalt "0" hat. Die Abbruchbedingung ist: i<5; Also sobald diese Bedingung nicht wahr ist, wird die For-Schleife beendet. Die Abbruchbedingung wird immer beim Start überprüft. Zum Schluss noch die Variablen Veränderung, diese ist in dem Beispiel: i++; Also wird nach jeder durchlaufenden Schleife die Avriable "i" um 1 erhöht (inkrementiert). Somit können wir sagen, diese Schleife würde 5 mal durchlaufen werden.

 Do While-Schleife

Die Do While Schleife ist wie eine ganz normale While Schleife, blo&szlig; dass diese mindestens einmal durchlafen wird. Am Ende der Schleife wird dann die Bedingung überprüft, wenn dieser Ausdurck von 0 verschieden ist (wahr), dann wird die Schleife wiederholt, ist der Wert 0 (falsch), dann wird die Schleife beendet! Beispiel:



 3.9 Weitere Steuerungen (Break, Continue,...)


 Break Anweisung

Die Break Anweisung beendet einen gerade laufenden Programmblock. Hinweis: Es wird die Schleife komplett beendet! Siehe hierzu auch das Beispiel bei Switch Case.

 Continue Anweisung

Ist ähnlich wie die Break Anweisung. Wenn in einer Schleife die Continue Anweisung erscheint, wird der Rest der Schleife nicht mehr durchlaufen sondern der PIC springt sofort zu Schleifenabfrage vor. Also wird nicht wie bei der Break Anweisung die Schleife ganz beendet sondern nur der aktuelle Durchlauf!

 Return Anweisung

Dir Return Anweisung wird hauptsächlich dafür verwendet, wenn Werte aus einer Funktion zurück gegeben werden sollen. Siehe hierzu folgendes Beispiel.

 4.0 Speziell für PIC

 Konfiguration

Selbstverständlich müssen in C genauso wie in Assembler (ASM) die Konfiguration für den PIC bestimmt werden. Wenn dies vergessen wird, wird der PIC nichts machen! Nun unterscheidet sich die Einstellung der einzelnen Einheiten etwas. Hier ein beispiel wie Sie in C, die Konfiguration für einen PIC vornehmen können:



Durch die Einleitung #pragma wei&szlig; der PIC, dass es sich um Konfigurationsinformationen handelt. Natürlich können Sie die Konfigurationsbits, wie in Assembler, auch manuell einstellen mit der Brennersoftware (MPLAB IDE, usburn,..).

 Eignänge / Ausgänge

Hier ein Beispiel, wie Sie in der Sprache C Zustände von Port Pins einlesen und wie Sie Pins (Ausgang) setzten oder löschen können:



Also Schreibregel beim Setzten/Löschen von Ausgängen:

LATXbits.LATXY = Z;

X: Hier wird der Port eingetragen (A, B,..).
Y: Hier kommt das Bit hinein (0-7) des Ports.
Z: Der Zustand den der Port-Pin annehmen soll.

Dabei darf man natürlich nicht vergessen die TRIS Bits dementsprechend einzustellen! Wir errinern uns: Eine "1" im TRIS Register schaltet den jeweiligen Port-Pin als Eingang, eine "0" als Ausgang. Im obigen Beispiel sehen Sie ebenfalls wie Sie die TRIS-Register beschreiben können.

Schreibregel beim Lesen von Eingängen:

if (PORTXbits.RXY == 0) {...} else {...}

X: Hier wird der Port eingetragen (A, B,..).
Y: Hier den Pin eintragen der gelesen werden soll (0-7)

Dies ist ein Beispiel, wie man in Abhängigkeit vom Zustand eines Einganges den Programmablauf steuern kann!

 Wann PORT und wann LAT?

Ein weiterer wichtiger Punkt ist zu wissen, wann man mit PORT und wann mit LAT abeiten muss. Ganz kurz und knapp:

PORT: Bei Eingängen (TRIS=1) zum einlesen! LAT: Bei Ausgängen (TRIS=0)

Also: Wenn Sie den Zustand eines Eingang-Pins abfragen möchten, hierzu wäre das zugehörige TRIS-Bit auf 1, dann verwenden Sie PORT! Möchten Sie aber einen Ausgang setzten oder löschen (ASM = BSF, BCF), hierfür muss das TRIS-Bit 0 sein, dann benutzen Sie die LAT Register! Das hängt damit zusammen, dass beim Schreiben auf PORT Fehler unterlaufen können. Wenn Sie dazu noch mehr wissen möchten müssen Sie die Suchmaschine anschmei&szlig;en. Ich habe es einfach so hingenommen und kann ganz gut damit leben =)

 Interrupts

Der Compiler C18 unterscheidet bei PIC18F zwischen zwei Interrupt Routinen. Während wir in Assembler alles in einer ISR (Interrupt Service Routine) erledigen mussten, können wir in C zwei verschiedene Interrupts unterscheiden. Low und High.



Hinweis: Die rot makierte Zeile im Interrupt-Beispiel-Code zeigt, dass der High Interrupt ebenfalls mit #pragma interruptlow eingeleitet wird. Das kommt daher, dass es hier einen Haken gibt. Es gibt bei verschiedenen PIC18 Typen einen Fehler, der bei der High Interrupt-Routine auftritt. Wenn Ihrer PIC diesen Fehler hat (siehe Errata Dokument), dann können Sie den High Interrupt einfach ebenfalls mit #pragma interruptlow einleiten.

 Häufig gestellte Fragen und gern gemachte Fehler


Hier entsteht eine Sammlung von Fragen mit Antworten, welche sehr häufig gestellt werden. Au&szlig;erdem wird eine Liste mit Fehlern erstellt, welche immer wieder vorkommen. Solltet Ihr noch Ideen haben, was noch in diese Liste aufgenommen werden könnte, dann zögert bitte nicht und mach einen Vorschlag im Forum

 Gleich (=) ist nicht gleich Gleich (==)

Ein Fehler der immer wieder gern gemacht wird ist, dass man bei einem Vergleich das falsche Zeichen wählt. Ein einfaches geschriebenes "=" Zeichen ist dafür da um z.B. Variablen einen Wert zu zuweisen aber nicht als Vergleichsoperator, dies würde einen Syntax Fehler nach sich ziehen und das Compilieren beenden. Siehe:

Falsch

if (A=B) {..} else {..}

Richtig

if (A==B) {..} else {..}

 Funktion aufrufen

Wenn man eine Funktion aufrufen möchte die Assembler Leute kennen dies besser unter dem Namen Unterprogramm, dann sollte man doch auf die korrekte Schreibweise achten.(Am besten gleich den nächsten Punkt in diesem Kapitel lesen!) Siehe:

Falsch

void Funktion();
Funktion()
Funktion;

Richtig

Funktion();

 Funktion anmelden

Dieser Fehler wird sehr häufig gemacht und dann wundert man sich warum beim Compilieren so ein seltsamer Fehler kommt. Bevor man eine Funktion innerhalb der Main-Routine aufrufen kann, muss man sie vorher angemeldet haben. Dafür gibt es zwei Möglichkeiten: Entweder man schreibt die Funktion oberhalb der Main-Routine oder aber man meldet sie vorher an. Siehe:

Falsch

void main (void)
 {
   FunktionA();
 }

void FunktionA (void) {..}

Richtig

void FunktionA (void) {..}

 void main (void)
 {
   FunktionA();
 }

Oder aber man meldet Sie vorher an:

Richtig

void FunktionA (void);


 void main (void)
 {
   FunktionA();
 }


void FunktionA (void) {..}

 Warteschleifen

Warteschleifen lassen sich in C im Handumdrehen verwirklichen, da wir glücklicherweise hierfür schon eine Bibliothek mitgeliefert bekommen: delays.h Diese muss lediglich in das Projekt mit eingebunden werden und kann dann schon verwendet werden.

Das Einbinden der Bibliothek erfolgt ganz einfach: Ihr müsst in MPLAB IDE einfach beim geöffneten Projekt im Fenster "Project" im Ordner "Header Files" mit einem Rechtsklick und "Add Files" die Datei "delays.h" im MCC18 Ordner suchen. Wenn Ihr das erledigt habt, könnt Ihr die Funktionen aus der Bibliothek schon benutzen. Lest euch dazu am besten mal die Datei durch, es stehen Erklärungen zu den einzelnen Funktionen dabei.

So könnt Ihr die Funktionen benutzen/aufrufen:

Warteschleifen

Delay10TCYx(X);
Delay100TCYx(X);
Delay1KTCYx(X);
Delay10KTCYx(X);

Dort wo das rote X steht müsst Ihr dann nur noch einen Zahlenwert zwischen 0 und 255 eintragen. Die Auflistung in dem grünen Kasten entspricht auch gleich der Wartzeit von kurz zu lang. Also die erste Funktion macht eine kürzere Pause wie die nachfolgende, usw. Ebenso ist es mit den Zahlenwerten: 1 ist die kürzeste Pause und 255 die längste Pause!

 C18 Libraries


Ich habe es schon einmal erwähnt der Compiler C18 liefert bei seiner Installation ein paar sehr nützliche Bibliotheken mit. Dabei befinden sich viele Funktionen, welche Dinge übernehmen die Anfägern meistens schwer fallen. Die oben beschriebene Warteschleife ist ebenfalls eine dieser Funktionen. Ich möchte Ihnen hier etwas näher bringen wie Sie diese Funktionen benutzen können. Es wird unterschieden zwischen verschiedenen Bereichen. Erstmal eine Sammlung mit den wichtigsten Funktionen:

- Analog-Digital Umsetzer
- Pulsweitenmodulation
- Timer Funktionen
- HD44780 LCD Funktionen

Hier können Sie sich das zugehörige PDF Dokument herunter laden indem (Englisch) nochmal alle Funktionen aufgeführt und erklärt sind:  C18-Libraries

Beispiel - ADC


Folgende Funktionen hällt die Bibliothek für den ADC bereit:

Funktion Beschreibung
BusyADC Überprüft ob gerade eine AD Umsetzung statt findet. (Rückgabewert)
CloseADC Beendet den Analog Digital Umsetzer.
ConvertADC Startet eine Analog Digital Umsetzung.
OpenADC Konfiguriert den Analog Digital Umsetzer
ReadADC Diese Funktion liest das Ergebnis des ADC aus (Rückgabewert).
SetChanADC Mit der Funktion kann man den gewünschten Kanal wählen (ANx).

So könnte der Vorgang für einen PIC18F4550 aussehen:



Das oben genannte PDF Dokument von Microchip dient zur Hilfe beim Benutzen der einzelnen Funktionen und ist eigentlich auch ganz einfach gehalten und somit lassen sich die Funktionen sehr einfach verwenden. Hier eine kleiner roter Pfaden:

1. Funktion aussuchen die bnötigt wird. (Zum Beispiel ADC)
2. Nachschauen welche Header Datei dafür in das Projekt eingebunden werden muss.
3. Nach den Funktionen schauen (PIC Typ beachten).
4. Funktionen ins Programm übertragen.

1. Funktion aussuchen die benötigt wird. (Beispiel ADC)


Zu Beginn suchen Sie im PDF Inhaltsverzeichnis nach der Funktion die sie verwenden möchten. Wir halten hier an unserem Beispiel fest und suchen uns die ADC Funktion heraus, diese finden wir unter Hardware, da es ja eben auch ein Hardware Modul ist.

 

 

 

 

 

 

2. Nachschauen welche Header Datei dafür in das Projekt eingebunden werden muss.



Als nächstes müssen Sie sich informieren welche Header Datei Sie benötigen, damit sie die gewünschten Funktionen verwenden können. Diese binden Sie dann wie gewohnt in ihr C Programm ein mit dem Befehl: #include "adc.h"

3. Nach den Funktionen schauen (PIC Typ beachten).


Jetzt können Sie schon nachschauen, welche Funktion sie verwenden möchten. Dabei müssen sie aber aufpassen, dass sie auch die richtige Funktion für Ihren PIC Typ wählen. Wenn ihr Typ nicht aufgeführt ist, dann nehmen Sie z.B. wie in unserem Beispiel "all other processors".

4. Funktionen ins Programm übertragen.


Im Anschluss müssen Sie nur noch die Funktionen an gewünschter Stelle aufrufen. Siehe dazu das Beispiel (oben).

 

 5.0 Codesnipsel


Hier soll eine Sammlung mit kleinen nützlichen Codesnipseln entsehen, welche Anfängern immer wieder vor Probleme stellen.

Zwei 8-Bit Variablen in eine 16 Bit umwandeln

So könnt Ihr zwei 8 Bit Variablen in eine 16 Bit Variable umwandeln:


Zweite Möglichkeit, als Funktion (Danke an pic18 aus dem PIC-Forum):

Zusammenführen / Trennen


 

 6.0 Schlusswort


Ich hoffe dieser kleine C Kurs hat Ihnen etwas geholfen mit PIC Microcontrollern und der Verwendung der Sprache C umzugehen. Sollten Sie dennoch Fragen haben zögern Sie nicht diese in meinem  Forum zu stellen!


 

Sie können sich mit einem Klick für den Artikel bedanken!
Danke    Leute haben sich bereits bedankt!
Letzte Änderung am 06.08.2011