Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Rasenmaehroboter fuer schwierige und grosse Gaerten im Test


Einführung

Bit, Byte, Nibble, Bin und Hex

Ein Mikrocontroller (kurz: µC) kann eigentlich nur durch ein Portpin eine Spannung einlesen bzw. ausgeben. Er kann aber nur erkennen, ob eine Spannung vorhanden ist oder nicht. Wenn fast keine Spannung vorhanden ist erkennt er das als 0 und wenn eine Spannung fast so gross, wie seine Versorgungsspannung anliegt, als 1.

Genauso bei der Ausgabe, wenn er 0 ausgibt ist auf dem Portpin fast keine Spannung, wenn 1, eine Spannung fast gleich gross seiner Versorgungsspannung. Und das ist ein Bit, die kleinste Menge einer Information. Das Bit ist binär, d.h. es kann nur zwei unterschiedliche Werte haben: 0 oder 1.

Wenn wir gleichzeitig (parallel) 8 Bits haben, dann ist es ein Byte, das 256 Bitkombinationen von 00000000b bis 11111111b enhält, weil ein Bit (X) auf jeder Stelle 0 bzw. 1 sein kann.

X X X X X X X X
High Nibble Low Nibble
Byte

Das "b" bedeutet, dass es sich um binäre (kurz: bin) Darstellung (auch Zahl genannt) handelt. Binäre Zahlen sind aber lang, weil jedes Bit eine Stelle benötigt.

Um die Schreibweise zu verkürzen, wurden hexadezimale (kurz: hex) Zahlen eingeführt. Zuerst wurde ein Byte auf zwei 4-Bit Halbbytes (Nibbles) verteilt und danach ein Nibble als Ziffer genommen. Weil 4 Bits 16 Kombinationen ergeben, haben die Ziffer 0 bis 9 aus dem Dezimalsystem (d) nicht ausgereicht und wurden um Buchstaben A bis F erweitert. Die hexadezimalen Zahlen haben ein "h" Zeichen am Ende. Für die Zahlen 0 bis 9 sind die (h) und (d) Zeichen nicht nötig, da sie beide gleich den entsprechenden bin Zahlen sind.

Die Umwandlung zwischen bin, hex und dec Zahlen für ein Nibble zeigt folgende Tabelle:

            0b = 0h = 0d      100b = 4h = 4d     1000b = 8h = 8d     1100b = Ch = 12d
            1b = 1h = 1d      101b = 5h = 5d     1001b = 9h = 9d     1101b = Dh = 13d
           10b = 2h = 2d      110b = 6h = 6d     1010b = Ah = 10d    1110b = Eh = 14d
           11b = 3h = 3d      111b = 7h = 7d     1011b = Bh = 11d    1111b = Fh = 15d

Damit kann ein Byte mit zwei hex Ziffern definiert werden z.B. 1100 0011b = C3h. Für zwei Bytes braucht man 4 hex Ziffern z.B.

101 0111 1010 1001b = 57A9h, usw. Für MPASM wird sehr oft die Schreibweise für hex Zahlen 0x57A9 oder 0xC3 benutzt.

So wie im Dezimalsystem werden führende Nullen nicht geschrieben. Bei einer Wandlung bin->hex fängt man immer von der rechten Seite der bin Zahl an, da die Anzahl führender Nullen unbekannt ist.

Speicher und Register

Als Speicher bezeichnet man das Teil der Hardware, in das eine Information geschrieben, gespeichert und von dort wieder ausgelesen werden kann.

Es gibt eigentlich nur zwei Arten von elektronischen Speichern: flüchtige und nichtflüchtige. Die Information die sich im flüchtigen Speicher befindet, geht verloren, wenn die Versorgungsspannung des Speichers unterbrochen oder abgeschaltet wird. Bei PICs ist es Datenspeicher (RAM).

Wenn die Versorgungsspannung vom nichtflüchtigen Speicher abgeschaltet wird, ist die gespeicherte Information zwar momentan nicht lesbar, bleibt aber erhalten und sobald der Speicher wieder mit Spannung versorgt wird, kann sie ausgelesen werden. Ein PIC hat zwei solche Speicher: Programmspeicher (Flash) und EEPROM.

Der wichtigste Unterschied zwischen den Speicherarten ist, dass die flüchtigen direkt (sehr schnell) beschreibbar sind, dagegen das Beschreiben des nichtflüchtigen Speichers spezielle Algorithmen benötigt, die im Vergleich zu direkten Zugriffen langsamer sind. Beim Auslesen gibt es praktisch keinen Unterschied.

Speicher besitzen eine bestimmte Menge von s.g. Speicherstellen. Jede Speicherstelle hat eine individuelle Adresse und kann eine binäre Information mit bestimmter Anzahl von Bits abspeichern.

PIC Prozessoren haben drei Arten von Speicher, wegen verschiedener Anwendung, auch unterschiedliche Struktur. Die beiden Speicher für Daten (RAM und EEPROM) haben jeweils 8 Bit Breite und Programmspeicher (Flasch) bei Mid-Range hat 14-bitige Speicherstellen. Die Anzahl den Speicherstellen im bestimmten Speicher ist vom PIC-Typ abhängig.

Eine 8-bitige Speicherstelle im RAM wird bei PICs Register genannt und kann so skizziert werden:

X X X X X X X X
MSB LSB
bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
High Nibble Low Nibble
Byte


Bit 7 wird als hochwertigstes (MSB = Most Significant Bit) und bit 0 als niederwertigstes (LSB = Least Significant Bit) bezeichnet. Jedes Bit im Register (X) kann gleich 0 bzw. 1 sein. In einem Register existieren immer 8 Bits also auch führende Nullen. Zum Beispiel die hex Zahl 3h sieht im PIC Register so aus: 00000011.

Um ein Datenbyte in ein Register zu schreiben oder aus einem Register zu lesen, muss zuerst das Register durch seine Adresse gewählt werden. Dafür gibt es beim PIC folgende Möglichkeiten:

Direkte Adressierung per absolute Adresse: movwf 0x20

Direkte Adressierung per vorher definiertem Namen des Registers (z.B. Temp equ 0x20): movwf Temp

Indirekte Adressierung durch "FSR" Register, in das die absolute Adresse des Registers "Temp" eingeschrieben wird. Durch Zugriff mittels "INDF" Register wird auf die Speicherstelle zugegriffen, die durch das Register "FSR" adressiert wird. ("INDF" ist dabei kein real in Hardware implementiertes Register). Wie vorher wurde "Temp equ 0x20" definiert und weiter:

      movlw   Temp      ;ins W-Register wird die absolute Adresse des Registers "Temp" geladen
      movwf   FSR       ;diese Adresse wird ins "FSR" Register kopiert
      movf    INDF,0    ;der Wert aus dem indirekt adressierten Register "Temp"
                        ;wird aus dem "INDF" Register ins W-Register geladen.

Weil in jedem 14-bittigen Befehl, der mit Datenspeicher verbunden ist, für die Adresse des ansprechenden Registers nur 7 Bits existieren, die bis zur Adresse 7Fh (128d) Register direkt ansprechen können, ist bei PICs der Datenspeicher (RAM) in s.g. Bänke verteilt.

Für Auswahl einer Bank sind zwei Bits "RP0" und "RP1" im "STATUS" Register zuständig. Die Anzahl der Bänke und ihre Verwendung ist von der gesamten Größe des RAMs abhängig und kann dem Datenblatt des PICs entnommen werden. Siehe: Speicherbankorganisation

Prozessor

Der Prozessor von Mid-Range PICs gehört zu den RISC (Reduced Instruction Set Computer) Prozessoren und man hat nur 35 Befehle zu erlernen, was seine Programmierung im Vergleich zu anderen µCs deutlich vereinfacht. Jeder Befehl benötigt im Programmspeicher nur eine Speicherstelle und im Quellcode nur eine Zeile. Die Ausführung des Befehls dauert, abhängig vom Befehl, 1 bis 2 Prozessortakte.

Die Prozessoren der Mid-Range Serie von Microchip sind alle in der "Harvard"-Architektur gefertigt. Das bedeutet, dass Datenspeicher und Programmspeicher einen eigenen Bus zur CPU (Central Processing Unit) besitzen. Der Vorteil zur "von Neumann"-Architektur ist, dass sich die Busgrößen damit unterscheiden können. Das ermöglicht eine größere Bandbreite.

Der Befehl (beim PIC 14 bit) kann in nur einem Takt verarbeitet werden. Daher kommt auch das Aufteilen der Ausführung des Befehls in 4 verschiedene Vorgänge. Wärend der neue Befehl eingelesen ("fetch") wird, wird der vorige gerade gelesen ("read") und der vorvorige verarbeitet ("execute") und der vorvorvorige schreibt gerade in den Datenspeicher ("write"). Das heißt, 4 Befehle werden jeweils um einen Oszillatortaktzyklus verschoben gleichzeitig verarbeitet.

Das geschieht in vier Perioden des Oszillators. Deswegen entspricht die Taktfrequenz der CPU der durch 4 geteilten Frequenz des Oszillators.

                CPU Vorgang                   Richtung   Speicher
                -------------------------------------------------   -
                1.Befehl lesen (fatch)        <-------   Flash       |
                2.Daten lesen (read)          <-------   RAM         | 1 Prozessortakt =
                3.Daten verarbeiten (execute)                        | 4 Oszillatortakte
                4.Daten schreiben (write)     ------->   RAM         |  
                                                                    -

Nur o.g. CPU Vorgänge sind direkt möglich. Es können deswegen keine Befehle aus dem RAM oder EEPROM ausgeführt werden. Um ein Databyte aus einem RAM Register in ein anderes zu kopieren, muss er zuerst aus dem ersten RAM Register in das W-Register (eigenen s.g. Arbeitsregister des CPU) und erst davon in das zweite RAM Register kopiert werden.

Der Prozessor hat kleinen eigenen Speicher, der weder zum Programmspeicher noch zum Datenspeicher gehört. Er besteht aus dem Programmzähler PC (programm counter) und dem Stapel (stack).

In dem PC befindet sich die 13-Bittige Adresse des momentan ausfürbaren Befehls. Nach der Ausführung jedes Befehls wird der PC um 1 erhöht und "zeigt" somit auf den nächsten Befehl im Programmspeicher, der zum Ausführen wäre. Wenn der nächste Befehl aber ein Sprung ("call") ist, wird die Adresse aus dem Befehl genommen. Nach dem Sprung wird das Programm bis zum Befehl "return" ausgeführt und danach muss der Prozessor zurückspringen, und zwar, genau zur nächsten Adresse im Programmspeicher nach dem "call" Befehl.

Damit es möglich wäre, wird vor dem Sprung die Adresse aus dem PC "auf dem Stapel gelegt" (gespeichert). Für den Rücksprung wird die abgelegte Adresse "aus dem Stapel genommen" (vom Stapel in den PC geladen). Beim Auftreten eines Interrupts wird auf dem Stapel die Adresse, der zuletzt ausgefürten Zeile abgelegt, damit der Prozessor, wenn er nach der Ausführung der "Interruppt Service Routine" (ISR) an "retfie" kommt, das unterbrochene Program an der richtigen Stelle wieder startet.

Der Stapel kann aber nur 8 Adressen speichern, deswegen dürfen nur 8 nacheinander folgende "call" Befehle benutzt werden. Wenn ein Interrupt benutzt wird, reduziert sich es auf 7, da eine Spaicherstelle immer fur die Adresse, an der das Programm unterbrechen wird, reserviert werden muss. Sonst findet der Prozessor nicht mehr zurück und in die "Nirvana" springt, was einen Absturz des Programms bedeutet. Bei dem "goto" Befehl wird die nächste Adresse nicht gespeichert, da der Prozessor nicht zurückkehren braucht.

Das Lesen/Schreiben aus/in den EEPROM Speicher ist mit Hilfe speziellen Register und Unterprogrammen bei allen Mid-Range PICs möglich. Der Lese und Schreibzugriff auf den Programmspeicher ist aber nur bei wenigen PIC-Typen (z.B. PIC16F87X) möglich. Dies ermöglicht ein "sich selbst Programmieren", was bei Bootloadern genutzt wird.

Assembler

Die Maschinensprache, auch Assembler oder kurz ASM genannt, ist eine Sprache die nur eine bestimmte CPU versteht. Für einen Menschen ist sie unverständlich, da sie nur aus hex Zahlen besteht.

Um sich die Sprache verständlicher zu machen wurden den hex Zahlen s.g. Mnemonics aus Buchstaben zugewiesen. Jeder Befehl für einen CPU hat somit ein "Namen", der aus der englischen Sprache stammt. Siehe: Kurzübersicht Assembler Befehle

Obwohl sie 200 bis 1000 mal schneller als die meisten Hochsprachen ist, wird sie, wegen des grossen Aufwands bei der Erstellung von umfangreichen Programmen, selten benutzt. Man findet sie aber oft in fast allen Hochsprachen, in eigebundenen Funktionen, überall dort wo die Hochsprachen zu langsam sind oder die nötigen Aufgaben nicht unterstützen (z.B. Maus in Q-Basic).

ASM eignet sich sehr gut für kleine Anwendungen (meistens Steuerungen) mit µC, weil nur bei dieser Programmiersprache ein direkter Zusammenhang zwischen einem Bit im Programm und einer Spannung am I/O Pin besteht. Aus dem Grund sind Hardware-Kentnisse sehr vorteilhaft. Man kann sagen, dass je mehr externer Hardware, um so weniger Software, und umgekehrt. In der Praxis wird immer ein Kompromiss gesucht.

Dank der integrierten oder an Portpins angeschlosenen Hardware und dem entsprechenden Program kann ein µC umfangreiche Aufgaben realisieren, die fast unbegrenzt und schwer vorstellbar sind.

Die Aufgabe eines ASM-Programmierers ist, ein Programm zu schreiben, das das Assemblerprogramm (z.B. MPASM) fehlerfrei in die Machinensprache assembliert ("übersetzt"), die dann eine bestimmte CPU "versteht". Sie endet eigentlich erst dann, wenn das geschriebene Programm so wie geplant funktioniert.

Die beste Methode um ASM Programmierung zu lernen ist einfach mit den Befehlen, wie mit "LEGO" Bausteinen, zu spielen. Dafür wurde ein Programm "PIC Trainer" entwickelt. Der PIC kann durch ein Programmfehler nicht kaputtgehen. Vorsicht ist nur bei der angeschlossener Hardware nötig, da ein Fehler den PIC oder die Hardware sogar "töten" kann.

Weil ASM Programme nicht besonders durchschaubar sind, wurde als Hilfsmittel ein Programmablaufdiagramm (kurz: PAD) erfunden. Bei der Programmerstellung fängt man damit an, ein PAD zu erstellen, das alle Programmschritte enthält.

Weiter werden alle Befehle nach dem PAD mit einem üblichen Texteditor in eine Textdatei mit Erweiterung ".asm" (Quellcode) geschrieben, durch ein Assemblerprogramm (für PICs: MPASM oder GPASM) von dem für Menschen noch verständlichen Code in die Maschinensprache assembliert und als Texdatei mit Erweiterung ".hex" gespeichert. Diese Datei wird danach in den Programmspeicher des µC übertragen ("gebrannt").

Das Assemblerprogramm MPASM kann kostenlos von der Homepage des Herstellers von PICs [1] runtergeladen werden. Es muss zuerst vom Downloads die "MPLAB IDE v7.50 Full Zipped Installation" runtergeladen und erst danach können gewählte Programme (z.B. nur MPASM) intalliert werden. Für MPASM benutzer werden auch folgende ".pdf" Dateien empfohlen:

MPASM/MPLINK User's Guide (2628 KB) [Benutzerhandbuch]

MPASM™/MPLINK™ PICmicro® Quick Chart (81 KB) [Kurzübersicht]

Nach dem Eischalten der Betriebsspannung des µC, fängt der CPU an, sich im Programmspeicher befindliches Programm mit dem Befehl, der an der Adresse 0 steht, auszuführen.

Aber wann das Programm endet? Natürlich wenn die Versorgungsspannung abgeschaltet wird. Nein! Das ist die einfachste Lösung um ein laufendes Programm an zufälliger Stelle zu unterbrechen, aber keine, um es an einer definierten Stelle zu beenden.

Wenn an den µC angeschlossene externe Hardware (z.B. Grafikdisplay), eine bestimmte Befehlsfolge vor dem Abschalten benötigt oder wichtige Daten (in EEPROM oder Flash) abgespeichert werden sollen, darf die Spannung erst dann abgeschaltet werden, wenn die CPU eine Meldung ausgibt, dass sie sich schon auf der "STOP" Stelle des Programms befindet. Es muss auch definiert werden (z.B. durch eine Tastenkombination), wann die CPU zum letzten Fragment des ASM Programms vor dem "STOP" gehen soll.

Grundbeschaltung

Der Prozessor von einem PIC kann sofort nach dem Einschalten der Versorgungsspannung (z.B. + 5V DC) arbeiten. Allerdings nur, wenn er den Takt, in dem er die Befehle ausführen soll, vorgegeben hat. Manche PICs besitzen einen internen RC-Oszillator, (z.B. PIC12F629, PIC16F630, PIC16F628, usw.). Bei diesen reicht es die Versorgungsspannung anzulegen und sie laufen bereits.

Die meisten haben ihn aber nicht (z.B. PIC16F84, PIC16F870, usw.) und brauchen fürs Funktionieren zusätzliche Bauteile. Grundsätzlich gibt es mehrere Möglichkeiten:

  • Quarz oder Keramik-Resonator + 2 Kondensatoren (LP,HS oder XT)
  • Keramik-Resonator mit integrierten Kondensatoren (HS oder XT)
  • Quarzoszillator (EC); genau und stabil
  • Widerstand + Kondensator (RC); keine hohe Frequenzstabilität

Die entsprechenden Bauteile werden an die Pins OSC1/OSC2 angeschlossen, um den notwendigen Prozessortakt zu erzeugen. Im Konfiguration-Word "__config" muss noch angegeben werden, welcher Oszillator (LP, HS, XT bzw. RC) verwendet wird.

Desweiteren existiert ein MCLR-Pin, der beim PIC einen Neustart (=Reset) auslösen kann (Low-Pegel). Diesen Pin sollte man, wenn er in "__config" aktiviert ist, über einen Widerstand (pull-up) an Versorgungsspannung legen, damit der PIC anfängt, sein Programm abzuarbeiten. Der Anschluss wird auch für die Programmierung benötigt. Beim sog. High-Voltage-Programming wird MCLR auf ca. 12-14 Volt gelegt, um den PIC in den Programmiermodus zu schalten. Bei manchen PICs kann dieser Anschluss auch als normalen I/O Pin eingestellt werden. In dem Fall, bei ICSP Benutzung, soll noch eine Diode zwischen den pull-up und Versorgungsspannung angeschlossen werden, um die an PIC angeschlossene Hardware während der Programmierung vom Auftreten der Vpp an Vcc zu schützen und die Vpp nicht zu belasten.

                                     VCC
                                      +
                                      |
                                      V Diode
                                      -
                                      |
                                     .-.
                                     | | Pull-up
                                     | | (10k)
                                     '-'
                            MCLR/Vpp  | .-------.
                                 Pin  +-|       |
                                      | |  PIC  |
                                    | o |       |
                            Reset |=|>  |       |
                            Taster  | o '-------'
                                      |
                                     ===
                                     GND 

Bei externen Oszillatoren bleibt der Pin OSC2 nicht angeschlossen und kann als I/O benutzt werden. Falls ein interner Oszillator benutzt wird, können beide OSC Pins als I/O dienen.

Damit ein Programm zuverlässig ausgeführt werden kann, muss die Versorgungspannung störungsfrei sein. Dafür wird ein Keramik-Vielschicht-Kondensator 100 nF (0,1 µF) möglichts am kürzesten direkt zwischen VDD und VSS Pins geschaltet.

Folgende Skizzen zeigen die Grundbeschaltung eines PICs:

Entstörkondensator beim PIC
Quarz
externer Quarzoszillator
externer RC-Oszillator

Konfiguration

Die Konfiguration eines PICs wird beim "brennen" fest programmiert. Sie ist eigentlich für fast jeden PIC-Typ anders. Um Probleme zu vermeinden, muss sie für bestimmten PIC aus einer im MPASM Verzeichnis enthaltenen Datei "PXXFXX.INC" entnommen werden. Die Erklärung der Optionen befindet sich im entsprechenden Datenblatt unter "Special Features of the CPU"

Als Beispiel für PIC16F84 haben wir in der Datei "P16F84.INC":

;==========================================================================
;
;       Configuration Bits
;
;==========================================================================

_CP_ON                       EQU     H'000F'
_CP_OFF                      EQU     H'3FFF'
_PWRTE_ON                    EQU     H'3FF7'
_PWRTE_OFF                   EQU     H'3FFF'
_WDT_ON                      EQU     H'3FFF'
_WDT_OFF                     EQU     H'3FFB'
_LP_OSC                      EQU     H'3FFC'
_XT_OSC                      EQU     H'3FFD'
_HS_OSC                      EQU     H'3FFE'
_RC_OSC                      EQU     H'3FFF'

Wobei:

_CP_ON     - Programmspeicher ist vorm Auslesen geschützt
_CP_OFF    - Programmspeicher kann ausgelesen werden (ist nicht geschützt)
_PWRTE_ON  - Das Programm wird mit Verzögerung von ca. 72 ms nach dem Einschalten gestartet
_PWRTE_OFF - Das Program wird sofort nach dem Einschalten gestartet
_WDT_ON    - Watchdog Timer ist aktiv
_WDT_OFF   - Watchdog Timer ist unaktiv

Die alle folgende Optionen beziehen sich an den Oszillatortyp:

_LP_OSC    - Oszillator bis ca. 200 kHz (z.B. Uhrenquarz 32768 Hz)
_XT_OSC    - Oszillator bis ca. 3,5 MHz
_HS_OSC    - Oszillator über 3,5 MHz (z.B. 4 MHz)
_RC_OSC    - Externer RC Oszillator

Die Konfiguration wird im Quellcode (*.asm Datei) mit Direktive "__config" definiert, z.B. so:

	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC

Alle Optionen müssen groß geschrieben sein, genauso wie in der "*.inc" Datei.

Wahl des PICs

Es gibt PIC µC die in der Typenbezeichnung den Buchstaben "C" oder "F" haben.

Die älteren mit "C" haben EPROM Programmspeicher und die gibt es in zwei Versionen: ohne und mit Fenster (aus Quarz-Glass) fürs Löschen des EPROMs mit UV Strahlung. Bei denen ohne Fenster kann der Programmspeicher nur einmal beschrieben und nicht mehr gelöscht werden.

Die neuen mit "F" besitzen einen Flash-Programmspeicher, der bis zu 100 000 mal mit angelegter Spannung gelöscht und danach neu beschrieben werden kann.

Für die Wahl eines PICs für bestimmte Anwendung wichtig sind:

- Max. Taktfrequenz des Prozessors.

- Grösse des Datenspeichers (für Variablen).

- Grösse des Programmspeichers (für Programm).

- Integrierte Hardware (Komparatoren, A/D Wandler, Timer, USART, I²C, SPI, PWM, usw.).

- Freie I/O Pins für externe Hardware (Display, Tasten, usw.).

- Vorhandene Betriebspannung (Netzteil, Akku, Batterie).

In der Praxis wird meistens für die Programmerstellung ein grösserer PIC genommen (wenn möglich pinkompatibler z.B. PIC16F628 für PIC16F84 oder PIC16F630 für PIC12F629) und erst nach der Optimierung des lauffägiges Programms, der tatsächlich nötige, da seine Parameter am Anfang nur geschätzt werden können. Wenn man viele Programme für verschiedene PICs entwickelt, wäre der größte PIC16F877 mit 20 MHz max. Taktfrequenz optimal.

Diese Lösung hat auch den Vorteil, dass während der Programmerstellung kurze Hilfsprogramme (z.B. "PIC Trainer") in den Programmspeicher kopiert und benutzt werden können, da sie sowohl ein bischen Programmspeicher und RAM als auch 2 freie I/O Pins fürs PIC Miniterminal brauchen.

Programm

Allgemeines

Jedes Program kann man in kleinere Fragmente unterteilen, die auf bestimmte Weise miteinander verknüpft sind und gemeinsam die Aufgabe des Programms erfüllen. Das wichtigste Teil eines Programms ist das s.g. Hautprogram (kurz:HP), das eine führende Rolle spielt. Dem HP sind fast alle andere Programmteile untergeordnet (weiter als Unterprogramm (kurz:UP) genannt) und werden nach Bedarf von ihm aufgerufen, um eine bestimmte Aufgabe zu erledigen.

Die Struktur eines Programs ist aber komplizierter, da ein UP auch ein oder mehrere UPs nacheinander aufrufen kann. Ganz unten sind die UP1s, die ganz einfache Sachen erledigen. Höher ist das nächste Ebene mit UP2s die schon mehr komplizierten Aufgaben durch ein Aufruf der UP1s erledigen können, usw. Bei Mid-Range PICs (12FXXX und 16FXXX) können ohne Tricks maximal bis zu 8 Ebenen benutzt werden, da auf dem Stapel (stack) nur max. 8 Rücksprungadressen abgespeichert werden können. Siehe hierzu: Prozessor und Programmspeicher

Hauptprogramm - Unterprogramm

Jedes UP kann jederzeit aufgerufen werden, je nach dem was gerade eledigt werden muss. Weil das nicht egal ist, welches UP aufgerufen wird, da jedes nur eine bestimmte Funktion im Programm hat, muss der Programmierer dafür sorgen, dass alles richtig nach Programablaufdiagramm, und nicht chaotisch, abläuft.

Die Programmierung in ASM ist ähnlich wie bei Hochsprachen, wenn man sich Bibliotheken mit geprüften prozessorspezifischen UPs erstellt. Um ein lauffähiges Programm zu erstellen, braucht man nur benötigte UPs ins Programm kopieren oder einbinden und ein geeignetes HP, das sie aufruft, schreiben.

Ein ASM Programm (Quellcode) muss in einer Texdatei mit der Endung ".asm" in der vom Assemblerprogramm erwarteten Form verfasst werden, um die fehlerfreie Konvertierung in die Maschinensprache (Assemblierung) zu gewährleisten. Dieser Prozess verläuft in der Form eines Dialoges.

