Vorbereitungen
Bevor wir loslegen können bedarf es ein paar Vorbereitungen. Zunächst einmal braucht ihr die entsprechende Entwicklungsumgebung um das Programm für den PIC zu schreiben. Ich empfehle euch die kostenlose Software MPLABX IDE direkt von Microchip. Sie ist zum einen kostenlos und lässt sich mit den neuen Compilern (zum Beispiel dem XC8) verbinden, welchen wir ebenfalls für das Schreiben unserer C-Programme benötigen. Nachdem ihr die IDE und den Compiler (wir benötigen den XC8) installiert habt, kann es auch schon losgehen.
Erstellen eines neuen Projekts
Damit ihr euch für die ersten Schritte in der neuen Benutzeroberfläche der Entwicklungsumgebung zurecht findet, habe ich einen kurzen übersichtlichen Artikel geschrieben, der sich ausschließlich mit der Entwicklungsumgebung MPLABX beschäftigt. Hier findet ihr auch einen Abschnitt darüber, wie ihr ein neues Projekt erstellt. Hierfür bitte einmal mal diesen Artikel durchlesen.
Erstes Beispielprogramm
Wenn ihr gleich das erste Beispielprogramm seht, werdet ihr euch wahrscheinlich erstmal wundern. Zu sehen sind bloß irgendwelche Buchstaben und Zeichen. Verstehen werdet ihr vermutlich erstmal wenig. Aber lasst euch davon nicht abschrecken. Es verhält sich ganz ähnlich wie bei euren ersten Englisch Unterrichtsstunden, da habt ihr auch erstmal wenig verstanden. Genauso ist es auch beim Programmieren, denn es ist auch eine Fremdsprache.
Also gut, legen wir los: Das erste Programm (noch einmal der Hinweis: Dies ist nur ein Beispielcode und kein vollständig kompilierbares Projekt):
// Unser erstes Programm /* Einbinden einer Headerdatei */ #include <xc.h> /* Beginn der main-Routine */ void main (void) { /*Aufruf eines Unterprogramms*/ led_blinken(); } /*Ende der Funktion*/
Wie ich oben bereits erwähnt habe wird euch das eventuell erstmal abschrecken. Aber keine Sorge, es sieht komplizierter 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 man als Anfänger schon genug damit zu tun hat die Sprache C an sich zu begreifen, da braucht man nicht noch eine Erklärung in einer weiteren Fremdsprache! Wenn ihr euch dann irgendwann etwas geübter in der Sprache C bewegt, kann ruhig mal ein englischer Kommentar Platz finden. Aber Schritt für Schritt. Nun zurück zum Beispielprogramm. Nehmen wir das Programm einmal auseinander und schauen uns die einzelnen Befehle/Zeilen an:
// Unser erstes Programm
Das ist ein Kommentar und wird somit nicht vom Compiler (der Compiler übersetzt das Programm in die Maschinensprache) berücksichtigt. Durch die einleitenden zwei //
wird ein Kommentar angezeigt. Alles was sich nach diesen beiden Slashs in dieser (!) Zeile befindet hat keinen Einfluss auf das Programm. Sobald aber die Zeile zu Ende ist und ihr in der nächsten Zeile weiterschreibt, ist der Kommentar zu Ende! Wenn ihr längere Kommentare über mehrere Zeilen verfassen möchtet, dann solltet ihr euch einer alternativen Schreibweise bedienen:
/*So könnt ihr ruhig in mehreren Zeilen schreiben*/
Alternativ ginge natürlich auch:
// Mehrzeilige Kommentare können auch // so geschrieben werden
Durch einen Schrägstrich mit einem folgenden Sternchen /*
leitet ihr ein Kommentar ein. Alles was nun bis zu dem beendenden des Kommentars mit */
geschrieben wird ist Teil des Kommentar und wird den Programmcode nicht beeinflussen. Nun schauen wir uns den nächsten Teil des Programms an:
#include <xc.h>
Durch den #include
Befehl können wir Funktions-Bibliotheken in unser Programm einbinden und benutzen. Die Bibliothek des verwendenden PIC ist natürlich gezwungenermaßen erforderlich. Durch den include
Befehl werden Dateien in das Programm eingebunden, welche die Dateiendung .h
haben. Also sind es sogenannte Headerdateien. Zusammengefasst: Ihr müsst immer die Bibliothek des PIC, den Sie verwenden einbinden. Anders als noch unter dem alten C18-Compiler wird unter Verwendung der XC-Compiler einfach nur noch die XC-Headerdatei eingebunden.
Programmaufbau
Damit ein Programm kompilierbar / ausführbar ist, muss ein für einen PIC-Controller geschriebenes C-Programm muss immer die folgenden Komponenten enthalten:
- Das individuelle Konfigurationswort
- Die eingebundene
xc.h
Headerdatei (nur beim Einsatz des XC-Compiler) - Eine Endlosschleife in der
main
Funktion
Damit sollte auch nochmal deutlich klar werden, warum das in diesem Tutorial gezeigte erste Beispielprogramm nicht vollständig ist, denn es fehlt (ganz abgesehen davon, dass die Funktion led_blinken()
nicht definiert ist) das Konfigurationswort für den jeweiligen PIC-Typen. Ein paar Beispiele für vollständige Programme gibt es hier (diese Beispiele beziehen sich dann auch auf konkrete Hardware / einen bestimmten PIC).
Projektstruktur
Man ist (oder besser sollte man) als Programmierer immer bemüht sein eine gewisse Struktur in seinem Projekt zu bekommen, damit es übersichtlich bleibt. Nichts ist schlimmer als eine Codewüste wo nach zwei Wochen kein Mensch mehr durchsteigt. Muss man nach einer gewissen Zeit wieder an seinen Quellcode heran treten um ihn zu Warten (elementarer Bestandteil der Softwareentwicklung) und der Code ist derart schlecht strukturiert, dass man erstmal Stunden braucht um ihn wieder zu verstehen, dann ist damit keinem geholfen. Es hätte den selben Effekt ein ganz neues Programm zu schreiben. Daher sollte man sich an gewisse Konventionen halten:
- Programmcode immer gut kommentieren
- moularisieren (Aufteilung in Source und Header Dateien)
- Funktionen hinreichend verwenden
- Dokumentation verfassen (oft vernachlässigt)
- einem gewissen Stil folgen (Funktionsnamen immer klein / Makros immer groß geschrieben …)
Mit diesen genannten Punkten kann man sich im Nachhinein viel Arbeit, Ärger und Kummer ersparen. Ihr findet in meinem Artikel – Projektstruktur – eine detailliertere Einführung in das Strukturieren eines Softwareprojektes. Ich möchte besonders für die Einsteiger auf den zweiten und dritten Punkt noch einmal eingehen:
Stellt euch folgendes Szenario vor: Ihr schreibt ein Programm indem der Analogwert einer Knopfzelle eingelesen/verarbeitet und formatiert auf einem Display dargestellt wird. An sich kein großer Programmieraufwand. Als Einsteiger (aber auch als Fortgeschrittener) könnte man hierfür die Bibliothek des Compilers verwenden. Man bindet diese in mit einem Präprozessor Befehl #include
ein und es stehen einem ihre Funktionen zur Verfügung. Schon hat man eine Struktur erstellt. Es würde wenig Sinn machen den gesamten Code direkt in die C-Datei zu kopieren, denn es wird furchtbar unübersichtlich.
Grundsätzlich bekommt jede Sourcedatei C
eine sogenannte Headerdatei H
zugewiesen. In dieser Headerdatei stehen wichtige Dinge, welche unter anderem auch für andere Dateien relevant sein können. Ein Beispiel: Wir haben in unserem Projekt zwei C-Dateien: dcf.c
und clock.c
. In der DCF Datei wird das DCF77 Zeitsignal eingelesen und dekodiert. Die einzelnen Zeitinformationen speichern wir in einem Vektor ab. Diese Informationen ist nun aber auch in der clock.c
Datei notwendig. Wir können also in der Headerdatei der clock.c
(also in clock.h
) bekannt machen (siehe Schlüsselwort extern
), dass dieser Vektor existiert und wir ihn somit auch in der clock.c
Datei verwenden können. Oftmals bietet es sich an in der main.c
auch nur die main-Funktion zu schreiben. Alles andere lagern wir in weitere C-Dateien (auch Module genannt) aus. Somit erhalten wir maximale Übersicht und es ergeben sich mehrere Source Dateien, welche sich jeweils mit Problemen des Projekts befassen. Diese sollten daher auch einen geeigneten Dateinamen bekommen. Zum Beispiel könnte man sich vorstellen, dass sich die Datei bzw. das Modul clock.c
in irgend einer Weise mit der Uhrzeit befasst. In der main.c
haben wir dann den vollen Überblick über den Ablauf des Programms.
Präprozessor
Der Präprozessor ist ein nützlicher Helfer, der uns das Schreiben von Programmen erleichtert. Wir haben den Präprozessor weiter oben schon angewendet:
Alle Codezeilen, die mit dem Zeichen #
beginnen sind Anweisungen für den Präprozessor. Beim Kompilieren eines Programms wird zunächst der Präprozessor ausgeführt. Erst danach beginnt das eigentliche Übersetzen des Quellcodes. Wir erinnern uns an folgende Zeile aus unsrem ersten Programm:
#include <xc.h>
Diese Zeile beginnt mit der Raute #
und ist folglich eine Anweisung für den Präprozessor. Hier soll er den Inhalt der Headerdatei xc.h
an dieser Stelle in die Datei einbinden. Man könnte genauso gut den gesamten Inhalt der Headerdatei durch Copy & Paste an dieser Stelle einfügen. Damit wäre die Übersichtlichkeit des Moduls jedoch völlig vernichtet. Der Präprozessor kann aber noch mehr…
Makros
Gerne benutzen wir den Präprozessor um Makros in unser Programm einzubauen. So lassen sich Dinge realisieren wie:
#define LED_EIN LATAbits.LATA0=1; #define LED_AUS LATAbits.LATA0=0; ... LED_EIN /*Anwendung des Makros*/ ... LED_AUS ...
Dies ist an sich kein C-Code doch durch den Präprozessor können wir uns nützliche Makros einfallen lassen, damit wir den Code leserlicher gestalten. Der Präprozessor sucht im Programmcode nun alle Stellen wo LED_EIN
bzw. LED_AUS
steht und ersetzt sie mit dem was wir oben im #define
definiert haben.
Es geht aber noch weiter: Makros bzw. Defines können auch mir Parametern ausgestattet werden:
#define SUM(a,b) (a + b)
Das gezeigte Beispiel SUM(a,b)
würde in diesem Fall nichts anders tun als die Summe aus a
und b
zu berechnen.
Bedingtes Compilieren
Wenn wir ein Programm erstellen verwenden wir häufig kleine Codesnipsel um die Funktionalität des Programms zu prüfen. Zum Beispiel möchten wir eine LED einschalten, wenn eine if
Bedingung erfüllt wurde. Dies soll allerdings im fertigen Programm (Release) nicht mehr der Fall sein. Für solche (und noch mehr) Zwecke eignet sich die bedingte Compilierung des Präprozessors.
Hier ein Beispiel:
#define DEBUG #define LEDEIN LATBbits.LATB3=1; ... if(TEMPOVER) { #ifdef DEBUG LEDEIN #endif ... }
Hier würde die LED nur dann eingeschaltet werden, wenn oben das #define DEBUG
einkommentiert ist. Sobald die Zeile auskommentiert werden würde (z.B. durch vorangestellte //
) wird der Programmcode im Bereich zwischen #if DEBUG
und #endif
weder ausgeführt noch compiliert. Das bedeutet tatsächlich, dass dieser Code noch nicht einmal compiliert wird. Somit kriegt zum Beispiel ein Kunde nichts von diesem Code mit. Da der Präprozessor keine geschweiften Klammern kennt, wie wir sie für Funktionen einsetzen {}
, müssen wir Anweisungen zwischen #ifdef
und #endif
verpacken.
Liste aller Präprozessoranweisungen
Nachfolgend eine Aufzählung mit Anweisungen, sowie der Bedeutung, die Ihr mit dem Präprozessor verwenden könnt.
Anweisung | Bedeutung |
#include |
Fügt einen Dateiinhalt ein z.B. eine Headerdatei |
#define |
Definiert ein Makro oder eine symbolische Konstante |
#if |
Verzweige in Abhängigkeit von einem Ausdruck |
#undef |
Aufheben von Makros, d.h. ab jetzt wird Makro nicht mehr ersetzt |
#elif |
Weitere Alternative wenn #if nicht zutrifft |
#else |
Alternative wenn #if , #ifdef oder #ifndef nicht zutreffen |
#ifdef |
Verzeige, wenn eine Präprozessorkonstante definiert wurde |
#ifndef |
Verzeige, wenn eine Präprozessorkonstante nicht definiert wurde |
#endif |
Abschluss für #if , #ifdef oder #ifndef |
#error |
Fehlernachricht ausgeben |
#warning |
Ausgabe von Warntexten ohne Abbruch des Vorgangs |
#pragma |
Herstellerspezifische Anweisung |
Die Main Funktion
void main (void)
Durch diese Codezeile wird die main
Funktion angeführt. Alles was nun in den beiden folgenden geschweiften Klammern {}
steht, gehört zur 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 aufrufenden Funktion einen Wert zurück geben (dies wird aber später unter Funktionen erklärt). Das void
nach main
in den runden Klammern ist der Übergabeparameter (können mehrere sein). Da wir in diesem Beispiel-Programm auf Über- bzw. Rückgabeparameter verzichten, wird an die stellen void
geschrieben was soviel bedeutet wie, wird nicht verwendet / leer.
Da es immer wieder vorkommt, dass Leute in ihrem Programm eine fehlerhafte Struktur haben, hier nochmal ein allgemeiner Hinweis wie ihr euer Programm bzw. in diesem Fall eure main
Funktion strukturieren solltet:
void main (void) { /* Hier können sämtliche Initialisierungen durchgeführt werden (PIC, Display, usw.) */ while(1) { /* Hier kommt der Algorithmus/ die Algorithmen hinein, jedoch keine Initialisierungen! */ } }
Aufwendige Algorithmen haben im Allgemeinen nichts in der main
Funktion verloren. Nach meinem Dafürhalten befinden sich in der main
Funktion neben einigen notwendigen Funktionsaufrufen zu diversen Initialisierungen lediglich noch die notwendige Endlosschleife mit dem Aufruf bzw. dem Hauptzyklus des Programms.
Ausblick
Im nächsten Kapitel des PIC-C-Tutorials beschäftigen wir uns mit den Grundlagen der Programmiersprache C. Dabei wird es hauptsächlich um Variablen gehen und wie wir damit arbeiten können. Wenn ich dein Interesse geweckt habe, dann lies doch direkt im nächsten Kapitel weiter: PIC C Tutorial – Grundlagen
Toll das du dir solche Arbeit machst. Bisher hatte ich nichts mit “C” am Hut. Nun aber werde ich es wohl mal versuchen.
Freut mich, dass dir meine Seite gefällt 🙂 Danke!