PIC-C-Compiler


zurück zu PIC-Prozessoren , Elektronik , Homepage

Einleitung
Allgemeines
Der Weg zum HEX-File

Die Sprache C
ein einfaches Projekt
ein umfangreiches Projekt
Bibliotheken


weiter zu C18
weiter zu C30
weiter zur Einführung in C für PICs

zurück


Einleitung

Meine ersten Programmiererfahrungen machte ich mit Pascal und Algol auf einem IBM360-kompatiblem System. Wenn man solche Hochsprachen benutzt, dann sitzt man als Programmierer in einem Elfenbeinturm, denkt sich die tollsten Datenstrukturen und Algorithmen aus und verschwendet dabei keinen Gedanken an den armen Prozessor, der diese Phantasiegebilde letztendlich abarbeiten muss. Der Programmierer ist von der Hardware völlig isoliert.

Meine ersten Eigenbaurechner Auf Z80-Basis musste ich anfangs mangels Compiler in Maschinencode und in Assembler programmieren. Später programmierte ich auch PICs (14- und 12-Bit-Kern-PICs) in Assembler. Assemblerprogrammierung ist das genaue Gegenteil der Hochsprachenprogrammierung. Man arbeitet nur auf der Hardwareebene, hat ständig alle Finger im Getriebe des Prozessors und immer ölverschmierte Hände.

Das Programmieren in einer Hochsprache ist zweifellos komfortabler und man kommt deutlich schneller zum Ergebnis. Der Nachteil der Hochsprachen ist aber der langsamere und größere Code, den sie erzeugen. Besonders deutlich sieht man das an modernen PCs. Trotz Gigaherz-Prozessoren und Gigabyte-RAM sind moderne Windows-Programme nicht schneller als ihre Vorgänger vor 10 Jahren auf den damaligen, wesentlich schwächeren Maschinen.

Im Gegensatz zu Assemblercode sind richtige Programmiersprachen (wie C) portabel. Das bedeutet, dass ein C-Programm auf völlig unterschiedlichen Prozessoren zum Laufen gebracht werden kann. Die entsprechende Anpassung erledigt der Compiler. Ein Assemblerprogramm ist dagegen an eine bestimmte Hardware (Prozessoren- oder Microcontollerfamilie) gebunden.

Die minimalistischen 12-Bit-Kern-Prozessoren sind hochsprachen-ungeeignet, und die 14-Bit-Kern-Prozessoren stellen mit ihren zerklüfteten Speicherstrukturen und den kleinen Hardware-Stacks auch nicht die ideale Basis für Hochsprachen dar. Die größeren PICs (PIC18F, PIC24F/H, dsPIC30F, dsPIC33F) haben aber ausreichend Ressourcen, um mit Hochsprachen programmiert zu werden. Für diese Typen gibt es auch C-Compiler als kostenlose Student-Edition von Microchip.

C sieht zwar aus wie eine Hochsprache, ist aber viel maschinennäher als z.B. Pascal. Man kann zwar ein Programm viel schneller erstellen als in Assembler, aber man macht sich immer  noch die Hände schmutzig. Kenntnisse über den Prozessor sind bei der Programmierung in C sehr hilfreich.

nach oben

Allgemeines

Da ich einfach keine Lust hatte, eine neue Programmiersprache zu lernen, und da Assembler frei verfügbar waren, habe ich die PICs zunächst ausschließlich in Assembler programmiert.
Dann beschäftigte ich mich mit dem USB-Interface der PIC18Fxx5x. Die hierfür nötige Firmware lag nur als C-Quelltext vor. Deshalb war es naheliegend, auch den Rest des PIC-Programms in C zu schreiben. Möglich war das auch, da Microchip die C-Compiler C18 (für PIC18Fxxxx) und C30 (für dsPIC30Fxxxx) als Student-Edition (mit leichten Funktionseinschränkungen) zur freien Nutzung anbietet. Inzwischen bevorzuge ich bei PIC18-Typen prinzipiell C.
 
nach oben

Der Weg zum HEX-File

Die Sprache C
Ich suche aber immer noch nach einem brauchbaren C-Buch für Anfänger und Umsteiger. Die meisten C-Bücher zielen auf C, C++ und C# der Personalcomputer. Damit sind diese Bücher viel zu kompliziert, und schrecken nur ab. Sehr gut gefiel mir aber ein Büchlein aus Großbritannien:
PIC-C
An Introduction to programming the Microchip PIC in C
Autor: Nigel Gardner
ISBN: 1-899013-04-0
Sprache: englisch
hier online zu lesen
Wer ein vergleichbares, einfach verständliches Buch in deutscher Sprache kennt, sollte mir das mitteilen. Es wäre eine echte Kaufempfehlung.