Der Programmierer schreibt und gibt es dem Assemblerprogramm zum Übersetzen. Alles was der Assemblerprogramm nicht versteht oder nicht richtig ist, erscheint als Fehlermeldungen, die der Programmierer kennen muss, um die Fehler korrigieren zu können. Eine ".hex" Datei wird erst dannn erstellt, wenn das Assemblerprogramm keine Fehler mehr im Quellcode findet. Deswegen ist es sehr wichtig, sich mit dem Assemblerprogramm vertraut zu machen, um die Dialogzeit zu minimieren.

Programmdurchlaufdiagramm

Der Programdurchlaufdiagram (kurz: PAD) ist eine vorläufige und laufend änderbare Stufe zwischen einer Idee und ihrer Verwirklichung. Die Kreativität des Programmierers ist nur für die Erstellung des PADs nötig. Jedes sein Symbol (ausser "Start/Stop") muss als Befehlsreihenfolge für einen bestimmten Prozessor in den Quellcode übertragen werden. Das ist aber nur reine "Übersetzung". Der Quellcode ist nur eine andere Form des PADs und hoffentlich wird es zukünftig Komputerprogramme geben, die ein PAD direkt in eine "*.hex" Datei für bestimmten Prozessor wandeln werden. Zur Zeit muss die "Übersetzung" leider vom Programmierer gemacht werden. Der PAD wird erst dann fertig, wenn nach ihm erstelltes ASM Program auf einem µC so wie gewünscht funktioniert.

Die Anschriften "Ein" und "Aus" gehören nicht zu Symbolen des PADs und wurden nur zur Erklärung benutzt.

Beispiel für ein PAD

Der PAD ist sehr eifach zu erstellen, weil dafür nur drei Symbole benötigt sind:

Symbole des PAD

Das "Start/Stopp" Symbol bedeutet, dass das gesamte Programm sich im stabilen Zustand befindet und nicht "läuft". Anstatt "Stopp" kann auch "Schlaf" (Sleep) agewendet werden, da das Programm in dem Fall auch nicht aktiv ist. Das "Tun" Symbol stellt meistens ein UP mit Reihenfolge von Befehlen dar. Das "Prüfen" bedeutet eine Prüfung bestimmter Bedingung und abhängig davon einen weiteren Lauf eines Programms, endweder in der "ja" (J) oder "nein" (N) Richtung.


Als allgemeinnutziges Standard für µCs kann man folgender PAD bezeichnen:

PAD                                _____
                                  /     \
        Spannung ein (Ein) ----->( Start )
                                  \_____/
                                     |                   -
                                     V                    |
                             .---------------.            |
                             |Initialisierung|            |
                             '---------------'            |
                                     |                    |
                          .--------->V                    |
                          |  .---------------.            |
                          |  | Hauptprogramm |            |
                          |  '---------------'            |
                          |          |                    |
                          |          V                    |
                          |          A                    |
                          |         / \                    > Gesamtes Programm 
                          |        /   \                  |
                          |       /Ende \____             |
                          |       \  ?  / J  |            |
                          |        \   /     |            |
                          |         \ /      |            |
                          |          V       |            |
                          |         N|       |            |
                          `----------´       |            |
                                             V            |
                                     .---------------.    |
                                     |    Beenden    |    |
                                     '---------------'    |
                                             |            |
                                             V           -
                                           _____
                                          /     \
        Spannung aus (Aus) <-------------( Stopp )
                                          \_____/

Das Hauptprogram wird in einer endlosen Schleife ausgeführt, die durch die Prüfung "Ende?" unterbrochen werden kann. In dem Fall wird vor dem Beenden des gesammten Programms noch ein UP "Beenden" ausgeführt, das z.B. Daten im EEPROM speichert.

Es ist nicht nötig, immer die Symbole zu zeichnen, man kann sie sich vorstellen und nur den Text schreiben. Die Prüfungen werden mit "?" gekenzeichnet und die Zeichen "V", "A", "<" und ">" zeigen die Richtung des weiteren Verlaufs. Dann sieht der PAD so aus:

                                    Ein > Start
                                            V                 - 
                                     Initialisierung           |
                                   .------->V                  |
                                   |  Hauptprogramm             > Gesamtes Programm
                                   |        V                  | 
                                   |      Ende? J > Beenden    |
                                   |        N          V      -
                                   |        V        Stopp > Aus
                                   `--------´

Man kann auch die unbedeutenden Richtungspfeilen weg lassen:

PAD1                                Ein > Start               _
                                     Initialisierung           |
                                   .------->V                  |  Gesamtes
                                   |  Hauptprogramm            |  Programm
                                   |      Ende? J > Beenden   _|
                                   |        N        Stopp    
                                   |        V          V
                                   `--------´         Aus

In der Praxis werden aus Platzgründen meistens die vereinfachten PADs benutzt. Als "movxx" wird oft ein Zeichen "->" benutzt. Zum Beispiel "A->W" bedeutet, dass der Inhalt des A Registers ins W-Register geladen (kopiert) wird. Ausserdem werden Zeichen ("+", "-", "*", "/", "^", usw.) für arithmetische und ("&", "or", "xor", usw.) für logische Operationen angewendet. In den Quellcode werden für diese Zeichen entsprechende Befehle geschrieben. Beispiel:

                                            V
                                         10h->W
                                         W+B->B
                                            V

wird im Quelcode so aussehen:

                                       ...........
                                       movlw   10h
                                       addwf   B,1
                                       ...........

Siehe auch: Das erste... und Mausrad

Der PAD1 kann aber für Hauptprogramme, die in beliebigem Moment unterbrochen werden dürfen, deutlich vereinfacht werden, da die Prüfung "Ende?" ob das Hauptprogram beendet werden soll, und das UP "Beenden", entfallen.

Die meisten ASM Programme für µC sind deswegen nach solchem PAD erstellt:

PAD2                               Ein > Start        _
                                    Initialisierung    |
                                  .------->V           |
                                  |  Hauptprogramm      > Gesamtes Programm
                                  |        V           |
                                  `--------´          _|
                                       

Für Testprogramme wird meistens fogender PAD angewendet, weil es ziemlich einfach festzustellen ist (z.B. durch Stromverbrauchmessung des µCs), wann sich die CPU schon im Schlaf befindet. Erst dann, darf die Betriebspannung des µCs ausgeschaltet werden.

PAD3                               Ein > Start        _
                                    Initialisierung    |  Gesamtes
                                     Hauptprogramm    _|  Programm
                                         Schlaf > Aus

Und eine batteriebetriebene Uhr wird überwiegend so gestaltet:

PAD4                               Ein > Start        _
                      Interrupt     Initialisierung    |
            Timer------------------------->V            > Gesamtes Programm
                                     Hauptprogramm    _|
                                         Schlaf

In dem Fall reicht es aus, wenn die CPU jede Minute vom Timer aufgeweckt wird, um die Zeit zu aktualisieren. Eine Uhr ist immer (ausser Batteriewechsel) ununterbrochen mit Spannung versorgt.

Für komplizierte Programme ist es praktisch unmöglich ein PAD zu erstellen, in dem jeder CPU Befehl sein eigenes Symbol hat. Man beschränkt sich nur auf alle Prüfungen, die über den Lauf des Programms entscheiden, und ganze UPs (z.B. "Initialisierung") nur als ein Symbol verwendet. Für jedes UP wird dann ein eigener PAD erstellt.

Das Erstellen von PAD bei ASM Programmen ist sehr wichtig und darf nicht unterschätzt werden. Je stärker ein Programmierer glaubt, dass er das ohne PAD schafft, um so mehr Zeit wird er danach bei Fehlersuche oder Änderungen im ASM Programm verlieren.

Beispiel aus dem Alltag:

"Das programm hat anfangs auf Anhieb funktioniert. Doch leider habe ich dann was geändert was ich nicht mehr weiß. Das Programm macht nun komische Sachen...... Ich kann mir nicht erklären warum, kann mir jemand helfen???"

Für einfache ASM Programme, die gut kommentiert sind, reicht es meistens aus, ein PAD nur "im Kopf" zu erstellen, aber ganz ohne PAD geht es sicher nicht.

Der PAD kann auch benutzt werden, um einen "fremden" Code verständlich zu machen. In dem Fall werden alle Befehle (Zeilen) aus dem Quellcode nacheinander in PAD Symbole umgewandelt und daraus ein PAD erstellt.

Hauptprogramm

Wie sein Namen schon vermuten lässt, ist das Hauptprogram das wichtigste Teil des gesamten Programms. Meistens ist es auch das kleinste Teil, vor allem, wenn die UPs sehr komplex sind. Seine Aufgabe ist die benötigte UPs in bestimmter Reihenfolge nacheinander aufzurufen, um die alle Funktionen des gesamten Programms zu realisieren.

Das HP ist meistens als endlose Schleife, wie im PAD2, aufgebaut. Weil die endlose Schleife sehr schnell läuft, werden alle durch die UPS realisierten Aufgaben quasi gleichzeitig ausgeführt. Wenn es unerwünscht ist, müssen einige UPs als Verzögerungen realisiert werden. Dafür können auch UPs mit fester Ausführungszeit (z.B. Displayausgabe) angewendet werden.

Typischer PAD für ein HP sieht so aus:

                                     Haupt    .--->V
                                              |   UP1
                                              |   UP2
                                              |   ...
                                              |   UPn
                                              |    V
                                              `----´

In den Quellcode wird es so eigeschrieben:

                                    Haupt   call    UP1	
                                            call    UP2
                                            ...........
                                            call    UPn
                                            goto    Haupt

In der Praxis wird das HP schrittweise erstellt. Am Anfang wird sich nur ein Aufruf vom UP im HP befinden und die folgenden kommen nach Erstellung und Prüfen weiteren UPs dazu, bis das HP fertig wird.

Unterprogramm

Unterprogramm wird durch übergeordnetes Programmteil (Aufrufer) mit "call" aufgerufen und nach seinem Ausführen, wird zurück zum Aufrufer in die Zeile nach dem "call" gesprungen. Der Rückkehr zum Aufrufer wird durch "return" bzw. "retlw" Befehl, der sich am Ende jedes UPs befinden muss, erreicht. Und das ist der einzige Unterschied zwischen einem HP und einem UP.

Jedes UP hat folgender PAD:

                               vom Aufrufer ------------>V
                                                        Tun
                                                         V
                        zurück zum Aufrufer <----------return bzw. retlw 

Bei dem Rückkehr aus dem UP mit "retlw" befindet sich im W-Register der Wert aus dem Befehl "retlw". Dies wird benutzt um zu erkennen aus welchem UP das verzweigte Programm zurückkommt und ermöglicht eventuelle Fehler beim Ausführung des Programmteils zu erkennen.

Bei den PIC16FXX, wegen Stapeltiefe, können max. 8 UPs nacheinander aufgerufen werden.Die einfachste Möglichkeit um das zu umgehen ist: mehrere Unterprogramme (UP), die immer in gleicher Reihenfolge aufgerufen werden, als ein UP zusammenfassen und somit mit einem "call" aufrufen.

Main	call	UP1
	call	Up2
	call	Up3
	call	UP4
	call	UP5
	call	UP6
	call	UP7
	call	UP8
	call	UP9
	call	UP10
	call	UP11
	call	UP12
	call	UP13
	call	UP14
	goto	Main

Nach dem Zusammenfassen:

UPX1	call	UP1
	call	Up2
	call	UP3
	call	UP4
	call	UP5
	call	UP6
	call	UP7
	return

UPX2	call	UP8
	call	UP9
	call	UP10
	call	UP11
	call	UP12
	call	UP13
	call	UP14
	return

werden nur 2 anstatt 14 "call"s benötigt:

Main	call	UPX1
	call	UPX2
	goto	Main 

Ein HP von einem ASM Programm kann in anderem, mehr umfangreichem ASM Program als UP benutzt werden, wenn der sich am Ende des HPs befindlicher Befehl "goto" durch "return" ersetzt wird. Ein Beispiel dazu:

            Haupt1  call    UP11                          Haupt1  call    UP11
                    call    UP21                                  call    UP21
                    ...........             ------->              ...........
                    call    UPn1                                  call    UPn1 
                    goto    Haupt1                                return 

Jetzt können wir im mehr komplexen HP (Haupt) das Haupt1 als Unterprogramm aufrufen:

                                  Haupt    call    UP1      
                                           call    Haupt1
                                           ...........
                                           call    UPn
                                           goto    Haupt

Jedes UP kann auch von einem anderen übergeordneten UP aufgerufen werden, wenn das was es realisiert, benötigt wird.

In der Praxis wird oft ein UP von mehreren anderen UPs benutzt. Zum Beispiel um LCD Display zu steuern, brauchen wir entweder ein Befehl (Cmd) oder ein Zeichen (Data) an Display zu schicken. In beiden Fällen wird ein Byte geschickt, einmal mit RS=0 (Befehl) und einmal mit RS=1 (Zeichen) laut folgendem PAD:

                                       "Cmd"   "Data" 
                                        RS=0    RS=1
                                         V       V 
                                         `-->V<--´
                                   "Send" Byte schicken
                                             V
                                           return

Das wird in den Quellcode z.B. so eingeschrieben:

                                    Cmd     bcf     RS
                                            goto    Send
                                    Data    bsf     RS
                                    Send    ............
                                            return

Das UP "Send" ist den UPs "Cmd" und "Data" untergeordnet, da es von beiden benutzt wird, kann aber weder "Cmd" noch "Data" benutzen.

Initialisierung

Damit der PIC ein Programm asführen kann, muss er vollständig und richtig konfiguriert und initialisiert werden. Deswegen als erstes UP, das von dem gesamten Programm noch vor dem HP aufgerufen wird , ist "Initialisierung" (kurz: Init)

Variablen

Weil sich nach dem Einschalten der Spannung im RAM zufällige Werte befinden, wird oft als erstes der benutzte Bereich des RAMs (z.B. 20h bis 7Fh) gelöscht. Es wird einfach und sparsam mit einer Schleife erledigt, die indirekte Adressierung verwendet:

                                                  V
                            Adresse des ersten Registers in FSR laden (20h)
                            .-------------------->V
                 RAMClr     |Indirekt adressierter Register löschen (INDF)
                            |              Adresse erhöhen
                            |        Letzte Adresse + 1 = 80h J > Return
                            |                     N
                            |                     V
                            `---------------------´

Es wird wie folgt in Quellcode eingeschrieben:

                                            movlw   0x20
                                            movwf   FSR
                                  RAMClr ,->clrf    INDF
                                         |  incf    FSR,1
                                         |  btfss   FSR,7
                                         `-<goto    RAMClr
                                            return

Um Anzahl den Marken (label) zu verringern, wird oft ein Symbol "$" benutzt. Es bedeutet die Adresse der aktuellen Befehlszeile. Es kann also auch so geschrieben werden:

                                            movlw   0x20
                                            movwf   FSR
                                         ,->clrf    INDF
                                         |  incf    FSR,1
                                         |  btfss   FSR,7
                                         `-<goto    $-3   ; springe zu aktueller Adresse -3
                                            return

Danach können den benötigten Variablen die gewünschten Werte zugewiesen werden:

                                            movlw   0x3C
                                            movwf   LimH
                                            movlw   0x5A
                                            movwf   LimL
                                            u.s.w.

Somit sind die Variablen initialisiert.

I/O Ports

Nach dem Einschalten der Spannung sind die für Komparatoren oder A/D Wandler benutzte Pins als analoge Eingänge initialisiert. Wenn sie alle als digitale I/Os verwendet werden sollen, müssen sie als solche definiert werden. Das geschieht durch Schreiben des Wertes 7 in das entsprechende Register (CMCON bzw. ADCON1):

                     movlw   7                bzw.             movlw   7             
                     movwf   CMCON                             movwf   ADCON1

Wenn einige als analoge Eingänge benutzt werden sollen, müssen die entsprechende Werte dem Datenblatt des jeweiligen PICs entnommen werden.

Danach werden in alle Ports nacheinander die gewünschte Werte die an den Pins vor dem Start des Hauptprogramms ausgegeben werden sollen, geschrieben:

                                      clrf    PORTA
                                      movlw   0x37
                                      movwf   PORTB 
                                      usw.

Anchliessend werden für jeden Port die Werte in TRISx Register eingeschrieben, wobei ein Bit einem Pin entspricht. Ein Pin wird in TRISx Register durch 1 als Eingang und durch 0 als Ausgang definiert. Beispielweise beim PORTB sollen B7,B5 und B3 als Eingänge und restliche Pins als Ausgänge definiert werden. Das ergibt den Wert 10101000b = A8h, der in den TRISB Register geschrieben werden muss. Weil die alle TRISx Register sich in der höheren Bank befinden, muss im STATUS-Register auf entsprechende Bank und danach zurück auf Bank 0 umgeschaltet werden. Zum Beispiel für die TRISB in der Bank1:

                                      bsf     STATUS,RP0
                                      movlw   0xA8
                                      movwf   TRISB
                                      bcf     STATUS,RP0

Bei einem Umschalten der Bank können selbstverständlich alle TRISx Register, die in der gleichen Bank liegen, nacheinander beschrieben werden. Siehe : PORTx und TRISx

Bei dem PORTA gibt es Pins, die nur "open drain" Ausgang haben (können nur auf GND schalten) bzw. nur als Eingang benutzt werden können. Bei Plannung der Verwendung von PORTA muss immer im Datenblatt geprüft werden, ob sich ein bestimmter Pin für die geplannte Anwendung eignet.

Hardware

Die für ASM Programm benutzte Hardware kann auf integrierte und externe geteilt werden. Für eine Initialisierung der integrierten Hardware (Komparatoren, A/D Wandler, Timer, USART, I²C, SPI, PWM, usw.), müssen entsprechende SFRs (Spezial Function Registers) laut Datenblatt des PICs definiert werden.

Die externe Hardware muss nach Datenblättern der Hersteller initialisiert werden.

Einlesen

Um ein Bit von einem Portpin einzulesen und in ein bestimmtes Register zu Kopieren wird folgender PAD benutzt, weil ein PIC kein Befehl dafür hat:

                                           V
                                  Quellbit = 0 ? N >------.
                                           J              |
                                           V              |
                              Bit im Zielregister löschen |
                                           V<-------------´
                                  Quellbit = 1 ? N >------.
                                           J              |
                                           V              |
                              Bit im Zielregister setzen  |
                                           V<-------------´

Wenn wir z.B. ein bit3 von PortA als bit1 in den Register Tasten kopieren wollen, dann wird es in Quellcode so geschrieben:

                                      btfss   PORTA,3
                                      bcf     Tasten,1
                                      btfsc   PORTA,3
                                      bsf     Tasten,1

Wenn es zullässig ist, dass sich das Bit im Zielregister kurzzeitig ändert, kann man sich die erste Zeile im Quellcode ersparen:

                                            V
                               Bit im Zielregister löschen
                                   Quellbit = 0 ? J >------.
                                            N              |
                                            V              |
                               Bit im Zielregister setzen  |
                                            V<-------------´
                                       bcf     Tasten,1
                                       btfsc   PORTA,3
                                       bsf     Tasten,1

Wenn ein ganzes Byte vom Port in das W-Register eingelesen wird, kann man den Wert gleich komplett in das Zielregister schreiben:

                                       movf     PORTA,0
                                       movwf    Tasten

Ausgeben

Um ein Bit an einem Portpin auszugeben wird ein bestimmter Bit mit "bcf" gelöscht oder mit "bsf" gesetzt. Zum Beispiel bit4 im PORTA:

                                       bcf   PORTA,4.

Um ein Byte auszugeben wird es zuerst in das W-Register geladen und danach an den Port übergeben, z.B.:

                                       movlw  0x12
                                       movwf  PORTA

Schleifen

Ein in ASM Programmen meist verbreitetes Codefragment ist eine Schleife.

Es kann eine endlose Schleife, die durch Prüfung einer bestimmten Bedingung (z.B. Taste) unterbrochen wird, sein:

                                            V<-----.
                                           Tun     |
                                            ? N >--´
                                            J
                                            V
                                          weiter

Es kann eine Schleife mit Schleifenzähler (z.B. Temp), die bestimmte Anzahl Durchläufe hat, sein:

                                            V
                                  Anzahl ins Temp laden  
                                            V<---------. 
                                           Tun         |
                                  Temp decrementieren  |   
                                       Temp = 0 ? N >--´
                                            J
                                            V
                                          weiter 
                                                                                      

Solche Schleifen können als UPs verwendet werden, wenn "weiter" mit "return" ersetzt wird.

Das waren Beispiele für Schleifen, die bewusst programmiert sind. Es gibt leider auch endlose Schleifen, die durch einen Fehler im Programm enstanden sind und zum "hängen" des Programms führen. Solche Schleifen sind im Programm nicht einfach zu finden, da ihre Parameter unbekannt sind. In dem Fall sehr behilflich ist der PIC RAM Monitor bzw. PIC Trainer.

Pause

Um eine Pause (Warten, Verzögerung) im Programm anzulegen wird der "nop" Befehl benutzt, während dessen Ausführung der CPU nichts macht. Mit einem "nop" kann eine Zeit gleich 4 Takten (Perioden) des Oszillators realisiert werden.

Für sehr kurze Wartezeiten benutzt man eine Reihe von nachfolgenden "nop"s. Beim einem Oszillator 4 MHz, die Ausführungszeit des Prozessors beträgt 1 µs pro "nop". Wenn z.B. 4 µs benötigt werden, werden dafür 4 "nop"s gebraucht:

                                       ...
                                       nop
                                       nop
                                       nop
                                       nop
                                       ...

Das gleiche bewirken 2 "goto"s, brauchen aber im Vergleich zu "nop"s nur die Hälfte der Bytes im Programmspeicher:


                                       ...
                                       goto $+1
                                       goto $+1
                                       ...

Der Befehl goto $+1 bedeutet "springe zur aktuellen Adresse + 1" (also nächster Adresse) und seine Ausführungszeit ist gleich 2 Prozessortakten.

Für kurze Zeiten werden s.g. Warteschleifen, wie im folgendem PAD benutzt:

                                        V
                                      n * nop
                                     P0 laden
                                        V<---------.
                                P0 decrementieren  |
                                     P0 = 0 ? N >--´
                                        J
                                        V

In Quellcode wird es als UP so eigeschrieben:

             Warte     nop                        Warte     nop
                       ...                                  ...
                       nop                                  nop 
                       movlw   0xXX                         movlw   0xXX
                       movwf   P0                           movwf   P0      
             Warte0    decfsz  P0,1                         decfsz  P0,1
                       goto    Warte0                       goto    $-1
                       return                               return

Der Sprung zur Marke "Warte0" ("goto Warte0") wurde in rechtem Beispiel durch "goto $-1" ersetzt.

Die gesammte Anzahl den CPU Takten (N) lässt sich aus folgender Formel berechnen:

N = 3 * P0 + 6 + n

und die Wartezeit (T) in Sekunden:

T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P0 * ( 4 / Fosc )

Wobei:

P0 = Zahl im Register P0, 6 = Ausführungszeit von "call" (2) + "movlw" (1) + "movwf" (1) + "return" (2), n = Anzahl "nop"s, Fosc = Frequenz des Oszillators (z.B. Quartz)

Wenn in ein Register PX eine 0 eingeschrieben wird, bedeutet es, dass die decrementierung des Registers 100h = 256d Takten dauern wird, da dekrementierte 0 eine FFh = 255d ergibt. Deshalb in Warteschleifen ist die Zahl 0 die grösste (256 Dürchläufe) und 1 die kleinste. Für solche einfache Schleifen (als UP) die Wartezeit beträgt min. 9 und max. ca. 800 Prozessortakten (ohne "nop"s).

Für mittlere Wartezeiten z.B. 0,1 Sekunde werden doppelte Warteschleifen verwendet:

                        Warte           V
                                      n * nop
                                      P1 laden
                        Warte1          V<-------------.
                                      P0 laden         |
                        Warte0          V<---------.   |
                                P0 decrementieren  |   |
                                     P0 = 0 ? N >--´   |
                                        J              |
                                        V              |
                                P1 dekrementieren      |
                                     P1 = 0 ? N >------´
                                        J
                                        V

Das wird in Quellcode als UP so aussehen:

             Warte     nop                        Warte     nop
                       ...                                  ...
                       nop                                  nop
                       movlw   0xXX                         movlw   0xXX
                       movwf   P1                           movwf   P1 
             Warte1    movlw   0xXX                         movlw   0xXX
                       movwf   P0                           movwf   P0
             Warte0    decfsz  P0,1                         decfsz  P0,1
                       goto    Warte0                       goto    $-1
                       decfsz  P1,1                         decfsz  P1,1
                       goto    Warte1                       goto    $-5
                       return                               return

Wie vorher wurden rechts die Marken durch "$-n" ersetzt.

Die gesammte Anzahl den CPU Takten (N) lässt sich aus folgender Formel berechnen:

N = P1 * (3 * P0 + 5) + 8 + n

und die Wartezeit (T) in Sekunden:

T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P1 * P0 * ( 4 / Fosc )

Wobei:

P0 = Zahl im Register P0, P1 = Zahl im Register P1, 8 = Ausführungszeit von "call" + "return" + 2 * ("movlw" + "movwf"), n = Anzahl "nop"s, Fosc = Frequenz des Oszillators (z.B. Quartz)

Für solche doppelte Schleifen (als UP) die Wartezeit beträgt min. 16 und max. ca. 200 000 Prozessortakten (ohne "nop"s).

Für lange Wartezeiten z.B. 1 Sekunde werden 3-fache Warteschleifen angewendet.

Solche Warteschleife ist im folgenden PAD abgebildet:

                        Warte           V
                                      n * nop
                                      P2 laden
                        Warte2          V<-----------------.
                                      P1 laden             |
                        Warte1          V<-------------.   |
                                      P0 laden         |   |
                        Warte0          V<---------.   |   |
                                P0 decrementieren  |   |   |
                                     P0 = 0 ? N >--´   |   |
                                        J              |   |
                                        V              |   |
                                P1 decrementieren      |   |
                                     P1 = 0 ? N >------´   |
                                        J                  |
                                        V                  |
                                P2 dekrementieren          |
                                     P2 = 0 ? N >----------´
                                        J
                                        V

Das wird in Quellcode als UP so aussehen:

             Warte     nop                        Warte     nop
                       ...                                  ...
                       nop                                  nop
                       movlw   0xXX                         movlw   0xXX
                       movwf   P2                           movwf   P2
             Warte2    movlw   0xXX                         movlw   0xXX
                       movwf   P1                           movwf   P1 
             Warte1    movlw   0xXX                         movlw   0xXX
                       movwf   P0                           movwf   P0
             Warte0    decfsz  P0,1                         decfsz  P0,1
                       goto    Warte0                       goto    $-1
                       decfsz  P1,1                         decfsz  P1,1
                       goto    Warte1                       goto    $-5
                       decfsz  P2,1                         decfsz  P2,1 
                       goto    Warte2                       goto    $-9
                       return                               return

Auch hier wurden rechts die Marken durch "$-n" ersetzt.

Die gesammte Anzahl den CPU Takten (N) lässt sich aus folgender Formel berechnen:

N = P2 * [ P1 * (3 * P0 + 5) + 9 ] + 10 + n

und die Wartezeit (T) in Sekunden:

T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P2 * P1 * P0 * ( 4 / Fosc )

Wobei:

P0 = Zahl im Register P0, P1 = Zahl im Register P1, P2 = Zahl im Register P2, 10 = Ausführungszeit von "call" + "return" + 3 * ("movlw" + "movwf"), n = Anzahl "nop"s, Fosc = Frequenz des Oszillators (z.B. Quartz)

Für solche 3-fache Schleifen (als UP) die Wartezeit beträgt min. 27 und max. ca. 50 000 000 Prozessortakten (ohne "nop"s).

Die "nop"s vor allen Warteschleifen sind nur notwendig um genaue Wartezeit einzustellen zu können. Sie können auch mit "goto $+1"s ersetzt, oder weg gelassen werden. Sie können auch innerhalb jeder Schleife eingesetzt werden, um sie deutlich zu verlängern.

Ein Beispiel:

                                        V
                                      n * nop
                                     P0 laden
                                        V<---------.
                                     n0 * nop 
                                P0 decrementieren  |
                                     P0 = 0 ? N >--´
                                        J
                                        V

Bei nur einem "nop" (n0=1) ist die max. Wartezeit ca. T ~ 4 * P0 * ( 4 / Fosc ), bei zwei (n0=2) ca. T ~ 5 * P0 * ( 4 / Fosc ) usw.

Anstatt "movlw 0xXX" kann auch "movf PauseX,0" angewendet werden, wenn die Schleife mehrmals mit verschiedenen Werten P0, P1 und P2 aus den Register Pause0, Pause1 und Pause2 benutzt wird.

Für sehr lange Wartezeiten können, nach gleichem Prinzip, Warteschleifen erstellt werden, die mehr als 3 Schleifen enthalten.

In zeitkritischen ASM Programmen werden anstatt Warteschleifen (z.B. für Tastenenentprellung) zusammengesetzte UPs mit benötigter Ausführungszeit (z.B. Frequenzmessung + Displayausgabe + ...) angewendet.

Tabellen

Es gibt zwei Arten von Tabellen: Sprungtabellen (computed goto) die "goto" Befehle enthalten und Wertetabellen (lookup table) in denen feste Werte in "retlw" gespeichert sind. Der wichtigste Unterschied zwischen dennen ist, dass die Sprungtabellen steuern den Programlauf abhängig vom Inhalt des W-Registers und die Wertetabellen liefern abhängig von Inhalt des W-Registers ein Wert an den Aufrufer zurück.

Beide werden in Programmspeicher erstellt. Sie können nur bis zu 256 Speicherstellen belegen, da in den W-Register auch nur so viel veschiedenen Zahlen "passen". Sie Fangen also (fast) immer bei einer Adresse XX00h an und enden bei XXFFh. Der Hochwertige Byte "XX" der Adresse an der sich der Anfang einer Tabelle befindet, muss vor dem Einsprung in die Tabelle ins PCLATH Register eingeschrieben werden, wenn die Tabelle weit vom Aufrufer liegt. In der Praxis werden solche Tabellen am oberen Ende des Programmspeichers angelegt, damit sie den ASM Code nicht unterbrechen.

Eine Sprungtabelle wird so aufgebaut:

                                org  (XX-1)FF <--- eine Direktive für Assemblerprogramm, wo es 
                                                   die Tabelle im Programmspeicher plazieren soll
                          Adresse     Inhalt
                          -------------------------                      
                Tab1     (XX-1)FF     addwf  PCL,1
                             XX00     goto   Marke0
                             XX01     goto   Marke1
                             .......................
                             XXFE     goto   Marke254
                             XXFF     goto   Marke255

Und so aufgerufen:

                             movlw    0xXX
                             movwf    PCLATH
                             movf     TWert,0
                             call     Tab1

Wobei:

0xXX = Hochwertiger Byte der Adresse von Tab1, TWert = ein Wert, der die Wahl wohin gesprungen wird bestimmt.

Nach ausführen der obiger Befehlsfolge, wird das ASM Programm z.B. für Twert=0x01 weiter ab Marke1 "laufen" bis es an "return" kommt. Dann springt es zurück zum Aufrufer der Tabelle.

Eine Sprungtabelle kann auch mit "goto" eingesprungen werden, dann wird aber beim Erreichen des "return"s bis zum letzten "call" zurückgesprungen. Siehe hierzu auch PIC Trainer.


Eine Wertetabelle wird so aufgebaut:

                                org  (XX-1)FF <--- eine Direktive für Assemblerprogramm, wo es 
                                                   die Tabelle im Programmspeicher plazieren soll
                          Adresse     Inhalt
                          -------------------------                      
                Tab1     (XX-1)FF     addwf  PCL,1
                             XX00     retlw  Wert0
                             XX01     retlw  Wert1
                             .......................
                             XXFE     retlw  Wert254
                             XXFF     retlw  Wert255

Und so aufgerufen:

                             movlw    0xXX
                             movwf    PCLATH
                             movf     TWert,0
                             call     Tab1

wobei:

0xXX = Hochwertiger Byte der Adresse von Tab1, TWert = ein Wert im W-Register, für welchen, an den Aufrufer bestimmter Wert aus der Tabelle im W-Register zurückgeliefert wird.

Wertetabelle kann auch mit Hilfe der MPASM Direktive "dt" erstellt werden. So kann man sich mehrfaches Schreiben von "retlw" Befehlen ersparen:

		org             0x(XX-1)FF
Tabelle         addwf  PCL,1 
                                                   ; nachfolgend der benötigte Wert im W- Register
                                                   ; vor dem Aufruf der Tabelle
	dt      0x03, 0xE8                         ; 0x00
	dt      0x03, 0xE8                         ; 0x02
	dt      'M','H','z',0x00                   ; 0x04
	dt      ' ',' ','W','A','I','T',0x00       ; 0x08
	dt      'C','o','n','s','t',' ','X',0x00   ; 0x0F
	dt      'C','=',0x00                       ; 0x17
	dt      'L','=',0x00                       ; 0x1A
	dt      'F','=',0x00                       ; 0x1D
	dt      'O','K',0x00                       ; 0x20
                                  usw.

Das Lesen eines Strings muss ab entsprechendem Wert im W-Register (z.B. für "MHz" -> 0x04) anfangen und auf dem Wert 0x00 enden, der hier als Stringende dient. Einfacher ist, wenn alle Strings gleich lang sind (wie z.B. in einem Zeichengenerator fur Grafikdisplay).

Interrupt

Prinzip

Ein Interrupt (Unterbrechung) unterbricht ein laufendes Programm, wenn ein bestimmtes Ereigniss, auf das reagiert werden soll, statt gefunden hat. Die Reaktion wird in s.g. Interrupt Service Routine (kurz: ISR) definiert.

Einfach gesagt, ein Interrupt stoppt ein laufendes Programm nach dem Ausführen der aktuellen Zeile, ruft die ISR auf und nach deren Ausführung startet das Programm wieder, ab der Zeile, vor der es durch Interrupt angehalten wurde.

                                               .--->V
                                               |   Tun
                                               |    V
                                  Interrupt ------>Tun-------->V
                                               |    V         ISR
                                               |   Tun<--------´
                                               |    V
                                               |   Tun
                                               |    V 
                                               `----´

