![LiFePO4 Speicher Test LiFePO4 Speicher Test](https://www.roboternetz.de/partner/solarspeicher-speicher-akkus-test.jpg)
BMS (Diskussion | Beiträge) (→Programm) |
K (→7-Segment mit 3 Backplanes ohne Kontroller) |
||
(968 dazwischenliegende Versionen von 10 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
= Einführung = | = Einführung = | ||
Zeile 15: | Zeile 5: | ||
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. | 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, | + | 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 ( | + | Wenn wir gleichzeitig (parallel) 8 Bits haben, dann ist es ein Byte, das 256 Bitkombinationen von 00000000b bis 11111111b enthält, weil ein Bit (X) auf jeder Stelle 0 bzw. 1 sein kann. |
<table border=0 cellpadding=3 cellspacing=2> | <table border=0 cellpadding=3 cellspacing=2> | ||
Zeile 41: | Zeile 31: | ||
</table> | </table> | ||
− | Das "b" bedeutet, | + | 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 | + | 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. | (d) Zeichen nicht nötig, da sie beide gleich den entsprechenden bin Zahlen sind. | ||
Zeile 51: | Zeile 41: | ||
1b = 1h = 1d 101b = 5h = 5d 1001b = 9h = 9d 1101b = Dh = 13d | 1b = 1h = 1d 101b = 5h = 5d 1001b = 9h = 9d 1101b = Dh = 13d | ||
10b = 2h = 2d 110b = 6h = 6d 1010b = Ah = 10d 1110b = Eh = 14d | 10b = 2h = 2d 110b = 6h = 6d 1010b = Ah = 10d 1110b = Eh = 14d | ||
− | 11b = 3h = 3d 111b = 7h = | + | 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. | 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, | + | 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 == | == Speicher und Register == | ||
− | Als Speicher bezeichnet man | + | 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 | + | 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. | 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, | + | 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 Basic-Line hat 12-bittige, Mid-Range 14-bittige und High-End 8-bittige Speicherstellen. Die Anzahl den Speicherstellen im bestimmten Speicher ist vom PIC-Typ abhängig. | |
− | Eine 8- | + | Eine 8-bittige Speicherstelle im RAM wird bei PICs Register genannt und kann so skizziert werden: |
<table border=0 cellpadding=3 cellspacing=2> | <table border=0 cellpadding=3 cellspacing=2> | ||
Zeile 113: | Zeile 105: | ||
− | + | 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 | + | 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 absolute Adresse: movwf 0x20 | ||
− | Direkte Adressierung per vorher | + | Direkte Adressierung per vorher definiertem Namen des Registers (z.B. Temp equ 0x20): movwf Temp |
− | Indirekte Adressierung durch FSR Register, in | + | 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 ; | + | movlw Temp ;ins W-Register wird die absolute Adresse des Registers "Temp" geladen |
− | movwf FSR ;diese Adresse wird | + | movwf FSR ;diese Adresse wird ins "FSR" Register kopiert |
− | movf INDF,0 ;der Wert aus dem indirekt adressierten Register Temp | + | movf INDF,0 ;der Wert aus dem indirekt adressierten Register "Temp" |
− | + | ;wird aus dem "INDF" Register ins W-Register geladen. | |
− | Weil in jedem 14-bittigem Befehl, der mit Datenspeicher verbunden ist, | + | Weil in jedem 12 bzw. 14-bittigem 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 Basic-Line und Mid-Range PICs der Datenspeicher (RAM) in s.g. Bänke verteilt. |
− | Für | + | 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. |
− | + | Die Beschreibung allen SFRs (Special Funktion Register), in den sämtliche Funktionen des PICs festgelegt werden, befinden sich im Datenblatt unter "Memory Organisation". | |
− | + | Siehe auch: [[#Speicherbankorganisation|Speicherbankorganisation]] | |
− | Die Prozessoren der | + | == Prozessor == |
+ | Die Prozessoren der PIC's 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. | ||
− | Der | + | Der Prozessor von PICs gehört zu den RISC (Reduced Instruction Set Computer) Prozessoren und man hat nur 35 bzw. 75 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. |
− | + | Ein sogenannter Prozessortakt besteht aus 4 Oszillator-Takten, die jeweils einen Teil eines Befehls abarbeiten. Deswegen entspricht die Taktfrequenz der CPU der durch 4 geteilten Frequenz des Oszillators. | |
CPU Vorgang Richtung Speicher | CPU Vorgang Richtung Speicher | ||
------------------------------------------------- - | ------------------------------------------------- - | ||
− | 1.Befehl lesen ( | + | 1.Befehl lesen (fetch) <------- Flash | |
2.Daten lesen (read) <------- RAM | 1 Prozessortakt = | 2.Daten lesen (read) <------- RAM | 1 Prozessortakt = | ||
3.Daten verarbeiten (execute) | 4 Oszillatortakte | 3.Daten verarbeiten (execute) | 4 Oszillatortakte | ||
Zeile 150: | Zeile 143: | ||
- | - | ||
− | 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. | + | 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 einen kleinen eigenen Speicher, der weder zum Programmspeicher noch zum Datenspeicher gehört. Er besteht aus dem Programmzähler PC (program counter) und dem Stapel (stack). | ||
+ | |||
+ | In dem PC befindet sich die Adresse des momentan ausführbaren 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 z.B. 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. | ||
+ | |||
+ | Um dies zu ermöglichen, 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ührten Zeile abgelegt, damit der Prozessor, wenn er nach der Ausführung der "Interruppt Service Routine" (ISR) an "retfie" kommt, das unterbrochene Programm an der richtigen Stelle wieder startet. | ||
+ | |||
+ | Der Stapel kann aber nur 8 bzw. 31 Adressen speichern, deswegen darf nur entsprechende Anzahl von nacheinander folgenden "call" Befehlen benutzt werden. Wenn ein Interrupt benutzt wird, reduziert sich es um 1, da eine Speicherstelle immer für die Adresse, an der das Programm unterbrochen wurde, reserviert werden muss. Sonst findet der Prozessor nicht mehr zurück und springt in die "Nirvana" , 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 | + | Das Lesen/Schreiben aus/in den EEPROM Speicher ist mit Hilfe speziellen Register und Unterprogrammen bei allen 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 == | == Assembler == | ||
− | Die Maschinensprache | + | Die Maschinensprache, ist eine Sprache die nur eine bestimmte CPU versteht. Für einen Menschen ist sie unverständlich, da sie nur aus Zahlen besteht. |
− | Um sich die Sprache verständlicher zu machen wurden den | + | Um sich die Sprache verständlicher zu machen wurden den Zahlen s.g. Mnemonics aus Buchstaben zugewiesen. Jeder Befehl für einen CPU hat somit ein "Namen", der aus der englischen Sprache stammt. In dieser Form wird auch von Assembler oder kurz ASM gesprochen. Siehe: [[#Kurzübersicht Assembler Befehle|Kurzübersicht Assembler Befehle]] |
− | Obwohl sie | + | Obwohl sie bis zu 1000 mal schneller als die meisten Hochsprachen ist, wird sie, wegen des großen Aufwands bei der Erstellung von umfangreichen Programmen, selten benutzt. Man findet sie aber oft in fast allen Hochsprachen, in eingebundenen 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 | + | 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-Kenntnisse sehr vorteilhaft. |
− | + | 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". Anders als bei einer Hochsprache ist die "Übersetzung" genau vorhersehbar und festgelegt. Sie endet eigentlich erst dann, wenn das geschriebene Programm so wie geplant funktioniert. | |
− | Die | + | 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 [http://gputils.sourceforge.net/ GPASM]) von dem für Menschen noch verständlichen Code in die Maschinensprache assembliert und als Textdatei 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 [http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1406&dDocName=en019469&part=SW007002] 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) installiert werden. Für MPASM Benutzer werden auch folgende ".pdf" Dateien empfohlen: | |
− | + | ||
− | Das Assemblerprogramm MPASM kann kostenlos von der Homepage des Herstellers von PICs [http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1406&dDocName=en019469&part=SW007002] 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) | + | |
MPASM/MPLINK User's Guide (2628 KB) [Benutzerhandbuch] | MPASM/MPLINK User's Guide (2628 KB) [Benutzerhandbuch] | ||
Zeile 180: | Zeile 179: | ||
MPASM™/MPLINK™ PICmicro® Quick Chart (81 KB) [Kurzübersicht] | MPASM™/MPLINK™ PICmicro® Quick Chart (81 KB) [Kurzübersicht] | ||
− | Nach dem | + | Nach dem Einschalten 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 | + | 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 | + | 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 | + | 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 | + | definiert werden (z.B. durch eine Tastenkombination), wann die CPU zum letzten Fragment des ASM Programms vor dem "STOP" gehen soll. |
== Grundbeschaltung == | == 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, | + | 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, | + | |
+ | 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) | * Quarz oder Keramik-Resonator + 2 Kondensatoren (LP,HS oder XT) | ||
Zeile 198: | Zeile 198: | ||
* Widerstand + Kondensator (RC); keine hohe Frequenzstabilität | * 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 verwendet wird. | + | 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 | + | 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. | 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 | + | Damit ein Programm zuverlässig ausgeführt werden kann, muss die Versorgungsspannung störungsfrei sein. Dafür wird ein Keramik-Vielschicht-Kondensator 100 nF (0,1 µF) möglichst am kürzesten direkt zwischen VDD und VSS Pins geschaltet. |
Folgende Skizzen zeigen die Grundbeschaltung eines PICs: | Folgende Skizzen zeigen die Grundbeschaltung eines PICs: | ||
Zeile 219: | Zeile 239: | ||
[[Bild:Rc-os.png|thumb|160px|externer RC-Oszillator]] | [[Bild:Rc-os.png|thumb|160px|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 vermeiden, 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 Programm 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. | ||
+ | |||
+ | Warnung ! | ||
+ | |||
+ | Anhand praktischer Erfahrung kann man feststellen, dass bei den kleinsten PICs der Familie 12FXXX, bei dennen ein I/O Pin mit VPP gemultiplext wird (z.B. PIC12F510, PIC12F629, PIC12F635 und 12F675), das Wählen des VPP Pins als I/O verwandelt ihn ins One Time Programming (OTP) Chip, der nur einmal programmiert werden kann. Die gewählte Konfuguration wird entgültig fest "gebrannt" und wegen seitdem fehlender Verbindung des I/O Pins mit VPP keine Umprogrammierung mehr möglich ist. Wer solchen PIC verwenden möchte, sollte aus dem Grund für Entwicklung anderen PIC-Typ nehmen und erst fertiges ausprobiertes Programm einmalig in den für konkrete Anwendung vorgesehenen PIC brennen. | ||
== Wahl des PICs == | == Wahl des PICs == | ||
− | Es gibt PIC µC die | + | 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 ä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. | ||
Zeile 232: | Zeile 301: | ||
- Max. Taktfrequenz des Prozessors. | - Max. Taktfrequenz des Prozessors. | ||
− | - | + | - Größe des Datenspeichers (für Variablen). |
− | - | + | - Größe des Programmspeichers (für Programm). |
− | - Integrierte Hardware (Komparatoren, A/D Wandler, Timer, USART, I²C, SPI, PWM, | + | - Integrierte Hardware (Komparatoren, A/D Wandler, Timer, USART, I²C, SPI, PWM, usw.). |
− | - Freie I/O Pins für externe Hardware (Display, Tasten, | + | - Freie I/O Pins für externe Hardware (Display, Tasten, usw.). |
- Vorhandene Betriebspannung (Netzteil, Akku, Batterie). | - 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 | + | 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ähiges 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 | + | 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 bißchen Programmspeicher und RAM als auch 2 freie I/O Pins fürs PIC Miniterminal brauchen. |
= Programm = | = Programm = | ||
Zeile 250: | Zeile 319: | ||
== Allgemeines == | == Allgemeines == | ||
− | Jedes | + | Jedes Programm 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. Hautprogramm (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 | + | Die Struktur eines Programms 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|Prozessor]] und [[#Programmspeicher|Programmspeicher]] |
<center> | <center> | ||
Zeile 258: | Zeile 327: | ||
</center> | </center> | ||
− | Jedes UP kann jederzeit aufgerufen werden, je nach dem was gerade | + | Jedes UP kann jederzeit aufgerufen werden, je nach dem was gerade erledigt 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 | + | 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 | + | Ein ASM Programm (Quellcode) muss in einer Textdatei 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 | + | 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 dann 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. |
− | == | + | == Programmablaufdiagramm (PAD)== |
− | Der | + | Der Programablaufdiagram (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 (außer "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 Computerprogramme 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 Programm auf einem µC so wie gewünscht funktioniert. |
− | Der PAD ist sehr | + | Die Anschriften "Ein" und "Aus" gehören nicht zu Symbolen des PADs und wurden nur zur Erklärung benutzt. |
+ | |||
+ | [[Bild:PAD_beispiel.png|thumb|80px|Beispiel für ein PAD]] | ||
+ | |||
+ | Der PAD ist sehr einfach zu erstellen, weil dafür nur drei Symbole benötigt sind: | ||
<center> | <center> | ||
[[Bild:PAD_kurz.png|Symbole des PAD]] | [[Bild:PAD_kurz.png|Symbole des PAD]] | ||
</center> | </center> | ||
− | |||
− | Als | + | Bei PAD Erstellung darf man selbstverständlich beliebige Symbole verwenden, die man selber am besten versteht. |
+ | |||
+ | Das "Start/Stopp" Symbol bedeutet, dass das gesamte Programm sich im stabilen Zustand befindet und nicht "läuft". Anstatt "Stopp" kann auch "Schlaf" ("sleep") angewendet 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, entweder in der "ja" (J) oder "nein" (N) Richtung. | ||
+ | |||
+ | Als allgemeinnütziges Standard für µCs kann man folgender PAD bezeichnen: | ||
PAD _____ | PAD _____ | ||
Zeile 288: | Zeile 364: | ||
'---------------' | | '---------------' | | ||
| | | | | | ||
− | .---------> | + | .--------->V | |
− | + | ||
| .---------------. | | | .---------------. | | ||
| | Hauptprogramm | | | | | Hauptprogramm | | | ||
Zeile 295: | Zeile 370: | ||
| | | | | | | | ||
| V | | | V | | ||
− | | | | + | | A | |
− | | | + | | / \ > Gesamtes Programm |
− | | /Ende | + | | / \ | |
− | | \ | + | | /Ende \____ | |
+ | | \ ? / J | | | ||
+ | | \ / | | | ||
| \ / | | | | \ / | | | ||
− | |||
| V | | | | V | | | ||
| N| | | | | N| | | | ||
Zeile 315: | Zeile 391: | ||
\_____/ | \_____/ | ||
− | Das | + | Das Hauptprogramm wird in einer endlosen Schleife ausgeführt, die durch die Prüfung "Ende?" unterbrochen werden kann. In dem Fall wird vor dem Beenden des gesamten 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 | + | 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 "?" gekennzeichnet und die Zeichen "V", "A", "<" und ">" zeigen die Richtung des weiteren Verlaufs. Dann sieht der PAD so aus: |
− | + | Ein > Start | |
V - | V - | ||
Initialisierung | | Initialisierung | | ||
Zeile 330: | Zeile 406: | ||
`--------´ | `--------´ | ||
− | + | 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. Außerdem 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: | |
− | PAD2 Ein > Start | + | V |
− | + | 10h->W | |
− | Initialisierung | + | W+B->B |
− | .------->V | + | V |
− | | Hauptprogramm | + | |
− | | V | + | wird im Quellcode so aussehen: |
− | `--------´ | + | |
+ | ........... | ||
+ | movlw 10h | ||
+ | addwf B,1 | ||
+ | ........... | ||
+ | |||
+ | Siehe auch: [[#Das erste Programm|Das erste Programm]] und [[#Mausrad bzw. Drehencoder|Mausrad bzw. Drehencoder]] | ||
+ | |||
+ | 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 Hauptprogramm 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 | + | Für Testprogramme wird meistens folgender PAD angewendet, weil es ziemlich einfach festzustellen |
− | ist (z.B. durch Stromverbrauchmessung des µCs), wann sich | + | 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 | + | PAD3 Ein > Start _ |
− | + | Initialisierung | Gesamtes | |
− | Initialisierung | + | Hauptprogramm _| Programm |
− | + | Schlaf > Aus | |
− | Hauptprogramm | | + | |
− | + | ||
− | + | ||
Und eine batteriebetriebene Uhr wird überwiegend so gestaltet: | Und eine batteriebetriebene Uhr wird überwiegend so gestaltet: | ||
− | PAD4 Ein > Start | + | PAD4 Ein > Start _ |
− | + | Interrupt Initialisierung | | |
− | Interrupt Initialisierung | + | Timer------------------------->V > Gesamtes Programm |
− | Timer------------------------->V | + | Hauptprogramm _| |
− | 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 (außer 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 == | == Hauptprogramm == | ||
− | Wie sein Namen schon vermuten lässt, ist das | + | Wie sein Namen schon vermuten lässt, ist das Hauptprogramm 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 | + | 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 | + | Typischer PAD für ein HP sieht so aus: |
− | + | Haupt .--->V | |
− | + | | UP1 | |
− | + | | UP2 | |
− | + | | ... | |
− | + | | UPn | |
− | + | | V | |
− | + | `----´ | |
− | + | ||
− | + | ||
− | + | ||
− | In den Quellcode wird es so | + | In den Quellcode wird es so eingeschrieben: |
− | + | Haupt call UP1 | |
− | + | call UP2 | |
− | + | ........... | |
− | + | call UPn | |
− | + | goto Haupt | |
− | In der Praxis wird das HP schrittweise erstellt. Am Anfang wird sich nur ein UP im HP befinden und die folgenden kommen nach | + | 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. |
+ | |||
+ | === Multitasking === | ||
+ | |||
+ | Echtes Multitasking ist nur mit mehr (Core)Prozessoren möglich. Bei PIC's ist echtes Multitasking (Parallellaufen) nur mit Timer und ADC Wandler möglich (siehe dazu: http://www.roboternetz.de/community/threads/42200-Frequenzz%C3%A4hler-mit-LPH2673-1-%28zum-Nachbauen%29?highlight=LPH2673-1 ) | ||
+ | |||
+ | Sonst gibt es nur Quasi-Multitasking, das kann auf zwei Weisen realisiert werden: | ||
+ | |||
+ | 1. Alle Tasks werden in fester bzw. per Interrupts bestimmter Reihenfolge auf Bedarf geprüft und nur die mit gesetztem Flag werden vollständig bis zum Ende ausgeführt. Als Beispiel sehe: [[http://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=47685]]. Es kann natürlich mit Proritäten für Interrupts versehen werden. | ||
+ | |||
+ | +<---------------------------------------+ | ||
+ | | | | ||
+ | V | | ||
+ | /\ /\ /\ | | ||
+ | / \ / \ / \ | | ||
+ | / \ J / \ J / \ J | | ||
+ | /F1=0 ?\->+- - ->/Fn=0 ?\->+- - ->/F8=0 ?\->+ | ||
+ | \ / A \ / A \ / A | ||
+ | \ / | \ / | \ / | | ||
+ | \ / | \ / | \ / | | ||
+ | \/ | \/ | \/ | | ||
+ | |N | |N | |N | | ||
+ | V | V | V | | ||
+ | .------. | .------. | .------. | | ||
+ | | | | | | | | | | | ||
+ | | | | | | | | | | | ||
+ | | TUN1 | | | TUNn | | | TUN8 | | | ||
+ | | | | | | | | | | | ||
+ | | | | | | | | | | | ||
+ | '------' | '------' | '------' | | ||
+ | | | | | | | | ||
+ | V | V | V | | ||
+ | | | | | | | | ||
+ | +------+ +------+ +------+ | ||
+ | |||
+ | 2. Der s.g. Taskmanager (meistens HP) gibt jedem Task (meistens UP) feste Zeit und wenn der Task aktiv ist, wird er nach dieser Zeit z.B. per Timer (TMR) unterbrochen und nächster Tast gestartet. Dafür muss immer nach Beenden von jedem Task die Endadresse vom Programm Counter (PC), wo der Task unterbrochen wurde (EA1), auf dem Stapel als nächste Startadresse (AA1) gespeichert werden. Wenn die Zeit für den unterbrochenen Task wieder kommt, wird er ab der unterbrochenen Stelle (AA1) wieder in der ihn zustehenden Zeit ausgeführt, wieder unterbrochen, u.s.w. | ||
+ | |||
+ | Dafür muss der Stapel mit Rücksprungadressen laufend mit "pop" and "push" Befehlen bearbeitet werden, was erst ab PIC18... möglich ist. Bei dieser Methode bei kurzen Laufzeiten für jeden Task, sieht der Beobachter praktisch keine Unterbrechungen von Tasks. Auch hier können Prioritäten benutzt werden, aber z.B. Zeiten für Warteschleifen können nicht genau berechnet werden. Das Unterbrechen von Tasks ist auch per Software-Interrupts möglich. | ||
+ | |||
+ | +--------------------------------------------------------+ | ||
+ | | .-----. .-----. .-----. | | ||
+ | +->|Task1|---->+- - - ->|Taskn|---->+- - - ->|Task8|---->+ | ||
+ | '-----' | '-----' | '-----' | | ||
+ | | | | | | | | ||
+ | V | V | V | | ||
+ | /\ | /\ | /\ | | ||
+ | / \ | / \ | / \ | | ||
+ | / \ J | / \ J | / \ J | | ||
+ | /F1=0 ?\---->+ /Fn=0 ?\---->+ /F8=0 ?\---->+ | ||
+ | \ / A \ / | \ / | | ||
+ | \ / | \ / | \ / | | ||
+ | \ / | \ / | \ / | | ||
+ | \/ | \/ | \/ | | ||
+ | |N | |N | |N | | ||
+ | +---+--+ | +---+--+ | +---+--+ | | ||
+ | | | | | | | | | | | ||
+ | V | | V | | V | | | ||
+ | .-----. | | .-----. | | .-----. | | | ||
+ | +->| AA1 | | | +->| AAn | | | +->| AA8 | | | | ||
+ | | '-----' | | | '-----' | | | '-----' | | | ||
+ | | | | | | | | | | | | | | ||
+ | | V V | | V V | | V V | | ||
+ | | .-----..-----. | | .-----..-----. | | .-----..-----. | | ||
+ | | | || | | | | || | | | | || | | | ||
+ | | | || | | | | || | | | | || | | | ||
+ | | | TMR || Tun |->+ | | TMR || Tun |->+ | | TMR || Tun |->+ | ||
+ | | | || 1 | | | || n | | | || 8 | | ||
+ | | | || | | | | || | | | | || | | | ||
+ | | '----- -----' | '----- -----' | '----- -----' | ||
+ | | | | | | | | | | | ||
+ | | V | V | V | ||
+ | | .-----. | | .-----. | | .-----. | | ||
+ | +--| EA1 |<- - - - + +--| EAn |<- - - - + +--| EA8 |<- - - - + | ||
+ | '-----' '-----' '-----' | ||
== Unterprogramm == | == Unterprogramm == | ||
− | Unterprogramm wird durch übergeordnetes Programmteil (Aufrufer) aufgerufen und nach seinem Ausführen, wird zurück zum Aufrufer gesprungen. Der Rückkehr zum Aufrufer wird durch "return" Befehl, der sich am Ende jedes UPs befinden muss, erreicht. Und das ist der einzige Unterschied zwischen einem HP und einem UP. | + | 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: | Jedes UP hat folgender PAD: | ||
− | vom Aufrufer -------> | + | vom Aufrufer ------------>V |
Tun | Tun | ||
V | V | ||
− | zurück zum Aufrufer <------- | + | zurück zum Aufrufer <----------return bzw. retlw |
− | Ein HP von einem ASM Programm kann in anderem, mehr umfangreichem ASM | + | 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. |
+ | |||
+ | Ein HP von einem ASM Programm kann in anderem, mehr umfangreichem ASM Programm 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 | Haupt1 call UP11 Haupt1 call UP11 | ||
Zeile 431: | Zeile 604: | ||
Jedes UP kann auch von einem anderen übergeordneten UP aufgerufen werden, wenn das was es realisiert, benötigt wird. | 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 | + | 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" | "Cmd" "Data" | ||
− | + | RS=0 RS=1 | |
− | + | ||
V V | V V | ||
`-->V<--´ | `-->V<--´ | ||
Zeile 442: | Zeile 614: | ||
return | return | ||
− | Das wird z.B. | + | Das wird in den Quellcode z.B. so eingeschrieben: |
Cmd bcf RS | Cmd bcf RS | ||
Zeile 454: | Zeile 626: | ||
=== Initialisierung === | === Initialisierung === | ||
− | Damit der PIC ein Programm | + | Damit der PIC ein Programm ausfü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 ==== | ==== Variablen ==== | ||
− | Weil nach dem Einschalten der Spannung im RAM | + | 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 | V | ||
Zeile 464: | Zeile 636: | ||
.-------------------->V | .-------------------->V | ||
RAMClr |Indirekt adressierter Register löschen (INDF) | RAMClr |Indirekt adressierter Register löschen (INDF) | ||
− | |||
| Adresse erhöhen | | Adresse erhöhen | ||
− | + | | Letzte Adresse + 1 = 80h J > Return | |
− | | Letzte Adresse + 1 | + | |
| N | | N | ||
| V | | V | ||
Zeile 482: | Zeile 652: | ||
return | 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 | + | 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 | movlw 0x20 | ||
Zeile 489: | Zeile 659: | ||
| incf FSR,1 | | incf FSR,1 | ||
| btfss FSR,7 | | btfss FSR,7 | ||
− | `-<goto $-3 ; springe | + | `-<goto $-3 ; springe zu aktueller Adresse -3 |
return | return | ||
− | Danach können den | + | Danach können den benötigten Variablen die gewünschten Werte zugewiesen werden: |
movlw 0x3C | movlw 0x3C | ||
Zeile 504: | Zeile 674: | ||
==== I/O Ports ==== | ==== 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 | + | 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 | + | movlw 7 bzw. movlw 7 |
movwf CMCON movwf ADCON1 | movwf CMCON movwf ADCON1 | ||
− | Wenn einige als | + | Wenn einige als analoge Eingänge benutzt werden sollen, müssen die entsprechende Werte dem Datenblatt des jeweiligen PICs entnommen werden. |
− | Danach werden alle Ports nacheinander | + | 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 | clrf PORTA | ||
movlw 0x37 | movlw 0x37 | ||
− | movwf | + | movwf PORTB |
usw. | usw. | ||
− | + | Anschließend 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 | bsf STATUS,RP0 | ||
Zeile 525: | Zeile 695: | ||
bcf STATUS,RP0 | bcf STATUS,RP0 | ||
− | Bei einem Umschalten der Bank können selbstverständlich alle | + | Bei einem Umschalten der Bank können selbstverständlich alle TRISx Register, die in der gleichen Bank liegen, nacheinander beschrieben werden. Siehe : [[#PORTx|PORTx]] und [[#TRISx|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 Planung der Verwendung von PORTA muss immer im Datenblatt geprüft werden, ob sich ein bestimmter Pin für die geplante Anwendung eignet. | ||
==== Hardware ==== | ==== 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, | + | 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 | + | Die externe Hardware muss nach Datenblättern der Hersteller initialisiert werden. |
=== Einlesen === | === Einlesen === | ||
Zeile 537: | Zeile 709: | ||
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: | 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 zulässig ist, dass sich das Bit im Zielregister kurzzeitig ändert, kann man sich die erste Zeile im Quellcode ersparen: | ||
V | V | ||
− | + | Bit im Zielregister löschen | |
− | + | Quellbit = 0 ? J >------. | |
− | Quellbit = 0 ? J> | + | |
N | | N | | ||
V | | V | | ||
Bit im Zielregister setzen | | Bit im Zielregister setzen | | ||
V<-------------´ | V<-------------´ | ||
− | |||
− | |||
bcf Tasten,1 | bcf Tasten,1 | ||
Zeile 552: | Zeile 741: | ||
bsf Tasten,1 | 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 === | === Ausgeben === | ||
Zeile 560: | Zeile 752: | ||
bcf PORTA,4. | bcf PORTA,4. | ||
− | Um ein Byte auszugeben wird | + | Um ein Byte auszugeben wird es zuerst in das W-Register geladen und danach an den Port übergeben, z.B.: |
movlw 0x12 | movlw 0x12 | ||
movwf PORTA | 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|PIC RAM Monitor]] bzw. [[#PIC Trainer|PIC Trainer]]. | ||
=== Pause === | === Pause === | ||
Zeile 586: | Zeile 807: | ||
... | ... | ||
− | Der Befehl goto $+1 bedeutet "springe zur | + | 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: | Für kurze Zeiten werden s.g. Warteschleifen, wie im folgendem PAD benutzt: | ||
Zeile 592: | Zeile 813: | ||
V | V | ||
n * nop | n * nop | ||
− | |||
P0 laden | P0 laden | ||
V<---------. | V<---------. | ||
P0 decrementieren | | P0 decrementieren | | ||
− | + | P0 = 0 ? N >--´ | |
− | P0 = 0 ? N> | + | |
J | J | ||
V | V | ||
Zeile 620: | Zeile 839: | ||
und die Wartezeit (T) in Sekunden: | und die Wartezeit (T) in Sekunden: | ||
− | T = N * ( 4 / Fosc ), für Schätzungen T ~ P0 * ( | + | T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P0 * ( 4 / Fosc ) |
Wobei: | Wobei: | ||
Zeile 626: | Zeile 845: | ||
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) | 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 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). | + | 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: | Für mittlere Wartezeiten z.B. 0,1 Sekunde werden doppelte Warteschleifen verwendet: | ||
Zeile 632: | Zeile 851: | ||
Warte V | Warte V | ||
n * nop | n * nop | ||
− | |||
P1 laden | P1 laden | ||
Warte1 V<-------------. | Warte1 V<-------------. | ||
Zeile 638: | Zeile 856: | ||
Warte0 V<---------. | | Warte0 V<---------. | | ||
P0 decrementieren | | | P0 decrementieren | | | ||
− | + | P0 = 0 ? N >--´ | | |
− | P0 = 0 ? N> | + | |
J | | J | | ||
V | | V | | ||
P1 dekrementieren | | P1 dekrementieren | | ||
− | + | P1 = 0 ? N >------´ | |
− | P1 = 0 ? N> | + | |
J | J | ||
V | V | ||
Zeile 671: | Zeile 887: | ||
und die Wartezeit (T) in Sekunden: | und die Wartezeit (T) in Sekunden: | ||
− | T = N * ( 4 / Fosc ), für Schätzungen T ~ P1 * P0 * ( | + | T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P1 * P0 * ( 4 / Fosc ) |
Wobei: | Wobei: | ||
Zeile 685: | Zeile 901: | ||
Warte V | Warte V | ||
n * nop | n * nop | ||
− | |||
P2 laden | P2 laden | ||
Warte2 V<-----------------. | Warte2 V<-----------------. | ||
Zeile 693: | Zeile 908: | ||
Warte0 V<---------. | | | Warte0 V<---------. | | | ||
P0 decrementieren | | | | P0 decrementieren | | | | ||
− | + | P0 = 0 ? N >--´ | | | |
− | P0 = 0 ? N> | + | |
J | | | J | | | ||
V | | | V | | | ||
− | P1 | + | P1 decrementieren | | |
− | + | P1 = 0 ? N >------´ | | |
− | P1 = 0 ? N> | + | |
J | | J | | ||
V | | V | | ||
P2 dekrementieren | | P2 dekrementieren | | ||
− | + | P2 = 0 ? N >----------´ | |
− | P2 = 0 ? N> | + | |
J | J | ||
V | V | ||
Zeile 735: | Zeile 947: | ||
und die Wartezeit (T) in Sekunden: | und die Wartezeit (T) in Sekunden: | ||
− | T = N * ( 4 / Fosc ), für Schätzungen T ~ P2 * P1 * P0 * ( | + | T = N * ( 4 / Fosc ), für Schätzungen T ~ 3 * P2 * P1 * P0 * ( 4 / Fosc ) |
Wobei: | Wobei: | ||
Zeile 743: | Zeile 955: | ||
Für solche 3-fache Schleifen (als UP) die Wartezeit beträgt min. 27 und max. ca. 50 000 000 Prozessortakten (ohne "nop"s). | 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 | + | 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 === | === 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 | + | 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 denen ist, dass die Sprungtabellen steuern den Programmlauf 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 | + | Beide werden in Programmspeicher erstellt. Sie können nur bis zu 256 Speicherstellen belegen, da in den W-Register auch nur so viel verschiedenen 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: | Eine Sprungtabelle wird so aufgebaut: | ||
− | + | org (XX-1)FF <--- eine Direktive für Assemblerprogramm, wo es | |
die Tabelle im Programmspeicher plazieren soll | die Tabelle im Programmspeicher plazieren soll | ||
Adresse Inhalt | Adresse Inhalt | ||
Zeile 775: | Zeile 1.001: | ||
movwf PCLATH | movwf PCLATH | ||
movf TWert,0 | 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|PIC Trainer]]. | |
− | + | ||
− | |||
Eine Wertetabelle wird so aufgebaut: | Eine Wertetabelle wird so aufgebaut: | ||
− | + | org (XX-1)FF <--- eine Direktive für Assemblerprogramm, wo es | |
die Tabelle im Programmspeicher plazieren soll | die Tabelle im Programmspeicher plazieren soll | ||
Adresse Inhalt | Adresse Inhalt | ||
Zeile 806: | Zeile 1.034: | ||
wobei: | 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 3, 0xE8 ; 0x00 | ||
+ | dt 5, 0xB7 ; 0x02 | ||
+ | dt 'M','H','z',0 ; 0x04 | ||
+ | dt ' ',' ','W','A','I','T',0 ; 0x08 | ||
+ | dt 'C','o','n','s','t',' ','X',0 ; 0x0F | ||
+ | dt 'C','=',0 ; 0x17 | ||
+ | dt 'L','=',0 ; 0x1A | ||
+ | dt 'F','=',0 ; 0x1D | ||
+ | dt 'O','K',0 ; 0x20 | ||
+ | usw. | ||
− | + | Das Lesen eines Strings muss ab entsprechendem Wert im W-Register (z.B. für "MHz" -> 0x04) anfangen und auf dem Wert 0 (Null) enden, der hier als Stringende dient. Einfacher ist, wenn alle Strings gleich lang sind (wie z.B. in einem Zeichengenerator für Grafikdisplay). | |
− | + | Bei Tabellen, die länger als 256 Byte sind, muss immer PCLATH an den Seitengrenzen korriegiert werden, wie auf der 3. Seite der Applikation Note AN556 vom Microchip vorgestellt: http://ww1.microchip.com/downloads/en/AppNotes/00556e.pdf . | |
− | + | === 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 der sogenannten 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 einen Interrupt auslösen zu können, muss er vorher, durch beschreiben nötigen Register (INTCON, PIR, usw.) vorbereitet werden. Siehe hierzu: [[#INTCON|INTCON]] und [[#Interrupts|Interrupts]] | ||
+ | |||
+ | ==== Quellen ==== | ||
+ | |||
+ | Es gibt folgende Interrupt-Quellen: | ||
+ | |||
+ | - interne Hardware (z.B. ein Timer) | ||
+ | |||
+ | - externe Hardware (z.B. eine Taste) | ||
+ | |||
+ | Die PICs der Mid-Range Familie haben im Programmspeicher nur eine Adresse (s.g. Interrupt Vektor 0x0004), wohin im Falle eines Interrupts, gesprungen wird. Um den Verursacher des Interrupts zu finden, hat jede Quelle ein eigenes Interrupt Flag (IF), das gesetzt wird, wenn der Interrupt ausgelöst wird. Somit kann in der ISR nach der Prüfung der IFs auf jeden Interrupt gezielt reagiert werden. Siehe hierzu: [[#Interrupts|Interrupts]]. | ||
+ | |||
+ | ==== Interrupt Service Routine ==== | ||
+ | |||
+ | Die Interrupt Service Routine ist ein besonderes UP das für Basic-Line und Mid-Range 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|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: [[http://www.roboternetz.de/phpBB2/viewtopic.php?t=13685]] und [http://www.roboternetz.de/phpBB2/viewtopic.php?t=22749] | ||
== Vorlage für MPASM == | == Vorlage für MPASM == | ||
− | Diese Vorlage ist nur für ASM Programme ohne Interrupts | + | Diese Vorlage ist nur für ASM Programme ohne Interrupts geeignet: |
list P=12F629 ; Prozessor definieren | list P=12F629 ; Prozessor definieren | ||
Zeile 881: | Zeile 1.135: | ||
#define _INT GPIO,4 | #define _INT GPIO,4 | ||
#define _RL GPIO,5 | #define _RL GPIO,5 | ||
+ | usw. | ||
SecondL equ 0x20 ; Variablen definieren (Register benennen) | SecondL equ 0x20 ; Variablen definieren (Register benennen) | ||
SecondH equ 0x21 | SecondH equ 0x21 | ||
Zeile 887: | Zeile 1.142: | ||
StundeL equ 0x24 | StundeL equ 0x24 | ||
StundeH equ 0x25 | StundeH equ 0x25 | ||
+ | usw. | ||
org 0x0000 ; bis da sind MPASM Direktiven | org 0x0000 ; bis da sind MPASM Direktiven | ||
; hier fängt das gesamte ASM Programm an | ; hier fängt das gesamte ASM Programm an | ||
− | call Init ; rufe UP Init (Initialisierung) auf | + | call Init ; rufe UP "Init" (Initialisierung) auf |
− | Haupt ............ ; Hauptprogramm als endlose Schleife | + | Haupt ............ ; hier fängt das Hauptprogramm als endlose Schleife an |
Eigener Code | Eigener Code | ||
............ | ............ | ||
− | goto Haupt ; gehe zum Anfang des | + | goto Haupt ; hier endet das HP, gehe zum Anfang des HPs (zurück) |
UP1 ............ ; Unterprogramme | UP1 ............ ; Unterprogramme | ||
Eigener Code | Eigener Code | ||
Zeile 912: | Zeile 1.168: | ||
bcf STATUS,RP0 ; auf Bank0 umschalten | bcf STATUS,RP0 ; auf Bank0 umschalten | ||
movlw 7 ; schalte Komparator aus | movlw 7 ; schalte Komparator aus | ||
− | movwf CMCON ; und | + | movwf CMCON ; und definiere GPIO 0-2 als digitale I/Os |
............ | ............ | ||
eigener Code | eigener Code | ||
Zeile 933: | Zeile 1.189: | ||
Bei sehr vielen Variablen sind aber die Registeradressen nicht so übersichtlich. | 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 Programm == | |
− | + | 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: | Dafür nötige Hardware zeigt folgende Skizze: | ||
.-----------------------------------------------. | .-----------------------------------------------. | ||
− | |||
− | |||
| | | | | | ||
| PIC12F629 | | | PIC12F629 | | ||
− | |||
− | |||
| | | | | | ||
| GPIO,3 GPIO,4 GPIO,5 GPIO,2 GPIO,1 GPIO,0| | | GPIO,3 GPIO,4 GPIO,5 GPIO,2 GPIO,1 GPIO,0| | ||
'-----------------------------------------------' | '-----------------------------------------------' | ||
4| 3| 2| 5| 6| 7| | 4| 3| 2| 5| 6| 7| | ||
− | |||
− | |||
− | |||
| | .-. .-. .-. .-. | | | .-. .-. .-. .-. | ||
− | | | | + | | | R | | R | | R | | R | | |
| | 470| | 470| | 470| | 470| | | | | 470| | 470| | 470| | 470| | | ||
| | '-' '-' '-' '-' | | | '-' '-' '-' '-' | ||
− | |||
\ o \ o | | | | | \ o \ o | | | | | ||
\ \ V -> V -> V -> V -> | \ \ V -> V -> V -> V -> | ||
\. \. - - - - | \. \. - - - - | ||
− | + | _T1 o _T2 o _L1 | _L2 | _L3 | _L4 | | |
| | | | | | | | | | | | | | ||
+-------+-------+---+---+-------+-------+ | +-------+-------+---+---+-------+-------+ | ||
Zeile 985: | Zeile 1.277: | ||
GND | GND | ||
− | Weil der Pin 4 (GPIO,3) nur einen open drain Ausgang hat (kann nur an GND schalten) wird er hier als Eingang benutzt. | + | 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: | Jetzt muss die Idee vom Programmierer in ein PAD verfasst werden, z.B. solcher: | ||
Start | Start | ||
− | |||
Initialisierung | Initialisierung | ||
.-------------->V | .-------------->V | ||
− | | | + | | _T1=0 gedrückt ? N >----. |
| J | | | J | | ||
| V | | | V | | ||
| links->rechts "wandern" | | | links->rechts "wandern" | | ||
| V<------------´ | | V<------------´ | ||
− | | | + | | _T2=0 gedrückt ? N >----. |
| J | | | J | | ||
| V | | | V | | ||
Zeile 1.004: | Zeile 1.295: | ||
| V<------------´ | | 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 Quellcode einspaltig. Um sich die "Übersetzung" des PADs in den Quellcode 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 Quellcode 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|Vorlage für MPASM]] | ||
+ | |||
+ | Fürs "Warten" wird eine dreifache Warteschleife verwendet. Siehe: [[#Pause|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|Einlesen]] und [[#Ausgeben|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. | ||
+ | |||
+ | Bei Erstellung von ASM Programmen, um sich das Kompilieren zu vereinfachen, kann folgende Prozedur verwendet werden: | ||
+ | |||
+ | Zuerst wird in gleichem Verzeichnis, wo sich die Sammlung Unter/Programme befindet, eine Textdatei (z.B. "Test.txt") erstellt, wo ein einspaltiges PAD mit Pfeilen nach oben und unten geschrieben/skizziert wird. In der Datei wird auch ganz oben ständig aktualisierte Liste aller Register erstellt, die in diesem Unter/Programm verwendet sind. | ||
+ | |||
+ | Wenn das PAD fertig ist, wird diese Datei mit gleichem Namen als "*.asm" Datei (z.B. "Test.asm")gespeichert und alle PAD "Symbole" mit Befehlen für bestimmten PIC/Assemblerprogramm ersetzt (z.B. für MPASM werden alle Register benannt). | ||
+ | |||
+ | Bei jeder Änderung wird sie gleich in beiden Dateien gemacht, damit sie am Ende wirklich aktuell sind. Letztendlich haben wir dann beide, wenn wir später etwas ändern müssen. Dank dessen braucht man ausser eventuellen Namen der UPs keine Kommentare im Programm schreiben, was seine Erstellung deutlich beschleinigt. | ||
+ | |||
+ | 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ünschten Programme in ein neues Programm einzubinden ist, 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ünschten Programme als "*.asm" Dateien mit der Direktive "include *.asm" am Ende des neuen Programms vor der Direktive "end" einzubinden. Die einbindende sowie alle einzubindenden Dateien müssen sich 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 eingebundenen Programmen keine "org" Direktiven gibt, werden die weiteren 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|PIC RAM Monitor]] bzw. [[#PIC Trainer|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 == | == 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 Unterprogramme meistens mit dem "call"-Befehl. Es "lohnt sich" erst ein Codefragment als UP zu definieren wenn es min. 2 mal aufgerufen wird. | ||
+ | |||
+ | Ein "goto"-Befehl ist zu diesem Zweck nur geeignet, wenn der Prozessor zum letzten Aufruf 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 Werte vor dem Aufruf des UPs in die benötigte Anzahl von Register einschreben und dann das Unterprogramm mit "call" aufrufen. Das Unterprogramm verwendet dann die Werte aus dem Register. Siehe: [[#Pause|Pause]] | ||
+ | |||
+ | Um gleiche Vorgänge in mehreren Registern durchzuführen (z.B. löschen) ist die Anwendung von Schleifen geeignet (mit bestimmter Anzahl der Abläufe und indirekter Adressierung). Siehe: [[#Variablen|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 <i>gesamten</i> 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|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|Matrix Display]] oder "ATmp" in [[#Hex Dec Wandlung|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|EEPROM]] und [[#Tabellen|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. | ||
+ | |||
+ | Man kann auch eine Bearbeitung eines UPs um 4 Takte beschleunigen ("call" + "return"), indem man bei dem letzten Aufruf eines UPs (direkt vorm "return") anstatt "call UP", "goto UP" anwendet, da das UP sowieso mit "return" bzw. "retlw" endet. | ||
+ | |||
+ | Ursprünglich: | ||
+ | |||
+ | movlw 0x28 | ||
+ | call Cmd | ||
+ | movlw 0x0C | ||
+ | call Cmd | ||
+ | movlw 6 | ||
+ | call Cmd <-- | ||
+ | return | ||
+ | |||
+ | Optimiert: | ||
+ | |||
+ | movlw 0x28 | ||
+ | call Cmd | ||
+ | movlw 0x0C | ||
+ | call Cmd | ||
+ | movlw 6 | ||
+ | goto Cmd <-- | ||
+ | |||
+ | Nebenbei sparrt man sich eine Zeile im Quellcode und eine Spaicherstelle im Programmspeicher. | ||
+ | |||
+ | = Basic-Line (PIC12...) & Mid-Range (PIC16...)= | ||
+ | |||
+ | Zu Basic-Line gehören alle PIC12... mit 12-bit und zu Mid-Range alle PIC16... mit 14-bit langen Befehlen. Weil beide Familien gleichen Befehlsatz haben, werden sie hier gemeinsam behandelt. Sie haben insgesamt 35 Befehle. | ||
− | + | Alle Befehle aus der Tabelle, ausser mit "*" gekenzeichneten, gehören zum Befehlsatz den High-End PIC18... dazu. | |
== Kurzübersicht Assembler Befehle == | == Kurzübersicht Assembler Befehle == | ||
Zeile 1.036: | Zeile 1.899: | ||
|CLRF||Clear f | |CLRF||Clear f | ||
|- | |- | ||
− | |CLRW||Clear W | + | |*CLRW||Clear W |
|- | |- | ||
|CLRWDT||Clear Watchdog Timer | |CLRWDT||Clear Watchdog Timer | ||
Zeile 1.080: | Zeile 1.943: | ||
|RETURN||Return from Subroutine | |RETURN||Return from Subroutine | ||
|- | |- | ||
− | |RLF||Rotate Left f through Carry | + | |*RLF||Rotate Left f through Carry |
|- | |- | ||
− | |RRF||Rotate Right f through Carry | + | |*RRF||Rotate Right f through Carry |
|- | |- | ||
|SLEEP||Go into standby mode | |SLEEP||Go into standby mode | ||
Zeile 1.101: | Zeile 1.964: | ||
==Ausführliche Beschreibung zu den Befehlen== | ==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]] | Eine ausgegliederte Beschreibung der Befehle findet sich unter [[PIC Assemblerbefehle]] | ||
Erklärungen zu den Verwendeten Platzhaltern: | Erklärungen zu den Verwendeten Platzhaltern: | ||
− | *'''k''' stellt einen fest definierten Wert da. z.B. <tt>0x20</tt>, <tt>d'42'</tt> oder <tt>b'00101010'</tt> | + | *'''k''' stellt einen fest definierten Wert da. z.B. hexadezimal <tt>0x20</tt> bzw. <tt>20</tt>, dezimal <tt>d'42'</tt> bzw. <tt>.42</tt> oder binär <tt>b'00101010'</tt> |
*'''W''' steht für das W-Register. | *'''W''' steht für das W-Register. | ||
*'''d''' steht für ''destination'' (Ziel). Im code wird d durch ein <tt>w</tt> bzw. <tt>0</tt> (der Wert wird in das W-Register gespeichert ) oder <tt>f</tt> bzw. <tt>1</tt> (der Wert wird in das davor definierte Register gespeichert) | *'''d''' steht für ''destination'' (Ziel). Im code wird d durch ein <tt>w</tt> bzw. <tt>0</tt> (der Wert wird in das W-Register gespeichert ) oder <tt>f</tt> bzw. <tt>1</tt> (der Wert wird in das davor definierte Register gespeichert) | ||
Zeile 1.113: | Zeile 1.984: | ||
*<tt>Schreibmaschinenstil</tt> bedeutet, dass es so im Quellcode geschrieben werden kann. | *<tt>Schreibmaschinenstil</tt> bedeutet, dass es so im Quellcode geschrieben werden kann. | ||
− | <b> ADDLW k </b> <i style="color:grey;"> | + | <b> ADDLW k </b> <i style="color:grey;">ADD Literal and W - Addiere Zahl (k) und W</i><hr> |
− | :Es wird die Rechenoperation <math> | + | :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|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] |
− | <b> ADDWF R,d </b> <i style="color:grey;"> | + | <b> ADDWF R,d </b> <i style="color:grey;">ADD W and F - Addiere W und f </i><hr> |
− | :Es wird die Rechenoperation <math> | + | :Es wird die Rechenoperation <math>W+R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] |
− | <b> | + | <b> ANDLW k</b> <i style="color:grey;">AND Literal with W</i><hr> |
− | :Es wird bitweise die logische Funktion <math> | + | :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: | :Zur Verdeutlichung der Operation: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
− | + | 11001010 ;k | |
− | + | 10101100 ;W-Register | |
− | ---- and | + | -------- and |
− | + | 10001000 ;W-Register | |
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | + | :Dieser Befehl wird zum Löschen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflussen. Die bits, die gelöscht werden sollen, werden in W-Register mit 0 und die unbeeinflußte mit 1 angegeben. | |
− | : | + | |
− | <b>BCF R,b</b> <i style="color:grey;">Bit Clear | + | <b> ANDWF R,d </b> <i style="color:grey;">AND W with F</i><hr> |
+ | :Es wird bitweise die logische Funktion <math>W\ and\ R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Vergleiche mit ANDLW. | ||
+ | |||
+ | <b>BCF R,b</b> <i style="color:grey;">Bit Clear F - Bit b im R wird gelöscht</i><hr> | ||
:Mit dem Befehl <tt>BCF</tt> wird das Bit '''b''' im Register '''R''' gelöscht. Ein Beispiel: | :Mit dem Befehl <tt>BCF</tt> wird das Bit '''b''' im Register '''R''' gelöscht. Ein Beispiel: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
Zeile 1.140: | Zeile 2.013: | ||
</dl></dd><!-- zum einrücken da--> | </dl></dd><!-- zum einrücken da--> | ||
− | <b>BSF R,b</b> <i style="color:grey;">Bit Set | + | <b>BSF R,b</b> <i style="color:grey;">Bit Set F - Bit b im R wird gesetzt</i><hr> |
:Mit dem Befehl <tt>BSF</tt> wird das Bit '''b''' im Register '''R''' gesetzt. Ein Beispiel: | :Mit dem Befehl <tt>BSF</tt> wird das Bit '''b''' im Register '''R''' gesetzt. Ein Beispiel: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
Zeile 1.148: | Zeile 2.021: | ||
</dl></dd><!-- zum einrücken da--> | </dl></dd><!-- zum einrücken da--> | ||
− | <b>BTFSC R,b</b> <i style="color:grey;">Bit Test | + | <b>BTFSC R,b</b> <i style="color:grey;">Bit Test F, Skip if Clear - Wenn das Bit b im Register R 0 ist, überspringe den nächsten Befehl</i><hr> |
:Mit dem Befehl <tt>BTFSC</tt> kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit '''b''' im Register '''R''' 0 ist, wird der nächste Befehl übersprungen. Ein Beispiel: | :Mit dem Befehl <tt>BTFSC</tt> kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit '''b''' im Register '''R''' 0 ist, wird der nächste Befehl übersprungen. Ein Beispiel: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
Zeile 1.154: | Zeile 2.027: | ||
BTFSC W,0 ;es wird bit 0 geprüft. | BTFSC W,0 ;es wird bit 0 geprüft. | ||
;wenn es 0 ist, wird der nächste Befehl übersprungen | ;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_EINS ;springt zur Marke "IST_EINS" <- in diesem Fall wird dieser |
+ | ;Sprungbefehl ausgeführt. | ||
goto IST_NULL ;springt zur Marke "IST_NULL" | goto IST_NULL ;springt zur Marke "IST_NULL" | ||
</dl></dd><!-- zum einrücken da--> | </dl></dd><!-- zum einrücken da--> | ||
− | <b>BTFSS R,b</b> <i style="color:grey;">Bit Test | + | <b>BTFSS R,b</b> <i style="color:grey;">Bit Test F, Skip if Set - Wenn das Bit b im Register R 1 ist, überspringe den nächsten Befehl</i><hr> |
:Mit dem Befehl <tt>BTFSS</tt> kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit '''b''' im Register '''R''' 1 ist, wird der nächste Befehl übersprungen. Ein Beispiel: | :Mit dem Befehl <tt>BTFSS</tt> kann eine Verzweigung im Programmablauf bewirkt werden. Wenn das Bit '''b''' im Register '''R''' 1 ist, wird der nächste Befehl übersprungen. Ein Beispiel: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
Zeile 1.171: | Zeile 2.045: | ||
− | <b>CALL</b> <i style="color:grey;"> | + | <b>CALL</b> <i style="color:grey;">CALL Subroutine - Rufe Unterprogramm auf</i><hr> |
− | :Mit dem <tt>CALL</tt> Befehl wird ein Unterprogramm aufgerufen. Mit | + | :Mit dem <tt>CALL</tt> Befehl wird ein Unterprogramm aufgerufen. Mit <tt>RETURN</tt> oder <tt>RETLW</tt>-Befehl wird das Unterprogramm beendet und man kehrt zum Befehl nach dem <tt>CALL</tt>-Befehl zurück. Das Unterprogramm wird so definiert, dass im Quellcode der Name des Unterprogramms nicht eingerückt steht. Ein Beispiel: |
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
movlw d'13' ;in das W-Register wird 13d geladen | movlw d'13' ;in das W-Register wird 13d geladen | ||
− | CALL | + | CALL UP1 ;es wird das Unterprogramm "UP1" aufgerufen |
movwf ergebnis ;das W-Register wird in das Register "ergebnis" kopiert. | 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 | |
− | + | ||
</dl></dd><!-- zum einrücken da--> | </dl></dd><!-- zum einrücken da--> | ||
− | <b>CLRF R</b> <i style="color:grey;"> | + | <b>CLRF R</b> <i style="color:grey;">CLeaR F- Schreibe 0 in das Register R</i><hr> |
:Das Register '''R''' wird mit Nullen gefüllt (gelöscht). | :Das Register '''R''' wird mit Nullen gefüllt (gelöscht). | ||
− | <b>CLRW</b> <i style="color:grey;"> | + | <b>CLRW</b> <i style="color:grey;">CLeaR W - Schreibe 0 in W</i><hr> |
− | :Das W-Register | + | :Das W-Register wird mit Nullen gefüllt (gelöscht). |
− | <b>CLRWDT</b> <i style="color:grey;"> | + | <b>CLRWDT</b> <i style="color:grey;">CLeaR WatchDog Timer - Setzt den Watchdog-Timer zurück</i><hr> |
: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. | :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. | ||
− | <b>COMF R,d</b> <i style="color:grey;"> | + | <b>COMF R,d</b> <i style="color:grey;">COMplement F - negiere alle bits im Register R</i><hr> |
− | :Von der Binärzahl im Register '''R''' werden | + | :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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Ein kleines Beispiel: aus <tt>AAh</tt> (<tt>10101010b</tt>) wird <tt>55h</tt> (<tt>01010101b</tt>). |
− | <b>DECF R,d</b> <i style="color:grey;"> | + | <b>DECF R,d</b> <i style="color:grey;">DECrement F - Subtrahiert 1 vom Regiser f</i><hr> |
− | :Vom Wert des Registers '''R''' wird 1 subtrahiert und das Ergebnis entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | + | :Vom Wert des Registers '''R''' wird 1 subtrahiert und das Ergebnis entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). |
+ | :Weil es keine "echte" Substraktion ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht. | ||
− | <b>DECFSZ R,d</b> <i style="color:grey;"> | + | <b>DECFSZ R,d</b> <i style="color:grey;">DECrement F, Skip if Zero - Subtrahiert 1 vom Regiser f, überspringe wenn 0</i><hr> |
:Vom Wert des Registers '''R''' wird 1 subtrahiert und das Ergebnis entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). 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. | :Vom Wert des Registers '''R''' wird 1 subtrahiert und das Ergebnis entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). 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. | ||
− | <b>GOTO</b> <i style="color:grey;"> | + | <b>GOTO</b> <i style="color:grey;">GO TO address - Gehe zu Adresse/Sprungmarke</i><hr> |
: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. | :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. | ||
− | + | :Um die Anzahl den Sprungmarken im Programm zu verringern, kann auch indirekte Adressierung von Sprungzielen mit "GOTO $+n" bzw. "GOTO $-n" benutzt werden. Das Symbol "$" bedeutet immer die aktuelle Adresse vom Programmzähler (PC). Das Assemblerprogramm, das sowieso jede Sprungmarke in entsprechende absolute Adresse wandelt, erzeugt in diesem Fall die Zieladresse als "PC+n" bzw. "PC-n", wobei "n" immer als hexadezimale Zahl genommen wird. | |
− | + | ||
− | <b> | + | :Der Befehl "goto $+1" verbraucht nur Zeit von zwei "nop". |
+ | |||
+ | :Beispiele dazu sind in [[#Pause|Pause]]. | ||
+ | |||
+ | <b>INCF R,d</b> <i style="color:grey;">INCrement F - Addiere 1 zum Register f</i><hr> | ||
+ | :Zum Wert des Registers '''R''' wird 1 addiert und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
+ | :Weil es keine "echte" Addition ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht. | ||
+ | |||
+ | <b>INCFSZ R,d</b> <i style="color:grey;">INCrement F, Skip if Zero - Addiere 1 zum Regiser f, überspringe wenn 0</i><hr> | ||
:Zum Wert des Registers '''R''' wird 1 addiert und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). 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. | :Zum Wert des Registers '''R''' wird 1 addiert und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). 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. | ||
− | <b> IORLW k</b> <i style="color:grey;">OR | + | <b> IORLW k</b> <i style="color:grey;">Inclusive OR Literal with W</i><hr> |
− | :Es wird bitweise die logische Funktion <math> | + | :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 | + | :Zur Verdeutlichung der Operation: |
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
− | + | 11001010 ;k | |
− | + | 10101100 ;W-Register | |
− | ---- or | + | -------- or |
− | + | 11101110 ;W-Register | |
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b> IORWF R,d </b> <i style="color:grey;">OR W with | + | :Dieser Befehl wird zum Setzen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflussen. Die bits, die gesetzt werden sollen, werden in W-Register mit 1 und die unbeeinflußte mit 0 angegeben. |
− | :Es wird bitweise die logische Funktion <math>W\ or\ R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Vergleiche IORLW | + | |
+ | <b> IORWF R,d </b> <i style="color:grey;">Inclusive OR W with F</i><hr> | ||
+ | :Es wird bitweise die logische Funktion <math>W\ or\ R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Vergleiche mit IORLW. | ||
− | <b>MOVF R,d</b> <i style="color:grey;"> | + | <b>MOVF R,d</b> <i style="color:grey;">MOVe F - Bewege f</i><hr> |
− | :Das Register R wird in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder wieder in R kopiert ('''d'''=<tt>F</tt>=<tt>1</tt>). Letzteres mag sinnlos scheinen, ist aber nützlich, da durch den Befehl das Z-Bit im STATUS- | + | :Das Register R wird in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder wieder in R kopiert ('''d'''=<tt>F</tt>=<tt>1</tt>). Letzteres mag sinnlos scheinen, ist aber nützlich, da durch den Befehl das Z-Bit im STATUS-Register gesetzt wird, falls R Null ist. |
− | <b>MOVLW k</b> <i style="color:grey;"> | + | <b>MOVLW k</b> <i style="color:grey;">MOVe Literal to W - Bewege Zahl (k) in W-Register</i><hr> |
− | :Der festgelegte Wert k wird in das W-Register | + | :Der festgelegte Wert k wird in das W-Register geladen. |
− | + | <dl><dd><!-- zum einrücken da--> | |
− | <b>MOVWF R</b> <i style="color:grey;"> | + | MOVLW 0xA3 ;ins W-Register wird eine Zahl A3h geladen |
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Dieser Befehl wird auch bei indirekter Adressierung zum laden der absoluter Adresse ins "FSR" Register benutzt. Beispiel: | ||
+ | :Der Register "A3" wurde mit "A3 equ 23" definiert. | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | MOVLW A3 ;ins W-Register wird die absolute Adresse 23h vom "A3" geladen | ||
+ | movwf FSR ;diese Adresse wird jetzt ins Register "FSR" kopiert | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | <b>MOVWF R</b> <i style="color:grey;">MOVe W to F- Bewege W-Register in das Register F</i><hr> | ||
:Das W-Register wird in das Register '''R''' kopiert. | :Das W-Register wird in das Register '''R''' kopiert. | ||
− | <b>NOP</b> <i style="color:grey;">No | + | <b>NOP</b> <i style="color:grey;">No OPeration - Kein Befehl zum Ausführen (warte)</i><hr> |
− | :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. | + | :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|Pause]] |
− | <b>RETFIE</b> <i style="color:grey;"> | + | <b>RETFIE</b> <i style="color:grey;">RETurn From Interrupt Enable - Kehre zurück aus der Unterbrechung, erlaube Unterbrechungen</i><hr> |
: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 | Interrupt]] | :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 | Interrupt]] | ||
− | <b>RETLW k</b> <i style="color:grey;"> | + | <b>RETLW k</b> <i style="color:grey;">RETurn with Literal in W - Kehre zurück mit Zahl k im W-Register</i><hr> |
− | :Wurde ein Programmteil mit dem Befehl <tt>CALL</tt> aufgerufen, dann springt man mit dem Befehl <tt>RETLW</tt> zurück in die nächste Zeile nach der Zeile aus der das <tt>CALL</tt> Befehl ausgeführt wurde. Der in k angegebene Wert wird dabei in das W-Register geschrieben. Dieser Befehl wird vor allem für s.g Wertetabellen (eng: lookup tables) verwendet. | + | :Wurde ein Programmteil mit dem Befehl <tt>CALL</tt> aufgerufen, dann springt man mit dem Befehl <tt>RETLW</tt> zurück in die nächste Zeile nach der Zeile aus der das <tt>CALL</tt> 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. |
− | <b>RETURN</b> <i style="color:grey;"> | + | <b>RETURN</b> <i style="color:grey;">RETURN from Subroutine - Kehre zurück zum Aufrufer</i><hr> |
:Wurde ein Programmteil mit dem Befehl <tt>CALL</tt> aufgerufen, dann springt man mit dem Befehl <tt>RETURN</tt> zurück zu der nächsten Zeile nach der Zeile aus der das <tt>CALL</tt> Befehl ausgeführt wurde. | :Wurde ein Programmteil mit dem Befehl <tt>CALL</tt> aufgerufen, dann springt man mit dem Befehl <tt>RETURN</tt> zurück zu der nächsten Zeile nach der Zeile aus der das <tt>CALL</tt> Befehl ausgeführt wurde. | ||
− | <b>RLF R,d</b> <i style="color:grey;">Rotate Left | + | <b>RLF R,d</b> <i style="color:grey;">Rotate Left F through carry - Rotiere das Register f mithilfe des Carry-bits nach links</i><hr> |
:Alle Bits im Register '''R''' werden um eine Position nach links verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | :Alle Bits im Register '''R''' werden um eine Position nach links verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
− | + | <dl><dd><!-- zum einrücken da--> | |
+ | |<--- Register R --->| | ||
+ | |c|<-7<-6<-5<-4<-3<-2<-1<-0 | ||
+ | V A | ||
+ | |________________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
:Zur Verdeutlichung: | :Zur Verdeutlichung: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
− | |C| |-Register R-| ;C steht für das Carry-bit, STATUS,C | + | |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 | + | c 7 6 5 4 3 2 1 0 ;vor der Rotation |
− | 7 6 5 4 3 2 1 0 c ;nach | + | 7 6 5 4 3 2 1 0 c ;nach der Rotation |
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b>RRF R,d</b> <i style="color:grey;">Rotate Right | + | <b>RRF R,d</b> <i style="color:grey;">Rotate Right F through carry - Rotiere das Register f mithilfe des Carry-bits nach rechts</i><hr> |
:Alle Bits im Register '''R''' werden um eine Position nach rechts verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | :Alle Bits im Register '''R''' werden um eine Position nach rechts verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
− | + | <dl><dd><!-- zum einrücken da--> | |
+ | |<--- Register R --->| | ||
+ | |c|->7->6->5->4->3->2->1->0 | ||
+ | A V | ||
+ | |________________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
:Zur Verdeutlichung: | :Zur Verdeutlichung: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
− | |C| |-Register R-| ;C steht für das Carry-bit, STATUS,C | + | |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 | + | C 7 6 5 4 3 2 1 0 ;vor der Rotation |
− | 0 C 7 6 5 4 3 2 1 ;nach | + | 0 C 7 6 5 4 3 2 1 ;nach der Rotation |
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b>SLEEP </b> <i style="color:grey;">Go into standby mode - Versetze den Mirokontroller in Bereitschaftsmodus</i><hr> | + | <b>SLEEP </b> <i style="color:grey;">Go into standby mode - Versetze den Mirokontroller in Bereitschaftsmodus (Schlaf)</i><hr> |
: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. | :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. | ||
− | <b> SUBLW k </b> <i style="color:grey;"> | + | <b> SUBLW k </b> <i style="color:grey;">SUBtract W from Literal - Ziehe W von Zahl (k) ab</i><hr> |
: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|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | :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|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
− | <b> SUBWF R,d </b> <i style="color:grey;"> | + | <b> SUBWF R,d </b> <i style="color:grey;">SUBtract W from F - Ziehe W von f ab</i><hr> |
:Es wird die Rechenoperation <math>R-W</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | :Es wird die Rechenoperation <math>R-W</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
Zeile 1.283: | Zeile 2.184: | ||
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b>SWAPF R,d </b> <i style="color:grey;"> | + | <b>SWAPF R,d </b> <i style="color:grey;">SWAP nibbles in F - Vertausche die Halbbytes (Nibbles)</i><hr> |
− | :Es werden die höheren 4 | + | :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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). |
:Beispiel: | :Beispiel: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
Zeile 1.294: | Zeile 2.195: | ||
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b> XORLW k</b> <i style="color:grey;"> | + | :Der Befehl wird immer zum Sichern und Wiederherstellen des STATUS-Registers am Anfang und Ende einer ISR (interrupt service routine) verwendet, da er das STATUS-Register nicht beeinflußt (verändert keine STATUS bits). |
− | :Es wird bitweise die logische Funktion <math> | + | |
+ | <b> XORLW k</b> <i style="color:grey;">EXclusive OR Literal with W</i><hr> | ||
+ | :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: | :Zur Verdeutlichung der Operation: | ||
<dl><dd><!-- zum einrücken da--> | <dl><dd><!-- zum einrücken da--> | ||
− | + | 11001010 ;k | |
− | + | 10101100 ;W-Register | |
− | ---- xor | + | -------- xor |
− | + | 01100110 ;W-Register | |
</dd></dl><!-- zum einrücken da--> | </dd></dl><!-- zum einrücken da--> | ||
− | <b> XORWF R,d </b> <i style="color:grey;"> | + | :Dieser Befehl wird vor allem zum Vergleichen eines Registers mit ins W-Register geladenem Wert benutzt. Wenn die Werte in beiden Register gleich sind, wird ein Z bit im STATUS-Register gesetzt. |
− | :Es wird bitweise die logische Funktion <math>W\ xor\ R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Vergleiche XORLW | + | |
+ | <b> XORWF R,d </b> <i style="color:grey;">EXclusive OR W with F</i><hr> | ||
+ | :Es wird bitweise die logische Funktion <math>W\ xor\ R</math> ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Vergleiche XORLW. Dieser Befehl setzt das Z bit des STATUS-Registers, falls W=k und das Ergebnis 0 ist. Er wird zum Vergleichen zwei Register benutzt, wobei ein Register vorher ins W-Register geladen wird.. | ||
==Besondere, oft gebrauchte Register== | ==Besondere, oft gebrauchte Register== | ||
Zeile 1.369: | Zeile 2.275: | ||
:: 0 = Kein Carry-out des MSB eines Rechenergebnisses existiert | :: 0 = Kein Carry-out des MSB eines Rechenergebnisses existiert | ||
− | Das " | + | 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 Bit umgekehrt als bei Additionen (ADDWF und ADDLW)!! Zum Beispiel | + | 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==== | ====Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Registers==== | ||
Zeile 1.420: | Zeile 2.326: | ||
|} | |} | ||
− | === | + | === OPTION_REG === |
+ | |||
+ | Durch OPTION_REG werden PORTB pull-ups, aktive Flanke des externen Interrupts, der Prescaler und der Timer0 konfiguriert. | ||
+ | |||
<table style="text-align: center;" cellspacing="0"> | <table style="text-align: center;" cellspacing="0"> | ||
<tr> | <tr> | ||
− | <td colspan="8" style>''' | + | <td colspan="8" style>'''OPTION_REG''' (ADDRESSE 81h, 181h)</td> |
</tr> | </tr> | ||
<tr style="border:0px;"> | <tr style="border:0px;"> | ||
Zeile 1.437: | Zeile 2.346: | ||
</tr> | </tr> | ||
<tr> | <tr> | ||
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''/RBPU'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''INTEDG'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''T0CS'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''T0SE'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''PSA'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''PS2'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''PS1'''</td> |
− | <td style="background:#f5f7ff;">''' | + | <td style="background:#f5f7ff;">'''PS0'''</td> |
</tr> | </tr> | ||
<tr> | <tr> | ||
Zeile 1.454: | Zeile 2.363: | ||
− | *Bit 7 ''' | + | *Bit 7 '''/RBPU''': PORTB Pull-up Enable bit ("/" bedeutet Negation) |
− | ::1 = | + | ::1 = Pull-ups sind deaktiviert |
− | ::0 = | + | ::0 = Pull-ups sind aktiviert für die Pins, die im Register TRISB als Eingänge definiert sind |
− | *Bit 6 ''' | + | *Bit 6 '''INTEDG''': Interrupt Edge Select bit |
− | ::1 = | + | ::1 = Interrupt auf steigende Flanke am RB0/INT pin |
− | ::0 = | + | ::0 = Interrupt auf fallende Flanke am RB0/INT pin |
− | *Bit 5 ''' | + | *Bit 5 '''T0CS''': Timer0 Clock Source Select bit |
− | ::1 = | + | ::1 = Wechsel am RA4/T0CKI pin |
− | ::0 = | + | ::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 | + | *Bit 3 '''PSA''': Prescaler Assignment bit |
− | ::1 = | + | ::1 = Prescaler ist dem WDT zugewiesen |
− | ::0 = | + | ::0 = Prescaler ist dem Timer0 zugewiesen |
− | *Bit | + | *Bit 2-0 '''PS2:PS0''': Prescaler Rate Select bit |
− | ::1 = | + | |
− | ::0 = | + | |
− | *Bit | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
+ | Je nach Zuweisung des Prescalers (durch PSA bit) wird die interne Taktfrequenz des Prozessors (Fosc/4) wie folgt geteilt: | ||
− | + | {| | |
− | + | |- | |
− | + | | | |
+ | {| {{Blauetabelle}} | ||
+ | | PS2:PS0 | ||
+ | || TMR0 | ||
+ | || WDT | ||
+ | |- | ||
+ | |align="center"|000 | ||
+ | |align="center"|1:2 | ||
+ | |align="center"|1:1 | ||
+ | |- | ||
+ | |align="center"|001 | ||
+ | |align="center"|1:4 | ||
+ | |align="center"|1:2 | ||
+ | |- | ||
+ | |align="center"|010 | ||
+ | |align="center"|1:8 | ||
+ | |align="center"|1:4 | ||
+ | |- | ||
+ | |align="center"|011 | ||
+ | |align="center"|1:16 | ||
+ | |align="center"|1:8 | ||
+ | |- | ||
+ | |align="center"|100 | ||
+ | |align="center"|1:32 | ||
+ | |align="center"|1:16 | ||
+ | |- | ||
+ | |align="center"|101 | ||
+ | |align="center"|1:64 | ||
+ | |align="center"|1:32 | ||
+ | |- | ||
+ | |align="center"|110 | ||
+ | |align="center"|1:128 | ||
+ | |align="center"|1:64 | ||
+ | |- | ||
+ | |align="center"|111 | ||
+ | |align="center"|1:256 | ||
+ | |align="center"|1:128 | ||
+ | |} | ||
+ | |} | ||
=== PORTx === | === PORTx === | ||
Zeile 1.521: | Zeile 2.461: | ||
BSP: "PORTB" oder "RA1" | BSP: "PORTB" oder "RA1" | ||
− | Diese Special Funktion Register liegen ALLE in Bank0 und Bank2(Falls vorhanden) | + | 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 [[PIC Assembler#I/O Ports|I/O Ports]]. Bei den "kleinsten" PICs der 12F Serie die nur einen I/O Port (6 Pins) haben, heisst der PORT-Register GPIO. | ||
{|{{Blauetabelle}} | {|{{Blauetabelle}} | ||
|+ Beispiel PICs und ihre Ports (Zahlen in der Klammer sind die Anzahl der Pins des Ports) | |+ Beispiel PICs und ihre Ports (Zahlen in der Klammer sind die Anzahl der Pins des Ports) | ||
Zeile 1.593: | Zeile 2.532: | ||
!Das "x" steht in allen Fällen für den Buchstaben des Ports! | !Das "x" steht in allen Fällen für den Buchstaben des Ports! | ||
− | BSP: "TRISB" oder " | + | 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. | 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 | + | 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: | Merke: | ||
Zeile 1.604: | Zeile 2.543: | ||
Siehe [[PIC Assembler#I/O Ports|I/O Ports]]. | Siehe [[PIC Assembler#I/O Ports|I/O Ports]]. | ||
+ | |||
+ | === INTCON === | ||
+ | |||
+ | <table style="text-align: center;" cellspacing="0"> | ||
+ | <tr> | ||
+ | <td colspan="8" style>'''INTCON''' (ADDRESSE 0Bh, 8Bh, 10Bh, 18Bh)</td> | ||
+ | </tr> | ||
+ | <tr style="border:0px;"> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-0</td> | ||
+ | <td style="width:60px;">R/W-x</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td style="background:#f5f7ff;">'''GIE'''</td> | ||
+ | <td style="background:#f5f7ff;">'''PEIE'''</td> | ||
+ | <td style="background:#f5f7ff;">'''TMR0IE'''</td> | ||
+ | <td style="background:#f5f7ff;">'''INT0IE'''</td> | ||
+ | <td style="background:#f5f7ff;">'''RBIE'''</td> | ||
+ | <td style="background:#f5f7ff;">'''TMR0IF'''</td> | ||
+ | <td style="background:#f5f7ff;">'''INT0IF'''</td> | ||
+ | <td style="background:#f5f7ff;">'''RBIF'''</td> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>Bit7</td> | ||
+ | <td colspan="6"></td> | ||
+ | <td>Bit0</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | |||
+ | *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== | ==Speicherbankorganisation== | ||
===Programmspeicher=== | ===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) | + | 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=== | ===Datenspeicher=== | ||
Zeile 1.649: | Zeile 2.651: | ||
== Codeschnipsel == | == Codeschnipsel == | ||
+ | |||
+ | Alle hier sich befindliche Programmbeispiele sind nur auf den PIC12... und PIC16... sicher lauffähig und müssen für PIC18... bei Problemen umgeschrieben werden. Weitere Beispiele befinden sich in http://www.rn-wissen.de/index.php/PIC_ASM_Beispiele | ||
=== Analog-Digital-Wandler (ADC) === | === Analog-Digital-Wandler (ADC) === | ||
+ | |||
Viele PICs besitzen einen Analog-Digital-Wandler, der eine angelegte Spannung messen kann und diese als Zahl speichert. | Viele PICs besitzen einen Analog-Digital-Wandler, der eine angelegte Spannung messen kann und diese als Zahl speichert. | ||
Zeile 1.661: | Zeile 2.666: | ||
Bevor überhaupt gemessen werden kann, muss einiges eingestellt werden: | 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). | 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: | Das Register ADCON0 besitzt 8 Bits, die folgendes bestimmen: | ||
Zeile 1.688: | Zeile 2.693: | ||
Codebeispiel: | Codebeispiel: | ||
− | + | bcf ADCON0,6 ;Takt für | |
− | bcf ADCON0,6 | + | bsf ADCON0,7 ;ADC (OSC/32) |
− | bsf ADCON0,7 | + | bsf ADCON0,0 ;ADC einschalten, ADON=1 |
− | + | bcf ADCON0,2 ;Go/Done-Bit zurücksetzen | |
− | bsf ADCON0,0 ; ADON=1 | + | |
− | ;Go/Done-Bit zurücksetzen | + | |
− | + | ||
Nun ist der AD-Wandler bereit, Spannungen in Zahlen umzuwandeln. | Nun ist der AD-Wandler bereit, Spannungen in Zahlen umzuwandeln. | ||
Zeile 1.713: | Zeile 2.715: | ||
Codebeispiel: | Codebeispiel: | ||
− | + | bcf ADCON0,3 ;RA2 auswählen | |
− | bcf ADCON0,3 | + | |
bsf ADCON0,4 | bsf ADCON0,4 | ||
bcf ADCON0,5 | bcf ADCON0,5 | ||
Zeile 1.721: | Zeile 2.722: | ||
Code: | Code: | ||
− | ;start | + | bsf ADCON0,2 ;start |
− | + | wa btfsz ADCON0,2 ;warten,bis es wieder low ist | |
− | + | goto wa ;noch nicht fertig | |
− | + | ;jetzt ist er fertig | |
− | goto wa | + | movf ADRES,W ;ADRES in W verschieben, um damit gleich weiter zu arbeiten |
− | + | ||
− | ;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. | 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. | ||
Zeile 1.737: | Zeile 2.735: | ||
− | + | ;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. | + | :-> 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. | 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 | + | :-> 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 Hilfsregister. 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 DClr ;Hex>A, D>Dec | ||
+ | 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 | ||
+ | DClr clrf D0 | ||
+ | clrf D1 | ||
+ | clrf D2 | ||
+ | clrf D3 | ||
+ | clrf C0 | ||
+ | clrf C1 | ||
+ | clrf C2 | ||
+ | clrf C3 | ||
+ | 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 Adresse 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 für Grafikdisplay). | ||
+ | |||
+ | Die Adresse "0x2100" in der "org" Direktive hat mit der Adresse im Programmspeicher nichts zu tun. | ||
+ | |||
+ | === Interrupts === | ||
+ | |||
+ | Das Programm misst eine Frequenz an T0CKI Pin, wurde für den PIC16F628 geschrieben und in einem genauen Frequenzzähler angewendet. Es ist nur auf PICs lauffähig, die mindestens zwei Timer besitzen. | ||
+ | |||
+ | Es gibt nur ein Messbereich von 1 Hz bis 99999999 Hz mit gleicher Auflösung 1 Hz, deswegen ist kein Dezimalpunkt benutzt. Für einen kompletten Frequenzzähler ist nur noch ein Eingangsverstärker nötig. | ||
+ | |||
+ | Benötigte Hardware: Widerstand 470 Ohm zwischen der Frequenzquelle und TOCKI Pin (A4). | ||
+ | |||
+ | Die Ausgabe erfolgt auf ein 2x8 standard Zeichendisplay. Das HP ("Main") als leere endlose Schleife macht nichts außer warten auf ein Interrupt von Timer0 bzw. Timer1. Die ISR benutzt keine Register vom UPs und deshalb müssen W- und STATUS Register nicht gesichert und wiederhergestellt werden. | ||
+ | |||
+ | 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 Timer 0 zählt die Impulse am TOCKI Pin (A4) in der Zeit, die vom Timer 1 bestimmt wird. | ||
+ | |||
+ | Der Zähler der Frequenz wurde um zwei zusätzliche Register A2 und A3 erweitert und bei jedem Timer0 Überlauf erhöht. Der Prescaler wird ins Register A0 und der TMR0 ins A1 kopiert. Somit ergibt sich 32-bittiges Ergebnis, 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 beliebigen Display angezeigt werden. | ||
+ | |||
+ | Die Quarzfrequenz 7,3728 MHz wurde gewählt, weil sie am nächsten der temperaturstabiltesten Frequenz für AT Quarzen 7,2 MHz ist. Die Quarzfrequenz muß nicht genau sein, da der Frequenzzähler lässt sich sehr genau mit den Werten von TMR1H und TMR1L "trimmen", die vor dem Start des Timers 1 geladen werden. | ||
+ | |||
+ | 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 [[http://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=13706&highlight=frequenzz%E4hler]] nachgebaut werden. In dem Fall müssen die Register vom Timer1 (TMR1H und TMR1L) vor seinem Start mit entsprechenden Werten geladen werden. | ||
+ | |||
+ | Hier wird die Messzeit 1 s genutzt und die Frequenz wird mit einer Auflösung von 1 Hz gemessen. Die Messzeit beträgt 3*10000h+(FFFFh-7BFFh) Timertakten. Für den Quarz 7,372800 MHz und Prescaler=8, wird der Takt von Timer1 gleich 7372800/4*8=230400 Hz. Deswegen wird der Timer1 beim Starten mit ca. 7BFFh geladen und nach dem 4. Überlauf gestoppt. Die in TMR1H und TMR1L praktisch geladene Werte unterscheiden sich ein bißchen von berechneten, weil die Quarzfrequenz fast nie genau bis auf 1 Hz stimmt. | ||
+ | |||
+ | Für z.B. 20 MHz Quarz werden 10 Überläufe des Timers benötigt und er wird beim Start mit 7697h geladen, da 1s gleich 9x10000h+(FFFFh-7697h) Timertakten ist. | ||
+ | |||
+ | In dem UP "Init" muss eventuell für ein anderes Display die Initialisierung des Displaykontrollers geändert werden. | ||
+ | |||
+ | In dem Program wurden unveränderte UPs verwendet, die näher in [[#Hex Dec Wandlung|Hex Dec Wandlung]] und [[#Matrix|Matrix]] erklärt sind. | ||
+ | |||
+ | ; Programm zum Messen der Frequenz an T0CKI Pin mit 7,3728 MHz Quarz | ||
+ | LIST P=16F628 | ||
+ | include "P16F628.inc" | ||
+ | __CONFIG _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _HS_OSC & _BODEN_OFF & _LVP_OFF | ||
+ | |||
+ | ; TOCKI PORTA,4 ;I Frequenzeingang | ||
+ | ; DB4 PORTB,0 ;O Display Data Bit 4 | ||
+ | ; DB5 PORTB,1 ;O Display Data Bit 5 | ||
+ | ; DB6 PORTB,2 ;O Display Data Bit 6 | ||
+ | ; DB7 PORTB,3 ;O Display Data Bit 7 | ||
+ | #define _RS PORTB,4 ;O Display RS | ||
+ | #define _E PORTB,5 ;O Display Enable | ||
+ | #define _C STATUS,C | ||
+ | #define _Z STATUS,Z | ||
+ | #define _DC STATUS,DC | ||
+ | #define _RP0 STATUS,RP0 | ||
+ | #define _Fcra Flags,0 | ||
+ | #define _Fcrp Flags,1 | ||
+ | #define _Fdca Flags,2 | ||
+ | #define _Ferr Flags,3 ;Überlauf (Fehler) | ||
+ | #define _Frs Flags,4 | ||
+ | #define _Fnz Flags,5 | ||
+ | A3 equ 0x20 ;Register fürs Rechnen Hex_Dec | ||
+ | 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 | ||
+ | Tmp equ 0x30 ;Register, die für Displayausgabe | ||
+ | Tmp1 equ 0x31 ;vorläufig benutzt werden | ||
+ | Flags equ 0x32 | ||
+ | ATmp equ 0x33 ;vorläufige Schleifenzähler | ||
+ | HTmp equ 0x34 | ||
+ | RTmp equ 0x35 ;Zwischenspeicher | ||
+ | ORG 0x0000 | ||
+ | call Init ;alles initialisieren | ||
+ | Main goto Main ;und in die leere endlose Schleife springen | ||
+ | ORG 0x0004 ;hier fängt ISR (interrupt service routine) an | ||
+ | bcf INTCON,GIE ;alle interrupts sperren | ||
+ | btfss INTCON,T0IF ;ist Timer0 überlaufen ? | ||
+ | goto CheckTMR1 ;nein, dann prüfe Timer1 | ||
+ | bcf INTCON,T0IF ;ja, Timer0 interrupt flag löschen und bearbeiten | ||
+ | movlw 1 | ||
+ | addwf A2,1 ;erhöhe A2 durch Addition | ||
+ | btfsc _C ;um Überlauf von A2 zu erkennen und | ||
+ | incf A3,1 ;increment A3, wenn A2 überlaufen ist | ||
+ | CheckTMR1 btfss PIR1,TMR1IF ;ist Timer1 überlaufen ? | ||
+ | retfie ;nein, dann ISR beenden und interrupts erlauben | ||
+ | bcf PIR1,TMR1IF ;Timer1 interrupt flag löschen | ||
+ | call Multip ;Interrupts vom Timer1 im UP "Multip" zählen | ||
+ | retfie ;ISR beenden und interrupts erlauben | ||
+ | Multip incf Tmp,1 ;Interruptszähler von Timer1 erhöhen | ||
+ | btfss Tmp,2 ;schon 4.interrupt? | ||
+ | return ;nein, dann zurück | ||
+ | bsf _RP0 ;ja, weiter | ||
+ | movf TRISA,0 ;stopp Timer0 usw. | ||
+ | andlw 0xEF | ||
+ | movwf TRISA ;TOCKI Pin (A4) als Ausgang | ||
+ | bcf _RP0 | ||
+ | bcf T1CON,TMR1ON ;stopp Timer1 | ||
+ | GetFreq movf TMR0,0 ;Prescaler ins Register A0 kopieren | ||
+ | movwf A1 | ||
+ | movwf RTmp | ||
+ | clrf ATmp | ||
+ | FToggle incf ATmp,1 | ||
+ | bsf _RP0 | ||
+ | bsf OPTION_REG,T0SE | ||
+ | bcf OPTION_REG,T0SE | ||
+ | bcf _RP0 | ||
+ | movf TMR0,0 | ||
+ | subwf RTmp,0 | ||
+ | btfsc _Z | ||
+ | goto FToggle | ||
+ | comf ATmp,1 | ||
+ | incf ATmp,0 | ||
+ | movwf A0 | ||
+ | call Hex_Dec ;Messergebnis auf Decimal wandeln | ||
+ | call AClr | ||
+ | call DispFrq ;eventuell eigenes UP für benutztes Display | ||
+ | clrf Tmp | ||
+ | clrf TMR0 | ||
+ | call TMRStart ;beide Timer starten | ||
+ | return | ||
+ | AClr clrf A0 ;Register A löschen | ||
+ | clrf A1 | ||
+ | clrf A2 | ||
+ | clrf A3 | ||
+ | return | ||
+ | TMRStart movlw 0x34 ;Timer1 konfigurieren | ||
+ | movwf T1CON ;und laden | ||
+ | movlw 0x7B ;berechneter Wert: TMR1H=7Bh | ||
+ | movwf TMR1H | ||
+ | movlw 0xFB ;berechneter Wert: TMR1L=FFh | ||
+ | movwf TMR1L | ||
+ | bsf _RP0 ;start Timer0 | ||
+ | movf TRISA,0 | ||
+ | iorlw 0x10 | ||
+ | movwf TRISA ;TOCKI Pin (A4)als Eingang | ||
+ | bcf _RP0 | ||
+ | bsf T1CON,TMR1ON ;start Timer1 | ||
+ | return | ||
+ | Hex_Dec call DClr ;Hex>A, D>Dec | ||
+ | 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 | ||
+ | DClr clrf D0 | ||
+ | clrf D1 | ||
+ | clrf D2 | ||
+ | clrf D3 | ||
+ | clrf C0 | ||
+ | clrf C1 | ||
+ | clrf C2 | ||
+ | clrf C3 | ||
+ | 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 | ||
+ | DispFrq bcf _Fnz ;Frequenz anzeigen (8 Ziffern) | ||
+ | call Fst ;mit Unterdrückung der führenden Nullen | ||
+ | movlw 3 | ||
+ | movwf ATmp | ||
+ | movlw D3 | ||
+ | movwf FSR | ||
+ | swapf INDF,0 | ||
+ | andlw 0x0F | ||
+ | btfsc _Z | ||
+ | goto $+2 | ||
+ | bsf _Fnz | ||
+ | btfss _Fnz | ||
+ | goto $+3 | ||
+ | call Num | ||
+ | goto $+3 | ||
+ | movlw " " | ||
+ | call Char | ||
+ | movf INDF,0 | ||
+ | andlw 0x0F | ||
+ | btfsc _Z | ||
+ | goto $+2 | ||
+ | bsf _Fnz | ||
+ | btfss _Fnz | ||
+ | goto $+3 | ||
+ | call Num | ||
+ | goto $+3 | ||
+ | movlw " " | ||
+ | call Char | ||
+ | incf FSR,1 | ||
+ | decfsz ATmp,1 | ||
+ | goto $-18 | ||
+ | swapf D0,0 | ||
+ | andlw 0x0F | ||
+ | btfsc _Z | ||
+ | goto $+2 | ||
+ | bsf _Fnz | ||
+ | btfss _Fnz | ||
+ | goto $+3 | ||
+ | call Num | ||
+ | goto $+3 | ||
+ | movlw " " | ||
+ | call Char | ||
+ | movf D0,0 | ||
+ | andlw 0x0F | ||
+ | call Num | ||
+ | call Snd ;zweite Zeile | ||
+ | movlw "M" | ||
+ | call Char | ||
+ | movlw "^" | ||
+ | call Char | ||
+ | movlw " " | ||
+ | call Char | ||
+ | movlw "k" | ||
+ | call Char | ||
+ | movlw " " | ||
+ | call Char | ||
+ | movlw "^" | ||
+ | call Char | ||
+ | movlw "H" | ||
+ | call Char | ||
+ | movlw "z" | ||
+ | call Char | ||
+ | return | ||
+ | Fst movlw 0x80 ;Adresse der ersten Zeile | ||
+ | goto Cmd | ||
+ | Snd movlw 0xC0 ;Adresse der zweiten Zeile | ||
+ | Cmd movwf Tmp ;Befehl ins Tmp laden | ||
+ | bcf _Frs ;RS=0 | ||
+ | goto Send | ||
+ | Val movwf Tmp1 | ||
+ | swapf Tmp1,0 | ||
+ | call Num | ||
+ | movf Tmp1,0 | ||
+ | Num andlw 0x0F | ||
+ | movwf Tmp | ||
+ | movlw 0x0A | ||
+ | subwf Tmp,0 | ||
+ | btfsc _C | ||
+ | addlw 7 | ||
+ | addlw 0x3A ;ASCII 0-F | ||
+ | Char movwf Tmp ;Zeichen ins Tmp laden | ||
+ | bsf _Frs ;RS=1 | ||
+ | Send swapf Tmp,0 ;zuerst High Nibble | ||
+ | andlw 0x0F | ||
+ | movwf PORTB ;an Port (Display) schicken | ||
+ | btfsc _Frs ;RS Flag an Port kopieren | ||
+ | bsf _RS | ||
+ | bsf _E ;Enable erzeugen | ||
+ | bcf _E | ||
+ | movf Tmp,0 ;Low Nibble | ||
+ | andlw 0x0F | ||
+ | movwf PORTB ;an Port (Display) schicken | ||
+ | Enab btfsc _Frs ;RS Flag in Port kopieren | ||
+ | bsf _RS | ||
+ | bsf _E ;Enable erzeugen | ||
+ | bcf _E | ||
+ | Del movlw 0x30 ;Verzögerung min. ca. 50µs | ||
+ | movwf Tmp | ||
+ | decfsz Tmp,1 | ||
+ | goto $-1 | ||
+ | return | ||
+ | Init clrf PORTA ;Register vorm Start löschen | ||
+ | clrf PORTB | ||
+ | clrf Tmp | ||
+ | clrf TMR0 | ||
+ | call AClr | ||
+ | bsf _RP0 ;Bank 1 | ||
+ | movlw 0xFF | ||
+ | movwf TRISA ;alle PORTA Pins als Eingänge | ||
+ | clrf TRISB ;und alle PORTB Pins als Ausgänge | ||
+ | movlw 0xE7 ;Takt für Timer0 vom T0CKI Pin usw. | ||
+ | movwf OPTION_REG ;Timer0 konfigurieren | ||
+ | bsf PIE1,TMR1IE ;TMR1 interrupt erlauben | ||
+ | bcf _RP0 ;Bank 0 | ||
+ | movlw 0xE0 ;GIE, PEIE & TMR0IE erlauben | ||
+ | movwf INTCON | ||
+ | call TMRStart ;beide Timer starten | ||
+ | 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 | ||
+ | |||
+ | === Mausrad bzw. Drehencoder === | ||
+ | |||
+ | Das UP wertet ein Mausrad bzw. Drehencoder aus und setzt, je nach Drehrichtung 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 bzw. Drehencoder | ||
+ | .-------. | ||
+ | | o---> PORTB,0 | ||
+ | +----o---- | | ||
+ | | | o---> PORTB,1 | ||
+ | === '-------' | ||
+ | GND | ||
+ | |||
+ | Es müssen die internen pull-ups vom PORTB aktiviert werden. Wenn das Mausrad bzw. Drehencoder 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 zusammengesetzte UPs (z.B. Displayausgabe) mit solcher Ausführungszeit benutzt werden. | ||
+ | |||
+ | In dem UP "Mouse" wird PORTB eingelesen, die alle Bits außer 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. Anschließend wird zum Aufrufer zurückgesprungen. | ||
+ | |||
+ | Detaillierter 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 [[http://www.roboternetz.de/phpBB2/viewtopic.php?t=20277]] | ||
+ | |||
+ | ==== Handy ==== | ||
+ | |||
+ | Als Beispiel die Hardware und ein Testprogramm für Nokia 3310/3330 Display: [[http://www.roboternetz.de/phpBB2/viewtopic.php?p=124669#124669]] | ||
+ | |||
+ | ==== 7-Segment mit 3 Backplanes ohne Kontroller ==== | ||
+ | |||
+ | Eine Schnittstelle und ein Testprogramm für LPH2673-1 von Pollin: | ||
+ | [[http://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=26005&highlight=lph26731]] und Anwendung: http://www.roboternetz.de/community/threads/42200-Frequenzz%C3%A4hler-mit-LPH2673-1-%28zum-Nachbauen%29 . | ||
+ | |||
+ | = High-End (PIC18...)= | ||
+ | |||
+ | Als High-End werden alle 8-bit PIC18F... klasiffieziert, die Befehlslänge 16-Bit, also 2 Bytes haben. Ihr Befehlsatz enthält insgesamt 75 Befehle. | ||
+ | |||
+ | Es werden nur nötige fürs Erstellen von ASM Programmen spezifische Unterschiede behandelt, die in der Praxis Probleme für Umsteiger von Basic-Line und Mid-Range bereiten können. Wenn sich jemand ausschliesslich mit PIC18F... beschäftigen will, wird sich keine Unterschiede merken müssen, da ausser erweitertem Befehlsatz die Programme von ihrer Struktur identisch sind. Genaue Parameter für bestimmten PIC befinden sich im entsprechendem Datenblatt. | ||
+ | |||
+ | == Hardware == | ||
+ | |||
+ | Weil die PIC18F... für einige Anwendungen schneller als Mid-Range laufen müssen, wurde bei Takterzeugung eine PLL-Option beim Oszillator zugefügt, die aus standard Quarzen (z.B. 12 MHz) durch Multiplikation mal 4 eine Taktfrequenz für CPU 12 MHz (z.B. für USB) erzeugt. Weil die Frequenz des Oszilators für interne Taktung der CPU durch 4 geteilt ist, ermöglichst diese Option, dass die CPU mit Oscillatorfrequenz, also bei z.B. 10 MHz Quarz mit echten 10 MHz (10 MIPs) arbeitet (intern mit 40 MHz). | ||
+ | |||
+ | Der Programmspeicher ist als 8-Bit breit organisiert. Aus dem Grund jeder 16 Bit langer Befehl belegt im Speicher 2 Bytes. Wenn im Datenblatt z.B. 16384 Bytes angegeben sind, bedeutet das, dass dort sich nur die Hälfte davon, also 8192 Befehle abspeichern lassen. Als Folge sind für Befehle nur gerade Adressen zulässig. | ||
+ | |||
+ | == Software == | ||
+ | |||
+ | Alle Befehle sind um 2 Bit länger, als bei Mid-Range, und es gibt keine Speicherbänke, was Erstellung von Programmen sehr vereinfacht. | ||
+ | |||
+ | Bisher für Mid-Range ausführlich beschriebene Befehle, ausser "CLRW", "RLF" und "RRF" und Speicherbankumschaltungen (z.B. "BSF RP0" und "BCF RP0") sind für PIC18F... "verständlich" und werden ausgeführt. Die PIC18F... haben zusätzlich 43 Befehle. | ||
+ | |||
+ | Wenn es um ASM Programm geht, ist er grundsätzlich, ausser zusätzlichen Befehlen, identisch wie bei Mid-Range. Die zusätzliche Befehle ermöglichen Erstellen von mehr kompakten Programmen. | ||
+ | |||
+ | Die PIC18... haben die Möglichkeit für Interuppts individuell Prioritäten definieren, was die Bearbeitung in ISR vereinfacht. Aus dem Grund besitzen sie zwei Interrupt-Vektoren (Anfangsadressen für ISR): 8h für höhere und 18h für niedrigere Prioritäten, die anders als bei übrigen PIC's sind (4h). | ||
+ | |||
+ | Ausser erweiterten Befehlsatz haben die PIC18F... drei Register für indirekte Adressierung FSR0, FSR1 und FSR2, die unabhängig voneinender und gleichzeitig benutzt werden können. | ||
+ | |||
+ | Die CPU's von PIC18F... haben tieferen Stapel für Rücksprungadressen mit 31 Ebenen, der zusätzlich mit "PUSH" und "POP" Befehlen während Ausführung eines Unterprogramms (UP) modifiziert werden kann. Das ermöglichst Änderungen von Rücksprungadressen, so dass nicht unbedingt nach der Ausführung des UP zurück, sondern fast beliebig gesprungen werden kann. Ausführlich ist es in AN818 vom Microchip beschrieben. Siehe dazu: http://ww1.microchip.com/downloads/en/AppNotes/00818a.pdf | ||
+ | |||
+ | Sehr nutzlich sind auch alle neue Sprungmöglichkeiten z.B. "BRA" der "GOTO" entspricht. Da die bedingte Sprünge von Bits des "STATUS" Register abhängen, muß davor kein Befehl aüsgeführt werden der benötigten Bit (z.B. "Z") prüft. | ||
+ | |||
+ | Wegen Struktur des Programspeichers ein relativer Sprung zur nächster Adresse muss um 2 Byte erfolgen. Deswegen ist für PIC18F... also "GOTO $+2" der kürzeste mögliche Sprung. Bei "GOTO $+1" springt die CPU "zwischen" gültige Befehle ins "Nirvana", was Absturz des Programms bedeutet. Bei bedingten Sprungen und "BRA" wird der angebebener Wert "n" für die Anzahl den übersprüngten Zeilen automatisch durch 2 multipliziert. | ||
+ | |||
+ | Alle PIC18F... können den Programspeicher beschreiben und sich selbst programmieren. Dafür gibt es die "TBLWT.." Befehle. Das ist aber nur für mindestens 8 Bytes am Stück möglich. Vor dem Beschreiben müssen die dafür vorgesehene Speicherplätze zuerst gelöscht werden, wobei das für minimum 64 Bytes möglich ist. | ||
+ | |||
+ | Als Beispiel ein geprüftes Fragment von Bearbeitung des Programmspeichers (Flash) in http://www.rn-wissen.de/index.php/PIC_ASM_Beispiele. | ||
+ | |||
+ | == Kurzübersicht zusätzliche Befehle == | ||
+ | <font style="font-size:10px;"> | ||
+ | {| | ||
+ | |- | ||
+ | | valign=top | | ||
+ | |||
+ | {| {{Blauetabelle}} | ||
+ | |ADDWFC||Add WREG and Carry bit to f | ||
+ | |- | ||
+ | |BC||Branch if Carry | ||
+ | |- | ||
+ | |BN||Branch if Negative | ||
+ | |- | ||
+ | |BNC||Branch if Not Carry | ||
+ | |- | ||
+ | |BNN||Branch if Not Negative | ||
+ | |- | ||
+ | |BNOV||Branch if Not Overflow | ||
+ | |- | ||
+ | |BNZ||Branch if Not Zero | ||
+ | |- | ||
+ | |BOV||Branch if Overflow | ||
+ | |- | ||
+ | |BRA||Branch unconditionally | ||
+ | |- | ||
+ | |BZ||Branch if Zero | ||
+ | |- | ||
+ | |BTG||Bit toggle in f | ||
+ | |- | ||
+ | |CPFSEQ||Compare f with W, skip if f=W | ||
+ | |- | ||
+ | |CPFSGT||Compare f with W, skip if f>W | ||
+ | |- | ||
+ | |CPFSLT||Compare f with W, skip if f<W | ||
+ | |} | ||
+ | |||
+ | | valign=top | | ||
+ | |||
+ | {| {{Blauetabelle}} | ||
+ | |- | ||
+ | |DAW||Decimal Adjust W | ||
+ | |- | ||
+ | |DCFSNZ||Decrement f, skip if not 0 | ||
+ | |- | ||
+ | |INFSNZ||Increment f, skip if not 0 | ||
+ | |- | ||
+ | |LFSR||Move literal to FSRx | ||
+ | |- | ||
+ | |MOVFF||Move f1 to f2 | ||
+ | |- | ||
+ | |MOVLB||Move literal to BSR < 3 : 0 > | ||
+ | |- | ||
+ | |MULLW||Multiply literal with W | ||
+ | |- | ||
+ | |MULWF||Multiply W with f | ||
+ | |- | ||
+ | |NEGF||Negate f | ||
+ | |- | ||
+ | |POP||Pop top of return stack (TOS) | ||
+ | |- | ||
+ | |PUSH||Push top of return stack (TOS) | ||
+ | |- | ||
+ | |RCALL||Relative call | ||
+ | |- | ||
+ | |RESET||Software device RESET | ||
+ | |- | ||
+ | |RLCF||Rotate left f through Carry | ||
+ | |- | ||
+ | |RLNCF||Rotate left f no Carry | ||
+ | |} | ||
+ | |||
+ | | valign=top | | ||
+ | |||
+ | {| {{Blauetabelle}} | ||
+ | |||
+ | |- | ||
+ | |RRCF||Rotate right f trough Carry | ||
+ | |- | ||
+ | |RRNCF||Rotate right f no Carry | ||
+ | |- | ||
+ | |SETF||Set f | ||
+ | |- | ||
+ | |SUBFWB||Subtract f from W with borrow | ||
+ | |- | ||
+ | |SUBWFB||Subtract W from f with borrow | ||
+ | |- | ||
+ | |TBLRD*||Table Read | ||
+ | |- | ||
+ | |TBLRD*+||Table Read with post-increment | ||
+ | |- | ||
+ | |TBLRD*-||Table Read with post-decrement | ||
+ | |- | ||
+ | |TBLRD+*||Table Read with pre-increment | ||
+ | |- | ||
+ | |TBLWT*||Table write | ||
+ | |- | ||
+ | |TBLWT*+||Table write with post-increment | ||
+ | |- | ||
+ | |TBLWT*-||Table write with post-decrement | ||
+ | |- | ||
+ | |TBLWT+*||Table write with pre-increment | ||
+ | |- | ||
+ | |TSTFSZ||Test f, skip if 0 | ||
+ | |} | ||
+ | |||
+ | |} | ||
+ | </font> | ||
+ | |||
+ | == Ausführliche Beschreibung zu den Befehlen == | ||
+ | |||
+ | Alle Sprunge ausser "BRA" können nur im Bereich eines Bytes, d.h. von -128 bis +127 erfolgen. Nur der Sprung "BRA" kann zwischen -1024 bis +1023 lang sein. | ||
+ | |||
+ | Wenn die Bedingung für ein bedingten Sprung nicht erfüllt ist, wird er in einem Takt übersprungen und nächster Befehl ausgeführt. | ||
+ | |||
+ | Erklärungen zu den Verwendeten Platzhaltern: | ||
+ | *'''k''' stellt einen fest definierten Wert da. z.B. hexadezimal <tt>0x20</tt> bzw. <tt>20</tt>, dezimal <tt>d'42'</tt> bzw. <tt>.42</tt> oder binär <tt>b'00101010'</tt> | ||
+ | *'''n''' stellt einen fest definierten Wert wie oben für Sprünge | ||
+ | *'''W''' steht für das W-Register. | ||
+ | *'''d''' steht für ''destination'' (Ziel). Im code wird d durch ein <tt>w</tt> bzw. <tt>0</tt> (der Wert wird in das W-Register gespeichert ) oder <tt>f</tt> bzw. <tt>1</tt> (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 | ||
+ | *<tt>Schreibmaschinenstil</tt> bedeutet, dass es so im Quellcode geschrieben werden kann. | ||
+ | |||
+ | <b>ADDWFC R,d</b> <i style="color:grey;"><b>ADD W</b> and <b>F</b> with <b>C</b>arry - Addiere W und f mit Übertrag </i><hr> | ||
+ | :Es wird die Rechenoperation <math>W+R</math> mit Berücksichtigung des schon vorhandenen Übertrags ausgeführt und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
+ | |||
+ | <b>BC n</b> <i style="color:grey;"><b>B</b>ranch if <b>C</b>arry - Springe wenn Übertrag </i><hr> | ||
+ | :Es wird das "C" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen. | ||
+ | |||
+ | <b>BN n</b> <i style="color:grey;"><b>B</b>ranch if <b>N</b>egative - Springe wenn negative </i><hr> | ||
+ | :Es wird das "N" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen. | ||
+ | |||
+ | <b>BNC n</b> <i style="color:grey;"><b>B</b>ranch if <b>N</b>ot <b>C</b>arry - Springe wenn kein Übertrag </i><hr> | ||
+ | :Es wird das "C" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen. | ||
+ | |||
+ | <b>BNN n</b> <i style="color:grey;"><b>B</b>ranch if <b>N</b>ot <b>N</b>egative - Springe wenn nicht negative </i><hr> | ||
+ | :Es wird das "N" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen. | ||
+ | |||
+ | <b>BNOV n</b> <i style="color:grey;"><b>B</b>ranch if <b>N</b>ot <b>OV</b>erflow - Springe wenn kein Überlauf </i><hr> | ||
+ | :Es wird das "OV" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen. | ||
+ | |||
+ | <b>BNZ n</b> <i style="color:grey;"><b>B</b>ranch if <b>N</b>ot <b>Z</b>ero - Springe wenn ungleich Null </i><hr> | ||
+ | :Es wird das "Z" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen. | ||
+ | |||
+ | <b>BOV n</b> <i style="color:grey;"><b>B</b>ranch if <b>OV</b>erflow - Springe wenn Überlauf </i><hr> | ||
+ | :Es wird das "OV" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen. | ||
+ | |||
+ | <b>BRA n</b> <i style="color:grey;"><b>BR</b>anch <b>A</b>bsolutly - Springe unbedingt </i><hr> | ||
+ | :Es wird immer unbedingt gesprungen. | ||
+ | |||
+ | <b>BZ n</b> <i style="color:grey;"><b>B</b>ranch if <b>Z</b>ero - Springe bei Null </i><hr> | ||
+ | :Es wird das "Z" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen. | ||
+ | |||
+ | <b>BTG R,b</b> <i style="color:grey;"><b>B</b>it <b>T</b>o<b>G</b>gle F - Kehre bestimmten Bitwert in f um </i><hr> | ||
+ | :Der bestimmte Bit im F-Register wird umgekehrt. Also wenn er vorm Ausführen des Befehls z.B. gleich 1 war, wird er danach gleich 0 sein und umgekehrt. | ||
+ | |||
+ | <b>CPFSEQ R,d</b> <i style="color:grey;"><b>C</b>om<b>P</b>are <b>F</b> with W, <b>S</b>kip if <b>EQ</b>ual - Vergleiche Register f mit W und überspringe, wenn f=W</i><hr> | ||
+ | :Es wird der Registers f mit dem W verglichen und bei f=W, wird der nächste Befehl übersprungen. Der Zusatz SEQ steht für ''skip if equal'', d.h. wenn die Werte in beiden Register gleich sind, wird der nächste Befehl übersprungen. | ||
+ | |||
+ | <b>CPFSGT R,d</b> <i style="color:grey;"><b>C</b>om<b>P</b>are <b>F</b> with W, <b>S</b>kip if <b>G</b>rea<b>T</b>er - Vergleiche Register f mit W und überspringe, wenn f>W</i><hr> | ||
+ | :Es wird der Registers f mit dem W verglichen und bei f>W der nächste Befehl übersprungen. Der Zusatz SGT steht für ''skip if greater'', d.h. wenn der Wert im f grösser als im W-Register ist, wird der nächste Befehl übersprungen. | ||
+ | |||
+ | <b>CPFSLT R,d</b> <i style="color:grey;"><b>C</b>om<b>P</b>are <b>F</b> with W, <b>S</b>kip if <b>L</b>i<b>T</b>tler - Vergleiche Register f mit W und überspringe, wenn f<W</i><hr> | ||
+ | :Es wird der Registers f mit dem W verglichen und bei f<W der nächste Befehl übersprungen. Der Zusatz SLT steht für ''skip if littler (lower)'', d.h. wenn der Wert im f kleiner als im W-Register ist, wird der nächste Befehl übersprungen. | ||
+ | |||
+ | <b>DAW</b> <i style="color:grey;"><b>D</b>ecimal <b>A</b>djust <b>W</b>REG - Justiere W-Register nach dezimaler Addition </i><hr> | ||
+ | :Es weden alle nötige Korrekturen für richtigen Ergebnis im W-Register nach dezimaler Addition durchgeführt. | ||
+ | |||
+ | <b>DCFSNZ R,d</b> <i style="color:grey;"><b>D</b>e<b>C</b>rement <b>F</b>, <b>S</b>kip if <b>N</b>ot <b>Z</b>ero - Subtrahiere 1 vom Regiser f und überspringe den nächsten Befehl, wenn f ungleich Null ist.</i><hr> | ||
+ | :Vom Wert des Registers '''R''' wird 1 subtrahiert und das Ergebnis entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Weil es keine "echte" Substraktion ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht. | ||
+ | |||
+ | <b>INFSNZ R,d</b> <i style="color:grey;"><b>IN</b>crement <b>F</b>, <b>S</b>kip if <b>N</b>ot <b>Z</b>ero - Addiere 1 zum Register f und überspringe den nächsten Befehl, wenn f ungleich Null ist</i><hr> | ||
+ | :Zum Wert des Registers '''R''' wird 1 addiert und das Ergebniss entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). Der Zusatz SNZ steht für ''skip if not zero'', d.h. wenn das Ergebnis der Rechnung ungleich Null ist, wird der nächste Befehl übersprungen. Weil es keine "echte" Addition ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht. | ||
+ | |||
+ | <b>LFSR R,k</b> <i style="color:grey;"><b>L</b>oad <b>FSR</b> - Lade FSR-Register mit einem Wert k </i><hr> | ||
+ | :Es wird gewähles FSR-Register (FSR0, FSR1, bzw. FSR2) mit dem Wert k geladen. | ||
+ | |||
+ | <b>MOVLB k</b> <i style="color:grey;"><b>MOV</b> <b>L</b>iteral to <b>B</b>SR <3:0> - Bewege k ins BSR-Register </i><hr> | ||
+ | :Es wird der Bank Select Register (BSR) mit dem Wert k geladen. | ||
+ | |||
+ | <b>MOVFF R1,R2</b> <i style="color:grey;"><b>MOV</b>e <b>F</b>1 to <b>F</b>2 - Bewege f1 in f2</i><hr> | ||
+ | :Das Register R1 wird in das Register R2 kopiert. | ||
+ | |||
+ | <b>MULLW k</b> <i style="color:grey;"><b>MUL</b>tiply <b>L</b>iteral with <b>W</b>REG - Multipliziere k mit W-Register </i><hr> | ||
+ | :Es wird W-Register mit k multiplieziert und das Ergebnis in Registern PRODH und PRODL gespeichert. Dieser Befehl beeinflusst das STATUS-Register nicht. | ||
+ | |||
+ | <b>MULWF R</b> <i style="color:grey;"><b>MUL</b>tiply <b>W</b> with <b>F</b> - Multipliziere W-Register mit F</i><hr> | ||
+ | :Es wird F-Register mit W-Register multiplieziert und das Ergebnis in F gespeichert. Dieser Befehl beeinflusst das STATUS-Register nicht. | ||
+ | |||
+ | <b>NEGF R</b> <i style="color:grey;"><b>NEG</b>ate <b>F</b> - Negiere F</i><hr> | ||
+ | :Es werden alle Bits von F-Regiester negiert. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
+ | |||
+ | <b>POP</b> <i style="color:grey;"><b>POP</b> top of return stack (TOS) - Nehme die oberste Rücksprungsadresse vom Stapel weg</i><hr> | ||
+ | :Es wird die oberste Rücksprungadresse vom Stapel entfernt und alle übrigen Adressen um eine Stelle nach oben geschoben. | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | alle <--- Stapel --->| | ||
+ | übrige A Adresse 1 -----> entfernt | ||
+ | Adressen | Adresse 2 | ||
+ | um 1 Stelle | Adresse 3 | ||
+ | nach oben | .......... | ||
+ | verschieben | Adresse 31 | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |||
+ | <--- Stapel --->| ;vor dem "POP" | ||
+ | Adresse 1 ---> verworfen | ||
+ | Adresse 2 | ||
+ | Adresse 3 | ||
+ | .......... | ||
+ | Adresse 31 | ||
+ | |||
+ | <--- Stapel --->| ;nach dem "POP" | ||
+ | Adresse 2 | ||
+ | Adresse 3 | ||
+ | Adresse 4 | ||
+ | .......... | ||
+ | ????????? | ||
+ | |||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>PUSH</b> <i style="color:grey;"><b>PUSH</b> top of return stack (TOS) - Lege neue oberste Rücksprungsadresse am Stapel </i><hr> | ||
+ | :Es wird eine neue oberste Rücksprungadresse am Stapel abgelegt und alle übrigen Adressen um eine Stelle nach unten geschoben. | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | alle <--- Stapel --->| | ||
+ | übrige | Adresse 1 <--- neue wird abgelegt | ||
+ | Adressen | Adresse 2 | ||
+ | um 1 Stelle | Adresse 3 | ||
+ | nach unten | .......... | ||
+ | verschieben V Adresse 31 | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |||
+ | <--- Stapel --->| ;vor dem "POP" | ||
+ | Adresse 1 | ||
+ | Adresse 2 | ||
+ | Adresse 3 | ||
+ | .......... | ||
+ | Adresse 31 | ||
+ | |||
+ | <--- Stapel --->| ;nach dem "POP" | ||
+ | PC + 2 | ||
+ | Adresse 1 | ||
+ | Adresse 2 | ||
+ | .......... | ||
+ | Adresse 30 | ||
+ | |||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>RCALL n</b> <i style="color:grey;"><b>R</b>elative <b>CALL</b> - Relativer Aufruf eines Unterprogramms </i><hr> | ||
+ | :Es wird ein Unterprogramm reletiv aufgerufen, der innerhalb 1 kB von aktueller Adresse liegen darf. Vergleiche mit "CALL". | ||
+ | |||
+ | <b>RESET</b> <i style="color:grey;"> < Software device <b>RESET</b> - Software Reset </i><hr> | ||
+ | :Es wird Reset des PIC's durchgeführt, genauso wie hardwaremässig mit /MCLR-Pin. | ||
+ | |||
+ | <b>RLCF R,d</b> <i style="color:grey;"><b>R</b>otate <b>L</b>eft through <b>C</b>arry <b>F</b> - Rotiere das Register f mithilfe des Carry-bits nach links</i><hr> | ||
+ | :Alle Bits im Register '''R''' werden um eine Position nach links verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |<--- Register R --->| | ||
+ | |c|<-7<-6<-5<-4<-3<-2<-1<-0 | ||
+ | V A | ||
+ | |________________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |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 | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>RLNCF R,d</b> <i style="color:grey;"><b>R</b>otate <b>L</b>eft <b>N</b>o <b>C</b>arry <b>F</b> - Rotiere das Register f ohne des Carry-bits nach links</i><hr> | ||
+ | :Alle Bits im Register '''R''' werden um eine Position nach links verschoben. Das Ergebnis wird entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |<--- Register R --->| | ||
+ | 7<-6<-5<-4<-3<-2<-1<-0 | ||
+ | V A | ||
+ | |____________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |-Register R-| ;0-7 stehen für Bitnummer im Register. | ||
+ | 7 6 5 4 3 2 1 0 ;vor der Rotation | ||
+ | 6 5 4 3 2 1 0 7 ;nach der Rotation | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>RRCF R,d</b> <i style="color:grey;"><b>R</b>otate <b>R</b>ight through <b>C</b>arry <b>F</b> - Rotiere das Register f mithilfe des Carry-bits nach rechts</i><hr> | ||
+ | :Alle Bits im Register '''R''' werden um eine Position nach rechts verschoben. Dabei wird das Carry bit (<tt>STATUS,C</tt>) 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'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |<--- Register R --->| | ||
+ | |c|->7->6->5->4->3->2->1->0 | ||
+ | A V | ||
+ | |________________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |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 | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>RRNCF R,d</b> <i style="color:grey;"><b>R</b>otate <b>R</b>ight <b>N</b>o <b>C</b>arry <b>F</b> - Rotiere das Register f ohne des Carry-bits nach rechts</i><hr> | ||
+ | :Alle Bits im Register '''R''' werden um eine Position nach rechts verschoben. Das Ergebnis wird entweder in das W-Register ('''d'''=<tt>W</tt>=<tt>0</tt>) oder in R gespeichert ('''d'''=<tt>F</tt>=<tt>1</tt>). | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |<--- Register R --->| | ||
+ | 7->6->5->4->3->2->1->0 | ||
+ | A V | ||
+ | |____________________| | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | :Zur Verdeutlichung: | ||
+ | <dl><dd><!-- zum einrücken da--> | ||
+ | |-Register R-| ; 0-7 stehen für Bitnummer im Register. | ||
+ | 7 6 5 4 3 2 1 0 ;vor der Rotation | ||
+ | 0 7 6 5 4 3 2 1 ;nach der Rotation | ||
+ | </dd></dl><!-- zum einrücken da--> | ||
+ | |||
+ | <b>SETF R</b> <i style="color:grey;"><b>SETF</b> - Setze das Register f</i><hr> | ||
+ | :Alle Bits im Register ''R'' werden gesetzt, also nach dem Ausführen des Befehls wird im Register "FFh" stehen. | ||
+ | |||
+ | <b>SUBFWB R,d</b> <i style="color:grey;"><b>SUB</b>stract <b>F</b> from <b>W</b> with <b>B</b>orrow - Substrahiere das F-Register von W-Register mit Übertrag</i><hr> | ||
+ | :Der inhalt des W-Registers wird vom F-Register mit Berücksichtigung des vorhandenes Übertrags abgezogen. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
+ | |||
+ | <b>SUBWFB R,d</b> <i style="color:grey;"><b>SUB</b>stract <b>W</b> from <b>F</b> with <b>B</b>orrow - Substrahiere das W-Register von F-Register mit Übertrag</i><hr> | ||
+ | :Der inhalt des F-Registers wird vom W-Register mit Berücksichtigung des vorhandenes Übertrags abgezogen. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu [[#Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register|Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register]] | ||
+ | |||
+ | <b>TBLRD*</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>R</b>ea<b>D*</b> - Lese Tabelle ohne Änderung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung bleibt der Zeiger unverändert. | ||
+ | |||
+ | <b>TBLRD*+</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>R</b>ea<b>D*+</b> with post-increment - Lese Labelle mit Nacherhöhung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erhöht. | ||
+ | |||
+ | <b>TBLRD*-</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>R</b>ea<b>D*-</b> with post-decrement - Lese Tabelle mit Nacherniedrigung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erniedrigt. | ||
+ | |||
+ | <b>TBLRD+*</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>R</b>ea<b>D+*</b> with pre-increment - Lese Tabelle mit Vorerhöhung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Vor der Ausführung wird der Zeiger um 1 erhöht. | ||
+ | |||
+ | <b>TBLWT*</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>W</b>ri<b>T*</b>e - Schreibe Tabelle ohne Änderung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung bleibt der Zeiger unverändert. | ||
+ | |||
+ | <b>TBLWT*+</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>W</b>ri<b>T*+</b>e with post-increment - Schreibe Tabelle mit Nacherhöhung des Zeigers </i><hr> | ||
+ | :Diesers Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erhöht. | ||
+ | |||
+ | <b>TBLWT*-</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>W</b>ri<b>T*-</b>e with post-decrement - Schreibe Tabelle mit Nacherniedrigung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erniedrigt. | ||
+ | |||
+ | <b>TBLWT+*</b> <i style="color:grey;"><b>T</b>a<b>BL</b>e <b>W</b>ri<b>T+*</b>e with pre-increment - Schreibe Tabelle mit Vorerhöhung des Zeigers </i><hr> | ||
+ | :Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Vor der Ausführung wird der Zeiger um 1 erhöht. | ||
+ | |||
+ | <b>TSTFSZ R</b> <i style="color:grey;"><b>T</b>e<b>ST</b>t <b>F</b> <b>S</b>kip if <b>Z</b>ero - Prüfe f, überspringe wenn gleich Null ist </i><hr> | ||
+ | :Es wird der nächste Befehl öbersprungen, wenn der Register f gleich Null ist. | ||
+ | |||
+ | == Umschreiben von Programmen == | ||
+ | |||
+ | Es werden alle nötige Änderungen beschrieben, die vorhandenes Programm lauffähig machen. | ||
+ | Ausserdem muß für gewünschten PIC nötige Konfiguration im Quellcode ersetzt werden. Weil einige Befehle von PIC18F... müssen für Mid-Range in UPs umgeschrieben werden, die Auführung Zeit vom umgeschriebenen Program kann sich erheblich ändern. Bei zeitkritischen Abläufen ist deswegen Umschreibung High-End -> Mid-Range nicht immer möglich. | ||
+ | |||
+ | === Basic-Line & Mid-Range -> High-End === | ||
+ | |||
+ | Alle Speicherbankumschaltungen müssen mit ";" ausgeblendet bzw. gelöscht werden. | ||
+ | |||
+ | Da die PIC18... drei "FSR" Register besitzen muss überall "FSR" konsequent anstatt "FSR" nach Wunsch "FSR0", "FSR1", bzw. "FSR2" eingeschrieben werden. | ||
+ | |||
+ | Die für PIC18... unverständliche Befehle von Mid-Range müssen, wie folgt, ersetzt werden | ||
+ | |||
+ | "CLRW" -> "MOVLW 0" | ||
+ | |||
+ | "RLF" -> "RLCF" | ||
+ | |||
+ | "RRF" -> "RRCF" | ||
+ | |||
+ | Alle Relative Sprunge mussen verdoppelt werden. Beispielweise jeder "GOTO $+4" Befehl muss durch "GOTO $+8" bzw. "BRA $+8" ersetzt werden (Asführungszeit bei Schleifen beachten). | ||
+ | |||
+ | Beim Umschreiben vorhandenen Programen von PIC12... und PIC16... ist für Interrupts ein kompatibilität Modus vorgesehen, die keine definierte Prioritäten für Interrupts benötigt, was folgendemassen in Initialisierung (Init) festgelegt wird: | ||
+ | |||
+ | bcf RCON,IPEN ; disable priority levels on interrupts | ||
+ | (compatibility modus) | ||
+ | |||
+ | === High-End -> Basic Line & Mid-Range === | ||
+ | |||
+ | Alle zusätzliche Befehle sind für Mid-Range PIC's unverständlich und müssen als kleine Unterprogramme erstellt werden. Für einige Befehle ist es enfach: | ||
+ | |||
+ | "MOVFF R1, R2" -> "MOVF R1,0" | ||
+ | "MOVWF R2" | ||
+ | |||
+ | Es kann auch komplizierter werden, z.B. für "DAW": | ||
+ | |||
+ | DecCor btfsc _DC ;dezimale Korrektur (ersetzt "DAW" von PIC18FXXX) | ||
+ | bsf _Fdca | ||
+ | btfsc _C | ||
+ | bsf _Fcra | ||
+ | movf INDF,0 | ||
+ | andlw 0x0F | ||
+ | sublw 9 | ||
+ | btfss _C | ||
+ | bsf _Fdca | ||
+ | movf INDF,0 | ||
+ | andlw 0xF0 | ||
+ | sublw 90 | ||
+ | btfss _C | ||
+ | bsf _Fcra | ||
+ | return | ||
+ | |||
+ | In dem UP sind "_Fcra" und "_Fdca" im Program definierte Flags. Siehe dazu [[#Hex Dec Wandlung|Hex Dec Wandlung]] | ||
+ | |||
+ | = Hilfsmittel = | ||
+ | |||
+ | Hier werden einige Programme presentiert, die das ASM Programmieren erleichtern. Sie sind nur auf PIC12... und PIC16... lauffähig und für PIC18... müssen umgeschrieben werden. | ||
+ | |||
+ | 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. | ||
+ | |||
+ | Hoffentlich hilfreiche Beiträge sind auch dort zu finden: http://www.roboternetz.de/community/threads/26098-Tips-Tricks . | ||
+ | |||
+ | == 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: [[http://www.roboternetz.de/phpBB2/viewtopic.php?t=13685]] | ||
+ | |||
+ | == 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 Oszillator 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 Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @Tmp equ 0x7F anstatt 0x4F). | ||
+ | |||
+ | Um "Kollisionen" mit dem Programm, in das er eingebunden wird, zu vermeiden, 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. Außerdem 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 | ||
+ | swapf 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 | ||
+ | swapf @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 vorherigen 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|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 "*" gekennzeichnete Zeilen (alle außer "goto @Init" können geändert werden), müssen im Programm, in das "PIC Trainer" eingebunden ist, enthalten sein. Wegen ISR darf das Programm, 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 Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @SWR equ 0x7F anstatt 0x4F).Dabei muss auch im UP @Test der Wert im PCLATH geändert werden. Siehe dazu [[#Tabellen|Tabellen]]. | ||
+ | |||
+ | Um "Kollisionen" mit dem Programm, in das er eingebunden wird, zu vermeiden, 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 bilibigen, in Register eingestellten Werten , getestet werden können. | ||
+ | |||
+ | 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 | ||
+ | swapf 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 | ||
+ | swapf @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 ; @DT und @CK als Ausgänge | ||
+ | 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 | ||
+ | |||
+ | Das Programm "PIC Trainer" kann auch für grösseres Display (mehr Register und Testx) modifiziert werden. Als Beispiel ein Quellcode für 4x16 Display (0 bis 1Fh Register/Test): | ||
+ | |||
+ | ; PIC Trainer32.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 | ||
+ | @LPC2 equ 0x45 | ||
+ | @Tasten equ 0x44 | ||
+ | ORG 0x0004 ; ISR | ||
+ | bcf INTCON,GIE ; interrupts sperren | ||
+ | movwf @SWR ; W-Register sichern | ||
+ | swapf 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,2 ; prüfen, ob schon 4. Timer0 interrupt | ||
+ | goto $+3 ; wenn nicht, zum "@IntTest" springen | ||
+ | clrf @Int ; Zähler löschen | ||
+ | call @ ; Anzeigen der Registerinhalte aufrufen | ||
+ | @IntTest ;**************; | ||
+ | ; eigener Code ; | ||
+ | ;**************; | ||
+ | movf @FSR,0 ; FSR Register | ||
+ | movwf FSR ; wiederherstellen | ||
+ | swapf @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 | ||
+ | ORG 0x0338 | ||
+ | @ 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 | ||
+ | call @3rd | ||
+ | call @Line | ||
+ | call @4th | ||
+ | call @Line | ||
+ | movlw 0x0E ; Cursor an | ||
+ | call @Cmd | ||
+ | swapf @LPC,0 ; Display Adresse berechnen und Cursor plazieren | ||
+ | andlw 0x0F | ||
+ | movwf @LPC2 | ||
+ | btfss STATUS,Z | ||
+ | goto $+3 | ||
+ | movlw 0x80 | ||
+ | goto @AddLPC | ||
+ | movf @LPC2,0 | ||
+ | sublw 1 | ||
+ | btfss STATUS,Z | ||
+ | goto $+3 | ||
+ | movlw 0x30 | ||
+ | goto @AddLPC | ||
+ | movf @LPC2,0 | ||
+ | sublw 2 | ||
+ | btfss STATUS,Z | ||
+ | goto $+3 | ||
+ | movlw 0x70 | ||
+ | goto @AddLPC | ||
+ | movf @LPC2,0 | ||
+ | sublw 3 | ||
+ | btfsc STATUS,Z | ||
+ | movlw 0xA0 | ||
+ | @AddLPC 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-1F starten | ||
+ | movwf PCLATH | ||
+ | movf @LPC1,0 | ||
+ | andlw 0x1F | ||
+ | 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 0x40 | ||
+ | btfsc STATUS,Z | ||
+ | clrf @LPC | ||
+ | movf @LPC,0 | ||
+ | movwf @LPC1 | ||
+ | rrf @LPC1,1 | ||
+ | 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 | ||
+ | goto @Cmd | ||
+ | @3rd movlw 0x90 ; Adresse der 3. Zeile an Display schicken | ||
+ | goto @Cmd | ||
+ | @4th movlw 0xD0 ; Adresse der 4. 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 | ||
+ | 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 0x3DF ; diese Adresse kann gleich max.Adresse - 20h 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 | ||
+ | goto Test10 | ||
+ | goto Test11 | ||
+ | goto Test12 | ||
+ | goto Test13 | ||
+ | goto Test14 | ||
+ | goto Test15 | ||
+ | goto Test16 | ||
+ | goto Test17 | ||
+ | goto Test18 | ||
+ | goto Test19 | ||
+ | goto Test1A | ||
+ | goto Test1B | ||
+ | goto Test1C | ||
+ | goto Test1D | ||
+ | goto Test1E | ||
+ | goto Test1F | ||
+ | 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 Quellcode 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 gesamte 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|Unterprogramm]] erklärt. | ||
+ | |||
+ | Zum Messen wurde Timer0 benutzt, der um 2 Byte Zähler erweitert wurde. Somit beträgt die maximale messbare Ausfü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 | ||
+ | swapf 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 | ||
+ | swapf @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 | ||
+ | swapf 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 | ||
+ | swapf @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 Programm "@Profiler" kann für PICs, die mehr Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @SWR equ 0x7F anstatt 0x4F). | ||
+ | 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 | ||
[[Kategorie:Microcontroller]] | [[Kategorie:Microcontroller]] | ||
[[Kategorie:Software]] | [[Kategorie:Software]] | ||
[[Category:PIC]] | [[Category:PIC]] |
Aktuelle Version vom 26. Januar 2015, 14:39 Uhr
Inhaltsverzeichnis
- 1 Einführung
- 2 Programm
- 3 Basic-Line (PIC12...) & Mid-Range (PIC16...)
- 4 High-End (PIC18...)
- 5 Hilfsmittel
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 enthä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 Basic-Line hat 12-bittige, Mid-Range 14-bittige und High-End 8-bittige Speicherstellen. Die Anzahl den Speicherstellen im bestimmten Speicher ist vom PIC-Typ abhängig.
Eine 8-bittige 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 12 bzw. 14-bittigem 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 Basic-Line und Mid-Range 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.
Die Beschreibung allen SFRs (Special Funktion Register), in den sämtliche Funktionen des PICs festgelegt werden, befinden sich im Datenblatt unter "Memory Organisation".
Siehe auch: Speicherbankorganisation
Prozessor
Die Prozessoren der PIC's 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.
Der Prozessor von PICs gehört zu den RISC (Reduced Instruction Set Computer) Prozessoren und man hat nur 35 bzw. 75 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.
Ein sogenannter Prozessortakt besteht aus 4 Oszillator-Takten, die jeweils einen Teil eines Befehls abarbeiten. Deswegen entspricht die Taktfrequenz der CPU der durch 4 geteilten Frequenz des Oszillators.
CPU Vorgang Richtung Speicher ------------------------------------------------- - 1.Befehl lesen (fetch) <------- 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 einen kleinen eigenen Speicher, der weder zum Programmspeicher noch zum Datenspeicher gehört. Er besteht aus dem Programmzähler PC (program counter) und dem Stapel (stack).
In dem PC befindet sich die Adresse des momentan ausführbaren 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 z.B. 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.
Um dies zu ermöglichen, 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ührten Zeile abgelegt, damit der Prozessor, wenn er nach der Ausführung der "Interruppt Service Routine" (ISR) an "retfie" kommt, das unterbrochene Programm an der richtigen Stelle wieder startet.
Der Stapel kann aber nur 8 bzw. 31 Adressen speichern, deswegen darf nur entsprechende Anzahl von nacheinander folgenden "call" Befehlen benutzt werden. Wenn ein Interrupt benutzt wird, reduziert sich es um 1, da eine Speicherstelle immer für die Adresse, an der das Programm unterbrochen wurde, reserviert werden muss. Sonst findet der Prozessor nicht mehr zurück und springt in die "Nirvana" , 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 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, ist eine Sprache die nur eine bestimmte CPU versteht. Für einen Menschen ist sie unverständlich, da sie nur aus Zahlen besteht.
Um sich die Sprache verständlicher zu machen wurden den Zahlen s.g. Mnemonics aus Buchstaben zugewiesen. Jeder Befehl für einen CPU hat somit ein "Namen", der aus der englischen Sprache stammt. In dieser Form wird auch von Assembler oder kurz ASM gesprochen. Siehe: Kurzübersicht Assembler Befehle
Obwohl sie bis zu 1000 mal schneller als die meisten Hochsprachen ist, wird sie, wegen des großen Aufwands bei der Erstellung von umfangreichen Programmen, selten benutzt. Man findet sie aber oft in fast allen Hochsprachen, in eingebundenen 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-Kenntnisse sehr vorteilhaft.
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". Anders als bei einer Hochsprache ist die "Übersetzung" genau vorhersehbar und festgelegt. 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 Textdatei 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) installiert 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 Einschalten 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 Versorgungsspannung störungsfrei sein. Dafür wird ein Keramik-Vielschicht-Kondensator 100 nF (0,1 µF) möglichst am kürzesten direkt zwischen VDD und VSS Pins geschaltet.
Folgende Skizzen zeigen die Grundbeschaltung eines PICs:
Konfiguration
Die Konfiguration eines PICs wird beim "brennen" fest programmiert. Sie ist eigentlich für fast jeden PIC-Typ anders. Um Probleme zu vermeiden, 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 Programm 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.
Warnung !
Anhand praktischer Erfahrung kann man feststellen, dass bei den kleinsten PICs der Familie 12FXXX, bei dennen ein I/O Pin mit VPP gemultiplext wird (z.B. PIC12F510, PIC12F629, PIC12F635 und 12F675), das Wählen des VPP Pins als I/O verwandelt ihn ins One Time Programming (OTP) Chip, der nur einmal programmiert werden kann. Die gewählte Konfuguration wird entgültig fest "gebrannt" und wegen seitdem fehlender Verbindung des I/O Pins mit VPP keine Umprogrammierung mehr möglich ist. Wer solchen PIC verwenden möchte, sollte aus dem Grund für Entwicklung anderen PIC-Typ nehmen und erst fertiges ausprobiertes Programm einmalig in den für konkrete Anwendung vorgesehenen PIC brennen.
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öße des Datenspeichers (für Variablen).
- Größe 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ähiges 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 bißchen Programmspeicher und RAM als auch 2 freie I/O Pins fürs PIC Miniterminal brauchen.
Programm
Allgemeines
Jedes Programm 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. Hautprogramm (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 Programms 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
Jedes UP kann jederzeit aufgerufen werden, je nach dem was gerade erledigt 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 Textdatei 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 dann 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.
Programmablaufdiagramm (PAD)
Der Programablaufdiagram (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 (außer "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 Computerprogramme 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 Programm 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.
Der PAD ist sehr einfach zu erstellen, weil dafür nur drei Symbole benötigt sind:
Bei PAD Erstellung darf man selbstverständlich beliebige Symbole verwenden, die man selber am besten versteht.
Das "Start/Stopp" Symbol bedeutet, dass das gesamte Programm sich im stabilen Zustand befindet und nicht "läuft". Anstatt "Stopp" kann auch "Schlaf" ("sleep") angewendet 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, entweder in der "ja" (J) oder "nein" (N) Richtung.
Als allgemeinnütziges 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 Hauptprogramm wird in einer endlosen Schleife ausgeführt, die durch die Prüfung "Ende?" unterbrochen werden kann. In dem Fall wird vor dem Beenden des gesamten 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 "?" gekennzeichnet 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. Außerdem 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 Quellcode so aussehen:
........... movlw 10h addwf B,1 ...........
Siehe auch: Das erste Programm und Mausrad bzw. Drehencoder
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 Hauptprogramm 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 folgender 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 (außer 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 Hauptprogramm 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 eingeschrieben:
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.
Multitasking
Echtes Multitasking ist nur mit mehr (Core)Prozessoren möglich. Bei PIC's ist echtes Multitasking (Parallellaufen) nur mit Timer und ADC Wandler möglich (siehe dazu: http://www.roboternetz.de/community/threads/42200-Frequenzz%C3%A4hler-mit-LPH2673-1-%28zum-Nachbauen%29?highlight=LPH2673-1 )
Sonst gibt es nur Quasi-Multitasking, das kann auf zwei Weisen realisiert werden:
1. Alle Tasks werden in fester bzw. per Interrupts bestimmter Reihenfolge auf Bedarf geprüft und nur die mit gesetztem Flag werden vollständig bis zum Ende ausgeführt. Als Beispiel sehe: [[2]]. Es kann natürlich mit Proritäten für Interrupts versehen werden.
+<---------------------------------------+ | | V | /\ /\ /\ | / \ / \ / \ | / \ J / \ J / \ J | /F1=0 ?\->+- - ->/Fn=0 ?\->+- - ->/F8=0 ?\->+ \ / A \ / A \ / A \ / | \ / | \ / | \ / | \ / | \ / | \/ | \/ | \/ | |N | |N | |N | V | V | V | .------. | .------. | .------. | | | | | | | | | | | | | | | | | | | | TUN1 | | | TUNn | | | TUN8 | | | | | | | | | | | | | | | | | | | | '------' | '------' | '------' | | | | | | | V | V | V | | | | | | | +------+ +------+ +------+
2. Der s.g. Taskmanager (meistens HP) gibt jedem Task (meistens UP) feste Zeit und wenn der Task aktiv ist, wird er nach dieser Zeit z.B. per Timer (TMR) unterbrochen und nächster Tast gestartet. Dafür muss immer nach Beenden von jedem Task die Endadresse vom Programm Counter (PC), wo der Task unterbrochen wurde (EA1), auf dem Stapel als nächste Startadresse (AA1) gespeichert werden. Wenn die Zeit für den unterbrochenen Task wieder kommt, wird er ab der unterbrochenen Stelle (AA1) wieder in der ihn zustehenden Zeit ausgeführt, wieder unterbrochen, u.s.w.
Dafür muss der Stapel mit Rücksprungadressen laufend mit "pop" and "push" Befehlen bearbeitet werden, was erst ab PIC18... möglich ist. Bei dieser Methode bei kurzen Laufzeiten für jeden Task, sieht der Beobachter praktisch keine Unterbrechungen von Tasks. Auch hier können Prioritäten benutzt werden, aber z.B. Zeiten für Warteschleifen können nicht genau berechnet werden. Das Unterbrechen von Tasks ist auch per Software-Interrupts möglich.
+--------------------------------------------------------+ | .-----. .-----. .-----. | +->|Task1|---->+- - - ->|Taskn|---->+- - - ->|Task8|---->+ '-----' | '-----' | '-----' | | | | | | | V | V | V | /\ | /\ | /\ | / \ | / \ | / \ | / \ J | / \ J | / \ J | /F1=0 ?\---->+ /Fn=0 ?\---->+ /F8=0 ?\---->+ \ / A \ / | \ / | \ / | \ / | \ / | \ / | \ / | \ / | \/ | \/ | \/ | |N | |N | |N | +---+--+ | +---+--+ | +---+--+ | | | | | | | | | | V | | V | | V | | .-----. | | .-----. | | .-----. | | +->| AA1 | | | +->| AAn | | | +->| AA8 | | | | '-----' | | | '-----' | | | '-----' | | | | | | | | | | | | | | | V V | | V V | | V V | | .-----..-----. | | .-----..-----. | | .-----..-----. | | | || | | | | || | | | | || | | | | || | | | | || | | | | || | | | | TMR || Tun |->+ | | TMR || Tun |->+ | | TMR || Tun |->+ | | || 1 | | | || n | | | || 8 | | | || | | | | || | | | | || | | | '----- -----' | '----- -----' | '----- -----' | | | | | | | | | | V | V | V | .-----. | | .-----. | | .-----. | +--| EA1 |<- - - - + +--| EAn |<- - - - + +--| EA8 |<- - - - + '-----' '-----' '-----'
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.
Ein HP von einem ASM Programm kann in anderem, mehr umfangreichem ASM Programm 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 ausfü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.
Anschließend 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 Planung der Verwendung von PORTA muss immer im Datenblatt geprüft werden, ob sich ein bestimmter Pin für die geplante 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 zulä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 denen ist, dass die Sprungtabellen steuern den Programmlauf 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 verschiedenen 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 3, 0xE8 ; 0x00 dt 5, 0xB7 ; 0x02 dt 'M','H','z',0 ; 0x04 dt ' ',' ','W','A','I','T',0 ; 0x08 dt 'C','o','n','s','t',' ','X',0 ; 0x0F dt 'C','=',0 ; 0x17 dt 'L','=',0 ; 0x1A dt 'F','=',0 ; 0x1D dt 'O','K',0 ; 0x20 usw.
Das Lesen eines Strings muss ab entsprechendem Wert im W-Register (z.B. für "MHz" -> 0x04) anfangen und auf dem Wert 0 (Null) enden, der hier als Stringende dient. Einfacher ist, wenn alle Strings gleich lang sind (wie z.B. in einem Zeichengenerator für Grafikdisplay).
Bei Tabellen, die länger als 256 Byte sind, muss immer PCLATH an den Seitengrenzen korriegiert werden, wie auf der 3. Seite der Applikation Note AN556 vom Microchip vorgestellt: http://ww1.microchip.com/downloads/en/AppNotes/00556e.pdf .
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 der sogenannten 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 einen 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 PICs der Mid-Range Familie haben im Programmspeicher nur eine Adresse (s.g. Interrupt Vektor 0x0004), wohin im Falle eines Interrupts, gesprungen wird. Um den Verursacher des Interrupts zu finden, hat jede Quelle ein eigenes Interrupt Flag (IF), das gesetzt wird, wenn der 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 für Basic-Line und Mid-Range 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: [[3]] und [4]
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 Programm
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 Quellcode einspaltig. Um sich die "Übersetzung" des PADs in den Quellcode 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 Quellcode 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.
Bei Erstellung von ASM Programmen, um sich das Kompilieren zu vereinfachen, kann folgende Prozedur verwendet werden:
Zuerst wird in gleichem Verzeichnis, wo sich die Sammlung Unter/Programme befindet, eine Textdatei (z.B. "Test.txt") erstellt, wo ein einspaltiges PAD mit Pfeilen nach oben und unten geschrieben/skizziert wird. In der Datei wird auch ganz oben ständig aktualisierte Liste aller Register erstellt, die in diesem Unter/Programm verwendet sind.
Wenn das PAD fertig ist, wird diese Datei mit gleichem Namen als "*.asm" Datei (z.B. "Test.asm")gespeichert und alle PAD "Symbole" mit Befehlen für bestimmten PIC/Assemblerprogramm ersetzt (z.B. für MPASM werden alle Register benannt).
Bei jeder Änderung wird sie gleich in beiden Dateien gemacht, damit sie am Ende wirklich aktuell sind. Letztendlich haben wir dann beide, wenn wir später etwas ändern müssen. Dank dessen braucht man ausser eventuellen Namen der UPs keine Kommentare im Programm schreiben, was seine Erstellung deutlich beschleinigt.
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ünschten Programme in ein neues Programm einzubinden ist, 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ünschten Programme als "*.asm" Dateien mit der Direktive "include *.asm" am Ende des neuen Programms vor der Direktive "end" einzubinden. Die einbindende sowie alle einzubindenden Dateien müssen sich 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 eingebundenen Programmen keine "org" Direktiven gibt, werden die weiteren 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 Unterprogramme meistens mit dem "call"-Befehl. Es "lohnt sich" erst ein Codefragment als UP zu definieren wenn es min. 2 mal aufgerufen wird.
Ein "goto"-Befehl ist zu diesem Zweck nur geeignet, wenn der Prozessor zum letzten Aufruf 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 Werte vor dem Aufruf des UPs in die benötigte Anzahl von Register einschreben und dann das Unterprogramm mit "call" aufrufen. Das Unterprogramm verwendet dann die Werte aus dem Register. Siehe: Pause
Um gleiche Vorgänge in mehreren Registern durchzuführen (z.B. löschen) ist die Anwendung von Schleifen geeignet (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.
Man kann auch eine Bearbeitung eines UPs um 4 Takte beschleunigen ("call" + "return"), indem man bei dem letzten Aufruf eines UPs (direkt vorm "return") anstatt "call UP", "goto UP" anwendet, da das UP sowieso mit "return" bzw. "retlw" endet.
Ursprünglich:
movlw 0x28 call Cmd movlw 0x0C call Cmd movlw 6 call Cmd <-- return
Optimiert:
movlw 0x28 call Cmd movlw 0x0C call Cmd movlw 6 goto Cmd <--
Nebenbei sparrt man sich eine Zeile im Quellcode und eine Spaicherstelle im Programmspeicher.
Basic-Line (PIC12...) & Mid-Range (PIC16...)
Zu Basic-Line gehören alle PIC12... mit 12-bit und zu Mid-Range alle PIC16... mit 14-bit langen Befehlen. Weil beide Familien gleichen Befehlsatz haben, werden sie hier gemeinsam behandelt. Sie haben insgesamt 35 Befehle.
Alle Befehle aus der Tabelle, ausser mit "*" gekenzeichneten, gehören zum Befehlsatz den High-End PIC18... dazu.
Kurzübersicht Assembler Befehle
|
|
|
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. hexadezimal 0x20 bzw. 20, dezimal d'42' bzw. .42 oder binär 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.
- 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
- 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
- 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
- Dieser Befehl wird zum Löschen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflussen. Die bits, die gelöscht werden sollen, werden in W-Register mit 0 und die unbeeinflußte mit 1 angegeben.
- 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 mit ANDLW.
- 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'
- 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'
- 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"
- 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.
- 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
- Das Register R wird mit Nullen gefüllt (gelöscht).
- Das W-Register wird mit Nullen gefüllt (gelöscht).
- 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.
- 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).
- 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.
- 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.
- 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.
- Um die Anzahl den Sprungmarken im Programm zu verringern, kann auch indirekte Adressierung von Sprungzielen mit "GOTO $+n" bzw. "GOTO $-n" benutzt werden. Das Symbol "$" bedeutet immer die aktuelle Adresse vom Programmzähler (PC). Das Assemblerprogramm, das sowieso jede Sprungmarke in entsprechende absolute Adresse wandelt, erzeugt in diesem Fall die Zieladresse als "PC+n" bzw. "PC-n", wobei "n" immer als hexadezimale Zahl genommen wird.
- Der Befehl "goto $+1" verbraucht nur Zeit von zwei "nop".
- Beispiele dazu sind in Pause.
- 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.
- 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.
- 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
- Dieser Befehl wird zum Setzen bestimmten Bits im Register benutzt, ohne restlichen Bits zu beeinflussen. Die bits, die gesetzt werden sollen, werden in W-Register mit 1 und die unbeeinflußte mit 0 angegeben.
- 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 mit IORLW.
- 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.
- Der festgelegte Wert k wird in das W-Register geladen.
- MOVLW 0xA3 ;ins W-Register wird eine Zahl A3h geladen
- Dieser Befehl wird auch bei indirekter Adressierung zum laden der absoluter Adresse ins "FSR" Register benutzt. Beispiel:
- Der Register "A3" wurde mit "A3 equ 23" definiert.
- MOVLW A3 ;ins W-Register wird die absolute Adresse 23h vom "A3" geladen movwf FSR ;diese Adresse wird jetzt ins Register "FSR" kopiert
- Das W-Register wird in das Register R kopiert.
- 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
- 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
- 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.
- 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.
- 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
- 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
- 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.
- 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
- 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
- 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'
- Der Befehl wird immer zum Sichern und Wiederherstellen des STATUS-Registers am Anfang und Ende einer ISR (interrupt service routine) verwendet, da er das STATUS-Register nicht beeinflußt (verändert keine STATUS bits).
- 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
- Dieser Befehl wird vor allem zum Vergleichen eines Registers mit ins W-Register geladenem Wert benutzt. Wenn die Werte in beiden Register gleich sind, wird ein Z bit im STATUS-Register gesetzt.
- 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. Er wird zum Vergleichen zwei Register benutzt, wobei ein Register vorher ins W-Register geladen wird..
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
|
|
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 geteilt:
|
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.
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>).
RP1 | RP0 | |
Bank0 | 0 | 0 |
Bank1 | 0 | 1 |
Bank2 | 1 | 0 |
Bank3 | 1 | 1 |
- FETTE Register sind in allen PICs vorhanden
- können je nach PIC unimplementierte Bereiche beinhalten - diese werden immer als 0 gelesen. (DATENBLATT!!)
- siehe 2
- Könnten je nach PIC auch nicht in Bank0 gemapped werden, sind dann eigenständige Register.
- je nach PIC kann es diese Bänke geben oder nicht geben.
Codeschnipsel
Alle hier sich befindliche Programmbeispiele sind nur auf den PIC12... und PIC16... sicher lauffähig und müssen für PIC18... bei Problemen umgeschrieben werden. Weitere Beispiele befinden sich in http://www.rn-wissen.de/index.php/PIC_ASM_Beispiele
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 Hilfsregister. 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 DClr ;Hex>A, D>Dec 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 DClr clrf D0 clrf D1 clrf D2 clrf D3 clrf C0 clrf C1 clrf C2 clrf C3 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 Adresse 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 für Grafikdisplay).
Die Adresse "0x2100" in der "org" Direktive hat mit der Adresse im Programmspeicher nichts zu tun.
Interrupts
Das Programm misst eine Frequenz an T0CKI Pin, wurde für den PIC16F628 geschrieben und in einem genauen Frequenzzähler angewendet. Es ist nur auf PICs lauffähig, die mindestens zwei Timer besitzen.
Es gibt nur ein Messbereich von 1 Hz bis 99999999 Hz mit gleicher Auflösung 1 Hz, deswegen ist kein Dezimalpunkt benutzt. Für einen kompletten Frequenzzähler ist nur noch ein Eingangsverstärker nötig.
Benötigte Hardware: Widerstand 470 Ohm zwischen der Frequenzquelle und TOCKI Pin (A4).
Die Ausgabe erfolgt auf ein 2x8 standard Zeichendisplay. Das HP ("Main") als leere endlose Schleife macht nichts außer warten auf ein Interrupt von Timer0 bzw. Timer1. Die ISR benutzt keine Register vom UPs und deshalb müssen W- und STATUS Register nicht gesichert und wiederhergestellt werden.
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 Timer 0 zählt die Impulse am TOCKI Pin (A4) in der Zeit, die vom Timer 1 bestimmt wird.
Der Zähler der Frequenz wurde um zwei zusätzliche Register A2 und A3 erweitert und bei jedem Timer0 Überlauf erhöht. Der Prescaler wird ins Register A0 und der TMR0 ins A1 kopiert. Somit ergibt sich 32-bittiges Ergebnis, 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 beliebigen Display angezeigt werden.
Die Quarzfrequenz 7,3728 MHz wurde gewählt, weil sie am nächsten der temperaturstabiltesten Frequenz für AT Quarzen 7,2 MHz ist. Die Quarzfrequenz muß nicht genau sein, da der Frequenzzähler lässt sich sehr genau mit den Werten von TMR1H und TMR1L "trimmen", die vor dem Start des Timers 1 geladen werden.
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 mit entsprechenden Werten geladen werden.
Hier wird die Messzeit 1 s genutzt und die Frequenz wird mit einer Auflösung von 1 Hz gemessen. Die Messzeit beträgt 3*10000h+(FFFFh-7BFFh) Timertakten. Für den Quarz 7,372800 MHz und Prescaler=8, wird der Takt von Timer1 gleich 7372800/4*8=230400 Hz. Deswegen wird der Timer1 beim Starten mit ca. 7BFFh geladen und nach dem 4. Überlauf gestoppt. Die in TMR1H und TMR1L praktisch geladene Werte unterscheiden sich ein bißchen von berechneten, weil die Quarzfrequenz fast nie genau bis auf 1 Hz stimmt.
Für z.B. 20 MHz Quarz werden 10 Überläufe des Timers benötigt und er wird beim Start mit 7697h geladen, da 1s gleich 9x10000h+(FFFFh-7697h) Timertakten ist.
In dem UP "Init" muss eventuell für ein anderes Display die Initialisierung des Displaykontrollers geändert werden.
In dem Program wurden unveränderte UPs verwendet, die näher in Hex Dec Wandlung und Matrix erklärt sind.
; Programm zum Messen der Frequenz an T0CKI Pin mit 7,3728 MHz Quarz LIST P=16F628 include "P16F628.inc" __CONFIG _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _HS_OSC & _BODEN_OFF & _LVP_OFF ; TOCKI PORTA,4 ;I Frequenzeingang ; DB4 PORTB,0 ;O Display Data Bit 4 ; DB5 PORTB,1 ;O Display Data Bit 5 ; DB6 PORTB,2 ;O Display Data Bit 6 ; DB7 PORTB,3 ;O Display Data Bit 7 #define _RS PORTB,4 ;O Display RS #define _E PORTB,5 ;O Display Enable #define _C STATUS,C #define _Z STATUS,Z #define _DC STATUS,DC #define _RP0 STATUS,RP0 #define _Fcra Flags,0 #define _Fcrp Flags,1 #define _Fdca Flags,2 #define _Ferr Flags,3 ;Überlauf (Fehler) #define _Frs Flags,4 #define _Fnz Flags,5 A3 equ 0x20 ;Register fürs Rechnen Hex_Dec 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 Tmp equ 0x30 ;Register, die für Displayausgabe Tmp1 equ 0x31 ;vorläufig benutzt werden Flags equ 0x32 ATmp equ 0x33 ;vorläufige Schleifenzähler HTmp equ 0x34 RTmp equ 0x35 ;Zwischenspeicher ORG 0x0000 call Init ;alles initialisieren Main goto Main ;und in die leere endlose Schleife springen ORG 0x0004 ;hier fängt ISR (interrupt service routine) an bcf INTCON,GIE ;alle interrupts sperren btfss INTCON,T0IF ;ist Timer0 überlaufen ? goto CheckTMR1 ;nein, dann prüfe Timer1 bcf INTCON,T0IF ;ja, Timer0 interrupt flag löschen und bearbeiten movlw 1 addwf A2,1 ;erhöhe A2 durch Addition btfsc _C ;um Überlauf von A2 zu erkennen und incf A3,1 ;increment A3, wenn A2 überlaufen ist CheckTMR1 btfss PIR1,TMR1IF ;ist Timer1 überlaufen ? retfie ;nein, dann ISR beenden und interrupts erlauben bcf PIR1,TMR1IF ;Timer1 interrupt flag löschen call Multip ;Interrupts vom Timer1 im UP "Multip" zählen retfie ;ISR beenden und interrupts erlauben Multip incf Tmp,1 ;Interruptszähler von Timer1 erhöhen btfss Tmp,2 ;schon 4.interrupt? return ;nein, dann zurück bsf _RP0 ;ja, weiter movf TRISA,0 ;stopp Timer0 usw. andlw 0xEF movwf TRISA ;TOCKI Pin (A4) als Ausgang bcf _RP0 bcf T1CON,TMR1ON ;stopp Timer1 GetFreq movf TMR0,0 ;Prescaler ins Register A0 kopieren movwf A1 movwf RTmp clrf ATmp FToggle incf ATmp,1 bsf _RP0 bsf OPTION_REG,T0SE bcf OPTION_REG,T0SE bcf _RP0 movf TMR0,0 subwf RTmp,0 btfsc _Z goto FToggle comf ATmp,1 incf ATmp,0 movwf A0 call Hex_Dec ;Messergebnis auf Decimal wandeln call AClr call DispFrq ;eventuell eigenes UP für benutztes Display clrf Tmp clrf TMR0 call TMRStart ;beide Timer starten return AClr clrf A0 ;Register A löschen clrf A1 clrf A2 clrf A3 return TMRStart movlw 0x34 ;Timer1 konfigurieren movwf T1CON ;und laden movlw 0x7B ;berechneter Wert: TMR1H=7Bh movwf TMR1H movlw 0xFB ;berechneter Wert: TMR1L=FFh movwf TMR1L bsf _RP0 ;start Timer0 movf TRISA,0 iorlw 0x10 movwf TRISA ;TOCKI Pin (A4)als Eingang bcf _RP0 bsf T1CON,TMR1ON ;start Timer1 return Hex_Dec call DClr ;Hex>A, D>Dec 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 DClr clrf D0 clrf D1 clrf D2 clrf D3 clrf C0 clrf C1 clrf C2 clrf C3 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 DispFrq bcf _Fnz ;Frequenz anzeigen (8 Ziffern) call Fst ;mit Unterdrückung der führenden Nullen movlw 3 movwf ATmp movlw D3 movwf FSR swapf INDF,0 andlw 0x0F btfsc _Z goto $+2 bsf _Fnz btfss _Fnz goto $+3 call Num goto $+3 movlw " " call Char movf INDF,0 andlw 0x0F btfsc _Z goto $+2 bsf _Fnz btfss _Fnz goto $+3 call Num goto $+3 movlw " " call Char incf FSR,1 decfsz ATmp,1 goto $-18 swapf D0,0 andlw 0x0F btfsc _Z goto $+2 bsf _Fnz btfss _Fnz goto $+3 call Num goto $+3 movlw " " call Char movf D0,0 andlw 0x0F call Num call Snd ;zweite Zeile movlw "M" call Char movlw "^" call Char movlw " " call Char movlw "k" call Char movlw " " call Char movlw "^" call Char movlw "H" call Char movlw "z" call Char return Fst movlw 0x80 ;Adresse der ersten Zeile goto Cmd Snd movlw 0xC0 ;Adresse der zweiten Zeile Cmd movwf Tmp ;Befehl ins Tmp laden bcf _Frs ;RS=0 goto Send Val movwf Tmp1 swapf Tmp1,0 call Num movf Tmp1,0 Num andlw 0x0F movwf Tmp movlw 0x0A subwf Tmp,0 btfsc _C addlw 7 addlw 0x3A ;ASCII 0-F Char movwf Tmp ;Zeichen ins Tmp laden bsf _Frs ;RS=1 Send swapf Tmp,0 ;zuerst High Nibble andlw 0x0F movwf PORTB ;an Port (Display) schicken btfsc _Frs ;RS Flag an Port kopieren bsf _RS bsf _E ;Enable erzeugen bcf _E movf Tmp,0 ;Low Nibble andlw 0x0F movwf PORTB ;an Port (Display) schicken Enab btfsc _Frs ;RS Flag in Port kopieren bsf _RS bsf _E ;Enable erzeugen bcf _E Del movlw 0x30 ;Verzögerung min. ca. 50µs movwf Tmp decfsz Tmp,1 goto $-1 return Init clrf PORTA ;Register vorm Start löschen clrf PORTB clrf Tmp clrf TMR0 call AClr bsf _RP0 ;Bank 1 movlw 0xFF movwf TRISA ;alle PORTA Pins als Eingänge clrf TRISB ;und alle PORTB Pins als Ausgänge movlw 0xE7 ;Takt für Timer0 vom T0CKI Pin usw. movwf OPTION_REG ;Timer0 konfigurieren bsf PIE1,TMR1IE ;TMR1 interrupt erlauben bcf _RP0 ;Bank 0 movlw 0xE0 ;GIE, PEIE & TMR0IE erlauben movwf INTCON call TMRStart ;beide Timer starten 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
Mausrad bzw. Drehencoder
Das UP wertet ein Mausrad bzw. Drehencoder aus und setzt, je nach Drehrichtung 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 bzw. Drehencoder .-------. | o---> PORTB,0 +----o---- | | | o---> PORTB,1 === '-------' GND
Es müssen die internen pull-ups vom PORTB aktiviert werden. Wenn das Mausrad bzw. Drehencoder 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 zusammengesetzte UPs (z.B. Displayausgabe) mit solcher Ausführungszeit benutzt werden.
In dem UP "Mouse" wird PORTB eingelesen, die alle Bits außer 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. Anschließend wird zum Aufrufer zurückgesprungen.
Detaillierter 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 Testprogramm 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]] und Anwendung: http://www.roboternetz.de/community/threads/42200-Frequenzz%C3%A4hler-mit-LPH2673-1-%28zum-Nachbauen%29 .
High-End (PIC18...)
Als High-End werden alle 8-bit PIC18F... klasiffieziert, die Befehlslänge 16-Bit, also 2 Bytes haben. Ihr Befehlsatz enthält insgesamt 75 Befehle.
Es werden nur nötige fürs Erstellen von ASM Programmen spezifische Unterschiede behandelt, die in der Praxis Probleme für Umsteiger von Basic-Line und Mid-Range bereiten können. Wenn sich jemand ausschliesslich mit PIC18F... beschäftigen will, wird sich keine Unterschiede merken müssen, da ausser erweitertem Befehlsatz die Programme von ihrer Struktur identisch sind. Genaue Parameter für bestimmten PIC befinden sich im entsprechendem Datenblatt.
Hardware
Weil die PIC18F... für einige Anwendungen schneller als Mid-Range laufen müssen, wurde bei Takterzeugung eine PLL-Option beim Oszillator zugefügt, die aus standard Quarzen (z.B. 12 MHz) durch Multiplikation mal 4 eine Taktfrequenz für CPU 12 MHz (z.B. für USB) erzeugt. Weil die Frequenz des Oszilators für interne Taktung der CPU durch 4 geteilt ist, ermöglichst diese Option, dass die CPU mit Oscillatorfrequenz, also bei z.B. 10 MHz Quarz mit echten 10 MHz (10 MIPs) arbeitet (intern mit 40 MHz).
Der Programmspeicher ist als 8-Bit breit organisiert. Aus dem Grund jeder 16 Bit langer Befehl belegt im Speicher 2 Bytes. Wenn im Datenblatt z.B. 16384 Bytes angegeben sind, bedeutet das, dass dort sich nur die Hälfte davon, also 8192 Befehle abspeichern lassen. Als Folge sind für Befehle nur gerade Adressen zulässig.
Software
Alle Befehle sind um 2 Bit länger, als bei Mid-Range, und es gibt keine Speicherbänke, was Erstellung von Programmen sehr vereinfacht.
Bisher für Mid-Range ausführlich beschriebene Befehle, ausser "CLRW", "RLF" und "RRF" und Speicherbankumschaltungen (z.B. "BSF RP0" und "BCF RP0") sind für PIC18F... "verständlich" und werden ausgeführt. Die PIC18F... haben zusätzlich 43 Befehle.
Wenn es um ASM Programm geht, ist er grundsätzlich, ausser zusätzlichen Befehlen, identisch wie bei Mid-Range. Die zusätzliche Befehle ermöglichen Erstellen von mehr kompakten Programmen.
Die PIC18... haben die Möglichkeit für Interuppts individuell Prioritäten definieren, was die Bearbeitung in ISR vereinfacht. Aus dem Grund besitzen sie zwei Interrupt-Vektoren (Anfangsadressen für ISR): 8h für höhere und 18h für niedrigere Prioritäten, die anders als bei übrigen PIC's sind (4h).
Ausser erweiterten Befehlsatz haben die PIC18F... drei Register für indirekte Adressierung FSR0, FSR1 und FSR2, die unabhängig voneinender und gleichzeitig benutzt werden können.
Die CPU's von PIC18F... haben tieferen Stapel für Rücksprungadressen mit 31 Ebenen, der zusätzlich mit "PUSH" und "POP" Befehlen während Ausführung eines Unterprogramms (UP) modifiziert werden kann. Das ermöglichst Änderungen von Rücksprungadressen, so dass nicht unbedingt nach der Ausführung des UP zurück, sondern fast beliebig gesprungen werden kann. Ausführlich ist es in AN818 vom Microchip beschrieben. Siehe dazu: http://ww1.microchip.com/downloads/en/AppNotes/00818a.pdf
Sehr nutzlich sind auch alle neue Sprungmöglichkeiten z.B. "BRA" der "GOTO" entspricht. Da die bedingte Sprünge von Bits des "STATUS" Register abhängen, muß davor kein Befehl aüsgeführt werden der benötigten Bit (z.B. "Z") prüft.
Wegen Struktur des Programspeichers ein relativer Sprung zur nächster Adresse muss um 2 Byte erfolgen. Deswegen ist für PIC18F... also "GOTO $+2" der kürzeste mögliche Sprung. Bei "GOTO $+1" springt die CPU "zwischen" gültige Befehle ins "Nirvana", was Absturz des Programms bedeutet. Bei bedingten Sprungen und "BRA" wird der angebebener Wert "n" für die Anzahl den übersprüngten Zeilen automatisch durch 2 multipliziert.
Alle PIC18F... können den Programspeicher beschreiben und sich selbst programmieren. Dafür gibt es die "TBLWT.." Befehle. Das ist aber nur für mindestens 8 Bytes am Stück möglich. Vor dem Beschreiben müssen die dafür vorgesehene Speicherplätze zuerst gelöscht werden, wobei das für minimum 64 Bytes möglich ist.
Als Beispiel ein geprüftes Fragment von Bearbeitung des Programmspeichers (Flash) in http://www.rn-wissen.de/index.php/PIC_ASM_Beispiele.
Kurzübersicht zusätzliche Befehle
|
|
|
Ausführliche Beschreibung zu den Befehlen
Alle Sprunge ausser "BRA" können nur im Bereich eines Bytes, d.h. von -128 bis +127 erfolgen. Nur der Sprung "BRA" kann zwischen -1024 bis +1023 lang sein.
Wenn die Bedingung für ein bedingten Sprung nicht erfüllt ist, wird er in einem Takt übersprungen und nächster Befehl ausgeführt.
Erklärungen zu den Verwendeten Platzhaltern:
- k stellt einen fest definierten Wert da. z.B. hexadezimal 0x20 bzw. 20, dezimal d'42' bzw. .42 oder binär b'00101010'
- n stellt einen fest definierten Wert wie oben für Sprünge
- 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.
- Es wird die Rechenoperation [math]W+R[/math] mit Berücksichtigung des schon vorhandenen Übertrags 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
- Es wird das "C" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen.
- Es wird das "N" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen.
- Es wird das "C" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen.
- Es wird das "N" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen.
- Es wird das "OV" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen.
- Es wird das "Z" Bit im "STATUS"-Register überprüft und beim gelöschten gesprungen.
- Es wird das "OV" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen.
- Es wird immer unbedingt gesprungen.
- Es wird das "Z" Bit im "STATUS"-Register überprüft und beim gesetzten gesprungen.
- Der bestimmte Bit im F-Register wird umgekehrt. Also wenn er vorm Ausführen des Befehls z.B. gleich 1 war, wird er danach gleich 0 sein und umgekehrt.
- Es wird der Registers f mit dem W verglichen und bei f=W, wird der nächste Befehl übersprungen. Der Zusatz SEQ steht für skip if equal, d.h. wenn die Werte in beiden Register gleich sind, wird der nächste Befehl übersprungen.
- Es wird der Registers f mit dem W verglichen und bei f>W der nächste Befehl übersprungen. Der Zusatz SGT steht für skip if greater, d.h. wenn der Wert im f grösser als im W-Register ist, wird der nächste Befehl übersprungen.
- Es wird der Registers f mit dem W verglichen und bei f<W der nächste Befehl übersprungen. Der Zusatz SLT steht für skip if littler (lower), d.h. wenn der Wert im f kleiner als im W-Register ist, wird der nächste Befehl übersprungen.
- Es weden alle nötige Korrekturen für richtigen Ergebnis im W-Register nach dezimaler Addition durchgeführt.
- 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.
- 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 SNZ steht für skip if not zero, d.h. wenn das Ergebnis der Rechnung ungleich Null ist, wird der nächste Befehl übersprungen. Weil es keine "echte" Addition ist, beeinflusst dieser Befehl das C-Flag im STATUS-Register nicht.
- Es wird gewähles FSR-Register (FSR0, FSR1, bzw. FSR2) mit dem Wert k geladen.
- Es wird der Bank Select Register (BSR) mit dem Wert k geladen.
- Das Register R1 wird in das Register R2 kopiert.
- Es wird W-Register mit k multiplieziert und das Ergebnis in Registern PRODH und PRODL gespeichert. Dieser Befehl beeinflusst das STATUS-Register nicht.
- Es wird F-Register mit W-Register multiplieziert und das Ergebnis in F gespeichert. Dieser Befehl beeinflusst das STATUS-Register nicht.
- Es werden alle Bits von F-Regiester negiert. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
- Es wird die oberste Rücksprungadresse vom Stapel entfernt und alle übrigen Adressen um eine Stelle nach oben geschoben.
- alle <--- Stapel --->| übrige A Adresse 1 -----> entfernt Adressen | Adresse 2 um 1 Stelle | Adresse 3 nach oben | .......... verschieben | Adresse 31
- Zur Verdeutlichung:
- <--- Stapel --->| ;vor dem "POP" Adresse 1 ---> verworfen Adresse 2 Adresse 3 .......... Adresse 31 <--- Stapel --->| ;nach dem "POP" Adresse 2 Adresse 3 Adresse 4 .......... ?????????
- Es wird eine neue oberste Rücksprungadresse am Stapel abgelegt und alle übrigen Adressen um eine Stelle nach unten geschoben.
- alle <--- Stapel --->| übrige | Adresse 1 <--- neue wird abgelegt Adressen | Adresse 2 um 1 Stelle | Adresse 3 nach unten | .......... verschieben V Adresse 31
- Zur Verdeutlichung:
- <--- Stapel --->| ;vor dem "POP" Adresse 1 Adresse 2 Adresse 3 .......... Adresse 31 <--- Stapel --->| ;nach dem "POP" PC + 2 Adresse 1 Adresse 2 .......... Adresse 30
- Es wird ein Unterprogramm reletiv aufgerufen, der innerhalb 1 kB von aktueller Adresse liegen darf. Vergleiche mit "CALL".
- Es wird Reset des PIC's durchgeführt, genauso wie hardwaremässig mit /MCLR-Pin.
- 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
- Alle Bits im Register R werden um eine Position nach links verschoben. Das Ergebnis wird entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
- |<--- Register R --->| 7<-6<-5<-4<-3<-2<-1<-0 V A |____________________|
- Zur Verdeutlichung:
- |-Register R-| ;0-7 stehen für Bitnummer im Register. 7 6 5 4 3 2 1 0 ;vor der Rotation 6 5 4 3 2 1 0 7 ;nach der Rotation
- 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
- Alle Bits im Register R werden um eine Position nach rechts verschoben. Das Ergebnis wird entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1).
- |<--- Register R --->| 7->6->5->4->3->2->1->0 A V |____________________|
- Zur Verdeutlichung:
- |-Register R-| ; 0-7 stehen für Bitnummer im Register. 7 6 5 4 3 2 1 0 ;vor der Rotation 0 7 6 5 4 3 2 1 ;nach der Rotation
- Alle Bits im Register R werden gesetzt, also nach dem Ausführen des Befehls wird im Register "FFh" stehen.
- Der inhalt des W-Registers wird vom F-Register mit Berücksichtigung des vorhandenes Übertrags abgezogen. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
- Der inhalt des F-Registers wird vom W-Register mit Berücksichtigung des vorhandenes Übertrags abgezogen. Dieser Befehl beeinflusst das STATUS-Register. Siehe hierzu Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Register
- Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung bleibt der Zeiger unverändert.
- Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erhöht.
- Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erniedrigt.
- Dieser Befehl wird fürs Lesen des Programmspeichers (Flash) angewendet. Vor der Ausführung wird der Zeiger um 1 erhöht.
- Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung bleibt der Zeiger unverändert.
- Diesers Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erhöht.
- Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Nach der Ausführung wird der Zeiger um 1 erniedrigt.
- Dieser Befehl wird fürs Beschreiben des Programmspeichers (Flash) angewendet. Vor der Ausführung wird der Zeiger um 1 erhöht.
- Es wird der nächste Befehl öbersprungen, wenn der Register f gleich Null ist.
Umschreiben von Programmen
Es werden alle nötige Änderungen beschrieben, die vorhandenes Programm lauffähig machen. Ausserdem muß für gewünschten PIC nötige Konfiguration im Quellcode ersetzt werden. Weil einige Befehle von PIC18F... müssen für Mid-Range in UPs umgeschrieben werden, die Auführung Zeit vom umgeschriebenen Program kann sich erheblich ändern. Bei zeitkritischen Abläufen ist deswegen Umschreibung High-End -> Mid-Range nicht immer möglich.
Basic-Line & Mid-Range -> High-End
Alle Speicherbankumschaltungen müssen mit ";" ausgeblendet bzw. gelöscht werden.
Da die PIC18... drei "FSR" Register besitzen muss überall "FSR" konsequent anstatt "FSR" nach Wunsch "FSR0", "FSR1", bzw. "FSR2" eingeschrieben werden.
Die für PIC18... unverständliche Befehle von Mid-Range müssen, wie folgt, ersetzt werden
"CLRW" -> "MOVLW 0"
"RLF" -> "RLCF"
"RRF" -> "RRCF"
Alle Relative Sprunge mussen verdoppelt werden. Beispielweise jeder "GOTO $+4" Befehl muss durch "GOTO $+8" bzw. "BRA $+8" ersetzt werden (Asführungszeit bei Schleifen beachten).
Beim Umschreiben vorhandenen Programen von PIC12... und PIC16... ist für Interrupts ein kompatibilität Modus vorgesehen, die keine definierte Prioritäten für Interrupts benötigt, was folgendemassen in Initialisierung (Init) festgelegt wird:
bcf RCON,IPEN ; disable priority levels on interrupts (compatibility modus)
High-End -> Basic Line & Mid-Range
Alle zusätzliche Befehle sind für Mid-Range PIC's unverständlich und müssen als kleine Unterprogramme erstellt werden. Für einige Befehle ist es enfach:
"MOVFF R1, R2" -> "MOVF R1,0" "MOVWF R2"
Es kann auch komplizierter werden, z.B. für "DAW":
DecCor btfsc _DC ;dezimale Korrektur (ersetzt "DAW" von PIC18FXXX) bsf _Fdca btfsc _C bsf _Fcra movf INDF,0 andlw 0x0F sublw 9 btfss _C bsf _Fdca movf INDF,0 andlw 0xF0 sublw 90 btfss _C bsf _Fcra return
In dem UP sind "_Fcra" und "_Fdca" im Program definierte Flags. Siehe dazu Hex Dec Wandlung
Hilfsmittel
Hier werden einige Programme presentiert, die das ASM Programmieren erleichtern. Sie sind nur auf PIC12... und PIC16... lauffähig und für PIC18... müssen umgeschrieben werden.
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.
Hoffentlich hilfreiche Beiträge sind auch dort zu finden: http://www.roboternetz.de/community/threads/26098-Tips-Tricks .
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 Oszillator 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 Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @Tmp equ 0x7F anstatt 0x4F).
Um "Kollisionen" mit dem Programm, in das er eingebunden wird, zu vermeiden, 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. Außerdem 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 swapf 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 swapf @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 vorherigen 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 "*" gekennzeichnete Zeilen (alle außer "goto @Init" können geändert werden), müssen im Programm, in das "PIC Trainer" eingebunden ist, enthalten sein. Wegen ISR darf das Programm, 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 Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @SWR equ 0x7F anstatt 0x4F).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 vermeiden, 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 bilibigen, in Register eingestellten Werten , getestet werden können.
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 swapf 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 swapf @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 ; @DT und @CK als Ausgänge 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
Das Programm "PIC Trainer" kann auch für grösseres Display (mehr Register und Testx) modifiziert werden. Als Beispiel ein Quellcode für 4x16 Display (0 bis 1Fh Register/Test):
; PIC Trainer32.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 @LPC2 equ 0x45 @Tasten equ 0x44 ORG 0x0004 ; ISR bcf INTCON,GIE ; interrupts sperren movwf @SWR ; W-Register sichern swapf 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,2 ; prüfen, ob schon 4. Timer0 interrupt goto $+3 ; wenn nicht, zum "@IntTest" springen clrf @Int ; Zähler löschen call @ ; Anzeigen der Registerinhalte aufrufen @IntTest ;**************; ; eigener Code ; ;**************; movf @FSR,0 ; FSR Register movwf FSR ; wiederherstellen swapf @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 ORG 0x0338 @ 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 call @3rd call @Line call @4th call @Line movlw 0x0E ; Cursor an call @Cmd swapf @LPC,0 ; Display Adresse berechnen und Cursor plazieren andlw 0x0F movwf @LPC2 btfss STATUS,Z goto $+3 movlw 0x80 goto @AddLPC movf @LPC2,0 sublw 1 btfss STATUS,Z goto $+3 movlw 0x30 goto @AddLPC movf @LPC2,0 sublw 2 btfss STATUS,Z goto $+3 movlw 0x70 goto @AddLPC movf @LPC2,0 sublw 3 btfsc STATUS,Z movlw 0xA0 @AddLPC 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-1F starten movwf PCLATH movf @LPC1,0 andlw 0x1F 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 0x40 btfsc STATUS,Z clrf @LPC movf @LPC,0 movwf @LPC1 rrf @LPC1,1 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 goto @Cmd @3rd movlw 0x90 ; Adresse der 3. Zeile an Display schicken goto @Cmd @4th movlw 0xD0 ; Adresse der 4. 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 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 0x3DF ; diese Adresse kann gleich max.Adresse - 20h 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 goto Test10 goto Test11 goto Test12 goto Test13 goto Test14 goto Test15 goto Test16 goto Test17 goto Test18 goto Test19 goto Test1A goto Test1B goto Test1C goto Test1D goto Test1E goto Test1F 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 Quellcode 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 gesamte 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 Ausfü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 swapf 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 swapf @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 swapf 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 swapf @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 Programm "@Profiler" kann für PICs, die mehr Programmspeicher besitzen, mit entsprechender "org" Direktive höher verschoben werden. Entsprechend können auch alle im Programm benutzte Register höchstmögliche Adressen im RAM haben (z.B. @SWR equ 0x7F anstatt 0x4F).
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 LiFePO4 Speicher Test](https://www.roboternetz.de/partner/solarspeicher-speicher-akkus-test.jpg)