Tips mit PICs 10: Die Befehle #define und #ifdef
Auch dieser 'Tip' ist ein Hinweis, was man machen könnte, aber nicht unbedingt machen
müßte.
Einschränkung: Wir beziehen uns hier auf das Programmsystem MPLAB IDE von microchip, das kostenlos
heruntergeladen werden kann.
Wenn man von der Programmierung mit Hochsprachen kommt (man kann auch BASIC dazurechnen), dann nervt die
'Bit-Klopferei' der Assemblersprache doch gewaltig. Nur ein Beispiel: Es braucht immer etliche Phantasie, Befehle
zur Null-Abfrage richtig zu interpretieren, zumal bei 'Null' das Zero-Bit im STATUS-Register auf '1' abgefragt
werden muß. Da ist jedesmal eine Art Stolperstein integriert. Wenn Sie lesen
btfss STATUS,Z
dann erschließt sich der Sinn des Befehles nicht sofort. Dies ist aber für das Verstehen des Programms
immens wichtig, da hier ja eine Verzweigung stattfindet. (Der Autor hat auch nach Jahren intensiver
Programmier-Tätigkeit immer noch Probleme!)
Oder:
Sie programmieren eine Steuerung, die viele Ausgänge einzeln setzen soll. Später, beim Entwerfen der
Platine, stellt sich aber heraus, daß sich viele Leiterbahnen am PIC überkreuzen würden und
daß es daher sinnvoll wäre, diese zu entwirren. Kein Problem, nur müssen Sie in Ihrem Programm genau
das Gleiche tun und dürfen dabei nicht einen einzigen Fehler machen. Ansonsten läuft Ihr Programm 'falsch'.
Derartige Probleme kann man auf einfachste Weise lösen, indem man den Befehl
#define
einsetzt. Dieser ist 'nur' die Anweisung an das Assemblier-Programm, einen Text durch einen anderen zu ersetzen. So
steht es, reichlich dürftig, in der Beschreibung. Was man aber tatsächlich alles damit machen kann,
läßt sich kaum erahnen.
Null-Abfrage:
Sie meinen, mit einem Befehl, der z.B.
ist_null?
heißen könnte, kämen Sie besser zurecht als mit dem (oben beschriebenen) btfss-Konstrukt. Dann
definieren Sie sich doch diesen neuen Befehl, indem Sie schreiben:
#define ist_null? btfss STATUS,Z
Sogar das Fragezeichen funktioniert. Damit sagen Sie dem Assemblier-Programm, es soll jedesmal, wenn es die
Zeichenfolge 'ist_null?' findet, stattdessen 'btfss STATUS,Z' einsetzen.
Das war's schon. Nur: Leerzeichen sind nicht erlaubt. Wenn Sie sich selber noch einen Gefallen tun wollen, schreiben
Sie vor die Definitions-Zeile noch eine Kommentarzeile, etwa so:
; Ersatz der Null-Abfrage: skip, wenn Null
Das hilft Ihnen später beim Lesen enorm; und auch anderen, die Ihr Programm lesen und verstehen sollen. Nun
können Sie an beliebiger Stelle im Programm den neuen Befehl einsetzen, und wenigstens dieser Stolperstein ist
weg.
Der hier genannte Ersatz ist natürlich nur ein Vorschlag. Sie können jeden beliebigen Text (außer
einem bereits definierten Schlüsselwort) verwenden, Beispiele:
skip_wenn_null
sze
Das Letztere ist aus dem Befehlssatz der alten AEG 60-10 entlehnt. Dort hieß die Nullabfrage 'tze' (test zero
equal); hier würde es analog heißen: 'skip zero equal'.
Natürlich geht das auch mit Eigen-Konstruktionen für das ach so heißgeliebte Carry-Bit (bei dem man
immer wieder Überraschungen erlebt), wie z.B. mit:
ist_ueberlauf?
ist_unterlauf?
Bevor wir hier jetzt etwas Falsches schreiben: Sie wissen ja jetzt, wie es geht ...
Setzen eines Bits an einem E/A-Port:
Hier sollten Sie jeder Ein- und Ausgabe-Leitung einen sinnvollen Namen geben. Beispiel:
#define Lampe1 PORTA,4
Es ist klar erkenntlich, daß Sie mit Leitung 4 von Port A die Lampe 1 schalten wollen. Diese Namensvergabe
sollten Sie für jede Leitung vornehmen. Und im Programm können Sie jetzt ohne Kommentar schreiben:
bsf Lampe1
oder
bcf Lampe1
Nun brauchen Sie bei Änderungen in der Verdrahtung (Lampe 1 wird z.B. nunmehr von Port A, Leitung 5 geschaltet)
nur noch ein einziges Mal dieses 'define' zu ändern,
#define Lampe1 PORTA,5
und alles läuft wie vorher.
Luxus-Version:
Wenn Sie schon einmal beim 'Um-Definieren' sind, können Sie auch noch einen Schritt weiter gehen und
schreiben:
#define Lampe1_ein bsf Lampe1
#define Lampe1_aus bcf Lampe1
oder
#define Lampe1_ein bsf PORTA,4
#define Lampe1_aus bcf PORTA,4
Auch das doppelte Um-Definieren im ersten Vorschlag wird richtig verarbeitet. Die '#define'-Anweisung dürfen
Sie beliebig oft verwenden. Nur muß die Neu-Definition aus einem einzigen Wort bestehen, das bisher nicht
verwendet wurde und auch nicht als Kommentar auftritt. Ansonsten kommt der Assembler 'ins Schleudern'. Das zweite
Vorschlags-Pärchen ist auch möglich, aber nicht zu empfehlen, da das Ändern bei Um-Verdrahtung (s.
oben, PORTA,4 in PORTA,5 ändern) evtl. an mehreren Stellen geschehen muß. Und gerade das wollten
wir ja vermeiden!
Falls Sie die Lampe gegen Masse (ein-)schalten wollen, muß der Ausgang natürlich auf 'Low' gesetzt
werden. Elegant können Sie dann schreiben:
#define Lampe1_ein bcf Lampe1
#define Lampe1_aus bsf Lampe1
Jedesmal, wenn Sie die Lampe 1 einschalten wollen, schreiben Sie nur noch
Lampe1_ein
oder
Lampe1_aus
Wie und in welche Richtung das E/A-Bit gesetzt wird, ist hier völlig egal. Das haben Sie an anderer Stelle mit
dem '#define' festgelegt. Sie entfernen Sich damit wohltuend vom Bit-Klopfen zur logisch verständlichen
Programmierung.
Bei uns wird von diesen hier beschriebenen Um-Definierungen exzessiv Gebrauch gemacht:
Der Weichendecoder für das Faller-Car-System besteht aus einer ausgetesteten Platine, die nicht mehr
verändert werden wird. In dem darauf sitzenden PIC existiert ein Rumpf-Programm, in dem alle Zuordnungen
definiert worden sind. Diese Platine wird maximal 63-mal benötigt, für 63 verschiedene Weichen. Die
individuellen Programme, eines für jede Weiche, sind noch nicht geschrieben (auch weil die Weichen und der
Straßenverlauf noch in der Planung sind).
Wenn sich jemand daran macht, für eine neue Weiche ein Programm zu schreiben, braucht er nur noch anzugeben,
welchen Ein- oder Ausgangspin der Platine er bearbeiten will. Die sind ihm, auch wegen der Verdrahtung in
die Anlage, bestens bekannt. Um die Beinchen am PIC braucht er sich nicht mehr zu kümmern: eine erhebliche
Vereinfachung und Fehlervermeidung.
Eine weitere Vereinfachung ist das Erstellen eines eigenen Programm-Schnipsels, das nur die Neu-Definierungen
der Befehle beinhaltet. Dieses wird bei jedem Programm dazugebunden. Dadurch werden diese Befehle quasi zu einem
Standard beim MEC.
noch eine wichtige Anwendung:
Eine Änderung der Verdrahtung, wie oben beschrieben, könnte auch den Wechsel des PICs beinhalten. Nehmen
wir an, infolge gesteigerter Anforderungen (so etwas soll vorkommen!!) muß ein PIC mit mehr Beinchen eingesetzt
werden. Auch hier genügt es, die neue Konfiguration (wir meinen hier die Zuordnung von Funktion zur Hardware)
per #define einzugeben. Und wieder ist dieses Problem gelöst.
Ein Nachteil soll hier nicht verschwiegen werden:
Da das Assemblier-Programm die neu geschriebenen Texte nicht sofort als Befehle erkennt, werden sie auf dem
Bildschirm nicht in der Befehls-Farbe (blau) dargestellt. Das ist gewöhnungsbedürftig!
Die Befehlsfolge #ifdef - #else - #endif
ist eine Anweisung an das Assemblier-Programm, die nachfolgenden Befehlszeilen nur dann einzubinden, wenn eine
bestimmte Variable vorher definiert wurde (Stichwort: bedingte Assemblierung). Sinnvollerweise muß dieser
Programmteil mit der Anweisung '#endif' abgeschlossen werden.
Beispiel:
Sie wollen beim Testen Ihres Programms an mehreren Stellen jeweils ein paar Befehle ausführen, die in der
endgültigen Version nicht erwünscht sind, z.B irgendwelche Anzeigen setzen. Sie können sich das
Schreiben und spätere Löschen vereinfachen und damit Fehler umgehen, wenn Sie das '#define' in Kombination
mit '#ifdef' verwenden.
Als allererste Zeile in Ihrem Programm (muß dort nicht sein, steht aber an herausragender Stelle) schreiben
Sie:
#define testfall 1
Hiermit erzeugen Sie eine Variable mit den Namen 'testfall' und geben ihr einen beliebigen Wert, hier '1'.
An beliebiger Stelle im Programm können Sie nun Befehle einfügen, die Sie nur zum Testen
benötigen:
#ifdef testfall
...
...
#endif
Wenn Sie die Definition in der ersten Programmzeile aus-kommentieren, werden der beschriebene Block (und evtl.
noch viele andere Blöcke) nicht mit assembliert, da 'testfall' nun nicht bekannt ist. So ist die einzige
Umstellung zum 'Normalfall' das Einfügen eines Semikolons; mit dem Vorteil, daß auch wirklich alle
Test-Stellen gelöscht werden.
Wollen Sie Befehlsfolgen beim Testen durch andere ersetzen, so ist auch dies möglich:
#ifdef testfall
... ; Ersatz-Befehle zum Testen
#else
... ; Original-Befehle
#endif
... und jetzt kommt die Kür ... :
Die folgenden Befehle hat der Autor in keiner Beschreibung gefunden, sondern selber ausprobiert:
#if testfall == 1
#if testfall > 1
Hier wird 'testfall' auf ganz bestimmte Werte abgefragt.
Und es geht noch schöner:
Es gibt eine Antwort auf die Ur-Frage 'wo bin ich?'. Dabei wird der Wert eines Labels abgefragt. Nehmen wir an, das
Label 'xxx' ist die Ansprungadresse eines Unterprogramms und steht (rein zufällig) auf der Adresse 0x100; dann
können Sie abfragen:
#if xxx == 0x100
und dann davon abhängig Befehle einfügen oder weglassen. (Merke: wir sind hier nicht mehr beim
Testen!) Dies ist durchaus sinnvoll, wenn in einem Adreß-Bereich > 0x7f berechnete Sprünge
ausgeführt werden sollen. Hier müssen, abhängig vom Programmzähler, von Hand
Adreßerweiterungsbits gesetzt werden, ansonsten springt der Rechner 'in den Wald'. S. dazu auch die sehr gute
Dokumentation von
sprut.
Für weitere Fragen stehen gern zur Verfügung:
- der MEC; Besichtigung und Fachsimpelei z.B. an unseren "Club-Abenden"
- der Autor: Hans Peter Kastner
Version vom: 19.04.2025; erstellt am: 26.03.2010
Copyright © 2010 - 2025 by Modelleisenbahnclub Castrop-Rauxel 1987 e.V.