Damit das unterbrochene Programm nach dem Rückkehr aus der ISR weiter fehlerfrei ausgeführt werden kann, darf die ISR keine Änderungen in vom Programm benutzten Register verursachen. Deswegen wenn UPs aus dem Programm durch ISR benutzt werden, müssen alle Register, dessen Inhalt durch ISR geändert wird, vor dem Ausführen der ISR (als ersten Befehle der ISR nach dem "bcf INTCON,GIE") im RAM gespeichert und nach der Ausführung der ISR (vorm "retfie") wiederhergestellt werden.

Um ein Interrupt auslösen zu können, muss er vorher, durch beschreiben nötigen Register (INTCON, PIR, usw.) vorbereitet werden. Siehe hierzu: INTCON und Interrupts

Quellen

Es gibt folgende Interrupt-Quellen:

- interne Hardware (z.B. ein Timer)

- externe Hardware (z.B. eine Taste)

Die alle PICs der Mid-Range Familie haben im Programmspeicher nur eine Adresse (s.g. Interrupt Vektor 0x0004), wohin im Falle eines Interrupts, gesprungen wird. Um ermöglichen den Verursacher des Interrupts zu finden, hat jede Quelle ein eigenes Interrupt Flag (IF), das gesetzt wird, wenn das Interrupt ausgelöst wird. Somit kann in der ISR nach der Prüfung der IFs auf jeden Interrupt gezielt reagiert werden. Siehe hierzu: Interrupts.

Interrupt Service Routine

Die Interrupt Service Routine ist ein besonderes UP das immer an der Adresse 0x0004 beginnt und immer mit dem Befehl "retfie" endet. Ihr Umfang ist von der Menge der Quellen und der Reaktionen auf ihre Interrupts abhängig. Folgender PAD zeigt ein allgemeiner Aufbau der ISR:

                                        org 0x0004
                                    Interrupts sperren            ; bcf   INTCON,GIE
                               Beeinflüsste Register sichern
                                Interrupt-Quelle ermitteln
                                  Interrupt-Flag löschen
                                und entsprechend reagieren
                            Beeinflüsste Register restaurieren
                               zurück ins Programm springen       ; retfie

Je nach Bedürfnissen, wird eine ISR nur nötige Elemente davon enthalten. Um auf alle Interrupts reagieren zu können muss die ISR vor dem nächsten Interrupt beendet werden. Da das Programm nur in den Pausen zwischen Ausführungen der ISR laufen kann, sollte die Ausführungszeit der ISR möglichst kurz sein.

Die kürzeste ISR setzt nur ein Flag ("_Fint"), das dem Programm zeigt, dass ein Interrupt statt gefunden hat. Sie eignet sich für den Fall, wenn nur eine Interrupt-Quelle erlaubt ist. Das Programm wird für nur 5 Prozessortakten unterbrochen.

                                        org     0x0004
                                        bcf     INTCON,GIE        ; Interrupts sperren
                                        bcf     IF                ; Interrupt Flag löschen
                                        bsf     _Fint             ; Flag setzen
                                        retfie                    ; zurück ins Programm

Das Programm prüft das Flag "_Fint", löscht es und reagiert entsprechend. Somit kann ein Interrupt im Hauptprogramm bearbeitet werden.

Die ISR kann aber auch ein HP darstellen. Siehe hierzu: Interrupts

Schnittstellen und Treiber

Als Schnittstelle wird externe Hadware, die zum Steuern eines an sie angeschlossenes "Gerätes" (z.B. eines Displays) dient, genannt. Das ASM Programmteil, das die Steuerung ermöglicht ist ein Treiber. Als Beispiele siehe: [[2]] und [3]

Vorlage für MPASM

Diese Vorlage ist nur für ASM Programme ohne Interrupts geeignet:

	list      P=12F629		; Prozessor definieren
	include "P12F629.inc"		; entsprechende .inc Datei für MPASM
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT  ; Konfiguration
#define	_DTT1	GPIO,0			; Portpins benennen
#define	_CKT2	GPIO,1
#define	_T3	GPIO,2
#define	_RNG	GPIO,3
#define	_INT	GPIO,4
#define	_RL	GPIO,5
        usw.
SecondL	equ	0x20			; Variablen definieren (Register benennen)
SecondH	equ	0x21
MinuteL	equ	0x22
MinuteH	equ	0x23
StundeL equ	0x24
StundeH equ	0x25
        usw.
		org 	0x0000		; bis da sind MPASM Direktiven
                                        ; hier fängt das gesamte ASM Programm an
                call	Init		; rufe UP "Init" (Initialisierung) auf
Haupt		............		; hier fängt das Hauptprogramm als endlose Schleife an
		Eigener Code
		............
		goto	Haupt		; hier endet das HP, gehe zum Anfang des HPs (zurück)
UP1		............		; Unterprogramme
		Eigener Code
		............
		return
		############
UPn		............
		Eigener Code
		............
		return
Init		clrf	GPIO		; lösche Port
		bsf	STATUS,RP0	; auf Bank1 umschalten
		call	0x3FF		; hole Kalibrationswert
		movwf	OSCCAL		; kalibriere internen RC oscillator
		bcf	OPTION_REG,7	; aktiviere pull-ups
		movlw	0x38		; definiere Portpins GPIO, (z.B. 0-2 Aus- und 3-5 Eingänge)
		movwf	TRISIO		; schreibe in TRIS Register
		bcf	STATUS,RP0	; auf Bank0 umschalten
		movlw	7		; schalte Komparator aus
		movwf	CMCON		; und definiere GPIO 0-2 als digitale I/Os
		............
		eigener Code
		............
		return			; springe zurück (zum Haupt)
                                        ; hier endet das gesamte ASM Programm 
                end			; MPASM Direktive

Die Variablen können auch kürzer mit s.g. cblock definiert werden:

cblock 0x20 
SecondL
SecondH
MinuteL
MinuteH
StundeL
StundeH
endc

Bei sehr vielen Variablen sind aber die Registeradressen nicht so übersichtlich.

Für Programme mit Interrupts ist diese Vorlage geeignet:

	list      P=12F629		; Prozessor definieren
	include "P12F629.inc"		; entsprechende .inc Datei für MPASM
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT  ; Konfiguration
#define	_DTT1	GPIO,0			; Portpins benennen
#define	_CKT2	GPIO,1
#define	_T3	GPIO,2
#define	_RNG	GPIO,3
#define	_INT	GPIO,4
#define	_RL	GPIO,5
        usw. 
SecondL	equ	0x20			; Variablen definieren (Register benennen)
SecondH	equ	0x21
MinuteL	equ	0x22
MinuteH	equ	0x23
StundeL equ	0x24
StundeH equ	0x25
        usw.
		org 	0x0000		; bis da sind MPASM Direktiven
                                        ; hier fängt das gesamte ASM Programm an
                call	Init		; rufe UP "Init" (Initialisierung) auf
                goto    Haupt           ; gehe zum Hauptprogramm "Haupt" 
                org     0x004           ; hier fängt die ISR (interrupt service routine) an
                bcf     INTCON,GIE      ; interrupts sperren
                .............
                Eigener Code
                .............
                retfie                  ; hier endet die ISR, interrupts wieder erlauben
Haupt		............		; hier fängt das Hauptprogramm als endlose Schleife an
		Eigener Code
		............
		goto	Haupt		; hier endet das HP, gehe zum Anfang des HPs (zurück)
UP1		............		; Unterprogramme
		Eigener Code
		............
		return
		############
UPn		............
		Eigener Code
		............
		return
Init		clrf	GPIO		; lösche Port
		bsf	STATUS,RP0	; auf Bank1 umschalten
		call	0x3FF		; hole Kalibrationswert
		movwf	OSCCAL		; kalibriere internen RC oscillator
		bcf	OPTION_REG,7	; aktiviere pull-ups
		movlw	0x38		; definiere Portpins GPIO, (z.B. 0-2 Aus- und 3-5 Eingänge)
		movwf	TRISIO		; schreibe in TRIS Register
		bcf	STATUS,RP0	; auf Bank0 umschalten
		movlw	7		; schalte Komparator aus
		movwf	CMCON		; und definiere GPIO 0-2 als digitale I/Os
		............
		eigener Code
		............
		return			; springe zurück (zum Haupt)
                                        ; hier endet das gesamte ASM Programm 
                end			; MPASM Direktive

Das erste...

Hier wird das ganze Prozess der Erstellung eines ASM Programms detailiert beschrieben. Die Idee:

Es gibt 4 LEDs (_L1, _L2, _L3 und _L4), die mit 2 Tastern gesteuert werden sollen. Nach dem Einschalten soll keine LED leuchten. Solange der linke Taster (_T1) gedrückt ist, sollte eine leuchtende LED von links nach rechts "wandern" und von der letzten rechten Position wieder nach ganz linke "springen". Solange der rechte Taster (_T2) gedrückt ist, sollte eine leuchtende LED von rechts nach links "wandern" und von der letzten linken Position wieder nach ganz rechte "springen". Solange beide Taster gedrückt sind soll die leuchtende LED von links nach rechts und zurück "wandern".

Dafür nötige Hardware zeigt folgende Skizze:

       .-----------------------------------------------.
       |                                               |
       |                   PIC12F629                   |
       |                                               |
       | GPIO,3  GPIO,4  GPIO,5  GPIO,2  GPIO,1  GPIO,0|
       '-----------------------------------------------'
          4|      3|      2|      5|      6|      7|
           |       |      .-.     .-.     .-.     .-.
           |       |    R | |   R | |   R | |   R | |
           |       |   470| |  470| |  470| |  470| |
           |       |      '-'     '-'     '-'     '-'
        \  o    \  o       |       |       |       |
         \       \         V ->    V ->    V ->    V ->
          \.      \.       -       -       -       -
       _T1 o   _T2 o   _L1 |   _L2 |   _L3 |   _L4 |
           |       |       |       |       |       |
           +-------+-------+---+---+-------+-------+
                               |
                              ===
                              GND

Weil der Pin 4 (GPIO,3) nur einen "open drain" Ausgang hat (kann nur an GND schalten), wird er hier als Eingang benutzt.

Jetzt muss die Idee vom Programmierer in ein PAD verfasst werden, z.B. solcher:

                              Start
                         Initialisierung
                .-------------->V
                |     _T1=0 gedrückt ? N >----.
                |               J             |
                |               V             |
                |   links->rechts "wandern"   |
                |               V<------------´
                |     _T2=0 gedrückt ? N >----.
                |               J             |
                |               V             |
                |   rechts->links "wandern"   |
                |               V<------------´
                `---------------´

danach detailierter:

                             Start
                         Initialisierung
                               V<-------------------------------------------.
                     _T1=0 gedrückt ? N >---+--->_T2=0 gedrückt ? N >---+---´
                               J            A              J            A
                               V            |              V            |
                             _L1 an         |            _L4 an         |
                             Warten         |            Warten         |
                             _L1 aus        |            _L4 aus        |
                             _L2 an         |            _L3 an         |
                             Warten         |            Warten         |
                             _L2 aus        |            _L3 aus        |
                             _L3 an         |            _L2 an         |
                             Warten         |            Warten         |
                             _L3 aus        |            _L2 aus        |
                             _L4 an         |            _L1 an         |
                             Warten         |            Warten         |
                             _L4 aus        |            _L1 aus        |
                               V            |              V            |
                               `------------´              `------------´

Weil das Assemblerprogram (z.B. MPASM) und der Prozessor (CPU) die Befehle nacheinander liest, ist jeder Quelcode einspaltig. Um sich die "Übersetzung" des PADs in den Quelcode zu erleichtern wird er noch z.B. so umgestaltet:

                             Start
                         Initialisierung
                               V<-----------------+<----.
         Haupt       _T1=0 gedrückt ? N >---.     A     | 
                               J            |     |     |
                               V            |     |     |
                             _L1 an         |     |     |
                             Warten         |     |     |
                             _L1 aus        |     |     |
                             _L2 an         |     |     |
                             Warten         |     |     |
                             _L2 aus        |     |     |
                             _L3 an         |     |     |
                             Warten         |     |     |
                             _L3 aus        |     |     |
                             _L4 an         |     |     |
                             Warten         |     |     |
                             _L4 aus        |     |     |
                               V<-----------´     |     |
         T2Test      _T2=0 gedrückt ? N >---------´     |
                               J                        |
                               V                        |
                             _L4 an                     |
                             Warten                     |
                             _L4 aus                    |
                             _L3 an                     |
                             Warten                     |
                             _L3 aus                    |
                             _L2 an                     |
                             Warten                     |
                             _L2 aus                    |
                             _L1 an                     |
                             Warten                     |
                             _L1 aus                    |
                               V                        |
                               `------------------------´

Alle Pfeilen aus diesem PAD werden als "goto" in den Quelcode eingetragen.

Zuerst wird im MPASM Verzeichnis ein neuer Verzeichniss (z,B. "PIC Programme") angelegt und in dem Verzeichniss neue Textdatei (z.B. "Erstes.txt") erstellt. Als nächstes wird die "Vorlage für MPASM" dorthin kopiert.

Danach müssen, die im Programm benutzte Portpins und Register mit "#define" und "equ" definiert werden.

Als Initialisierung wird das UP "Init" aus der Vorlage für MPASM mit geändertem "TRISIO" Wert (0x18) übernommen. Siehe: Vorlage für MPASM

Fürs "Warten" wird eine dreifache Warteschleife verwendet. Siehe: Pause

Die Taster werden mit "btfsc" Befehl geprüft und zum LEDs An- und Ausschalten werden Befehle "bsf" und "bcf" für ensprechenden Portpin von GPIO angewendet. Siehe: Einlesen und Ausgeben

Anschlessend wird alles laut PAD in die Vorlage für MPASM eingeschrieben und der Quellcode ist fertig:

;       Erstes.asm
	list      P=12F629             ; Prozessor definieren
	include "P12F629.inc"          ; entsprechende .inc Datei für MPASM
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT  ; Konfiguration 
#define 	_T1	GPIO,3         ; Portpins benennen
#define 	_T2	GPIO,4
#define 	_L1	GPIO,5
#define 	_L2	GPIO,2
#define 	_L3	GPIO,1
#define 	_L4	GPIO,0
P0 	equ	0x20                   ; Variablen definieren (Register benennen)
P1 	equ	0x21
P2 	equ	0x22
 	org 	0x0000                 ; bis da sind MPASM Direktiven
                                       ; hier fängt das gesamte ASM Programm an
         call    Init                  ; rufe UP "Init" (Initialisierung) auf
Haupt    btfsc   _T1                   ; Taster _T1=0 gedrückt ?, wenn ja, überspringe "goto T2Test"
         goto    T2Test                ; wenn nicht, springe zu "T2Test"
         bsf     _L1                   ; schalte _L1 an  (setze den Pin 2 auf "1")
         call    Warten                ; warte ca. 0,4 s (ca. 400 000 Prozessortakten)
         bcf     _L1                   ; schalte _L1 aus (setze den Pin 2 auf "0")
         bsf     _L2                   ; schalte _L2 an  (setze den Pin 5 auf "1")
         call    Warten                ; warte
         bcf     _L2                   ; schalte _L2 aus (setze den Pin 5 auf "0")
         bsf     _L3                   ; schalte _L3 an  (setze den Pin 6 auf "1")
         call    Warten                ; warte
         bcf     _L3                   ; schalte _L3 aus (setze den Pin 6 auf "0")
         bsf     _L4                   ; schalte _L4 an  (setze den Pin 7 auf "1")
         call    Warten                ; warte
         bcf     _L4                   ; schalte _L4 aus (setze den Pin 7 auf "0")
T2Test   btfsc   _T2                   ; Taster _T2=0 gedrückt ?, wenn ja, überspringe "goto Haupt"
         goto    Haupt                 ; wenn nicht, springe zu "Haupt"
         bsf     _L4                   ; schalte _L4 an  (setze den Pin 7 auf "1")
         call    Warten                ; warte
         bcf     _L4                   ; schalte _L4 aus (setze den Pin 7 auf "0")
         bsf     _L3                   ; schalte _L3 an  (setze den Pin 6 auf "1")
         call    Warten                ; warte
         bcf     _L3                   ; schalte _L3 aus (setze den Pin 6 auf "0")
         bsf     _L2                   ; schalte _L2 an  (setze den Pin 5 auf "1")
         call    Warten                ; warte
         bcf     _L2                   ; schalte _L2 aus (setze den Pin 5 auf "0")
         bsf     _L1                   ; schalte _L1 an  (setze den Pin 2 auf "1")
         call    Warten                ; warte
         bcf     _L1                   ; schalte _L1 aus (setze den Pin 2 auf "0")
         goto    Haupt                 ; springe zu "Haupt"
Warten   movlw   2                     ; schreibe "2" für ca. 0,4 s
         movwf   P2                    ; ins Register P2
         clrf    P1                    ; lösche Register P1 ("0" für 256 Durchläufe) 
         clrf    P0                    ; lösche Register P0 ("0" für 256 Durchläufe)
         decfsz  P0,1                  ; dekrementiere P0 und überspringe "goto $-1" bei P0 = 0
         goto    $-1                   ; sonst gehe zur voherigen Adresse (Befehl "decfsz  P0") 
         decfsz  P1,1                  ; dekrementiere P1 und überspringe "goto $-4" bei P1 = 0 
         goto    $-4                   ; sonst gehe zur aktueller Adresse - 4 (Befehl "clrf P0")
         decfsz  P2,1                  ; dekrementiere P2 und überspringe  "goto $-7" bei P2 = 0
         goto    $-7                   ; sonst gehe zur aktueller Adresse - 7 (Befehl "clrf P1")
         return                        ; springe zurück zum Aufrufer ("Haupt"),
Init     clrf 	 GPIO 	               ; lösche Port (setze alle werdende Ausgänge auf "0")
	 bsf     STATUS,RP0            ; auf Bank1 umschalten
	 call 	 0x3FF	    	       ; hole Kalibrationswert (als "retlw" unter Adresse 0x3FF)
	 movwf	 OSCCAL                ; und kalibriere internen RC oscillator (4 MHz)
	 bcf  	 OPTION_REG,7          ; aktiviere pull-ups für die Portpins
	 movlw	 0x18                  ; definiere Portpins GPIO,3 und 4 als Eingänge
                                       ; und restlichen als Ausgänge (00011000b)
	 movwf	 TRISIO                ; schreibe in TRIS Register
	 bcf	 STATUS,RP0            ; auf Bank0 umschalten
	 movlw   7                     ; schalte Komparator aus
	 movwf   CMCON                 ; und definiere GPIO 0-2 als digitale I/Os
	 return                        ; springe zurück (zum Haupt) 
                                       ; hier endet das gesamte ASM Programm 
         end                           ; MPASM Direktive

