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 die Software MPLAB IDE von microchip, die 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. Beispiel: Es braucht immer etliche Phantasie, Befehle zur
Null-Abfrage richtig zu interpretieren, zumal bei 'Null' das Zero-Bit 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 einem Jahr 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 überschneiden und daß es
sinnvoll wäre, diese zu entwirren. Kein Problem, nur müssen Sie im Programm genau das Gleiche tun und
dürfen dabei nicht einen einzigen Fehler machen. Ansonsten läuft Ihr Programm 'falsch'.
Derartige Probleme kann man umgehen, 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 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. 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. 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.
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
erstellt am: 26.03.2010
Copyright © 2010 by Modelleisenbahnclub Castrop-Rauxel 1987 e.V.