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.

Valid HTML 4.01!