Jetzt wird der Quellcode "Erstes.txt" in "Erstes.asm" umbenannt. Diese "*.asm" Datei können wir dem MPASM zum "Übersetzten" geben. Nach der Assemblierung sehen wir 3 Meldungen (Messages) vom MPASM. Diese befinden sich in der vom MPASM erstellter Datei "Erstes.lst" und lauten:

Message[302]: Register in operand not in bank 0.  Ensure that bank bits are correct.

Diese Meldung kann man übersetzen als:

Meldung[302]: Register im Befehl nicht in Bank 0. Vergewisse Dich, dass die Bank-Bits korrekt sind.

Wir sind sicher, dass die Register "OSCCAL", "OPTION_REG" und "TRISIO" in der Bank 1 sich befinden. Wenn man aber nicht sicher ist, soll man im Datenblatt nachschauen. Die Meldungen kommen immer für alle SFRs (Special Function Register) die sich nicht in der Bank 0 befinden und können ignoriert werden.

Am Ende der Datei "Erstes.lst" befinden sich noch einige Informationen über Programmspeicher:

Program Memory Words Used:    52       ; benutzte Speicherstellen
Program Memory Words Free:   972       ; freie Speicherstellen

und die Anzahl den eventuellen Fehlern (Errors), Warnungen (Warnings) und Meldungen (Messages):

Errors   :     0
Warnings :     0 reported,     0 suppressed
Messages :     3 reported,     0 suppressed

Insgesamt werden durch MPASM 4 Dateien erstellt: "*.cod", "*.err", "*.hex" und "*.lst".

Wichtigste davon, falls wegen Fehler keine "*.hex" Datei erstellt wird, ist die "*.err" Datei, wo alles was dem MPASM nicht "passt" aufgelistet ist. In der "*.lst" Datei kann man sich ein Programm mit genauen Adressen für jeden Befehl anschauen.

Die während der Assemblierung vom MPASM generierte Datei "Erstes.hex" kann jetzt durch ein Brenner mit geeignetem Programm in den PIC Programmspeicher eingeschrieben werden.

Für anderen PIC umschreiben

Die wichtigste Vorraussetzung ist, das der andere PIC2, auf dem das vorhandene ASM Programm für PIC1 laufen soll, zumindest für das ASM Programm nötige interne Hardware hat. Die Frequenz des Oszillators sollte gleich sein. Der Code benötigt ausser UP "Init" keine Änderungen.

Wenn der benutzte Port vom PIC2 anderen Namen hat, muss man das im Quellcode umdefinieren, z.B.:

#define   PORTC      PORTB
#define   TRISC      TRISB

Dann wird das Assemblerprogramm, wenn es PORTC findet, immer PORTB nehmen. Das gleiche Betrifft die "__config" Ausdrücke, die entsprechend der "*.ini" Datei für den PIC2, geändert werden müssen.

Das Assemblerprogramm findet sicher alles, was ihm nicht "passt" und bringt Fehlermeldungen, auf die man entsprechend reagieren muss.

Als Beispiel, schreiben wir "Erstes.asm" für den PIC16F84A um.

Zum Ändern sind die ersten 3 Zeilen:

	list      P=12F629		  ; Prozessor definieren
	include "P12F629.inc"		  ; entsprechende *.inc Datei für MPASM
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT ; Konfiguration

Sie werden für den PIC16F84A so aussehen:

	list      P=16F84A		  ; Prozessor definieren
	include "P16F84A.inc"             ; entsprechende *.inc Datei für MPASM 
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC        ; Konfiguration

Dazu muss noch "GPIO" Port umdefiniert werden, da der PIC16F84A solchen nicht hat, z.B. wegen internen pull-ups auf "B":

#define   GPIO      PORTB
#define   TRISIO    TRISB

Die Hardware muss natürlich an entsprechende Pins angeschlossen werden:

_T1 auf 12 (B3), _T2 auf 13 (B4), _L1 auf 14 (B5), _L2 auf 11 (B2), _L3 auf 10 (B1) und _L4 auf 9 (B0)

Jetzt assemblieren wir die Qelldatei und der MPASM bringt in der Datei "Erstes.err" folgende Fehler:

Error[113]   C:\MPASM\PICPROG\PIC16F84\ERSTES.ASM 61 : Symbol not previously defined (OSCCAL)
Error[113]   C:\MPASM\PICPROG\PIC16F84\ERSTES.ASM 67 : Symbol not previously defined (CMCON)

In dem UP "Init" müssen also noch die Befehle mit "OSCCAL" und "CMCON" Register entfernt werden, da der PIC16F84A sie nicht hat:

          call 	0x3FF 	     	; hole Kalibrationswert
          movwf	OSCCAL          ; kalibriere internen RC oscillator (4 MHz)

und:

          movlw   7    	        ; schalte Komparator aus
          movwf   CMCON        	; und mache GPIO 0-2 als digital I/O

Weiter kann schon alles übernommen werden und die Datei "Erstes.asm" für den PIC16F84A ist fertig:

;       Erstes.asm    
 	list      P=16F84A	       ; Prozessor definieren
 	include "P16F84A.inc"          ; 4.000 MHz
 	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
#define GPIO    PORTB                  ; Ports umbenennen
#define TRISIO  TRISB
#define	_T1	GPIO,3		       ; Portpins benennen
#define	_T2	GPIO,4
#define	_L1	GPIO,5
#define	_L2	GPIO,2
#define	_L3	GPIO,1
#define	_L4	GPIO,0
P0	equ	0x20                   ; Variablen definieren (Register benennen)
P1	equ	0x21
P2	equ	0x22
         org 	0x0000                 ; bis da sind MPASM Direktiven
                                       ; hier fängt das gesamte ASM Programm an
         call    Init                  ; rufe UP "Init" (Initialisierung) auf
Haupt    btfsc   _T1                   ; Taster _T1=0 gedrückt ?, wenn ja, überspringe "goto T2Test"
         goto    T2Test                ; wenn nicht, springe zu "T2Test"
         bsf     _L1                   ; schalte _L1 an
         call    Warten                ; warte ca. 0,4 s (ca. 400 000 Prozessortakten)
         bcf     _L1                   ; schalte _L1 aus 
         bsf     _L2                   ; schalte _L2 an
         call    Warten                ; warte
         bcf     _L2                   ; schalte _L2 aus
         bsf     _L3                   ; schalte _L3 an
         call    Warten                ; warte
         bcf     _L3                   ; schalte _L3 aus
         bsf     _L4                   ; schalte _L4 an
         call    Warten                ; warte
         bcf     _L4                   ; schalte _L4 aus
T2Test   btfsc   _T2                   ; Taster _T2=0 gedrückt ?, wenn ja, überspringe "goto Haupt"
         goto    Haupt                 ; wenn nicht, springe zu "Haupt"
         bsf     _L4                   ; schalte _L4 an
         call    Warten                ; warte
         bcf     _L4                   ; schalte _L4 aus
         bsf     _L3                   ; schalte _L3 an
         call    Warten                ; warte
         bcf     _L3                   ; schalte _L3 aus
         bsf     _L2                   ; schalte _L2 an
         call    Warten                ; warte
         bcf     _L2                   ; schalte _L2 aus
         bsf     _L1                   ; schalte _L1 an
         call    Warten                ; warte
         bcf     _L1                   ; schalte _L1 aus
         goto    Haupt                 ; springe zu "Haupt"
Warten   movlw   2                     ; schreibe "2" für ca. 0,4 s
         movwf   P2                    ; ins Register P2
         clrf    P1                    ; lösche Register P1 ("0" für 256 Durchläufe) 
         clrf    P0                    ; lösche Register P0 ("0" für 256 Durchläufe)
         decfsz  P0,1                  ; dekrementiere P0 und überspringe "goto $-1" bei P0 = 0
         goto    $-1                   ; sonst gehe zur voherigen Adresse (Befehl "decfsz  P0") 
         decfsz  P1,1                  ; dekrementiere P1 und überspringe "goto $-4" bei P1 = 0 
         goto    $-4                   ; sonst gehe zur aktueller Adresse - 4 (Befehl "clrf P0")
         decfsz  P2,1                  ; dekrementiere P2 und überspringe  "goto $-7" bei P2 = 0
         goto    $-7                   ; sonst gehe zur aktueller Adresse - 7 (Befehl "clrf P1")
         return                        ; springe zurück zum Aufrufer ("Haupt"),
Init     clrf 	GPIO	               ; lösche Port (setze alle werdende Ausgänge auf "0")
 	 bsf    STATUS,RP0  	       ; auf Bank1 umschalten
 	 bcf  	OPTION_REG,7           ; aktiviere pull-ups
         movlw	0x18                   ; definiere Portpins GPIO,3 und 4 als Eingänge
                                       ; und die restlichen als Ausgänge (00011000b) 
 	 movwf	TRISIO	               ; schreibe in TRIS Register
 	 bcf	STATUS,RP0  	       ; auf Bank0 umschalten
 	 return	                       ; springe zurück (zum Haupt), nach der Zeile
                                       ; aus der du aufgerufen wurdest 
                                       ; hier endet das gesamte ASM Programm 
         end                           ; MPASM Direktive

Man kann auch die Portpins neu definieren und das UP "Warten" für z.B. 20 MHz Quarz anpassen:

;       Erstes.asm
 	list      P=16F84A		; Prozessor definieren
 	include "P16F84A.inc"		; 20.000 MHz
 	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
#define	_T1	PORTB,5		        ; Portpins benennen
#define	_T2	PORTB,4
#define	_L1	PORTB,3
#define	_L2	PORTB,2
#define	_L3	PORTB,1
#define	_L4	PORTB,0
P0	equ	0x20			; Variablen definieren (Register benennen)
P1	equ	0x21
P2	equ	0x22
	   org 	0x0000			; bis da sind MPASM Direktiven
					; hier fängt das gesamte ASM Programm an
          call	  Init			; rufe UP Init (Initialisierung) auf
Haupt     btfsc   _T1			; Taster T1 gedrückt ist, wenn ja, überspringe "goto T2Test"
          goto    T2Test		; wenn nicht, springe zu T2Test
          bsf     _L1			; schalte _L1 an
          call    Warten		; warte ca. 0,4 s (2 000 000 Prozessortakten)
          bcf     _L1			; schalte _L1 aus
          bsf     _L2			; schalte _L2 an
          call    Warten		; warte
          bcf     _L2			; schalte _L2 aus
          bsf     _L3			; schalte _L3 an
          call    Warten		; warte
          bcf     _L3			; schalte _L3 aus
          bsf     _L4			; schalte _L4 an
          call    Warten		; warte
          bcf     _L4			; schalte _L4 aus
T2Test    btfsc   _T2			; Taster T2 gedrückt ist, wenn ja, überspringe "goto Haupt"
          goto    Haupt		        ; Wenn nicht, springe zu Haupt
          bsf     _L4			; schalte _L4 an
          call    Warten		; warte
          bcf     _L4			; schalte _L4 aus
          bsf     _L3			; schalte _L3 an
          call    Warten		; warte
          bcf     _L3			; schalte _L3 aus
          bsf     _L2			; schalte _L2 an
          call    Warten		; warte
          bcf     _L2			; schalte _L2 aus
          bsf     _L1			; schalte _L1 an
          call    Warten		; warte
          bcf     _L1			; schalte _L1 aus
          goto    Haupt		        ; springe zu Haupt
Warten    movlw   0x0A			; schreibe "0x0A" für ca. 0,4 s
          movwf   P2			; ins Register P2
          clrf    P1			; lösche Register P1 (schreibe "0" für 256 Durchläufe) 
          clrf    P0			; lösche Register P0 (schreibe "0" für 256 Durchläufe)
          decfsz  P0,1			; dekrementiere P0 und überspringe "goto $-1" bei P0 = 0
          goto    $-1			; sonst gehe zur voherigen Adresse (Befehl "decfsz  P0") 
          decfsz  P1,1			; dekrementiere P1 und überspringe "goto $-4" bei P1 = 0 
          goto    $-4			; sonst gehe zur aktueller Adresse - 4 (Befehl "clrf P0")
          decfsz  P2,1			; dekrementiere P2 und überspringe "goto $-7" bei P2 = 0
          goto    $-7			; sonst gehe zur aktueller Adresse - 7 (Befehl "clrf P1")
          return			; springe zurück zum Aufrufer ("Haupt"),
Init      clrf    PORTB       		; lösche Port (setze alle werdende Ausgänge auf "0")
 	  bsf     STATUS,RP0		; auf Bank1 umschalten
 	  bcf     OPTION_REG,7    	; aktiviere pull-ups
          movlw   0x30                  ; definiere PortB: Pins 5 und 4 als Eingänge
                                        ; und die restlichen als Ausgänge (00011000b)
 	  movwf	  TRISB   		; schreibe in TRIS Register
 	  bcf     STATUS,RP0		; auf Bank0 umschalten
      	  return	                ; springe zurück (zum Haupt), nach der Zeile
                                        ; aus der du aufgerufen wurdest 
					; hier endet das gesamte ASM Programm 
          end				; MPASM Direktive

In dem Fall müssen natürlich die Taster und LEDs an für sie definierte Portpins angeschlossen werden.

Einbinden

Die einfachste Möglichkeit die gewünschte Programme in ein neues Programm einzubinden ist, die alle Programme in das neue Programm zu kopieren. Dafür müssen die ersten 3 Zeilen, die den Prozessor und die Konfiguration des PICs festlegen aus den eigebundenen Programmen entfernt werden. Danach müssen alle "#define", "equ" und "org" Direktiven, verglichen und wenn nötig geändert werden. Dabei hilft der MPASM mit seinen Fehlermeldungen in der Datei "*.err".

Die zweite Möglichkeit ist die gewünschte Programme als "*.asm" Dateien mit der Direktive "include *.asm" am Ende des neuen Programms vor der Direktive "end" einzubinden. Die alle Programme die zusammen gebinden werden sollen, müssen sich wegen Assemblerprogramm in einem Verzeichniss befinden.

                                        include "1.asm" 
                                        include "2.asm"
                                        ...............
                                        include "n.asm" 
                                        end 

In dem Fall gelten die gleichen o.g. Regeln wie beim direkten Kopieren. Wenn es in den eingebindenen Programmen keine "org" Direktiven gibt, dann werden die weitere Programme im Programmspeicher ab Ende des ersten Programms nacheinander plaziert.

Fehlersuche

Als erstes wird an den PIC angeschlossene externe Hardware geprüft. Das sollte noch vor dem Einstecken oder Einlöten des PICs gemacht werden, da die gravierende Fehler (z.B. Kurzschlüsse, Umpolung von VCC und GND, usw.) für den PIC schädlich werden können. Für einige Teile der Hardware kann entsprechende Spannung (VCC oder GND) an bestimmten Pin gelegt werden und die richtige Reaktion der Hardware optisch (z.B. für LEDs) oder akustisch (z.B. für Relais) festgestellt werden. Sonst müssen die elektrischen Verbindungen mit einem Messgerät überprüft werden. Danach kann man den PIC einstecken bzw. einlöten.

Die Fehlersuche in der Software fängt mit der ersten und endet mit der letzten Zeile des ASM Programms. Sie läuft die ganze Zeit parallel zum Programmschreiben. Jedes UP sollte vor dem Übertragen ins Programm geprüft und eventuelle Fehler beseitigt werden. Dazu arbeitet man gleichzeitig mit zwei "*.asm" Dateien, einer für die UPs und einer für das gesamte Programm. Es wird empfohlen, nach der "Initialisierung", als erstes UP, das für Ausgabegerät (z.B. Display) zu erstellen, um das funktionieren des Programms, vom Anfang an beobachten zu können.

Für die Fehlersuche in "hängenden" Programmen ist der PIC RAM Monitor bzw. PIC Trainer unersetzlich. Weil sie durch Interrupt das "hängende" Programm unterbrechen und auf dem "PIC Miniterminal" Registerinhalte während der Unterbrechung zeigen, können alle in der endlosen Schleife sich befindliche Register schnell festgestellt werden, da nur dessen Inhalte sich ständig ändern.

Wenn aus geprüften Unterprogrammen erstelltes Programm nicht richtig funktioniert, müsste ein Fehler im PAD des Hauptprogramms sein.

Optimierung

Work in Progress... Wer sonst noch Beispiele hat, bitte hier vervollständigen... BMS ...

Speicherbedarf

Programmspeicher

Die ersten Programme für den PIC sind natürlich einfach und kurz. Doch nach und nach werden die Codes länger und unübersichtlicher, das Brennen dauert auch umso länger. Das Programm ist zu umfangreich. Doch wo soll man etwas kürzen?

Es gibt unzählige Möglichkeiten - hier ein paar Beispiele:

Unterprogramme

Bestimmt wird man im Code immer die selben Abschnitte brauchen, zum Beispiel Warteschleifen oder ADC-Routinen, etc. Wieso sollte man diese also mehrfach (doppelt, dreifach, u.s.w.) schreiben?

Die Lösung: Der Programmteil wird als Unterprogramm benutzt. Man erstellt eine Sprungmarke (ab hier beginnt das Unterprogramm) und endet wieder mit einem "return"- oder "retlw"-Befehl. Aufgerufen werden die Unterprogramme meistens mit dem "call"-Befehl. Es "lohnt sich" ein Codefragment als UP zu definieren erst wenn es min. 2 mal aufgerufen wird.

Ein "goto"-Befehl ist zu diesem Zweck geeignet nur, wenn der Prozessor zum letzten Aufrufer von "call" zurück springen soll, da die Rücksprungadresse vom letzten "call" auf dem Stapel (stack) abgelegt wurde. Somit kann man mehr als 8 Ebenen von UPs nutzen.

Den Unterprogrammen kann man natürlich auch Werte übergeben. Möchte man z.B. mehrere unterschiedliche Zeiten für Warteschleifen benutzen, so kann man die Parameter vor dem Aufruf des UPs in benötigte Anzahl von Register einschreben und dann das Unterprogramm mit "call" aufrufen. Das Unterprogramm verwendet dann die Zahlen von den Register. Siehe: Pause

Um gleiche Vorgänge in mehreren Register durchzuführen (z.B. löschen) geeignet ist Anwendung von Schleifen mit bestimmter Anzahl der Abläufe und indirekter Adressierung. Siehe: Variablen

bsf und bcf

Bestimmt gibt es Situationen, in denen der PIC mehrere Bits an einem Port/Register setzen oder löschen muss. Z.B. wenn mehrere LEDs an einem Port ein- oder ausgeschaltet werden sollen. Werden mehr als 2 Bits verändert, so kann man hier die "bsf"- und "bcf"-Befehle umgehen und einen kürzeren Code erstellen.

bsf PORTB,0 ;An PORTB sind 8 LEDs angeschlossen.
bsf PORTB,1 ;Es sollen die ersten 4 LEDs angeschaltet werden,
bsf PORTB,2 ;die anderen sollen ausgeschaltet werden.
bsf PORTB,3 ;links steht es mit bcf/bsf
bcf PORTB,4 ;ziemlich lang
bcf PORTB,5
bcf PORTB,6
bcf PORTB,7

movlw b'00001111' ;Das geht auch kürzer und übersichtlicher:
movwf PORTB

Man sieht - der Codeabschnitt umfasst nur noch 2 Zeilen anstatt 8. Somit braucht es weniger Speicher und wird von PIC schneller verarbeitet. Allerdings muss man aufpassen, da durch diese Routine der Inhalt des W-Registers und der Inhalt des gesamten Zielregisters verändert wird. Sollen die anderen Bits unangetastet bleiben, muss man es entweder bei den "bcf"/"bsf"-Befehlen belassen oder logische Funktionen (and bzw. or) durchführen.

Bit kopieren
;An den PIC ist ein Taster(RB0) und eine LED(RB1) angeschlossen.
;Taster: High=gedrückt  Low=nicht gedrückt
;LED:    High=Ein       Low=Aus
;Wenn der Taster gedrückt ist, soll die LED leuchten
;wenn er nicht gedrückt ist, soll die LED nicht leuchten.
;1.Vorschlag:
      btfss PORTB,0 ;ist der taster gedrückt?
      goto no       ;nein
      bsf PORTB,1   ;ja - LED ein
      goto endif    ;fertig abgefragt - unten weitermachen...
no    bcf PORTB,1   ;LED aus
endif

Der oben aufgeführte Code hat viele Nachteile: er ist relativ lang, braucht zwei Gotos und entspr. Sprungmarken. Ok, das ist vielleicht Erbsenzählerei, aber aus diesen 5 Zeilen kann man 3 machen.

bcf PORTB,1   ;Das Bit wird erst gelöscht
btfsc PORTB,0 ;Taster gedrückt?
bsf PORTB,1   ;JA-LED an

Man geht davon aus, dass der Taster nicht gedrückt ist. Somit wird die LED erst einmal ausgeschaltet. Ist der Taster aber doch gedrückt, dann wird die LED angeschaltet.

Achtung möglicher Nebeneffekt: Wird dies in einer Schleife sehr schnell und oft ausgeführt, kann die LED flimmern oder nicht in ihrer vollen Helligkeit leuchten. Das passiert, weil ja angenommen wird, dass der Taster nicht gedrückt wird und die LED somit aus sein muss. Dann wird sie aber bei Tastendruck eingeschaltet. Somit entsteht ein schnelles ein-/ausschalten (PWM) und die LED leuchtet nicht mehr so hell. Meist ist das aber nicht tragisch oder wird nicht unbedingt in einer Schleife sehr schnell abgefragt. Siehe hierzu: Einlesen

RAM

Um Anzahl benötigten Register zu sparen werden vorläufige Register (temporary) definiert, die von mehreren UPs benutzt werden. Als Beispiel, das Register "Tmp" in Matrix Display oder "ATmp" in Hex Dec Wandlung. Die Benutzung muss aber sehr genau nach PAD zugeteilt werden. Bei gleichzeitiger Benutzung des gleichen Registers durch mehrere UPS werden falsche Daten verarbeitet und im schlimmsten Fall können endlose Schleifen entstehen, die in Folge haben, dass das Programm nicht weiter ausgeführt wird ("hängt").

Viele im ASM Programm ungebrauchte SFRs können als "normale" Register (GPR) verwendet werden. Wenn zum Beispiel Timer1 nicht benutzt wird, können seine Register TMR1H und TMR1L für Speicherung von Daten benutzt werden. Es könnte aber gefährlich werden, wenn mehrere Programme gebindet sind. Deswegen muss es immer am Ende, beim volständigen Programm, geprüft werden, welche SFRs tatsächlich benutzt werden dürfen.

Für Konstanten (z.B. pi, ln2, Meldungen, usw.), die sich im Laufe des Programms nicht ändern, kann EEPROM (mit MPASM Direktive "de") oder Programmspeicher (mit MPASM Direktive "dt") als Ablage benutzt werden. Siehe auch: EEPROM und Tabellen

Ausführungszeit

Alles was schnellmöglichst ablaufen soll, widersetzt sich der Optimierung des Programmspeichers.

Es können keine Schleifen und keine indirekte Adressierung benutzt werden, alles muss direkt programmiert werden.

Als Beispiel zur Verdeutlichung: Es sollen 16 Werte vom PORTA ins RAM ab Adresse 0x20 eingelesen werden.

Als Schleife mit indirekter Adressierung:

					................
					movlw	0x10            ;Anzahl Durchläufe
					movwf	Temp		;in den Schleifenzähler
					movlw	0x20		;erste Adresse
					movwf	FSR             ;ins FSR Register
			Einlesen	movf	PORTA,0         ;PortA ins W-Register
					movwf	INDF            ;und ins aktuellen RAM Register
					incf	FSR,1           ;nächste Adresse
					decfsz	Temp,1          ;Temp = 0 ?
					goto	Einlesen        ;nein, zum Einlesen
					................        ;ja, weiter 
					Gesamtdauer: 100 Takten
					Pause zwischen Einlesungen: 6 Takten
                                        Programmspeicher Bedarf: 9 Speicherstellen 

Direkt:

					...............
					movf	PORTA,0
					movwf	0x20
					movf	PORTA,0
					movwf	0x21
					movf	PORTA,0
					movwf	0x22
					movf	PORTA,0
					movwf	0x23
					movf	PORTA,0
					movwf	0x24
					movf	PORTA,0
					movwf	0x25
					movf	PORTA,0
					movwf	0x26
					movf	PORTA,0
					movwf	0x27
					movf	PORTA,0
					movwf	0x28
					movf	PORTA,0
					movwf	0x29
					movf	PORTA,0
					movwf	0x2A
					movf	PORTA,0
					movwf	0x2B
					movf	PORTA,0
					movwf	0x2C
					movf	PORTA,0
					movwf	0x2D
					movf	PORTA,0
					movwf	0x2E
					movf	PORTA,0
					movwf	0x2F
					...............
					Gesamtdauer: 32 Takten
					Pause zwischen Einlesungen: 2 Takten
                                        Programmspeicher Bedarf: 32 Speicherstellen

Anstatt Warteschleifen (z.B. für Tastenentprellung) sollten Programmfragmente mit entsprechender Ausführungszeit benutzt werden (z.B. Ausgabe auf einem Display). Die nötige Zeit lässt sich aus mehreren UPs zusammensetzen.

Es sollte kein Prozessortakt ungenutzt bleiben.

Mid-Range

Zu Mid-Range gehören alle PICs mit 14-bit langen Befehlen aus den Familien 12FXXX und 16FXXX.

Kurzübersicht Assembler Befehle

ADDLW Add literal and W
ADDWF Add W and f
ANDLW AND literal with W
ANDWF AND W with f
BCF Bit Clear f
BSF Bit Set f
BTFSC Bit Test f, Skip if Clear
BTFSS Bit Test f, Skip if Set
CALL Call subroutine
CLRF Clear f
CLRW Clear W
CLRWDT Clear Watchdog Timer
COMF Complement f
DECF Decrement f
DECFSZ Decrement f, Skip if 0
GOTO Go to address or label
INCF Increment f
INCFSZ Increment f, Skip if 0
IORLW Inclusive OR literal with W
IORWF Inclusive OR W with f
MOVF Move f
MOVLW Move literal to W
MOVWF Move W to f
NOP No Operation
RETFIE Return from interrupt
RETLW Return with literal in W
RETURN Return from Subroutine
RLF Rotate Left f through Carry
RRF Rotate Right f through Carry
SLEEP Go into standby mode
SUBLW Subtract W from literal
SUBWF Subtract W from f
SWAPF Swap nibbles in f
XORLW Exclusive OR literal with W
XORWF Exclusive OR W with f

Kurzübersicht zum Ausdrucken

Ausführliche Beschreibung zu den Befehlen

Fast alle Befehle werden in einem Takt = 4 Oszillatortakten ausgeführt.