Ansonsten empfehle ich Literatur, die sich mit dem konventionellem ANSI-C beschäftigt. Die meisten heute erscheinenden Bücher befassen sich mit C++ oder C#, und sind für den PIC-Interessierten deshalb ungeeignet.

Ich arbeite zwischenzeitlich an einer eigenen Einleitung in die Grundlagen von C, die sich auf das für PICs notwendige beschränkt.

ein einfaches Projekt
Ein kleines C-Programm schreibt man einfach in ein Textfile. Da es dann das einzige und damit automatisch das Haupt-Quelltext-File ist, bekommt es den Namen "main.c".
Im Quelltextfile main.c  wird (mit einer #include-Direktive) das sogenannte Device-Header-File (p18cxxx.h) eingebunden. Aus ihm erfährt der C-Compiler die Namen und Typen aller Bezeichner, die man in Assemblerprogrammen benutzen kann (z.B. TRISA, PORTA u.s.w.). Mit diesem Wissen wandelt der Compiler das C-Programm in ein Assemblerprogramm um. Das wiederum wandeln Assembler und Linker in das HEX-File. Dafür benötigt der Linker das zum PIC gehörende Linker-Skript (p18xxx.LKR).

Der Programmierer muss also das Quelltext-File schreiben, in das er das Header-File einbindet, und er muss in das Projekt das Linker-Skript des PIC aufnehmen.  Device-Header-Files und Linker-Skripte werden für alle in Frage kommenden PIC-Typen mit den Compilern mitgeliefert. Man muss lediglich die richtigen Files aussuchen.

ein einfaches C-Projekt
 

ein umfangreiches Projekt
Bei großen Projekten ist es üblich, das Programm in mehrere Quelltext-Files (Endung *.c) aufzuteilen. Das hat zwei Vorteile

In jedem größeren C-Projekt findet man neben Files mit der Endung *.c auch sogenannte Header-Files, mit der Endung *.h. Wozu dienen diese Header-Files? Der Compiler kennt keinen Unterschied, er betrachtet beide als Quelltext-Files. Es ist aber üblich, globale Definitionen oder Deklarationen für andere Programmteile in Header-Files zur Verfügung zu stellen.

Erklärung
Wenn man einen Teil seines Programms in das Quelltext-File anderes.c auslagert, dann wird z.B. main.c Funktionen aus anderes.c benutzen wollen. Ebenso könnte main.c auf Konstanten, Typen oder Variablen aus anderes.c zurückgreifen wollen. All diese Definitionen schreibt man nun nicht direkt in anderes.c sondern in anderes.h. An den Anfang von anderes.c fügt man dann #include anderes.h ein, dadurch steht in anderes.c an dieser Stelle scheinbar der Inhalt von anderes.h.
Den gleichen include-Befehl setzt man auch an den Anfang von main.c, und schon kennt main.c ebenfalls all die Definitionen, die es braucht, um anderes.c sinnvoll zu nutzen.

Natürlich kann man die Definition von Funktionen nicht in das Header-File auslagern, da der C-Quelltext ja fast nur aus Funktions-Deklarationen besteht. Diese bleiben also schön im C-File. Die Kopfzeile jeder Deklaration wird aber noch einmal in das Header-File aufgenommen. Damit "kennt" jedes C-File, in das das Header-File mit include eingebunden ist, automatisch die Funktion und ihre Übergabeparameter. Ebenso sollte die Deklaration von Variablen im C-File verbleiben. In das Header-File kommt dann eine Kopie der Variablendeklaration mit dem vorgesetzten Bezeichner extern. Dadurch werden diese Variablen als globale Variablen allen C-Files "bekannt gemacht", die das Header-File einbinden.

Diese Aufteilung ist wie gesagt kein Zwang, sondern eine eindringliche Empfehlung. Wer will, kann auch ein beliebig großes Projekt mit nur einem Quelltext-File (main.c) erstellen, dann muss er natürlich kein Header-File schreiben.

ein größeres C-Projekt

Beim Aufruf des Compilers in MPLAB mit Build all werden Quelltext-Files neu compiliert. Wird dagegen der Compiler mit Make gestartet, dann prüft der Compiler zunächst, ob zu einem C-Quelltext-File schon ein Object-File existiert. Wenn er ein Object-File findet, das neuer ist als das Quelltextfile, dann wird dieses Object-File benutzt. Ansonsten wird das Quelltext-File neu compiliert. Make geht deshalb deutlich schneller als Build all.

Bibliotheken
Einige typische Probleme tauchen in vielen Programmen immer wieder auf, z.B. Warteschleifen oder die Bedienung  der speziellen IO-Hardware eines PIC. Für solche Probleme muss man dann jedes mal die gleichen C-Routinen in den Quelltext schreiben. Es wäre aber mühselig, das Rad immer wieder neu zu erfinden. Deshalb werden solche hilfreichen Programmschnipsel zu Object-Files assembliert oder compiliert, und diese kleinen Objectfiles in Bibliotheken zusammengefasst.

Wenn man dann diese Routine wieder benötigt, dann programmiert man diese nicht neu, sondern verwendet eine fertige Routine aus einer Bibliothek. Solche Bibliotheken sind Files mit der Endung *.LIB'. Zum Lieferumfang der C-Compiler gehören eine ganze Reihe äußerst hilfreicher Bibliotheken. Auch wenn man selber keine Bibliotheken anlegen will, würde nur ein extrem masochistisch veranlagter Programmierer auf die Nutzung der bereits fertigen Bibliotheken verzichten.

Wenn man in einem C-Programm einfach eine Funktion aus irgendeiner Bibliothek aufruft, dann wird der Compiler mit recht schimpfen, denn er kennt diese Funktion ja gar nicht. Wir müssen ihn also vorher mit allen Funktionen Vertraut machen, die wir benutzen wollen. Dazu wird am Quelltextanfang das Header-File der Bibliothek mit #include name.h eingebunden ("name" ist natürlich anzupassen).

 



Eine Warnung auf den Weg

Meine "Muttersprache" ist Pascal. Beim Umstieg von Pascal auf C hatte ich einige Lektionen zu lernen. C ist keine Sprache, die den Programmierer an die Hand nimmt - im Gegenteil. Es kann sehr leicht passieren, dass man in C etwas schreibt, das gar nicht den eigenen Intentionen entspricht, aber vom Compiler anstandslos compiliert wird. Während andere Sprachen wie Pascal oder gar ADA davon ausgehen, dass der Programmierer fehlbar ist, und durch ihre Syntax den Programmierer gewissermaßen überwachen, glaubt der C-Compiler an die Unfehlbarkeit des Programmierers. Ich möchte das an einem kleinen Beispiel zeigen. Im folgenden Codeschnipsel teste ich zunächst, ob die boolsche Variable Fehler den Wert true hat. Wenn ja, dann wird eine Funktion gerufen, die z.B. ein Alarmsignal auslöst. Danach möchte ich den Text  "Wir sind im Jahr 2007" ausgeben.

int wert = 02007;
if (Fehler = true);
     alarm_signal_on();
print("Wir sind im Jahr %/d\n", wert);

Der Codeschnipsel ist syntaktisch korrekt, und wird als Teil eines größeren C-Programms fehlerfrei compiliert. Trotzdem macht er aber absolut nicht das, was ich will.

  1. Der Alarm wird immer ausgelöst, unabhängig vom Wert von Fehler.
  2. Die Ausschrift lautet Wir sind im Jahr 1031
Schuld sind drei Programmierfehler:

1. Zeile:
Ganze Zahlen mit führender Null sind in C oktale Zahlen. Die Zahl 02007 ist also oktal, und hat den dezimalen Wert 1031.

2. Zeile:
Das Gleichheitszeichen bewirkt keinen Vergleich, sondern eine Zuweisung. Dadurch wird Fehler auf true gesetzt. Der Ausdruck (Fehler=true) hat den Wert 1, was wiederum dazu führt, dass die if-Bedingung immer erfüllt ist, egal was am Anfang in Fehler stand.
Das Semikolon am Zeilenende beendet aber die gesamte if-Struktur sowieso. Die Zeile 3 wird also ohnehin völlig unabhängig von der
if-Bedingung ausgeführt.

Der korrekte Code wäre also:

int wert = 2007;
if (Fehler == true)
   alarm_signal_on();
print("Wir sind im Jahr %/d\n", wert);

Die Ursache der Probleme waren also echte Fehler des Programmierers. Beim Programmieren in C können solche Fehler aber sehr leicht passieren, und werden vom Compiler auch nicht kritisiert. In einigen anderen Sprachen lassen sich solche Fehler oft gar nicht ausdrücken, oder sie führen zu Fehlermeldungen des Compilers. Das Programmieren in C erfordert also große Sorgfalt und Selbstdisziplin.

nach oben

weiter zu C18
weiter zu C30
weiter zur Einführung in C für PICs

nach oben

zurück zu PIC-Prozessoren , Elektronik , Homepage

Autor: sprut
erstellt: 23.03.2006
letzte Änderung: 03.07.2009