Aussnahmen:

"goto", "call", "return" und "retfie" benötigen wegen Zugriff auf den Programmzähler (PC) immer 2 Takten.

"btfsc", "btfss", "decfsz" und "incfsz" brauchen 1 Takt wenn der nächste Befehl ausgeführt wird bzw. 2 Takten wenn er überspringen wird, weil es immer anstatt des überspringenden Befehls ein "nop" ausgeführt wird.

Eine ausgegliederte Beschreibung der Befehle findet sich unter PIC Assemblerbefehle

Erklärungen zu den Verwendeten Platzhaltern:

  • k stellt einen fest definierten Wert da. z.B. 0x20, d'42' oder b'00101010'
  • W steht für das W-Register.
  • d steht für destination (Ziel). Im code wird d durch ein w bzw. 0 (der Wert wird in das W-Register gespeichert ) oder f bzw. 1 (der Wert wird in das davor definierte Register gespeichert)
  • b steht für Bitnummer im Register (eine Zahl zwischen 0 und 7)
  • R steht für ein Register
  • fett geschrieben Bedeutet, dass es ein Platzhalter ist und im Quellcode durch eine Registeradresse oder einen Wert ersetzt werden muss
  • Schreibmaschinenstil bedeutet, dass es so im Quellcode geschrieben werden kann.
ADDLW k ADD Literal and W - Addiere Zahl (k) und W
Es wird die Rechenoperation [math]W+k[/math] ausgeführt und das Ergebniss in das W-Register gespeichert. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
ADDWF R,d ADD W and F - Addiere W und f
Es wird die Rechenoperation [math]W+R[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
ANDLW k AND Literal with W
Es wird bitweise die logische Funktion [math]k\ and\ W[/math] ausgeführt und das Ergebniss in das W-Register gespeichert. Dieser Befehl setzt das Z bit des STATUS-Register, falls W=k und das Ergebnis 0 ist.
Zur Verdeutlichung der Operation:
11001010  ;k 10101100  ;W-Register -------- and 10001000  ;W-Register
ANDWF R,d AND W with F
Es wird bitweise die logische Funktion [math]W\ and\ R[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Vergleiche ANDLW. Dieser Befehl wird zum Löschen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflüssen.
BCF R,b Bit Clear F - Bit b im R wird gelöscht
Mit dem Befehl BCF wird das Bit b im Register R gelöscht. Ein Beispiel:
movlw b'11111111'  ;es wird b'11111111' in das W-Register geschrieben BCF W,2  ;es wird bit 2 im W-Register gelöscht.  ;das Ergebnis ist: b'11111011'
</dd> BSF R,b Bit Set F - Bit b im R wird gesetzt
Mit dem Befehl BSF wird das Bit b im Register R gesetzt. Ein Beispiel:
clrw  ;es wird b'00000000' in das W-Register geschrieben BSF W,2  ;es wird bit 2 im W-Register gesetzt.  ;das Ergebnis ist: b'00000100'
</dd> BTFSC R,b Bit Test F, Skip if Clear - Wenn das Bit b im Register R 0 ist, überspringe den nächsten Befehl
Mit dem Befehl BTFSC kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit b im Register R 0 ist, wird der nächste Befehl übersprungen. Ein Beispiel:
movlw b'00000001'  ;es wird die Zahl 1 in das W-Register kopiert. BTFSC W,0  ;es wird bit 0 geprüft.  ;wenn es 0 ist, wird der nächste Befehl übersprungen goto IST_EINS  ;springt zur Marke "IST_EINS" <- in diesem Fall wird dieser  ;Sprungbefehl ausgeführt. goto IST_NULL  ;springt zur Marke "IST_NULL"
</dd> BTFSS R,b Bit Test F, Skip if Set - Wenn das Bit b im Register R 1 ist, überspringe den nächsten Befehl
Mit dem Befehl BTFSS kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit b im Register R 1 ist, wird der nächste Befehl übersprungen. Ein Beispiel:
movlw b'00000001'  ;es wird die Zahl 1 in das W-Register kopiert. BTFSS W,0  ;es wird bit 0 geprüft.  ;wenn es 1 ist, wird der nächste Befehl übersprungen goto IST_NULL  ;springt zur Marke "IST_NULL" goto IST_EINS  ;springt zur Marke "IST_EINS" <- in diesem Fall wird dieser  ;Sprungbefehl ausgeführt, da der Befehl  ;darüber übersprungen wurde.
</dd>


CALL CALL Subroutine - Rufe Unterprogramm auf
Mit dem CALL Befehl wird ein Unterprogramm aufgerufen. Mit RETURN oder RETLW-Befehl wird das Unterprogramm beendet und man kehrt zum Befehl nach dem CALL-Befehl zurück. Das Unterprogramm wird so definiert, dass im Quellcode der Name des Unterprogramms nicht eingerückt steht. Ein Beispiel:
movlw d'13'  ;in das W-Register wird 13d geladen CALL UP1  ;es wird das Unterprogramm "UP1" aufgerufen movwf ergebnis  ;das W-Register wird in das Register "ergebnis" kopiert.  ;im Register "ergebnis" steht nun 23d UP1 addlw d'10'  ;es wird 10d zum W-Register addiert return  ;kehre zurück zum Aufrufer
</dd> CLRF R CLeaR F- Schreibe 0 in das Register R
Das Register R wird mit Nullen gefüllt (gelöscht).
CLRW CLeaR W - Schreibe 0 in W
Das W-Register wird mit Nullen gefüllt (gelöscht). Der Befehl bewirkt das gleiche wie MOVLW 0 und darf im Programmen, die fur zukünftige Anwendung bei den PICs der serie 18F vorgesehen sind, nicht benutzt werden, da sie ihn nicht kennen.
CLRWDT CLeaR WatchDog Timer - Setzt den Watchdog-Timer zurück
Es wird der WDT (Watchdog-Timer) zurückgesetzt und der Zähler des WDT auf 0 gesetzt, zusätzlich werden die STATUS-bits TO und PD gesetzt.
COMF R,d COMplement F - negiere alle bits im Register R
Von der Binärzahl im Register R werden alle 0 mit 1 und alle 1 mit 0 ersetzt. Das Ergebnis wird entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Ein kleines Beispiel: aus AAh (10101010b) wird 55h (01010101b).
DECF R,d DECrement F - Subtrahiert 1 vom Regiser f
Vom Wert des Registers R wird 1 subtrahiert und das Ergebnis entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
Weil es keine "echte" Substraktion ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht.
DECFSZ R,d DECrement F, Skip if Zero - Subtrahiert 1 vom Regiser f, überspringe wenn 0
Vom Wert des Registers R wird 1 subtrahiert und das Ergebnis entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Der Zusatz SZ steht für skip if zero, d.h. wenn das Ergebnis der Rechnung Null ist, wird der nächste Befehl übersprungen. Dieser Befehl wird für Schleifen mit bestimmter Anzahl der Durchläufe benutzt.
GOTO GO TO address - Gehe zu Adresse/Sprungmarke
Nach dem GOTO Befehl wird das Programm ab der Adresse weiter ausgeführt, die nach dem GOTO-Befehl steht. Diese Adresse wird durch so genannte Sprungmarke definiert, welche, im Gegensatz zu den Befehlen nicht eingerückt im Quellcode stehen.
INCF R,d INCrement F - Addiere 1 zum Register f
Zum Wert des Registers R wird 1 addiert und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
Weil es keine "echte" Addition ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht.
INCFSZ R,d INCrement F, Skip if Zero - Addiere 1 zum Regiser f, überspringe wenn 0
Zum Wert des Registers R wird 1 addiert und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Der Zusatz SZ steht für skip if zero, d.h. wenn das Ergebnis der Rechnung Null ist, wird der nächste Befehl übersprungen. Dieser Befehl wird für Schleifen mit bestimmter Anzahl der Durchläufe benutzt.
IORLW k Inclusive OR Literal with W
Es wird bitweise die logische Funktion [math]k\ or\ W[/math] ausgeführt und das Ergebniss in das W-Register gespeichert. Dieser Befehl setzt das Z bit des STATUS-Register, falls W=k und das Ergebnis 0 ist.
Zur Verdeutlichung der Operation:
11001010  ;k 10101100  ;W-Register -------- or 11101110  ;W-Register
IORWF R,d Inclusive OR W with F
Es wird bitweise die logische Funktion [math]W\ or\ R[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Vergleiche IORLW. Dieser Befehl wird oft zum Setzen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflüssen.
MOVF R,d MOVe F - Bewege f
Das Register R wird in das W-Register (d=W=0) oder wieder in R kopiert (d=F=1). Letzteres mag sinnlos scheinen, ist aber nützlich, da durch den Befehl das Z-Bit im STATUS-Register gesetzt wird, falls R Null ist.
MOVLW k MOVe Literal to W - Bewege Zahl (k) in W-Register
Der festgelegte Wert k wird in das W-Register kopiert.
Dieser Befehl wird auch bei indirekter Adressierung zum laden der absoluter Adresse ins "FSR" Register benutzt. Beispiel:
Der Register "Temp" wurde mit "Temp equ 0x20" definiert.
MOVLW Temp  ;ins W-Register wird die absolute Adresse 0x20 vom "Temp" geladen movwf FSR  ;diese Adresse wird jetzt ins Register "FSR" kopiert
MOVWF R MOVe W to F- Bewege W-Register in das Register F
Das W-Register wird in das Register R kopiert.
NOP No OPeration - Kein Befehl zum Ausführen (warte)
Dieser Befehl macht nichts. Er verbraucht nur Zeit, welche sich einfach mit folgender Formel berechnen lässt. [math]t=\frac{4}{f}[/math],wobei [math]f[/math] für die Frequenz des Oszillators steht. Siehe hierzu: Pause
RETFIE RETurn From Interrupt Enable - Kehre zurück aus der Unterbrechung, erlaube Unterbrechungen
Mit diesem Befehl wird die Interrupt Service Routine (ISR) beendet und das Programm wird an der Zeile weiter ausgeführt, vor der es durch den Interrupt angehalten wurde. Es werden auch alle Interrupts wieder erlaubt (das GIE bit wird gesetzt). Siehe hierzu auch Interrupt
RETLW k RETurn with Literal in W - Kehre zurück mit Zahl k im W-Register
Wurde ein Programmteil mit dem Befehl CALL aufgerufen, dann springt man mit dem Befehl RETLW zurück in die nächste Zeile nach der Zeile aus der das CALL Befehl ausgeführt wurde. Der in k angegebene Wert wird dabei in das W-Register geschrieben. Dies wird benutzt um zu erkennen aus welchem UP das verzweigte Programm zurückkommt. Dieser Befehl wird aber vor allem für s.g. Wertetabellen (eng: lookup tables) verwendet.
RETURN RETURN from Subroutine - Kehre zurück zum Aufrufer
Wurde ein Programmteil mit dem Befehl CALL aufgerufen, dann springt man mit dem Befehl RETURN zurück zu der nächsten Zeile nach der Zeile aus der das CALL Befehl ausgeführt wurde.
RLF R,d Rotate Left F through carry - Rotiere das Register f mithilfe des Carry-bits nach links
Alle Bits im Register R werden um eine Position nach links verschoben. Dabei wird das Carry bit (STATUS,C) in das Bit 0 des Registers R geschoben. Bit 7 aus dem Register R wird in das Carry bit "geschoben". Das Ergebnis wird entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
|<--- Register R --->| |c|<-7<-6<-5<-4<-3<-2<-1<-0 V A |________________________|
Zur Verdeutlichung:
|C| |-Register R-| ;C steht für das Carry-bit, STATUS,C ; 0-7 stehen für Bitnummer im Register. c 7 6 5 4 3 2 1 0 ;vor der Rotation 7 6 5 4 3 2 1 0 c ;nach der Rotation
RRF R,d Rotate Right F through carry - Rotiere das Register f mithilfe des Carry-bits nach rechts
Alle Bits im Register R werden um eine Position nach rechts verschoben. Dabei wird das Carry bit (STATUS,C) in das 7.Bit des Registers R geschoben. Bit 0 aus dem Register R wird in das Carry bit "geschoben". Das Ergebnis wird entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
|<--- Register R --->| |c|->7->6->5->4->3->2->1->0 A V |________________________|
Zur Verdeutlichung:
|C| |-Register R-| ;C steht für das Carry-bit, STATUS,C ; 0-7 stehen für Bitnummer im Register. C 7 6 5 4 3 2 1 0 ;vor der Rotation 0 C 7 6 5 4 3 2 1 ;nach der Rotation
SLEEP Go into standby mode - Versetze den Mirokontroller in Bereitschaftsmodus (Schlaf)
Der µC wird in den Sleep-Mode versetzt, in dem er weniger Strom verbraucht. Er kann durch einen Reset, einem Watchdog-Timer-Reset oder durch einen Interrupt wieder aufgeweckt werden.
SUBLW k SUBtract W from Literal - Ziehe W von Zahl (k) ab
Es wird die Rechenoperation [math]k-W[/math] ausgeführt und das Ergebniss in das W-Register gespeichert. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
SUBWF R,d SUBtract W from F - Ziehe W von f ab
Es wird die Rechenoperation [math]R-W[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
Beispiel:
movlw d'20'  ;schreibe 20 in das W-Register movwf Register1  ;bewegt das W-Register in das Register1 movlw d'10'  ;schreibt 10 in das W-Register SUBWF Register1,F ;schreibt Register1(20)-W(10) in Register1
SWAPF R,d SWAP nibbles in F - Vertausche die Halbbytes (Nibbles)
Es werden die Nibbles, also die höheren 4 bits (bit7-bit4) mit den niedrigeren 4 bits (bit3-bit0) eines Registers vertauscht und entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
Beispiel:
movlw b'00001111' ;schreibe b'00001111' in das W-Register movwf Register1  ;kopiert das W-Register in das Register1 SWAPF Register1,W ;vertauscht die ersten 4 bit mit den letzen  ;4 bit in Register 1 und schreibt es in das W-Register  ;im W-Register steht nun b'11110000'
XORLW k EXclusive OR Literal with W
Es wird bitweise die logische Funktion [math]k\ xor\ W[/math] ausgeführt und das Ergebniss in das W-Register gespeichert. Dieser Befehl setzt das Z bit des STATUS-Registers, falls W=k und das Ergebnis 0 ist.
Zur Verdeutlichung der Operation:
11001010  ;k 10101100  ;W-Register -------- xor 01100110  ;W-Register
XORWF R,d EXclusive OR W with F
Es wird bitweise die logische Funktion [math]W\ xor\ R[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Vergleiche XORLW. Dieser Befehl setzt das Z bit des STATUS-Registers, falls W=k und das Ergebnis 0 ist und wird zum Vergleichen zwei Register benutzt.

Besondere, oft gebrauchte Register

STATUS

Der Statusregister beinhaltet den Status der Recheneinheit ALU (Arithmetic-Logic Unit), Resetinformationen und die beiden Bits zur Wahl der Speicherbank

STATUS (ADDRESSE 03h, 83h, 103h, 183h)
R/W-0 R/W-0 R/W-0 R-1 R-1 R/W-x R/W-x R/W-x
IRP RP1 RP0 TO PD Z DC C
Bit7 Bit0


  • Bit 7 IRP: Register Bank Select Bit (für indirekte Adressierung)
1 = Bank 2, 3 (100h-1FFh)
0 = Bank 0, 1 (00h-FFh)
  • Bit 6-5 RP<1:0>: Register Bank Select Bits (für direkte Adressierung)
11 = Bank 3 (180h-1FFh)
10 = Bank 2 (100h-17Fh)
01 = Bank 1 (80h-FFh)
00 = Bank 0 (00h-7Fh)
  • Bit 4 TO: Time-out Bit
1 = Nach Power-up, CLRWDT Befehl oder SLEEP Befehl
0 = A Watchdogtimer time-out ist eingetreten
  • Bit 3 PD: Power-Down Bit
1 = Nach Power-up oder durch den CLRWDT
0 = Nach einem SLEEP befehl
  • Bit 2 Z: Zero bit
1 = Das Ergebnis einer arithmetischen oder logischen Operation ist 0
0 = Das Ergebnis einer arithmetischen oder logischen Operation ist NICHT 0
  • Bit 1 DC: Digit carry/borrow bit (ADDWF, ADDLW, SUBLW und SUBWF Befehle)
1 = Ein Carry-out des 4.Niedrigsten Bits (Low Nibble) eines Rechenergebnisses existiert
0 = Kein Carry-out des 4.Niedrigsten Bits eines Rechenergebnisses existiert
  • Bit 0 C: Carry/borrow Bit (ADDWF, ADDLW, SUBLW und SUBWF Befehle)
1 = Ein Carry-out des MSB eines Rechenergebnisses existiert
0 = Kein Carry-out des MSB eines Rechenergebnisses existiert

Das "Carry/Borrow Bit" (to carry = etwas übertragen, to borrow = etwas borgen) dient zum erkennen, wenn ein Übertrag einer Rechenoperation exisitiert. 250d+10d ergibt zum Beispiel 4, und setzt dabei das "Carry/Borrow Bit" auf 1. Damit kann das Programm erkennen, wenn wieder einmal ein Ergebnis größer als 255d herauskam. Bei Subtraktionen (SUBLW und SUBWF) verhält sich das "Carry/Borrow Bit" umgekehrt als bei Additionen (ADDWF und ADDLW)!! Zum Beispiel 55d-6=49d setzt "Carry/Borrow Bit" auf 1 aber 10d-25d=241d löscht das "Carry/Borrow Bit".

Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Registers

Auswirkungen auf das STATUS-Register bei Subtraktionen
Ergebnis STATUS,C STATUS,Z
positiv 1 0
negativ 0 0
Null 1 1
Auswirkungen auf das STATUS-Register bei Additionen
Ergebnis STATUS,C STATUS,Z
positiv 0 0
Überlauf 1 0
Null 1 1

OPTION_REG

Durch OPTION_REG werden PORTB pull-ups, aktive Flanke des externen Interrupts, der Prescaler und der Timer0 konfiguriert.


OPTION_REG (ADDRESSE 81h, 181h)
R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-x
/RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0
Bit7 Bit0


  • Bit 7 /RBPU: PORTB Pull-up Enable bit ("/" bedeutet Negation)
1 = Pull-ups sind deaktiviert
0 = Pull-ups sind aktiviert für die Pins, die im Register TRISB als Eingänge definiert sind
  • Bit 6 INTEDG: Interrupt Edge Select bit
1 = Interrupt auf steigende Flanke am RB0/INT pin
0 = Interrupt auf fallende Flanke am RB0/INT pin
  • Bit 5 T0CS: Timer0 Clock Source Select bit
1 = Wechsel am RA4/T0CKI pin
0 = Internes Taktsignal (Fosc/4), wo Fosc ist gleich der Frequenz des Oszillators (z.B. Quarz)
  • Bit 4 T0SE: TMR0 Source Edge Select bit
1 = Inkrementiere bei fallender Flanke am RA4/T0CKI pin
0 = Inkrementiere bei steigender Flanke am RA4/T0CKI pin
  • Bit 3 PSA: Prescaler Assignment bit
1 = Prescaler ist dem WDT zugewiesen
0 = Prescaler ist dem Timer0 zugewiesen
  • Bit 2-0 PS2:PS0: Prescaler Rate Select bit

Je nach Zuweisung des Prescalers (durch PSA bit) wird die interne Taktfrequenz des Prozessors (Fosc/4) wie folgt geteillt:

PS2:PS0 TMR0 WDT
000 1:2 1:1
001 1:4 1:2
010 1:8 1:4
011 1:16 1:8
100 1:32 1:16
101 1:64 1:32
110 1:128 1:64
111 1:256 1:128

PORTx

PORTx
R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-x
Rx7 Rx6 Rx5 Rx4 Rx3 Rx2 Rx1 Rx0
Bit7 Bit0

!Das "x" steht in allen Fällen für den Buchstaben des Ports! BSP: "PORTB" oder "RA1"

Diese Special Funktion Register liegen ALLE in Bank0 und Bank2 (Falls vorhanden).

Jedes Bit eines PORT-Registers steht für einen Pin (Ein/Ausgang) des jeweiligen Ports. Je nachdem Ob ein Pin als Ausgang oder als Eingang definiert ist, kann man dort entweder den Wert schreiben oder auslesen. Es hat nicht Jeder PIC gleich viele Ports und es sind auch nicht immer 8 Pins pro Port, es können auch weniger sein. Alle PICs der Midrange-Serie besitzen nur bidirektionale Ports, d.h. sie können sowohl Eingang als auch Ausgang sein. Bei Port B kann man bei allen Pins einen internen PULL-UP Widerstand mit dem Bit OPTION_REG<RBPU> hinzuschalten (Standardmäßig auf nicht aktiviert, Bit muss gelöscht werden um die Funktion zu aktivieren). Weiters ist zu beachten, dass einzelne Pins nach dem Reset mit anderen Funktionen belegt sein können. Das gilt im Speziellen für PORTA, wo sich die Komperatoren, AD's (falls vorhanden) und die Oszillatoreingänge befinden. Siehe I/O Ports. Bei den "kleinsten" PICs der 12F Serie die nur einen I/O Port (6 Pins) haben, heisst der PORT-Register GPIO.

Beispiel PICs und ihre Ports (Zahlen in der Klammer sind die Anzahl der Pins des Ports)
PIC16F628A PIC16F876 PIC16F877
PORTA Ja Ja(5) Ja(6)
PORTB ja ja ja
PORTC / ja ja
PORTD / / ja
PORTE / / ja (3)

TRISx

TRISx
R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-x
TRISx7 TRISx6 TRISx5 TRISx4 TRISx3 TRISx2 TRISx1 TRISx0
Bit7 Bit0

!Das "x" steht in allen Fällen für den Buchstaben des Ports! BSP: "TRISB" oder "TRISA"

Bei den "kleinsten" PICs der 12F Serie die nur einen I/O Port (6 Pins) haben, heisst der TRIS-Register TRISIO.

Die TRIS-Bytes dienen dazu, einzelne I/O Pins als Eingang oder Ausgang zu definieren. Sie liegen immer genau eine Bank über den jeweiligen PORT-Bytes, d.h. sie liegen alle in Bank1 und Bank3 (falls vorhanden.) Besonders zu beachten ist, dass manche Eingebaute Features wie die Serielle Schnittstelle, I2C, SPI usw nicht funktionieren, wenn die Jeweiligen Sende und Empfangspins nicht manuell umgestellt werden, ja es muss sogar manchmal umgestellt werden (bei I2C z.B.) Egal ist dies jedoch für Komperatoren und AD-Wandler.

Merke:

  • Eine 1 (als "I" für "Input" zu lesen) eines TRISxx-Bits definiert den zugehörigen PIN am PIC als Eingang.
  • Eine 0 (als "O" für "Output" zu lesen) eines TRISxx-Bits definiert den zugehörigen PIN am PIC als Ausgang.

Siehe I/O Ports.

INTCON

INTCON (ADDRESSE 0Bh, 8Bh, 10Bh, 18Bh)
R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-0 R/W-x
GIE PEIE TMR0IE INT0IE RBIE TMR0IF INT0IF RBIF
Bit7 Bit0


  • Bit 7 GIE: Global Interrupt Enable Bit
1 = Aktiviert alle Interrupts
0 = Deaktiviert alle Interrupts
  • Bit 6 PEIE: Peripheral Interrupt Enable Bit
1 = Aktiviert alle peripheren Interrupts
0 = Deaktiviert alle peripheren Interrupts
  • Bit 5 TMR0IE: TMR0 Overflow Interrupt Enable Bit (Overflow von Timer0 löst Interrupt aus)
1 = Aktiviert den TMR0 Interrupt
0 = Deaktiviert den TMR0 Interrupt
  • Bit 4 INT0IE: RB0/INT External Interrupt Enable Bit (Änderung an RB0 Pin löst Interrupt aus) *)
1 = Aktiviert den RB0/INT Interrupt (Extern ausgelöst)
0 = Deaktiviert den RB0/INT Interrupt (Extern ausgelöst)
  • Bit 3 RBIE: RB Port On-Change-Interrupt Enable Bit (Änderung an PortB löst Interrupt aus) **)
1 = Aktiviert den RB port on-change-interrupt
0 = Deaktiviert den RB port on-change-interrupt
  • Bit 2 TMR0IF: TMR0 Overflow Interrupt Flag Bit
1 = TMR0 Register ist Übergelaufen (muss per Software gelöscht werden)
0 = TMR0 register ist nicht Übergelaufen
  • Bit 1 INT0IF: RB0/INT External Interrupt Flag Bit
1 = Eine Änderung an RB0/INT Pin hat stattgefunden (muss per Software gelöscht werden)
0 = Eine Änderung an RB0/INT Pin hat nicht stattgefunden
  • Bit 0 RBIF: RB Port On-Change-Interrupt Flag bit
1 = Mind. einer der Pins von RB7:RB4 hat sich geändert (muss per Software gelöscht werden)
0 = Keiner der Pins von RB7:RB4 hat sich geändert


  • *) Die Flanke auf die reagiert werden soll, wird mit dem Bit OPTION_REG,INTEDG definiert. (steigende = 1 oder fallende = 0)
  • **) Der Interrupt klingt verführerischer Weise so, als ob er den gesamten Port überwacht. Dabei reagiert er nur auf RB7:RB4!!!! Er kann aber den Prozessor vom "Schlaf" aufwecken (z.B. durch einen Tastendruck).

Speicherbankorganisation

Programmspeicher

Die Mid-Range MCUs haben einen 2-8k großen Programmspeicher. Dieser hat aber in jeder Speicherzelle nicht 8, sondern 14 Bit - also genau die Länge eines Befehls. Die aktuelle Stelle im Programm wird im PC (Program Counter) verwaltet. Er speichert immer die aktuelle Adresse im Programmspeicher (wird im Code oft als "$" bezeichnet). Bei einem PIC mit 8k Adressen muss er also die Adressen 0000-1FFF speichern können. Daraus folgt die Größe von 13 Bit für den PC. Der Programmspeicher ist in mehrere Bänke geteilt, die alle 2k groß sind. Das Programm springt ohne zutun des Benutzers von einer in die Nächste. Wenn man aber selber springen will, muss man die Register PCLATH (Program Counter Latch High) und PCL (Program Counter Least Significant Byte) mit der Sprungadresse beschreiben.

Datenspeicher

Der Datenspeicher besteht aus den Special Function Registern (SFR) und den General Purpose Registern (GPR). Die SFRs sind für die Funktionen des PICs zuständig (Interrupts, Timer, ADCs, CCPM...) und die GPRs für die Speicherung von Variablen und Daten.

Da immer nur 7 Bit der (Ziel)Adresse in einem Befehl gespeichert werden können, sind nur 7Fh (128d) Adressen im Datenbereich möglich. Deswegen wurde das "Banking" eingeführt. 2 Bit im Statusregister (welcher in allen Bänken der selbe ist und auch an der gleichen Stelle sitzt) geben die akutelle "Bank" an und sind nichts anderes als die 2 höchstwertigsten (MSB) Bits der Adresse. Damit lassen sich max. 4 Bänke ansprechen. Je nach PIC gibt es 2-4 Bänke. Die beiden Bits im Register STATUS heißen RP0 (STATUS<5>) und RP1 (STATUS<6>).

Wechseln der Bänke mit RP0 und RP1
RP1 RP0
Bank0 0 0
Bank1 0 1
Bank2 1 0
Bank3 1 1

PIC midrange register.JPG

  1. FETTE Register sind in allen PICs vorhanden
  2. können je nach PIC unimplementierte Bereiche beinhalten - diese werden immer als 0 gelesen. (DATENBLATT!!)
  3. siehe 2
  4. Könnten je nach PIC auch nicht in Bank0 gemapped werden, sind dann eigenständige Register.
  5. je nach PIC kann es diese Bänke geben oder nicht geben.


Codeschnipsel

Analog-Digital-Wandler (ADC)

Viele PICs besitzen einen Analog-Digital-Wandler, der eine angelegte Spannung messen kann und diese als Zahl speichert.

Es gibt AD-Wandler mit 8Bit bzw. 10Bit Auflösung, d.h. die Spannung an einem analogen Eingang wird linear 2^8=256 bzw. 2^10=1024 Werten zugeordnet. Der höchste Wert, auch Referenzspannung genannt, (255 bzw. 1023; der Controller rechnet die Null auch mit) entspricht der Betriebsspannung des PICs oder wahlweise einer wählbaren Spannung, die an RA3 angelegt werden muss. Der niedrigste Wert entspricht 0 Volt.

Mit dem Analog-Digital-Wandler kann man somit z.B. Sensoren auswerten, die mehr als zwei Zustände ausgeben (Beispiele: Temperatursensor, Helligkeitssensor, Entfernungssensoren usw.) oder Akkuspannungen messen.


Bevor überhaupt gemessen werden kann, muss einiges eingestellt werden:

  • Eingangsverteilung Analog/Digital bzw.Referenzspannung (Betriebsspannung oder RA3)

die genauen Einstellungsmöglichkeiten entnehmen sie bitte dem Datenblatt ihres Controllers (Register ADCON1 Bank 1).

  • Wählen des Taktes für den ADC und Einschalten des ADCs

Das Register ADCON0 besitzt 8 Bits, die folgendes bestimmen:

Bit0 ADON - schaltet AD-Wandler ein oder aus (Strom sparen)
Bit1 - nicht vorhanden
Bit2 GO/Done - startet Wandlung / signalisiert, wann Umwandlung beendet ist.
Bit3 CHS0 - Channel Select 0 wählt Eingang aus
Bit4 CHS1 - Channel Select 1 wählt Eingang aus
Bit5 CHS2 - Channel Select 2 wählt Eingang aus
Bit6 ADCS0 - AD-Clock-Select wählt Takt
Bit7 ADCS1 - AD-Clock-Select wählt Takt

Kümmern wir uns zunächst um den Takt. Mit den zwei Bits ADCS0 und ADCS1 bestimmt man den Takt - hierbei gibt es vier Möglichkeiten:

ADCS1=0, ADCS0=0 -> Taktfrequenz des PICs :2
ADCS1=0, ADCS0=1 -> Taktfrequenz des PICs :8
ADCS1=1, ADCS0=0 -> Taktfrequenz des PICs :32
ADCS1=1, ADCS0=1 -> interner RC-Oszillator des ADCs verwenden

Die Taktfrequenz des ADCs darf 625kHz nicht überschreiten, dementsprechend ist ADCS0 und ADCS1 zu wählen.

Ist dies vollbracht, so kann man den AD-Wandler einschalten und das Go/Done-Bit löschen. Codebeispiel:

bcf ADCON0,6 ;Takt für
bsf ADCON0,7 ;ADC (OSC/32)
bsf ADCON0,0 ;ADC einschalten, ADON=1
bcf ADCON0,2 ;Go/Done-Bit zurücksetzen

Nun ist der AD-Wandler bereit, Spannungen in Zahlen umzuwandeln. Für eine Wandlung muss erst der entsprechende Eingang gewählt werden. Dafür sind die CHS0..CHS2-Bits zuständig:

CHS2 CHS1 CHS0  gewählter Eingang(falls vorhanden)
 0    0    0     RA0
 0    0    1     RA1
 0    1    0     RA2
 0    1    1     RA3
 1    0    0     RA5
 1    0    1     RE0
 1    1    0     RE1
 1    1    1     RE2
Achtung! RA4 ist kein analoger Eingang, folglich ist er oben nicht aufgelistet !!

Codebeispiel:

bcf ADCON0,3 ;RA2 auswählen
bsf ADCON0,4
bcf ADCON0,5

Als letztes muss die AD-Routine nur noch gestartet werden. Ist der AD-Wandler fertig, so löscht er das Bit wieder. Das Bit wird nun solange abgefragt, bis der AD-Wandler fertig ist. Den zugeordneten Wert findet man im Register 'ADRES'. Code:

   bsf ADCON0,2   ;start
wa btfsz ADCON0,2 ;warten,bis es wieder low ist
   goto wa        ;noch nicht fertig
                  ;jetzt ist er fertig
   movf ADRES,W   ;ADRES in W verschieben, um damit gleich weiter zu arbeiten

Die AD-Wandlung ist somit fertig. Liest man mehrere Werte von unterschiedlichen Eingängen ein und möchte diese miteinander vergleichen o.ä., sollte man die Zahlen von ADRES in anderen Registern zwischenspeichern.

Desweiteren ist zu beachten, dass der Analog-Digital-Wandler eine gewisse Pause zwischen den Wandlungen benötigt, die sog. 'Aquisition Time'. Diese Zeitangabe entnehmen sie bitte ihrem Datenblatt.

mögliche Probleme:


Der PIC liefert stark schwankende Werte
-> Mögliche Ursachen dafür sind z.B. nicht stabile Betriebsspannung, falsche Zeiteinstellungen. Abhilfe schafft auch das mehrmalige Einlesen eines Eingangs. Auch können z.B. 8 Werte eingelesen werden und dann der Durchschnitt gebildet werden.

Evtl. kann ein Kondensator gegen Masse (z.B. 1nF) helfen; Wichtig ist auch eine kleine Verzögerung zwischen Kanalauswahl und Start der Wandlung. In dieser Zeit kann sich der interne Kondensator des ADC's aufladen.


Die Spannung an einem Eingang wird erhöht, die Werte der anderen Eingänge werden aber auch beeinflusst.
-> Dann sind Sie Opfer des sog. 'Ghostings' geworden (Werte 'scheinen durch', verschleißen). Die Werte der anderen Eingänge gehen mit, weil der interne Kondensator noch auf die Spannung des Vorgängers aufgeladen ist. Maßnahme: Aquisition Time erhöhen, Werte öfters abfragen, Pause zwischen Kanalauswahl und Start


Der PIC liefert immer die gleichen Werte an einem Eingang, obwohl sich die angelegte Spannung ändert
-> möglicherweise falsche Eingangsverteilung (ADCON1) eingestellt oder falschen Pin gewählt

Hex Dec Wandlung

Das ist ein Unterprogramm das mit "call Hex_Dec" aufgerufen wird. Es gibt 4 Register (A, B, C und D) mit jeweils 32 Bits, die aus jeweils 4 Register mit 8 Bits (z.B. fur A: A3, A2, A1 und A0) bestehen.

       A3       A2       A1       A0
   .--------.--------.--------.--------.
   |76543210|76543210|76543210|76543210|
   '--------'--------'--------'--------'
   |                                   |
   |<----------- A Register ---------->|

Sie müssen (wegen FSR) so wie im Code nacheinander liegen (die absoluten Adressen sind nicht wichtig). In das Register "A" wird die hex Zahl eingeschrieben und aus dem "D" die umgewandelte dec Zahl ausgelesen. A3,7 und D3,7 sind MSB und A0,0 und D0,0 sind LSB. Die "B" und "C" sind Hifsregister. Das UP kann für kleinere als 32-bittige hex Zahlen modifiziert werden. Zum Beispiel für 16-bittige hex Zahlen bleiben nur Register A1,A0,B1,B0,C1,C0,D1,D0 und in den UPs werden in die Register und als X, folgende Zahlen eingeschrieben: HTmp -> 0x10 (Anzahl Bits der hex Zahl), ATmp -> 2 = X (Anzahl Bytes der hex Zahl). Es müssen auch die UPs "CClr", "DClr" und "CopyCB" entsprechend der Registerlänge angepasst werden. Genauso kann natürlich das UP auch für längere hex Zahlen modifiziert werden.

Das UP "AddCB" addiert dezimal die 32- bittige Register C und B, das Ergebniss befindet sich in C. Das UP "AddDC" addiert dezimal die 32-bittige Register D und C, das Ergebniss befindet sich in D.

Die 32-bit Additionen werden in 4 Schritten wie folgt realisiert:

C0+B0->C0, C1+B1->C1, C2+B2->C2 und C3+B3->C3

D0+C0->D0, D1+C1->D1, D2+C2->D2 und D3+C3->D3

Die Flags "_Fcra", "_Fcrp" und "_Fdca" steuern die alle nötigen Korrekturen.

Die Wandlung wird in 32 Schritten laut folgender Formel durchgeführt:

Zahl(d)=B0*2^0+B1*2^1+B2*2^2+B3*2^3+B4*2^4+B5*2^5+B6*2^6+B7*2^7+B8*2^8+B9*2^9+B10*2^10+B11*2^11+

B12*2^12+B13*2^13+B14*2^14+B15*2^15+B16*2^16+B17*2^17+B18*2^18+B19*2^19+B20*2^20+B21*2^21+

B22*2^22+B23*2^23+B24*2^24+B25*2^25+B26*2^26+B27*2^27+B28*2^28+B29*2^29+B30*2^30+B31*2^31

Wobei B0 bedeutet den LSB und B31 den MSB der hex Zahl.

Die dec Zahl wird im D Register durch "AddDC" gebildet. In dem C Register befindet sich immer die laufende 2^X. Am Anfang wird dort 1=2^0 geladen. Da die nächste zu addierende dec Zahl immer gleich 2* vorherige ist, wird sie einfach so berechnet, dass die aktuelle aus C Register ins B Register kopiert ("CopyCB") und zu C addiert ("AddCB") wird. Diese dec Zahl wird zum D Register addiert nur wenn das entsprechende Bit der hex Zahl gleich 1 ist. Weil im UP "Hex_Dec" immer das bit A0,0 dafür geprüft wird, wird das gesamte 32-bittige A Register nach jeder Addition "AddDC" um ein Bit mit "ARotRb" nach rechts rotiert. Die Flags "_Fcra" und "_Fcrp" übertragen die Bits zwischen den 8 Bit Registern A0, A1, A2 und A3. Es würde zwar reichen, wenn das A Register nur verschoben würde, aber hier wurde Rotation gewählt, damit nach der Wandlung die hex Zahl im A Register unverändert steht. Weil die dec Zahl nur 8-stellig ist, darf die max. hex Zahl 99999999d = 5F5E0FFh sein.

#define	_C	STATUS,C
#define	_Z	STATUS,Z
#define	_DC	STATUS,DC
#define	_Fcra	Flags,0
#define	_Fcrp	Flags,1
#define	_Fdca	Flags,2
#define	_Ferr	Flags,3                         ;Überlauf (Fehler)
;.................................................................................. 
A3		EQU	0x20			;Register fürs Rechnen
A2		EQU	0x21
A1		EQU	0x22
A0		EQU	0x23
B3		EQU	0x24
B2		EQU	0x25
B1		EQU	0x26
B0		EQU	0x27
C3		EQU	0x28
C2		EQU	0x29
C1		EQU	0x2A
C0		EQU	0x2B
D3		EQU	0x2C
D2		EQU	0x2D
D1		EQU	0x2E
D0		EQU	0x2F
;..................................................................................
Flags		EQU	0x30
ATmp		EQU	0x31			;Schleifenzähler
HTmp		EQU	0x32                    ;Schleifenzähler
RTmp		EQU	0x33                    ;Zwischenspeicher
;..................................................................................
Hex_Dec 	call	CClr			;Hex>A, D>Dec
		call	DClr
		movlw	1
		movwf	C0
		movlw	0x20			;32 bit Hex > 32 bit Dec (8 Stellen)
		movwf	HTmp
HexDecL 	btfsc	A0,0
		call	AddDC
		call	CopyCB
		call	AddCB
		call	ARotRb
		decfsz	HTmp,1
		goto	HexDecL
		return
CClr		clrf	C0
		clrf	C1
		clrf	C2
		clrf	C3
		return
DClr		clrf	D0
		clrf	D1
		clrf	D2
		clrf	D3
		return
CopyCB		movf	C0,0
		movwf	B0
		movf	C1,0
		movwf	B1
		movf	C2,0
		movwf	B2
		movf	C3,0
		movwf	B3
		return
AddCB		movlw	B0			;C+B>C
		movwf	FSR
		goto	AddR
AddDC		movlw	C0			;D+C>D
		movwf	FSR
AddR		bcf	_Ferr			;Addiere zwei Register
		bcf	_Fcrp
		movlw	4			;X=4 Bytes lang
		movwf	ATmp
AddRL		bcf	_Fdca
		bcf	_Fcra
		movf	INDF,0
		movwf	RTmp
		movlw	4                       ;X=4 
		addwf	FSR,1
		btfss	_Fcrp
		goto	AddRN
		movlw	1
		addwf	INDF,1
		call	DecCor
AddRN		movf	RTmp,0
		addwf	INDF,1
		call	DecCor
		btfss	_Fdca
		goto	AddCor1
		movlw	6
		addwf	INDF,1
AddCor1 	btfss	_Fcra
		goto	AddCor2
		movlw	0x60
		addwf	INDF,1
		btfsc	_C
		bsf	_Fcra
AddCor2 	movf	INDF,0
		andlw	0xF0
		sublw	0xA0
		btfss	_Z
		goto	AddCor3
		movf	INDF,0
		andlw	0x0F
		clrf	INDF
		addwf	INDF,1
		bsf	_Fcra
AddCor3 	bcf	_Fcrp
		btfsc	_Fcra
		bsf	_Fcrp
		movlw	5                       ;X+1=5
		subwf	FSR,1
		decfsz	ATmp,1
		goto	AddRL
		btfsc	_Fcra
		bsf	_Ferr
		return
DecCor		btfsc	_DC			;dezimale Korrektur (equ "DAW" bei PIC18FXXX)
		bsf	_Fdca
		btfsc	_C
		bsf	_Fcra
		movf	INDF,0
		andlw	0x0F
		sublw	9
		btfss	_C
		bsf	_Fdca
		movf	INDF,0
		andlw	0xF0
		sublw	0x90
		btfss	_C
		bsf	_Fcra
		return
ARotRb		movlw	A3			;rotiere A register 1 Bit rechts
		movwf	FSR			
RRotRb		movlw	4			;rotiere X=4 Bytes
		movwf	ATmp
		bcf	_Fcrp
		btfsc	A0,0
		bsf	_Fcrp
RRotRbL 	bcf	_Fcra
		btfsc	INDF,0
		bsf	_Fcra
		bcf	_C
		btfsc	_Fcrp
		bsf	_C
		rrf	INDF,1
		bcf	_Fcrp
		btfsc	_Fcra
		bsf	_Fcrp
		incf	FSR,1
		decfsz	ATmp,1
		goto	RRotRbL
		return

EEPROM

Alle PICs besitzen EEPROM in dem je nach Typ können 64 bis 256 Databytes abgespeichert werden. Die Adressierung des EEPROMs fängt immer mit 0x00 an. Hier werden nur geprüfte UPs kurz erklärt. Die ersten zwei brauchen nur ein Register "Temp" für Schleifenzähler, dessen Name, falls im Programm schon existiert, geändert werden muss. Die Adresse im EEPROM ist gleich der Adresse im RAM.

EEPROM beschreiben:

EEWrite         movlw	0x20	    ; ab der RAM Adresse wird in EEPROM abgespeichert
		movwf	FSR
		movlw	4           ; soviel Bytes
		movwf	Temp	    ; Schleifenzähler
EEWLoop         call	EEWrite1
		incf	FSR,1	    ; nächste Adresse
		decfsz	Temp,1
		goto	EEWLoop
		return	

EEWrite1        bcf	INTCON,GIE  ; Interrupts sperren
		movf	FSR,0
		bsf	STATUS,RP0  ; auf Bank1 umschalten
		movwf	EEADR       ; EEPROM Adresse
		movf	INDF,0
		movwf	EEDATA      ; EEPROM Data
		bsf	EECON1,WREN
		movlw	0x55
		movwf	EECON2
		movlw	0xAA
		movwf	EECON2
		bsf	EECON1,WR
		bcf	EECON1,WREN
		btfsc	EECON1,WR
		goto	$-1          ; warten bis WR=0
		bcf	STATUS,RP0   ; zurück auf Bank 0 umschalten
		bsf	INTCON,GIE   ; Interrupts erlauben
		return

EEPROM lesen und zurück in RAM schreiben:

EERead          movlw	0x20	     ; ab der Adressse wird aus EEPROM in RAM abgelegt	
		movwf	FSR
		movlw	4	     ; soviel Bytes
		movwf	Temp	     ; Schleifenzähler
EERLoop         call	EERead1
		incf	FSR,1        ; nächste Adresse
		decfsz	Temp,1
		goto	EERLoop
		return

EERead1         movf	FSR,0
		bsf	STATUS,RP0   ; auf Bank1 umschalten 
		movwf	EEADR        ; EEPROM Adresse
		bsf	EECON1,RD
		movf	EEDATA,0     ; EEPROM Data
		bcf	STATUS,RP0   ; zurück auf Bank 0 umschalten
		movwf	INDF
		return

Tabelle im EEPROM mit MPASM Direktive "de" erstellen:

		org             0x2100             ; EEPROM initialisieren
                                                   ; nachfolgend als Kommentar die EEPROM Adressen
	de      0x03, 0xE8                         ; 0x00
	de      0x03, 0xE8                         ; 0x02
	de      'M','H','z',0x00                   ; 0x04
	de      ' ',' ','W','A','I','T',0x00       ; 0x08
	de      'C','o','n','s','t',' ','X',0x00   ; 0x0F
	de      'C','=',0x00                       ; 0x17
	de      'L','=',0x00                       ; 0x1A
	de      'F','=',0x00                       ; 0x1D
	de      'O','K',0x00                       ; 0x20
                                  usw.

Wenn die Tabelle sich im Quellcode befindet, wird sie beim brennen des PICs in EEPROM eigeschrieben.

Das Lesen eines Strings muss ab entsprechender EEPROM Adresse (z.B. für "MHz" -> 0x04) anfangen und auf dem Wert 0x00, der hier als Stringende dient, enden. Einfacher ist, wenn alle Strings gleich lang sind (wie z.B. in einem Zeichengenerator fur Grafikdisplay).

Die Adreesse "0x2100" in der "org" Direktive hat mit der Adresse im Programmspeicher nichts zu tun.

Interrupts

Das Programmteil misst eine Frequenz an T0CKI Pin und wurde für PIC16F870 geschrieben.

Benötigte Hardware: Widerstand 470 Ohm zwischen der Frequenzquelle und TOCKI Pin (A4).

Es fehlen UPs "Hex_Dec" Hex Dec Wandlung und Ausgabe auf ein Display "DispFrq". Das HP ("Main") als leere endlose Schleife macht nichts ausser warten auf ein Interrupt von Timer0 bzw. Timer1.

Da der Timer0 nicht, wie der Timer1 durch ein Flag gestartet und gestoppt werden kann, wird es durch setzen und löschen des Bits für TOCKI (A4) Pin im TRISA Register, genauso wie in der AN592 vom Microchip, gemacht.

Der Zähler der Frequenz wurde um ein Zähler aus zwei zusätzlichen Register A2 und A3 erweitert und bei jedem Timer0 Überlauf erhöht. Der Prescaler wird ins Register A0 und der TIMER0 ins A1 kopiert. Somit ergibt sich 32-bittiges Ergebniss, der als hex Zahl in den Register A3 (MSB),A2, A1, und A0 (LSB) steht. Nach der "Hex_Dec" Wandlung kann die Frequenz aus den Register D3 (MSB), D2, D1 und D0 (LSB) an einem biliebigen Display angezeigt werden.

Es wird kein zusätzliches Register benötigt, da das "ATmp" und das "RTmp" die gleichen wie im UP "Hex_Dec" sind. Die Quarzfrequenz wurde gewählt, weil sie am nächsten der Temperaturstabiltesten Frequenz für AT Quarzen 7,2 MHz ist. Der PIC16F870 wurde nur wegen nötigen 19 I/O Pins für 6-stelligen 7-segment LCD Display LPH2673-1[[4]] (ohne Kontroller) vom Pollin angewendet. Mit einem üblichen 1x16 Zeichen LCD Matrixdisplay und anderen PICs wurde das Programm für Frequenzen bis zum 80 MHz mit einer Genauigkeit +/- 4 Hz positiv getestet.

Um die Frequenz mit Genauigkeit +/- 1Hz zu messen, muss die Messzeit 1 s von Timer1 erzeugt werden, das nur für Quarz bis 2,048 MHz möglich ist. Für sehr genaue Messungen wird empfohlen als Taktfrequenz für den Timer1, die Trägerfrequenz eines nächsten LW Senders (z.B. DLF 153 bzw. 207 kHz) zu nutzen. Die Genauigkeit der Frequenz ist ca. 1E-11 und geprüfter Empfänger kann laut der Skizze in [[5]] nachgebaut werden. In dem Fall müssen die Register vom Timer1 (TMR1H und TMR1L) vor seinem Start entsprechend geladen werden.

Mit dem Quarz 7,3728 MHz wird hier die Messzeit 0,25 s genutzt und die gemessene Frequenz durch 4 multipliziert (mittels 2 Additionen), was die Auflösung von +/- 4 Hz ergibt.

Das UP "Init" muss selbstverständlich für ein Display um Initialisierung des Displaykontrollers ergänzt werden.

;	Programm zum Messen der Frequenz an T0CKI Pin mit 7,3728 MHz Quarz 
	LIST      P=16F870
	include "P16F870.inc"
	__CONFIG	_CP_OFF & _DEBUG_OFF & _WRT_ENABLE_ON & _CPD_OFF & _LVP_OFF & _BODEN_OFF & 
                        _PWRTE_ON & _WDT_OFF & _HS_OSC
		ORG	0x0000
		call	Init
Main		goto	Main
		ORG	0x0004			;ISR (interrupt service routine)
		bcf	INTCON,GIE		;interrupts sperren
		btfss	INTCON,T0IF		;ist Timer0 überlaufen ?
		goto	CheckTMR1               ;nein, dann prüfe Timer1
		bcf	INTCON,T0IF		;Timer0 interrupt flag löschen
		movlw	1
		addwf	A2,1			;erhöhe A2
		btfsc	STATUS,C
		incf	A3,1			;increment A3, wenn A2 überlaufen ist
CheckTMR1	btfss	PIR1,TMR1IF		;ist Timer1 überlaufen ?
		retfie                          ;wenn nicht, springe zurück zu Main  
		bsf	STATUS,RP0		;stopp Timer0
		movf	TRISA,0
		andlw	0xEF
		movwf	TRISA                   ;TOCKI Pin (A4) als Ausgang
		bcf	STATUS,RP0
		bcf	T1CON,TMR1ON		;stopp Timer1
		bcf	PIR1,TMR1IF		;Timer1 interrupt flag löschen
GetFreq		movf	TMR0,0			;Prescaler ins Register A0 kopieren
		movwf	A1
		movwf	RTmp
		clrf	ATmp
FToggle		incf	ATmp,1
		bsf	STATUS,RP0
		bsf	OPTION_REG,T0SE
		bcf	OPTION_REG,T0SE
		bcf	STATUS,RP0
		movf	TMR0,0
		subwf	RTmp,0
		btfsc	STATUS,Z
		goto	FToggle
		comf	ATmp,1
		incf	ATmp,0
		movwf	A0
		call	Hex_Dec
		clrf	A3			;Register A löschen
		clrf	A2
		clrf	A1
		clrf	A0
		call	CopyDC
		call	AddDC			;gemessene Frequenz * 2
		call	CopyDC
		call	AddDC			;gemessene Frequenz * 4 = tatsächliche Frequenz
		call	DispFrq                 ;eigenes UP für benutztes Display
		clrf	TMR0			;Timer0 löschen
		movlw	0x34			;Timer1
		movwf	T1CON			;konfigurieren
		movlw	0x1F			;Timer1 laden (für 0,25 s)
		movwf	TMR1H
		clrf	TMR1L
		bsf	STATUS,RP0              ;start Timer0
		movf	TRISA,0
		iorlw	0x10
		movwf	TRISA                   ;TOCKI Pin (A4)als Eingang
		bcf	STATUS,RP0
		bsf	T1CON,TMR1ON		;start Timer1
		retfie				;erlaube interrupts und springe zurück zu Main
CopyDC  	movf	D0,0
		movwf	C0
		movf	D1,0
		movwf	C1
		movf	D2,0
		movwf	C2
		movf	D3,0
		movwf	C3
		return
Init		clrf	PORTA			;Ports löschen 
		clrf	PORTB
                clrf    PORTC
		movlw	0x20			;RAM (20-7Fh) löschen
		movwf	FSR
		clrf	INDF
		incf	FSR,1
		btfss	FSR,7
		goto	$-3     
		clrf	ADCON0			;schalte ADC aus
		movlw	7			;sperre ADC
		movwf	ADCON1			;und definiere RA0-7 als digitale I/Os
		movlw	0xE0			;GIE, PEIE & TMR0IE erlauben
		movwf	INTCON
		bsf	STATUS,RP0		;bank 1
		clrf	TRISA			;alle PORTA,
		clrf	TRISB			;alle PORTB,
                clrf    TRISC                   ;und alle PORTC Pins als Ausgänge
		movlw	0xE7			;Takt für Timer0 vom T0CKI Pin
		movwf	OPTION_REG		;Timer0 konfigurieren
		bsf	PIE1,TMR1IE		;TMR1 interrupt erlauben
		bcf	STATUS,RP0		;Bank 0
		clrf	TMR0			;Timer0 löschen
		movlw	0x34			;Timer1 konfigurieren
		movwf	T1CON			;(prescaler 8)
		movlw	0x1F			;Timer1 laden (für 0,25 s)
		movwf	TMR1H
		clrf	TMR1L
		bsf	STATUS,RP0              ;start Timer0
		iorlw	0x10
		movwf	TRISA                   ;TOCKI Pin (A4) als Eingang
		bcf	STATUS,RP0
		bsf	T1CON,TMR1ON		;start Timer1
		return
		end

Mausrad

Das UP wertet ein Mausrad aus und setzt, je nach Drehrichtug und sein Anschluss an PORT (PORTB,0 und PORTB,1), ein Flag "_Finc" bzw. "_Fdec", das vor der nächsten Abfrage, gelöscht werden muss. Diese Flags können z.B. für Inkrementierung und Dekrementierung von Zählern dienen.

Die Hardware:

                                            Mausrad
                                           .-------.
                                           |     o---> PORTB,0
                                        +----o---- |
                                        |  |     o---> PORTB,1
                                       === '-------'
                                       GND

Es müssen die internen pull-ups vom PORTB aktiviert werden. Wenn das Mausrad an anderen PORT angeschlossen wird, werden externe pull-ups (z.B. 10 kOhm) benötigt. Als "_Z" wurde "STATUS,Z" definiert. Zum Auswerten werden 4 Register "MausA", "MausB", "MausC" und "Flags" gebraucht, die im gesamten Programm definiert werden müssen. Das UP "Delay" soll ca. 10ms dauern. Dafür kann eine Warteschleife oder zusamengesetzte UPs (z.B. Displayausgabe) mit solcher Ausführungszeit benutzt werden.

In dem UP "Mouse" wird PORTB eigelesen, die alle Bits ausser 0 und 1 durch "and" Funktion gelöscht und in den Register "MausB" und "MausC" gespeichert. Nach ca. 10ms wird das gleiche gemacht und im Register "MausA" gespeichert. Der Wert aus "MausA" wird mit dem vorherigen aus "MouseC" durch "xor" verglichen. Sind sie nicht identisch, wird je nach dem Wert "X" (0, 1, 2, bzw. 3) im "MausB" in das entsprechende UP "MouseX" gesprungen. Sonst, wenn "MausA"="MausC", wird zum Aufrufer zurückgekehrt.

In einem UP "MouseX" wird der letzte Wert im "MausA" ermittelt und nach der Kombination der Werte im "MausB" und "MausA" (01 bzw. 02, 10 bzw. 13, 20 bzw. 23 oder 31 bzw. 32) entsprechendes der Drehrichtung Flag "_Finc" bzw. "_Fdec" gesetzt. Anschliessend wird zum Aufrufer zurückgesprungen.

Detailierter PAD:

                                      Mouse      V
                                             PORTB->W
                                            3 and W->W
                                             W->MausB
                                             W->MausC
                                           Warten 10ms
                                             PORTB->W 
                                            3 and W->W
                                             W->MausA
                                        W xor MausC->MausC
                                               _Z=1 ? J > return
                                                 N
                                                 V
                                            MausB->MausB
                .--------------------------< J _Z=1 ?
                |                                N 
                |                                V
                |                            MausB->W
                |                             1-W->W
                |                     .----< J _Z=1 ?
                |                     |          N
                |                     |          V
                |                     |      MausB->W
                |                     |       2-W->W
                |                     |        _Z=1 ? J >---------------------------.
                |                     |          N                                  |
                |                     |          V                                  |
                |                     |      MausB->W                               |
                |                     |       3-W->W                                |
                |                     |        _Z=1 ? J >-----.                     |
                |                     |          N            |                     |
                |                     |          V            |                     |
                |                     |        return         |                     |
                |                     |                       |                     |
                |                     |                       |                     |
    Mouse0      V        Mouse1       V           Mouse3      V        Mouse2       V
            MausA->W             MausA->MausA             MausA->W             MausA->MausA
             1-W->W                   V                    1-W->W                   V
           _Z=1 ? N >--.         _Z=1 ? N >--.           _Z=1 ? N >--.         _Z=1 ? N >--.
                J      |              J      |                J      |              J      |
                V      |              V      |                V      |              V      |
           setze _Fdec |         setze _Finc |           setze _Finc |         setze _Fdec |
                V<-----´              V<-----´                V<-----´              V<-----´
            MausA->W              MausA->W                MausA->W              MausA->W 
             2-W->W                3-W->W                  2-W->W                3-W->W
           _Z=1 ? N >--.         _Z=1 ? N >--.           _Z=1 ? N >--.         _Z=1 ? N >--.
                J      |              J      |                J      |              J      |
                V      |              V      |                V      |              V      |
           setze _Finc |         setze _Fdec |           setze _Fdec |         setze _Finc |
                V<-----´              V<-----´                V<-----´              V<-----´
              return                return                  return                return

und Quellcode:

Mouse		movf	PORTB,0
		andlw	3
		movwf	MausB
		movwf	MausC
		call	Delay		; ca. 10 ms
		movf	PORTB,0
		andlw	3
		movwf	MausA
		xorwf	MausC,1
		btfsc	_Z
		return
		movf	MausB,1
		btfsc	_Z
		goto	Mouse0
		movf	MausB,0
		sublw	1
		btfsc	_Z
		goto	Mouse1
		movf	MausB,0
		sublw	2
		btfsc	_Z
		goto	Mouse2
		movf	MausB,0
		sublw	3
		btfsc	_Z
		goto	Mouse3
		return
Mouse0		movf	MausA,0
		sublw	1
		btfsc	_Z
		bsf	_Fdec
		movf	MausA,0
		sublw	2
		btfsc	_Z
		bsf	_Finc
		return
Mouse1		movf	MausA,1
		btfsc	_Z
		bsf	_Finc
		movf	MausA,0
		sublw	3
		btfsc	_Z
		bsf	_Fdec
		return
Mouse2		movf	MausA,1
		btfsc	_Z
		bsf	_Fdec
		movf	MausA,0
		sublw	3
		btfsc	_Z
		bsf	_Finc
		return
Mouse3		movf	MausA,1
		sublw	1
		btfsc	_Z
		bsf	_Finc
		movf	MausA,0
		sublw	2
		btfsc	_Z
		bsf	_Fdec
		return

LCD Displays

Matrix

Ein Programm zum Ansteuern von Matrixdisplays im 4-bit Modus. Das Display ist an 6 Pins eines Ports (hier: B) angeschlossen. In diesem Beispielprogramm wurde 2x16 Zeichen Display verwendet, das Programm kann aber für andere Displays modifiziert werden. Für andere Taktfrequenzen muss lediglich nur die Verzögerung vom "Del" geändert werden. Zum Beispiel für 20 MHz Quarz, anstatt 0x10 auf 0x50. Die im "Main" ans Display geschickte Zeichen können beliebig geändert werden.

Das Display wird wie folgt an den PIC16F84A angeschlossen:

                                 VCC
                                  +
                                  |    .-------.
                                  +----|2      |
                                    +--|1,3,5  |
                                    |  |       |
                                   === |       |
                     .-------.     GND |       |
                     |  B4 10|---------|4  RS  |
                     |  B5 11|---------|6  E   |
                     |       |         |       |
                     |       |         |7  DB0 |
                     |       |         |8  DB1 |
                     |       |         |9  DB2 |
                     |       |         |10 DB3 |
                     |   B0 6|---------|11 DB4 |
                     |   B1 7|---------|12 DB5 |
                     |   B2 8|---------|13 DB6 |
                     |   B3 9|---------|14 DB7 |
                     '-------'         '-------'
                     PIC16F84A          DISPLAY
;	Display Test im 4-bit Modus
	LIST      P=16F84a
	include "P16F84a.inc"   		; 4.000 MHz
	__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
;		DB4	PORTB,0 		; Display Data-Bits
;		DB5	PORTB,1
;		DB6	PORTB,2
;		DB7	PORTB,3
#define 	_RS	PORTB,4        		; Display RS
#define 	_E	PORTB,5        		; Display Enable
#define 	_Frs	Flags,0        		; RS Flag
Tmp		equ	0x20	
Flags		equ	0x21
		org	0x0000
		call	Init
Main		call	Fst
		movlw	" "
		call	Char
		movlw	" "
		call	Char
		movlw	"D"
		call	Char
		movlw	"i"
		call	Char
		movlw	"s"
		call	Char
		movlw	"p"
		call	Char
		movlw	"l"
		call	Char
		movlw	"a"
		call	Char
		movlw	"y"
		call	Char
		movlw	" "
		call	Char
		movlw	"T"
		call	Char
		movlw	"e"
		call	Char
		movlw	"s"
		call	Char
		movlw	"t"
		call	Char
		call	Snd
		movlw	" "
		call	Char
		movlw	"i"
		call	Char
		movlw	"m"
		call	Char
		movlw	" "
		call	Char
		movlw	"4"
		call	Char
		movlw	"-"
		call	Char
		movlw	"b"
		call	Char
		movlw	"i"
		call	Char
		movlw	"t"
		call	Char
		movlw	" "
		call	Char
		movlw	"M"
		call	Char
		movlw	"o"
		call	Char
		movlw	"d"
		call	Char
		movlw	"u"
		call	Char
		movlw	"s"
		call	Char
		sleep
Fst		movlw	0x80			; Anfangsadresse der ersten Zeile
		goto	Cmd			
Snd		movlw	0xC0			; Anfangsadresse der zweiten Zeile
Cmd		movwf	Tmp			; Befehl ins Tmp laden
		bcf	_Frs			; RS=0
		goto	Send
Char		movwf	Tmp			; Zeichen ins Tmp laden
		bsf	_Frs			; RS=1
Send		swapf	Tmp,0			; zuerst High Nibble (ab jetzt Low Nibble)
		andlw	0x0F                    ; (aktuelles Low Nibble ausblenden)
		movwf	PORTB			; an Port (Display) schicken
		btfsc	_Frs			; RS Flag ans Port kopieren
		bsf	_RS
		bsf	_E			; Enable erzeugen
		bcf	_E
		movf	Tmp,0			; Low Nibble
		andlw	0x0F                    ; (High Nibble ausblenden) 
		movwf	PORTB			; an Port (Display) schicken
Enab            btfsc	_Frs			; RS Flag ans Port kopieren
		bsf	_RS
		bsf	_E			; Enable erzeugen
		bcf	_E
Del		movlw	0x10			; Verzögerung ca. 50µs
		movwf	Tmp
		decfsz	Tmp,1
		goto	$-1
		return
Init		clrf	PORTB			; PortB initialisieren
		bsf	STATUS,RP0              ; Bank 1 
		clrf	TRISB                   ; alle Pins als Ausgänge
		bcf	STATUS,RP0              ; Bank 0
		bcf	_Frs
 		movlw	2			; Display auf 4-bit umschalten und initialisieren
		movwf	PORTB
		call	Enab
		movlw	0x28                    ; 4 bit, 2 Zeilen, 5x7 Punkten
		call	Cmd
		movlw	0x0C			; display an, cursor aus, nicht blinken
		call	Cmd
		movlw	6			; incrementieren, nicht schieben
		goto	Cmd
		end

Grafik

2-pin Schnittstelle und ein Testprogramm für Grafikdisplay HYUNDAI HP12542R_DYO [[6]]

Handy

Als Beispiel die Hardware und ein Testprogram für Nokia 3310/3330 Display: [[7]]

7-Segment mit 3 Backplanes ohne Kontroller

Eine Schnittstelle und ein Testprogramm für LPH2673-1 von Pollin: [[8]]

Hilfsmittel

Hier werden einige Programme presentiert, die das ASM Programmieren erleichtern. Damit sie möglichst wenig Data- und Programmspeicher benötigen, sind alle Zahlen auf dem Display in der hex Darstellung. Für alle, die es in Dezimalsystem haben wollen, ist ein Taschenrechner mit hex<->dec Wandlung nötig.

PIC Miniterminal

Das ist eine Schnittstelle, die nur 2 Leitungen zum Anschluss an PIC braucht. Sie ermöglicht das Steuern von diversen LCD Displays mit üblichem Anschluss ("RS", "Enable" und 8 Databits) und Abfragen von 3 Tasten.

Sie wird mit einem 2x16 Zeichen Matrixdisplay und 3 Tasten für weiter beschriebene Hilfsprogramme verwendet.

Hardware und Testprogramm: [[9]]

PIC RAM Monitor

Er wurde entwickelt um 16 Register (0x20 bis 0x2F) im RAM eines PICs auf dem Display von "PIC Miniterminal", wie skizziert, beobachten zu können:

           Register Adressen (hex)  20 21 22 23 24 25 26 27
                                   .-----------------------.
         Zweistelligen Werte (hex) |XX XX XX XX XX XX XX XX|
                                   |                       |
                                   |XX XX XX XX XX XX XX XX|
                                   '-----------------------'
                                    28 29 2A 2B 2C 2D 2E 2F

Es können natürlich auch z.B. PORTs beobachtet werden, wenn sie im HP in ausgewähle Register (0x20 bis 0x2F) kopiert werden. Beispielweise so:

                         ..............
                         movf   PORTA,0    ; PORTA ins W-Register laden
                         movwf  0x20       ; und ins Register 0x20 kopieren
                         movf   PORTB,0    ; PORTB ins W-Register laden
                         movwf  0x21       ; und ins Register 0x21 kopieren
                         u.s.w.
                         ..............

Um das Beobachten zu ermöglichen, wenn wegen Fehler das ASM Programm in einer endloser Schleife "hängt", wurde Interrupt vom Timer0 angewendet. Dieser Timer wurde gewählt, weil er in allen PICs vorhanden ist. Das Programm zeigt die Registerinhalte auf dem Display ca. 1 mal pro Sekunde, wenn der PIC mit einem 4 MHz Quarz oder internem Oscillator arbeitet.

Der "PIC RAM Monitor" ist kein selbständiges Programm und wird so konzipiert, dass er in jedes ASM Programm, das den Timer0 nicht benutzt, eingebunden werden kann. Er braucht insgesamt 103 Speicherstellen im Programmspeicher, 10 Register im RAM (0x46 bis 0x4F) und 2 I/O Portpins des PICs für den er benutzt wird. Das beobachtete ASM Programm darf, wegen ISR vom "PIC RAM Monitor", erst ab der Adresse 0x0023 beginnen. Das UP "@" kann für PICs, die mehr Programspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden.

Um "Kollisionen" mit dem Programm, in das er eingebunden wird, zu vermeinden, fangen alle seine Marken mit "@" an.

In dem beobachteten Programm müssen lediglich zwei freie Portpins, an die PIC Miniterminal angeschlossen wird, als Ausgang definiert und initialisiert werden. Ausserdem muss in die Initialisierung "goto @Init" eingefügt werden.

Hier der Quellcode:

;	PIC RAM Monitor.asm
@Tmp	equ	0x4F
@Tmp1	equ	0x4E
@Tmp2	equ	0x4D
@Tmp3	equ	0x4C
@Tmp4	equ	0x4B
@Int	equ	0x4A
@FSR	equ	0x49
@SSR	equ	0x48
@SWR	equ	0x47
@PORT   equ     0x46
 		ORG	0x0004		; ISR
		bcf	INTCON,GIE	; interrupts sperren
		movwf	@SWR		; W-Register sichern
		movf	STATUS,0	; STATUS Register
		movwf	@SSR		; sichern
		movf	FSR,0		; FSR Register
		movwf	@FSR		; sichern
		clrf	@PORT		; Portpins Zustände sichern
		btfsc	@DT
		bsf	@PORT,0
		btfsc	@CK
		bsf	@PORT,1
		btfss	INTCON,T0IF	; Timer0 interrupt Flag prüfen
		goto	@IntTest	; wenn nicht gesetzt, eventuell andere prüfen
		bcf	INTCON,T0IF	; Timer0 interrupt Flag löschen
		incf	@Int,1		; Zähler erhöhen
		btfss	@Int,4		; prüfen, ob schon 16d. Timer0 interrupt
		goto	$+3		; wenn nicht, Anzeigen der Registerinhalte überspringen
		clrf	@Int		; Zähler löschen
		call	@		; Anzeigen der Registerinhalte aufrufen
@IntTest 	;**************;
		; eigener Code ;
		;**************;
		bcf	@CK		; Portpins Zustände wiederherstellen
		btfsc	@PORT,1
		bsf	@CK
		bcf	@DT
		btfsc	@PORT,0
		bsf	@DT
 		movf	@FSR,0		; FSR Register
		movwf	FSR		; wiederherstellen
		movf	@SSR,0		; STATUS Register
		movwf	STATUS		; wiederherstellen
		movf	@SWR,0		; W-Register wiederherstellen
		retfie			; zurück ins Programm springen, interrupts erlauben 
                org     0x03B8          ; diese Adresse kann gleich max.Adresse - 47h sein
@		movlw	0x20		; Anzeigen der Registerinhalte
		movwf	FSR		
		call	@1st
		call	@Line
		call	@2nd
		call	@Line
		return
@Line		movlw	8
		movwf	@Tmp
		movf	INDF,0
		call	@Val
		incf	FSR,1
		decfsz	@Tmp,1
		goto	$-4
		return
@1st		movlw	0x80		; Adresse der 1. Zeile an Display schicken
		goto	@Cmd
@2nd		movlw	0xC0		; Adresse der 2. Zeile an Display schicken
@Cmd		bcf	STATUS,C	; Befehl an Display schicken
		goto	@Send
@Val		movwf	@Tmp1		; Zahl (00-FF) an Display schicken
		swapf	@Tmp1,0
		call	@Num
		movf	@Tmp1,0
@Num		andlw	0x0F		; Ziffer (0-F) an Display schicken
		movwf	@Tmp2		
		movlw	0x0A
		subwf	@Tmp2,0
		btfsc	STATUS,C
		addlw	7
		addlw	0x3A
		bsf	STATUS,C
@Send		movwf   @Tmp2
	  	movlw   9		; ein Byte + RS aus @Tmp2 (9 Bits) an Display schicken
		movwf   @Tmp3
@Ser		bcf	@CK
		bcf	@DT
		btfsc	@Tmp2,7
		bsf	@DT
		bsf	@CK
		rlf	@Tmp2,1
		decfsz	@Tmp3,1
		goto	@Ser
		bcf	@DT		; setze Display
		bsf	@DT		; Enable
		bcf	@CK		; lösche
		bcf	@DT		; Display
		bsf	@DT		; Enable
                bcf     @DT
 		return
@Init		bsf	INTCON,GIE	; alle interrupts erlauben
		bsf	INTCON,T0IE	; Timer0 interrupt erlauben
		bcf	INTCON,T0IF	; Timer0 interrupt Flag löschen
		bsf	STATUS,RP0	; auf Bank 1 umschalten
		movf	OPTION_REG,0	; OPTION_REG in W-Register laden
		andlw	0xC0		; Timer0 konfigurieren
		iorlw	7		; Prescaler 256:1 
		movwf	OPTION_REG	; ins OPTION_REG schreiben
		bcf	STATUS,RP0	; zurück auf Bank 0 umschalten
		movlw	0x38		; Display vom PIC Miniterminal initialisieren
		call	@Cmd
		movlw	0x0C
		call	@Cmd
		movlw	6
		call	@Cmd
		return
		end

Und hier ein Beispiel für eine Anwendung mit dem Programm "Erstes.asm" für PIC16F84A:

;       Erstes.asm
 	list      P=16F84A		; Prozessor definieren
 	include "P16F84A.inc"		; 4.000 MHz
 	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
#define	@DT	PORTB,7  		; * definiere Anschlüsse @DT und @CK
#define	@CK	PORTB,6 		; * für "PIC RAM Monitor"
#define	_T1	PORTB,5 		; Portpins benennen
#define	_T2	PORTB,4
#define	_L1	PORTB,3
#define	_L2	PORTB,2
#define	_L3	PORTB,1
#define	_L4	PORTB,0
P0	equ	0x20			; Variablen definieren (Register benennen)
P1	equ	0x21
P2	equ	0x22
		org   0x0000		; hier fängt das gesamte ASM Programm an	
		call	Init		; rufe UP Init (Initialisierung) auf
		goto	Haupt		 
		org	0x0023          ; hier fängt das "Erstes" an
Haupt		btfsc	_T1		; prüfe, ob Taster T1 gedrückt ist, wenn ja,
					; überspringe "goto T2Test"
		goto	T2Test		; wenn nicht, springe zu T2Test
		bsf	_L1		; schalte _L1 an (setze den Pin 2 auf "1")
		call	Warten		; warte ca. 0,4 s (400 000 Prozessortakten)
		bcf	_L1		; schalte _L1 aus (setze den Pin2 auf "0")
		bsf	_L2		; schalte _L2 an (setze den Pin 5 auf "1")
		call	Warten		; warte
		bcf	_L2		; schalte _L2 aus (setze den Pin 5 auf "0")
		bsf	_L3		; schalte _L3 an (setze den Pin 6 auf "1")
		call	Warten		; warte
		bcf	_L3		; schalte _L3 aus (setze den Pin 6 auf "0")
		bsf	_L4		; schalte _L4 an (setze den Pin 7 auf "1")
		call	Warten		; warte
		bcf	_L4		; schalte _L4 aus (setze den Pin 7 auf "0")
T2Test	btfsc	_T2			; prüfe, ob Taster T2 gedrückt ist, wenn ja,
					; überspringe "goto Haupt"
		goto	Haupt		; Wenn nicht, springe zu Haupt
		bsf	_L4		; schalte _L4 an (setze den Pin 7 auf "1")
		call	Warten		; warte
		bcf	_L4		; schalte _L4 aus (setze den Pin 7 auf "0")
		bsf	_L3		; schalte _L3 an (setze den Pin 6 auf "1")
		call	Warten		; warte
		bcf	_L3		; schalte _L3 aus (setze den Pin 6 auf "0")
		bsf	_L2		; schalte _L2 an (setze den Pin 5 auf "1")
		call	Warten		; warte
		bcf	_L2		; schalte _L2 aus (setze den Pin 5 auf "0")
		bsf	_L1		; schalte _L1 an (setze den Pin 2 auf "1")
		call	Warten		; warte
		bcf	_L1		; schalte _L1 aus (setze den Pin2 auf "0")
		goto	Haupt		; springe zu Haupt
Warten	movlw	2			; schreibe "2" für ca. 0,4 s
		movwf	P2		; ins Register P2
		clrf	P1		; lösche Register P1 (schreibe "0" für 256 Durchläufe) 
		clrf	P0		; lösche Register P0 (schreibe "0" für 256 Durchläufe)
		decfsz	P0,1		; dekrementiere P0, überspringe nächsten Befehl bei P0 = 0
		goto	$-1		; gehe zur voherigen Adresse (Befehl "decfsz  P0") 
		decfsz	P1,1		; dekrementiere P1, überspringe nächsten Befehl bei P1 = 0 
		goto	$-4		; gehe zur aktueller Adresse - 4 (Befehl "clrf P0")
		decfsz	P2,1		; dekrementiere P2, überspringe nächsten Befehl bei P2 = 0
		goto	$-7             ; gehe zur aktueller Adresse - 7 (Befehl "clrf P1")
		return			; springe zurück zum Aufrufer ("Haupt"),
Init		clrf	PORTB	        ; lösche Port (setze alle werdende Ausgänge auf "0")
		bsf	STATUS,RP0	; auf Bank1 umschalten
		bcf	OPTION_REG,7	; aktiviere pull-ups
		movlw	0x30		; definiere PortB: Pins 5 und 4 als Eingänge
					; und die restlichen als Ausgänge (00110000b) 
		movwf	TRISB		; schreibe in TRIS Register
 		bcf	STATUS,RP0	; auf Bank0 umschalten
		goto	@Init		; * initialisiere "PIC RAM Monitor"	
					; hier endet das gesamte ASM Programm
		include "PIC RAM Monitor.asm"	 	
		end

Mit dem "*" wurden 3 Zeilen gekenzeichnet, die, in das mit PIC RAM Monitor beobachtetes ASM Programm, eingefügt werden müssen.

Falls im beobachteten ASM Programm Interrupts benutzt werden, muss die ISR um die Prüfungen anderen Interrupt Flags ergänzt werden, was in der ISR als "eigener Code" eingetragen ist. Der PIC RAM Monitor reagiert nur auf Timer0 Interrupt Flag.

Wenn keine I/O Pins mehr frei sind, kann der PIC Miniterminal parallel zur schon vorhandenen Hardware an die als Ausgänge definierte Portpins angeschlossen werden. In dem Fall werden aber die Ausgänge durch Ausgabe auf das Display gestört, was in Kauf genommen werden muss. Die Anschlüsse "@DT" und "@CK" müssen entsprechend definiert werden, z.B beim "Erstes.asm" für den PIC12F629:

#define 	@DT	_L3		; * definiere Anschlüsse @DT und @CK
#define 	@CK	_L4		; * für "PIC RAM Monitor"
#define 	_T1	GPIO,3          ; Portpins benennen
#define 	_T2	GPIO,4
#define 	_L1	GPIO,5
#define 	_L2	GPIO,2
#define 	_L3	GPIO,1
#define 	_L4	GPIO,0

Demensprechend wird die Leitung "@DT" an GPIO,1 (Pin 6) und "@CK" an GPIO,0 (Pin 7) angeschlossen.

PIC Trainer

Das ist im Prinzip ein "PIC RAM Monitor", das um ein paar Funktionen erweitert wurde. Mit diesem Programm wurden schon mehrere ASM Programme erstellt, z.B. Hex dec Wandlung.

Auch hier wurde Timer0 Interrupt verwendet, aber die Frequenz beträgt 2 Interrupts pro Sekunde. Das Programm benötigt ein "PIC Miniterminal", das an 2 freigewählte Portpins des PICs, die sowohl als Ein- als auch Ausgänge funktionieren, angeschlossen wird. Es ist kein selbständiges Programm, das in jedes Programm, das den Timer0 nicht benutzt, eingebunden werden kann. Es belegt 187 Speicherstellen im Programmspeicher und 11 Register im RAM (0x45 bis 0x4F). Die alle mit "*" gekenzeichnete Zeilen (alle ausser "goto @Init" können geändert werden), müssen im Programm, in das "PIC Trainer" eingebunden ist, enthalten sein. Wegen ISR darf das Program, in das "PIC Trainer" eingebunden ist, erst ab der Adresse 0x001E anfangen. Das HP "@Trainer", alle UPs und die Sprungtabelle können für PICs, die mehr Programspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Dabei muss auch im UP @Test der Wert im PCLATH geändert werden. Siehe dazu Tabellen.

Um "Kollisionen" mit dem Programm, in das er eingebunden wird, zu vermeinden, fangen alle seine Marken mit "@" an.

Der Zusammenhang zwischen der Register-Adresse und der Nummer des aufgerufenen Programm-Fragments zeigt folgende Skizze:


                          ----->0  1  2  3  4  5  6  7<-----TestX Nummer, für beiden Nibbles gleich
                         /     -----------------------
             Register   2     |XX XX XX XX XX XX XX XX|
             Adresse    \     |XX XX XX XX XX XX XX XX|
                         \     -----------------------
                          ----->8  9  A  B  C  D  E  F

Es wird empfohlen, für schnelle Orientierung, sich die Nummer auf das Display anzubringen.

Funktionen den Tasten:

T1 - bewegt den Cursor auf dem Display um eine Position nach rechts. Am rechten Ende der unteren Zeile springt der Cursor wieder nach ganz links in die obere Zeile.

T2 - erhöht (incrementiert) das Nibble an der Cursor-Position. Nach dem Fh kommt 0, 1, 2, usw.

T3 - startet ein TestX mit der Nummer 0 bis Fh, die gleich dem rechten Nibble der Adresse des Registers an der Cursor-Position ist.

Die Tasten werden 2 mal pro Sekunde während des Interrupts eigelesen und man braucht sie zum gewünschten Ergebniss gedrückt halten und dann los lassen. Gleichzeitiges Drücken mehr als einer Taste ist nicht vorgesehen.

Um "Üben" zu ermöglichen und das Funktionieren des Programms zu Verstehen, wurde ein kurzes Testprogramm geschrieben.

Der "PIC Trainer" eignet sich besonders gut zum erstellen von ASM Programmen, da beim einmaligen "brennen" des PICs bis zum 16 Programmfragmente die mit "return" enden (z.B. ein HP als UP) nacheinander aufgerufen und mit biliebigen, in Register eigestellten Werten , getestet werden können. Das Programm "PIC Trainer" kann auch für grösseres Display (mehr Register und Testx) modifiziert werden.

Der Quellcode:

;	PIC Trainer.asm
#define 	@Ftst	@Int,7                  ; Variablen definieren (Register benennen)
@SWR		equ	0x4F
@SSR		equ	0x4E
@FSR		equ	0x4D
@Tmp		equ	0x4C
@Tmp1		equ	0x4B
@Tmp2		equ	0x4A
@Tmp3		equ	0x49
@Int		equ	0x48
@LPC		equ	0x47
@LPC1		equ	0x46
@Tasten 	equ	0x45
		ORG	0x0004  		; ISR
		bcf	INTCON,GIE		; interrupts sperren
		movwf	@SWR			; W-Register sichern
		movf	STATUS,0		; STATUS Register
		movwf	@SSR			; sichern
		movf	FSR,0			; FSR Register
		movwf	@FSR			; sichern
		btfss	INTCON,T0IF		; Timer0 interrupt Flag prüfen
		goto	@IntTest		; wenn nicht gesetzt, eventuell andere prüfen
		bcf	INTCON,T0IF		; Timer0 interrupt Flag löschen
		incf	@Int,1  		; Zähler erhöhen
		btfss	@Int,3  		; prüfen, ob schon 8. Timer0 interrupt
		goto	$+3			; wenn nicht, zum "@IntTest" springen
		clrf	@Int			; Zähler löschen
		call	@			; Tasten auswerten und Registerinhalte anzeigen
@IntTest 	;**************;
		; eigener Code ;
		;**************;
		movf	@FSR,0  		; FSR Register
		movwf	FSR			; wiederherstellen
		movf	@SSR,0  		; STATUS Register
		movwf	STATUS  		; wiederherstellen
		movf	@SWR,0  		; W-Register wiederherstellen
		retfie  			; zurück ins Programm springen, interrupts erlauben 
 		ORG     0x0358                  ; diese Adresse kann gleich max.Adresse - A7h sein
@Trainer 	btfss	@Ftst
		goto	@Trainer
		bcf	@Ftst
		call	@Test
		call	@Zeigen
		goto	@Trainer
@ 		bsf	STATUS,RP0		; @DT und @CK als Eingänge
		bsf	@TDT
		bsf	@TCK
		bcf	STATUS,RP0
		clrf	@Tasten 		; Zustände von @DT und @CK ins @Tasten kopieren
		btfsc	@CK
		bsf	@Tasten,0
		btfsc	@DT
		bsf	@Tasten,1
		movf	@Tasten,1		; Tasten auswerten 
		btfsc	STATUS,Z
		bsf	@Ftst			; setze Flag, wenn Taste3 gedrückt
		movf	@Tasten,0
		sublw	1
		btfsc	STATUS,Z
		call	@IncLPC 		; nächstes Nibble, wenn T2 gedrückt
		movf	@Tasten,0
		sublw	2
		btfsc	STATUS,Z
		call	@Inc			; nibble erhöhen, wenn T1 gedrückt
@Zeigen 	call	@Out
		movlw	0x20			; Anzeigen der Registerinhalte
		movwf	FSR		
		movlw	0x0C			; Cursor aus
		call	@Cmd
		call	@1st
		call	@Line
		call	@2nd
		call	@Line
		movlw	0x0E			; Cursor an
		call	@Cmd
		movlw	0x80			; Display Adresse berechnen und Cursor plazieren
		btfsc	@LPC,4
		addlw	0x30
		addwf	@LPC,0
		call	@Cmd
		return
@Out		bsf	STATUS,RP0		; @DT und @CK als Ausgänge
		bcf	@TDT
		bcf	@TCK
		bcf	STATUS,RP0
		return
@Test		movlw	3			; Test0-F starten
		movwf	PCLATH
		movf	@LPC1,0
		andlw	0x0F
		goto	@TestTab
@Inc		movlw	0x20			; Register Wert erhöhen
		movwf	FSR
		movf	@LPC1,0
		addwf	FSR,1
		btfsc	@LPC,0	
		goto	@IncLN
@IncHN  	movlw	0x10			; High Nibble erhöhen
		addwf	INDF,1
		return
@IncLN          incf	INDF,1  		; Low Nibble erhöhen
		movf	INDF,0
		andlw	0x0F
		btfss	STATUS,Z
		return
		movlw	0x10
		subwf	INDF,1
		return
@IncLPC         incf	@LPC,1  		; Zähler der Position in der Zeile erhöhen
		movf	@LPC,0
		sublw	0x20
		btfsc	STATUS,Z
		clrf	@LPC
		movf	@LPC,0
		movwf	@LPC1
		rrf	@LPC1,1                 ; @LPC1=@LPC/2
		return
@Line		movlw	8			; Zeile an Display schicken
		movwf	@Tmp
		movf	INDF,0
		call	@Val
		incf	FSR,1
		decfsz	@Tmp,1
		goto	$-4
		return
@1st		movlw	0x80			; Adresse der 1. Zeile an Display schicken
		goto	@Cmd
@2nd		movlw	0xC0			; Adresse der 2. Zeile an Display schicken
@Cmd		bcf	STATUS,C		; Befehl an Display schicken
		goto	@Send
@Val		movwf	@Tmp1			; Zahl (00-FF) an Display schicken
		swapf	@Tmp1,0
		call	@Num
		movf	@Tmp1,0
@Num		andlw	0x0F			; Ziffer (0-F) an Display schicken
		movwf	@Tmp2		
		movlw	0x0A
		subwf	@Tmp2,0
		btfsc	STATUS,C
		addlw	7
		addlw	0x3A
		bsf	STATUS,C
@Send		movwf	@Tmp2
	  	movlw	9			; Byte + RS aus @Tmp2 (9 Bits) an Display schicken
		movwf	@Tmp3
@Ser		bcf	@CK
		bcf	@DT
		btfsc	@Tmp2,7
		bsf	@DT
		bsf	@CK
		rlf	@Tmp2,1
		decfsz	@Tmp3,1
		goto	@Ser
		bcf	@DT			; setze Display
		bsf	@DT			; Enable
		bcf	@CK			; lösche
		bcf	@DT			; Display
		bsf	@DT			; Enable
                bcf	@DT
		return
@Init		bsf	INTCON,GIE		; alle interrupts erlauben
		bsf	INTCON,T0IE		; Timer0 interrupt erlauben
		bcf	INTCON,T0IF		; Timer0 interrupt Flag löschen
		bsf	STATUS,RP0		; auf Bank 1 umschalten
		movf	OPTION_REG,0    	; OPTION_REG in W-Register laden
		andlw	0xC0			; Timer0 konfigurieren
		iorlw	7			; Prescaler 256:1 
		movwf	OPTION_REG		; ins OPTION_REG schreiben
		bcf	STATUS,RP0		; zurück auf Bank 0 umschalten
		clrf	@Int                    ; Variablen initialisieren (löschen)
		clrf	@LPC
		clrf	@LPC1
		call	@Out
		movlw	0x38			; Display vom PIC Miniterminal initialisieren
		call	@Cmd
		movlw	0x0C
		call	@Cmd
		movlw	6
		call	@Cmd
		return
		org	0x3EF                   ; diese Adresse kann gleich max.Adresse - 10h sein
@TestTab	addwf	PCL,1			; Sprungtabelle
		goto	Test0
		goto	Test1
		goto	Test2
		goto	Test3
		goto	Test4
		goto	Test5
		goto	Test6
		goto	Test7
		goto	Test8
		goto	Test9
		goto	TestA
		goto	TestB
		goto	TestC
		goto	TestD
		goto	TestE
		goto	TestF
		end

Das Testprogramm:

;	Testprogramm.asm
 	list      P=16F84A		; Prozessor definieren
 	include "P16F84A.inc"		; 4.000 MHz
 	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
#define	@DT	PORTB,7 		; * definiere Anschlüsse @DT und @CK
#define	@CK	PORTB,6 		; * für "PIC Trainer"
#define	@TDT	TRISB,7 		; * definiere ensprechende Bits im TRISx Register	
#define	@TCK	TRISB,6 		; * für o.g. Anschlüsse
		org     0x0000 		; hier fängt das gesamte ASM Programm an	
		call	Init		; rufe UP Init (Initialisierung) auf
		goto	@Trainer		 
		org	0x001E          ; ab da kann eigener Code anfangen
Test0		incf	0x2F,1
 		return
Test1		incf	0x2E,1
		return
Test2		incf	0x2D,1
		return
Test3		incf	0x2C,1
		return
Test4		incf	0x2B,1
		return
Test5		incf	0x2A,1
		return
Test6		incf	0x29,1
		return
Test7		incf	0x28,1
		return
Test8		incf	0x27,1
		return
Test9		incf	0x26,1
		return
TestA		incf	0x25,1
		return
TestB		incf	0x24,1
		return
TestC		incf	0x23,1
		return
TestD		incf	0x22,1
		return
TestE		incf	0x21,1
		return
TestF		incf	0x20,1
                goto    TestF
Init		;--------------;
                ; eigener Code ;
                ;--------------;
                goto	@Init		; * initialisiere "PIC Trainer"	
                                        ; hier endet das gesamte ASM Programm
		include "PIC Trainer.asm"	 	
		end

Es können eigene Namen für mit "PIC Trainer" erstellten und geprüften UPs vewendet werden. Sie müssen nur im eigenem Programm mit "#define" den entsprechenden TestX zugewiesen werden. Aus unerklärlichen Gründen funktioniert es aber nur, wenn die Definitionen als erste im Quelcode sind (MPASM Falle?).

#define	Test0		Init
#define	Test1		Lesen
#define	Test2		Schreiben
                 usw.

Somit wird z.B. das UP "Lesen" aufgerufen wenn die Taste T3 gedrückt wird und der Cursor auf der Register-Adresse "21" steht. Es wird empfohlen, eine Tabelle mit UPs-Namen und den enstprechenden Nummern (0 bis Fh) zum dessen Aufrufen, sich zu erstellen.

Für alle definierte, aber noch nicht existierende UPs, sollten "return" Befehle angewendet werden, um einen Programmabsturtz durch versehentliches Aufrufen von dennen, zu vermeinden. Zum Beispiel:

#define	TestD	RAMClr
#define	TestE	RAMSet
#define	TestF	KeyTst


RAMClr		return
RAMSet		return
KeyTst		return

Wenn ein UP (z.B. eine Warteschleife) läuft, kann ein nächstes erst nach seiner Beendigung gestartet werden.

Wenn ein UP in einer endloser Schleife "hängen" bleibt, dann kann nur anderes UP nicht gestartet werden. Die alle andere Funktionen des Programms werden nicht gestört. In dem Testprogramm wurde absichtlich TestF als endlose Schleife erstellt, um das Funktionieren des "PIC Trainer"s in diesem Fall zu zeigen.

PIC Profiler

Der "PIC Profiler" ermöglicht das Messen von Ausführungszeit eines ASM UPs, also Programm-Fragments das mit "return" endet. Es wird die gesammte Ausführungszeit gemessen mit "call" (2 Takten) und "return" (2 Takten) inklusive. Um ein HP messen zu können, muss es ins UP umgewandelt werden, wie im Unterprogramm erklärt.

Zum Messen wurde Timer0 benutzt, der um 2 Byte Zähler erweitert wurde. Somit beträgt die maximale messbare Auführungszeit FFFFFFFFh = 4 294 967 295 Prozessortakten, was mehr als einer Stunde beim 4 MHz Quarz entspricht. Bis FFFFh ist das Messergebniss genau, weiter wurde es nicht getestet.

Um die Durchführung der Messung zu ermöglichen, wird das "goto Haupt" für den MPASM mit einem Semikolon augeblendet und "goto @Profiler" eingeschrieben. Nach dem Einschalten wird anstatt ins "Haupt" zum "@Profiler" gesprungen. Der "@Profiler" misst die Ausführungszeit nur einmal, da er mit "sleep" endet. Wenn endlose Messung gewünscht wird, muss der Befehl "sleep" mit "goto @Profiler" ersetzt werden. Wegen ISR vom "PIC Profiler" darf das Programm, in dem gemessen wird, erst ab der Adresse 0x0013 anfangen.

Zum Darstellen des Messergebnisses kann entweder schon im zu messenden Programm vorhandenes Display bzw. "PIC Miniterminal" benutzt werden. Das hex Ergebniss befindet sich in den Register @A3 (MSB), @A2, @A1 und @A0 (LSB).

Die Version, die das im Programm vorhandenes Display benutzt, braucht 46 Speicherstellen im Programmspeicher und 8 Register im RAM.

;	PIC Profiler1.asm
@SWR		equ	0x4F            ; Variablen deklarieren (Register benennen)
@SSR		equ	0x4E
@A3		equ	0x4D
@A2		equ	0x4C
@A1		equ	0x4B
@A0		equ	0x4A
@ATmp		equ	0x49
@RTmp		equ	0x48
		ORG	0x0004		; ISR (interrupt service routine)
		bcf	INTCON,GIE	; interrupts sperren
		bcf	INTCON,T0IF	; Timer0 interrupt flag löschen
		movwf	@SWR		; W-Register sichern
		movf	STATUS,0	; STATUS Register
		movwf	@SSR		; sichern
		movlw	1
		addwf	@A2,1		; erhöhe @A2, wenn Timer0 überlaufen ist
		btfsc	STATUS,C
		incf	@A3,1		; erhöhe @A3, wenn @A2 überlaufen ist
		movf	@SSR,0		; STATUS Register
		movwf	STATUS		; wiederherstellen
		movf	@SWR,0		; W-Register wiederherstellen
		clrf	TMR0		; Timer0 und Prescaler löschen
		retfie			; zurück ins gemessene UP, Interrupts erlauben
		org	0x03E0          ; diese Adresse kann gleich max.Adresse - 1Fh sein
@Profiler	clrf	@A3             ; Messregister löschen
		clrf	@A2
		bsf	STATUS,RP0	; Bank1
		movlw	0xC7		; interner Takt
		movwf	OPTION_REG	; Timer0 konfigurieren
		bcf	STATUS,RP0	; Bank 0
		movlw	0xE0		; nur Timer0 Interrupt erlaubt
		movwf	INTCON		; Interrupts konfigurieren
		clrf	TMR0		; Timer0 und Prescaler löschen
;............................................................................
		call	Messen		; dieses UP wird gemessen
;............................................................................
		bsf	STATUS,RP0
		bsf	OPTION_REG,T0CS	; "stopp" Timer0
		bcf	STATUS,RP0	
		movf	TMR0,0		; TMR0 ins Register
		movwf   @A1		; @A1 kopieren
		movwf   @RTmp
		clrf	@ATmp		; Prescaler ins Register @A0 kopieren
		incf	@ATmp,1
		bsf	STATUS,RP0
		bsf	OPTION_REG,T0SE
		bcf	OPTION_REG,T0SE
		bcf	STATUS,RP0
		movf	TMR0,0
		subwf	@RTmp,0
		btfsc	STATUS,Z
		goto	$-8
		comf	@ATmp,1
		incf	@ATmp,0
		movwf	@A0
		decf	@A0,1
		call	Ausgeben	; Messergebniss aus @A3, @A2, @A1 und @A0
                                        ; auf einem Display anzeigen (eigener Code)
		sleep
		end

Die Version, die zur Ausgabe den "PIC Miniterminal" benutzt, belegt 94 Speicherstellen im Programmspeicher und 12 Register im RAM.

;	PIC Profiler2.asm
@SWR		equ	0x4F            ; Variablen deklarieren (Register benennen)
@SSR		equ	0x4E
@A3		equ	0x4D
@A2		equ	0x4C
@A1		equ	0x4B
@A0		equ	0x4A
@ATmp		equ	0x49
@RTmp		equ	0x48
@Tmp            equ     0x47		; * Variablen für "PIC Miniterminal" definieren
@Tmp1	        equ     0x46
@Tmp2	        equ     0x45
@Tmp3	        equ     0x44	
		ORG	0x0004		; ISR (interrupt service routine)
		bcf	INTCON,GIE	; interrupts sperren
		bcf	INTCON,T0IF	; Timer0 interrupt flag löschen
		movwf	@SWR		; W-Register sichern
		movf	STATUS,0	; STATUS Register
		movwf	@SSR		; sichern
		movlw	1
		addwf	@A2,1		; erhöhe @A2, wenn Timer0 überlaufen ist
		btfsc	STATUS,C
		incf	@A3,1		; erhöhe @A3, wenn @A2 überlaufen ist
		movf	@SSR,0		; STATUS Register
		movwf	STATUS		; wiederherstellen
		movf	@SWR,0		; W-Register wiederherstellen
		clrf	TMR0		; Timer0 und Prescaler löschen
		retfie			; zurück ins gemessene UP, Interrupts erlauben
		org	0x03B0          ; diese Adresse kann gleich max.Adresse - 4Fh sein
@Profiler	movlw   0x38		; Display vom "PIC Miniterminal" initialisieren
		call	@Cmd
		movlw	0x0C
		call	@Cmd
		movlw	6
		call	@Cmd 
	        clrf	@A3             ; Messregister löschen
		clrf	@A2
		bsf	STATUS,RP0	; Bank1
		movlw	0xC7		; interner Takt
		movwf	OPTION_REG	; Timer0 konfigurieren
		bcf	STATUS,RP0	; Bank 0
		movlw	0xE0		; nur Timer0 Interrupt erlaubt
		movwf	INTCON		; Interrupts konfigurieren
		clrf	TMR0		; Timer0 löschen
;............................................................................
		call	Messen		; dieses UP wird gemessen
;............................................................................
		bsf	STATUS,RP0
		bsf	OPTION_REG,T0CS	; "stopp" Timer0
		bcf	STATUS,RP0	
		movf	TMR0,0		; TMR0 ins Register
		movwf	@A1		; @A1 kopieren
		movwf	@RTmp
		clrf	@ATmp		; Prescaler ins Register @A0 kopieren
		incf	@ATmp,1
		bsf	STATUS,RP0
		bsf	OPTION_REG,T0SE
		bcf	OPTION_REG,T0SE
		bcf	STATUS,RP0
		movf	TMR0,0
		subwf	@RTmp,0
		btfsc	STATUS,Z
		goto	$-8
		comf	@ATmp,1
		incf	@ATmp,0
		movwf	@A0
		decf	@A0,1
		call	@1st		; Messergebniss aus @A3, @A2, @A1 und @A0
					; auf "PIC Miniterminal" ausgeben
		movf	@A3,0
		call	@Val
		movf	@A2,0
		call	@Val
		movf	@A1,0
		call	@Val
		movf	@A0,0
		call	@Val
		sleep
@1st		movlw	0x80		; Adresse der 1. Zeile an Display schicken
@Cmd		bcf	STATUS,C	; Befehl an Display schicken 
		goto	@Send
@Val		movwf	@Tmp1		; Zahl (00-FF) an Display schicken
		swapf	@Tmp1,0
		call	@Num
		movf	@Tmp1,0
@Num		andlw	0x0F		; Ziffer (0-F) an Display schicken
		movwf	@Tmp2 
		movlw	0x0A 
		subwf	@Tmp2,0
		btfsc	STATUS,C
		addlw	7
		addlw	0x3A
		bsf	STATUS,C
@Send          	movwf	@Tmp2
		movlw	9		; ein Byte + RS aus @Tmp2 (9 Bits) an Display schicken
		movwf	@Tmp3
@Ser 		bcf	@CK
		bcf	@DT
		btfsc	@Tmp2,7
		bsf	@DT
		bsf	@CK
		rlf	@Tmp2,1
		decfsz  @Tmp3,1
		goto	@Ser
		bcf	@DT		; setze Display
		bsf	@DT		; Enable
		bcf	@CK		; lösche
		bcf	@DT		; Display
		bsf	@DT		; Enable
		bcf	@DT
		return 
		end 

Das Program "@Profiler" kann für PICs, die mehr Programspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden.

Als praktisches Beispiel wurde das UP "Warten" vom "Erstes.asm" für den PIC16F84A gemessen und das Ergebniss auf dem "PIC Miniterminal" dargestellt.

;     Erstesp.asm
	list      P=16F84A		; Prozessor definieren
	include "P16F84A.inc"		; 4.000 MHz
	__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC
#define	    Messen  Warten	        ; definiere das zu messende UP
#define	    @DT	  PORTB,7	        ; * definiere Anschlüsse @DT und @CK
#define	    @CK	  PORTB,6	        ; * für "PIC Miniterminal"
#define	    _T1	  PORTB,5	        ; Portpins benennen
#define	    _T2	  PORTB,4
#define	    _L1	  PORTB,3
#define	    _L2	  PORTB,2
#define	    _L3	  PORTB,1
#define	    _L4	  PORTB,0
P0         equ   0x20			; Variablen definieren (Register benennen)
P1	   equ   0x21
P2	   equ   0x22
	   org   0x0000 		; hier fängt das ASM Programm in dem gemessen wird an	
	   call    Init 		; rufe UP Init (Initialisierung) auf
	   ;goto    Haupt		 
	   goto    @Profiler	
	   org     0x0013               ; hier fängt das Program, in dem gemessen wird, an
Haupt	   btfsc   _T1			; prüfe, ob Taster T1 gedrückt ist, wenn ja,
					; überspringe "goto T2Test"
	   goto    T2Test		; wenn nicht, springe zu T2Test
           bsf     _L1			; schalte _L1 an
           call    Warten		; warte ca. 0,4 s (400 000 Prozessortakten)
           bcf     _L1			; schalte _L1 aus
           bsf     _L2			; schalte _L2 an
           call    Warten		; warte
           bcf     _L2			; schalte _L2 aus
           bsf     _L3			; schalte _L3 an
           call    Warten		; warte
           bcf     _L3			; schalte _L3 aus
           bsf     _L4			; schalte _L4 an
           call    Warten		; warte
           bcf     _L4			; schalte _L4 aus
T2Test     btfsc   _T2			; prüfe, ob Taster T2 gedrückt ist, wenn ja,
					; überspringe "goto Haupt"
           goto    Haupt		; Wenn nicht, springe zu Haupt
           bsf     _L4			; schalte _L4 an
           call    Warten		; warte
           bcf     _L4			; schalte _L4 aus
           bsf     _L3			; schalte _L3 an
           call    Warten		; warte
           bcf     _L3			; schalte _L3 aus
           bsf     _L2			; schalte _L2 an
           call    Warten		; warte
           bcf     _L2			; schalte _L2 aus
           bsf     _L1			; schalte _L1 an
           call    Warten		; warte
           bcf     _L1			; schalte _L1 aus
           goto    Haupt		; springe zu Haupt
Warten     movlw   2			; schreibe "2" für ca. 0,4 s
           movwf   P2			; ins Register P2
           clrf    P1			; lösche Register P1 (schreibe "0" für 256 Durchläufe) 
           clrf    P0			; lösche Register P0 (schreibe "0" für 256 Durchläufe)
           decfsz  P0,1		        ; dekrementiere P0, überspringe nächsten Befehl bei P0 = 0
           goto    $-1			; gehe zur voherigen Adresse (Befehl "decfsz  P0") 
           decfsz  P1,1		        ; dekrementiere P1, überspringe nächsten Befehl bei P1 = 0 
           goto    $-4			; gehe zur aktueller Adresse - 4 (Befehl "clrf P0")
           decfsz  P2,1		        ; dekrementiere P2, überspringe nächsten Befehl bei P2 = 0
           goto    $-7			; gehe zur aktueller Adresse - 7 (Befehl "clrf P1")
           return			; springe zurück zum Aufrufer ("Haupt"),
Init	   clrf    PORTB		; lösche Port (setze alle werdende Ausgänge auf "0")
   	   bsf	    STATUS,RP0		; auf Bank1 umschalten
  	   bcf	    OPTION_REG,7	; aktiviere pull-ups
     	   movlw   0x30 		; definiere PortB: Pins B5 und B4 als Eingänge
					; und die restlichen als Ausgänge (00110000b) 
 	   movwf  TRISB 		; schreibe in TRIS Register
 	   bcf    STATUS,RP0		; auf Bank0 umschalten
           return
      					; hier endet das ASM Programm in dem gemessen wird
 	   include "PIC Profiler2.asm"	 	
           end

LiFePO4 Speicher Test