Inhaltsverzeichnis
- 1 Einladung zur Diskussion...
- 2 Einführung
- 3 Programm
- 4 Mid-Range
Einladung zur Diskussion...
Es wird hier versucht die ASM Programmierung von PIC Mikrocontroller zu beschreiben.
Damit das enstehende Artikel wirklich nutzlich wird, ist Ihre Mitwirkung nötig. Bitte schreib uns Deine Meinung, was ewentuell noch geändert (z.B. ergänzt) werden soll in diesem Tread:
http://www.roboternetz.de/phpBB2/viewtopic.php?p=271211#271211
Die Autoren bedanken sich im voraus für jeden Beitrag mit Vorschlägen !
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, weil er nur zwei unterschiedliche Werte 0 bzw. 1 haben kann.
Wenn wir gleichzeitig (paralell) 8 Bits haben, dann ist es ein Byte, der mehrere Bitkombinationen von 00000000b bis 11111111b enhält, weil ein Bit (X) auf jeder Stelle 0 bzw. 1 sein kann.
X | X | X | X | X | X | X | X |
High Nibble | Low Nibble | ||||||
Byte |
Das "b" bedeutet, das 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 Bit mehr als 10 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 = 8d 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, u.s.w. So wie im Dezimalsystem werden führende Nullen nicht geschrieben, aber in einem PIC Register existieren immer 8 Bits also auch führende Nullen. Zum Beispiel die hex Zahl 3h sieht im Register so aus: 00000011b. Bei einer Wandlung bin->hex fängt man immer von der rechten Seite der bin Zahl an, da die Anzahl führenden Nullen unbekannt ist.
Speicher und Register
Als Speicher bezeichnet man ein Teil der Hardware, in die eine Information eingeschrieben, in der gespeichert und aus der wieder ausgelesen werden kann.
Es gibt eigentlich nur zwei Arten von elektronischen Speicher: 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 Dataspeicher (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, das die flüchtigen direkt (sehr schnell) beschreibbar sind und das Beschreiben den nichtflüchtigen benötigt spezielle Algorithmen, die leider im Vergleich zu direkten Zugriffen langsamer sind.
Ein Speicher besitzt bestimmte Menge von s.g. Speicherstellen. Jede Speicherstelle hat seine individuelle Adresse und kann eine binäre Information mit bestimmter Anzahl von Bits abspeichern.
Bei PIC haben die drei Arten von Speicher, wegen verschiedener Anwendung, auch unterschiedliche Struktur. Die beiden Speicher für Daten (RAM und EEPROM) haben jeweils 8-bitigen und Programmspeicher (Flasch) bei Mid-Range hat 14-bitigen Speicherstellen. Die Anzahl den Speicherstellen im bestimmten Speicher ist vom PIC-Typ abhängig.
Eine 8-bitige Speicherstelle im RAM wird bei PICs Register genannt und kann so skiziert 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 |
Der bit 7 wird als hochwertigste (MSB = Most Significant Bit) und bit0 als niederwertigste (LSB = Least Significant Bit) bezeichnet. Jeder Bit im Register (X) kann gleich 0 bzw. 1 sein.
Um ein Databyte in ein Register schreiben oder aus einem Register 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 definierten Namen des Registers (z.B. Temp equ 0x20): movwf Temp
Indirekte Adressierung durch FSR Register, in den die absolute Adresse des Registers Temp eingeschrieben wird und der Wert aus dem Temp sich im INDF Register befindet. Wie vorher wurde Temp equ 0x20 definiert und weiter:
movlw Temp ;in W-Register wird die absolute Adresse des Registers Temp geladen movwf FSR ;diese Adresse wird in das FSR Register kopiert movf INDF,0 ;der Wert aus dem indirekt adressierten Register Temp wird in das W-Register geladen.
Weil in jedem 14-bittigem Befehl, der mit Datenspeicher verbunden ist, fur Adresse des ansprechenden Registers nur 7 Bits existieren, die bis zum 7Fh (128d) Register direkt ansprechen können, ist bei PICs der Datenspeicher (RAM) in s.g. Bänke verteilt.
Für Aswahl einer Bank sind zwei Bits RP0 und RP1 im STATUS Register zuständig. Die Anzahl von Bänke und ihre Verwendung ist von gesamter Grösse des RAMs abhängig und kann dem Datenblatt des PICs entnommen werden. Siehe: Speicherbankorganisation
Prozessor
Der Prozessor von Mid-Range PICs gehört zu den RISC (Reduced Instruction Set Computer) Prozessoren und man hat nur 35 Befehle zu Erlernen, was seine Programmierung 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 zwischen 1-2 Prozessortakten.
Die Prozessoren der Mid-Range Serie von Microchip sind alle in der "Harvard"-Architektur gefertigt. Das Bedeuted, dass der Datenspeicher und Programmspeicher einen eigenen Bus zur CPU besitzen. Der Vorteil zur "von Neumann"-Architektur ist, dass sich die Busgrößen damit unterscheiden können. Das ermöglicht eine größere Bandbreite.
Der Befehl (beim PIC 14 bit) kann in nur einem Takt verarbeitet werden. Daher kommt auch das Aufteilen der Ausführung des Befehls in die 4 verschiedenen Vörgänge. Wärend der neue Befehl eingelesen ("gefatched") wird, wird der Vorige gerade gelesen ("read") und der Vorvorige verarbeited ("executed") und der Vorvorvorige schreibt gerade in den Datenspeicher ("write"). Das heist 4 Befehle werden jeweils um einen Oszillatortaktzyklus verschoben gleichzeitig verarbeitet.
Das geschieht in vier Perioden des Oszillators. Deswegen die Taktfrequenz des CPUs entspricht durch 4 geteilter Frequenz des Oszillators.
CPU Vorgang Richtung Speicher ------------------------------------------------- - 1.Befehl lesen (fatch) <------- Flash | 2.Daten lesen (read) <------- RAM | 1 Prozessortakt = 3.Daten verarbeiten (execute) | 4 Oszillatortakte 4.Daten schreiben (write) -------> RAM | -
Nur o.g. CPU Vorgänge sind direkt möglich. Es können deswegen keine Befehle aus dem RAM oder EEPROM ausgeführt werden. Um ein Databyte aus einem RAM Register in ein anderes zu kopieren, muss er zuerst aus dem ersten RAM Register in das W-Register (eigenen s.g. Arbeitsregister des CPU) und erst davon in das zweite RAM Register kopiert werden.
Das Lesen/Schreiben aus/in den EEPROM Speicher ist mit Hilfe speziellen Register und Unterprogrammen bei allen Mid-Range PICs möglich. Der Lese und Schreibzugriff auf den Programmspeicher ist aber nur bei wenigen PIC-Typen (z.B. PIC16F87X) möglich. Dies ermöglicht ein "sich selbst Programmieren", was bei Bootloadern genützt wird.
Assembler
Die Maschinensprache, auch Assembler oder kurz ASM genannt, ist eine Sprache die nur bestimmter CPU versteht. Für einen Menschen ist sie unverständlich, da sie nur aus hex Zahlen besteht.
Um sich die Sprache verständlicher zu machen wurden den hex Zahlen s.g. Mnemonics aus Buchstaben zugewiesen. Jeder Befehl für einen CPU hat somit ein "Namen", der aus englischer Sprache stammt. Siehe: Kurzübersicht Assembler Befehle
Obwohl sie 200 bis 1000 mal schneller als die meisten Hochsprachen ist, wird sie wegen dem grossen Aufwand bei Erstellung umfangreichen Programmen, selten benutzt. Man findet sie aber oft in fast allen Hochsprachen, in eigebundenen Funktionen, überall dort wo die Hochsprachen zu langsam sind oder nötigen Aufgaben nicht unterstützen (z.B. Maus in Q-Basic).
ASM eignet sich aber 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.
Dank der integrierten oder an Portpins angeschlosenen Hardware und dem entsprechenden Program kann ein µC umfangreiche Aufgaben realisieren, die fast unbegrenzt und schwer vorstellbar sind.
Die Aufgabe eines ASM-Programmierers ist, ein Programm zu schreiben, das das Assemblerprogramm (z.B. MPASM) fehlerfrei in die Machinensprache "übersetzt" und der bestimmte CPU "versteht". Sie endet eigentlich erst dann, wenn das geschriebene Programm so wie geplannt funktioniert.
Weil ASM Programme nicht besonders durchschaubar sind, wurde als Hilfsmittel ein Programmablaufdiagramm (kurz: PAD) erfunden. Beim Programmerstellung fängt man damit an ein PAD zu erstellen, das die wichtigsten 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 "übersetzt" und als Texdatei mit Erweiterung .hex gespeichert. Diese Datei wird danach in den Programmspeicher des µC übertragen ("gebrannt").
Das Assemblerprogramm MPASM kann kostenlos von der Homepage des Herstellers von PICs [1] runtergeladen werden. Es muss zuerst vom Downloads die "MPLAB IDE v7.50 Full Zipped Installation" runtergeladen und erst danach können gewählte Programme (z.B. nur MPASM) intalliert werden. Für MPASM benutzer werden auch folgende .pdf Dateien empfohlen:
MPASM/MPLINK User's Guide (2628 KB) [Benutzerhandbuch]
MPASM™/MPLINK™ PICmicro® Quick Chart (81 KB) [Kurzübersicht]
Nach dem Eischalten der Betriebsspannung des µC, fängt der CPU an, sich im Programmspeicher befindliches Programm mit dem Befehl, der an der Adresse 0 steht, auszuführen.
Aber wann das Programm endet? Natürlich wenn die Versorgungsspannung abgeschaltet wird. Nein! Das ist die einfachste Lösung um ein laufendes Programm auf zufälliger Stelle zu unterbrechen, aber keine um ihn auf 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 der CPU eine Meldung ausgibt, dass er sich schon auf der "STOP" Stelle des Programms befinet. Es muss auch definiert werden (z.B. durch eine Tastenkombination), wann der 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, u.s.w.). Bei diesen reicht es bereits Spannung anzulegen und sie laufen bereits. Die meisten haben ihn aber nicht (z.B. PIC16F84, PIC16F870, u.s.w.) 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 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 MCLR Pin angeschlossene Hardware während der Programmierung zu schützen.
Bei externen Oszillatoren bleibt der Pin OSC2 nicht angeschlossen und kann als I/O benutzt werden. Falls ein interner Oszillator benutzt wird, können beide OSC Pins als I/O dienen.
Damit ein Programm zuverlässig ausgeführt werden kann, muss die Versorgungspannung störungsfrei sein. Dafür wird ein Keramik-Vielschicht-Kondensator 100 nF möglichts am kürzesten direkt zwischen VDD und VSS Pins geschaltet.
Folgende Skizzen zeigen die Grundbeschaltung eines PICs:
Wahl des PICs
Es gibt PIC µC die im Typenbezeichnung den Buchstaben "C" oder "F" haben.
Die älteren mit "C" haben EPROM Programmspeicher und die gibt es in zwei Versionen: ohne und mit Fenster (aus Quarz-Glass) fürs Löschen des EPROMs mit UV Strahlung. Bei denen ohne Fenster kann der Programmspeicher nur einmal beschrieben und nicht mehr gelöscht werden.
Die neuen mit "F" besitzen einen Flash-Programmspeicher, der bis zu 100 000 mal mit angelegter Spannung gelöscht und danach neu beschrieben werden kann.
Für die Wahl eines PICs für bestimmte Anwendung wichtig sind:
- Max. Taktfrequenz des Prozessors.
- Grösse des Datenspeichers (für Variablen).
- Grösse des Programmspeichers (für Programm).
- Integrierte Hardware (Komparatoren, A/D Wandler, Timer, USART, I²C, SPI, PWM, u.s.w.).
- Freie I/O Pins für externe Hardware (Display, Tasten, u.s.w.).
- Vorhandene Betriebspannung (Netzteil, Akku, Batterie).
In der Praxis wird meistens für die Programmerstellung ein grösserer PIC genommen (wenn möglich pinkompatibler z.B. PIC16F628 für PIC16F84 oder PIC16F630 für PIC12F629) und erst nach der Optimierung des lauffägiges Programms, der tatsächlich nötiger, da seine Parameter am Anfang nur geschätzt werden können. Wenn man viel Programme für verschiedene PICs entwickelt, optimal wäre der grösste PIC16F877 mit 20 MHz max. Taktfrequenz.
Diese Lösung hat auch den Vorteil, dass während der Programmerstellung kurze Hilfsprogramme (z.B. PIC Trainer) in den Programmspeicher kopiert und benutzt werden können, da sie sowohl ein bischen Programmspeicher und RAM als auch 2 freie I/O Pins fürs PIC Miniterminal brauchen.
Programm
Allgemeines
Jedes Program kann man auf klenere Fragmente unterteilen, die auf bestimmter Weise miteinander verknüpft sind und gemeinsam die Aufgabe des Programms erfüllen. Das wichtigste Teil eines Programms ist s.g. Hautprogram (kurz:HP), das eine führende Rolle spielt. Dem HP sind fast alle andere Programmteile untergeordnet (weiter als Unterprogramm (kurz:UP) genannt) und werden nach Bedarf von ihm aufgerufen um eine bestimmte Aufgabe zu erledigen.
Die Struktur eines Programs ist aber komplizierter, da ein UP kann auch ein oder mehrere UPs nacheinander aufrufen. 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, u.s.w. 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. Sishe hierzu: Programmspeicher
Jedes UP kann jederzeit aufgerufen werden, je nach dem was gerade eledigt werden muss. Weil das nicht egal ist, welches UP augerufen 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 änlich wie bei Hochsprachen, wenn man sich Bibliotheken mit Prozessorspezifischen UPs erstellt. Um ein lauffähiges Programm zu erstellen, braucht man nur benötigte UPs ins Program kopieren und ein geignetes HP, das sie aufruft, schreiben.
Ein ASM Programm (Quellcode) muss in einer Texdatei .asm in der vom Assemblerprogramm erwarteter Form verfasst werden, um fehlerfreie Konvertierung in die Maschinensprache (Assemblierung) zu gewährleisten. Dieses Prozess verläuft in der Form eines Dialoges.
Der Programmierer schreibt und gibt es dem Assemblerprogram zum Übersetzen. Alles was das Programm nicht versteht oder nicht richtig ist, erscheint als Fehlermeldungen, die der Programmierer kennen muss um die Fehler korrigieren zu können. Eine .hex Datei wird erst dannn erstellt, wenn das Assemblerprogramm keine Fehler mehr im Quellcode findet. Deswegen sehr wichtig ist, sich mit dem Assemblerprogramm vertaut zu machen, um die Dialogzeit zu minimieren.
Programmdurchlaufdiagramm
Der Programdurchlaufdiagram (kurz: PAD) ist eine vorläufige und laufend änderbare Stufe zwischen einer Idee und ihrer Verwirklichung. Er wird erst dann fertig, wenn nach ihm erstelltes ASM Program auf einem µC so wie gewünscht funktioniert. Jedes sein Symbol (ausser "Start/Stop") muss später als Befehlsreihenfolge für den bestimmten CPU in den Quellcode übertragen werden. Die Anschriften "Ein" und "Aus" gehören nicht zu Symbolen des PADs und wurden nur zur Erklärung benutzt.
Der PAD ist sehr eifach zu erstellen, weil dafür nur drei Symbole benötigt sind:
Das "Start/Stopp" Symbol bedeutet, dass das gesamte Programm sich im stabilen Zustand befindet und nicht "läuft". Anstatt "Stopp" kann auch "Schlaf" (Sleep) agewendet werden, da das Programm in dem Fall auch nicht aktiv ist. Das "Tun" Symbol stellt meistens ein UP mit Reihenfolge von Befehlen dar. Das "Prüfen" bedeutet eine Prüfung bestimmter Bedingung und abhängig davon einen weiteren Lauf eines Programms, endweder in der "ja" (J) oder "nein" (N) Richtung.
Als allgemeinnutziges Standard für µCs kann man folgender PAD bezeichnen:
PAD _____ / \ Spannung ein (Ein) ----->( Start ) \_____/ | - V | .---------------. | |Initialisierung| | '---------------' | | | .--------->| | | V | | .---------------. | | | Hauptprogramm | | | '---------------' | | | | | V | | | > Gesamtes Programm | / \ | | /Ende?\____ | | \ /J | | | \ / | | | | | | | V | | | N| | | `----------´ | | V | .---------------. | | Beenden | | '---------------' | | | V - _____ / \ Spannung aus (Aus) <-------------( Stopp ) \_____/
Das Hauptprogram wird in einer endlosen Schleife ausgeführt, die durch die Prüfung "Ende?" unterbrochen werden kann. In dem Fall wird vor dem Beenden des gesamten Programms noch ein UP "Beenden" ausgeführt, das z.B. Daten in EEPROM speichert.
Es ist nicht nötig immer die Symbole zu zeichnen, man kann sich sie vorstellen und nur den Text schreiben. Die Prüfungen werden mit "?" gekenzeichnet und die Zeichen "V", "A", "<" und ">" zeigen die Richtung des weiteren Verlaufs. Dann sieht der PAD so aus:
PAD1 Ein > Start V - Initialisierung | .------->V | | Hauptprogramm > Gesamtes Programm | V | | Ende? J > Beenden | | N V - | V Stopp > Aus `--------´
In der Praxis werden aus Platzgründen meistens die vereinfachten PADs benutzt.
Der PAD1 kann aber für Hauptprogramme, die in beliebigem Moment unterbrochen werden dürfen, deutlich vereifacht werden, da die Prüfung "Ende?" ob das Hauptprogram beendet werden soll, und das UP "Beenden", entfallen.
Die meisten ASM Programme für µC sind deswegen nach solchem PAD erstelt:
PAD2 Ein > Start V - Initialisierung | .------->V | | Hauptprogramm > Gesamtes Programm | V | `--------´ _|
Für Testprogramme wird meistens fogender PAD angewendet, weil es ziemlich einfach festzustellen ist (z.B. durch Stromverbrauchmessung des µCs), wann sich der CPU schon im Schlaf befindet. Erst dann, darf die Betriebspannung des µCs ausgeschaltet werden.
PAD3 Ein > Start V - Initialisierung | V > Gesamtes Programm Hauptprogramm | V - Schlaf > Aus
Und eine batteriebetriebene Uhr wird überwiegend so gestaltet:
PAD4 Ein > Start V - Interrupt Initialisierung | Timer------------------------->V > Gesamtes Programm Hauptprogramm | V - Schlaf
In dem Fall reicht es aus, wenn der CPU jede Minute vom Timer aufgeweckt wird, um die Zeit zu aktualisieren. Eine Uhr ist immer (ausser Batteriewechsel) ununterbrochen mit Spannung versorgt.
Für komplizierte Programme ist es 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 erstelt.
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 schaft, um so mehr Zeit wird er danach bei Fehlersuche oder Änderungen im ASM Programm verlieren. 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.
Wenn ein ASM Programm nicht wie geplannt funktioniert, wird zuerst ein Fehler im PAD gesucht. Und erst wenn er i.O. ist, im als fehlerhaft festgestellten Codefragment.
Hauptprogramm
Wie sein Namen schon vermuten lässt, ist das Hauptprogram das wichtigste Teil des gesamten Programms. Meistens ist es auch das kleinste Teil, vor allem, wenn die UPs sehr komplex sind. Seine Aufgabe ist die benötigte UPs in bestimmter Reihenfolge nachainander 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 die alle, die durch die UPS realisierte Aufgaben quasi gleichzeitig ausgeführt. Wenn es unerwünscht ist, müssen einige UPs als Verzögerungen realisiert werden.
Typischer PDA für ein HP sieht so aus:
Haupt .--->V | UP1 | V | UP2 | V | ... | V | UPn | V `----´
In den Quellcode wird es so eigeschrieben:
Haupt call UP1 call UP2 ........... call UPn goto Haupt
In der Praxis wird das HP schrittweise erstellt. Am Anfang wird sich nur ein UP im HP befinden und die folgenden kommen nach dessen Erstellung und Prüfen dazu, bis das HP fertig wird.
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.
Jedes UP hat folgender PAD:
vom Aufrufer -------> V Tun V zurück zum Aufrufer <------- return
Ein HP von einem ASM Programm kann in anderem, mehr umfangreichem ASM Program als UP benutzt werden, wenn der sich am Ende des HPs befindlicher Befehl "goto" durch "return" ersetzt wird. Ein Beispiel dazu:
Haupt1 call UP11 Haupt1 call UP11 call UP21 call UP21 ........... -------> ........... call UPn1 call UPn1 goto Haupt1 return
Jetzt können wir im mehr komplexen HP (Haupt) das Haupt1 als Unterprogramm aufrufen:
Haupt call UP1 call Haupt1 ........... call UPn goto Haupt
Jedes UP kann auch von einem anderen übergeordneten UP aufgerufen werden, wenn das was es realisiert, benötigt wird.
In der Praxis wird oft ein UP von mehreren anderen UPs benutzt. Zum Beispiel um LCD Display zu steuern, brauchen wir entweder ein Befehl (Cmd) oder ein Zeichen (Data) an Display zu schicken. In beiden Fällen wird ein Byte geschickt, einmal mit RS=0 (Befehl) und einmal mit RS=1 (Zeichen) laut folgendem PDA:
"Cmd" "Data" V V RS=0 RS=1 V V `-->V<--´ "Send" Byte schicken V return
Das wird z.B. in den Quellcode so eingeschrieben:
Cmd bcf RS goto Send Data bsf RS Send ............ return
Das UP "Send" ist den UPs "Cmd" und "Data" untergeordnet, da es von beiden benutzt wird, kann aber weder "Cmd" noch "Data" benutzen.
Initialisierung
Damit der PIC ein Programm asführen kann, muss er vollständig und richtig initialisiert werden. Deswegen als erstes UP, das von dem gesamten Programm noch vor dem HP aufgerufen wird , ist "Initialisierung" (kurz: Init)
Variablen
Weil nach dem Einschalten der Spannung im RAM sich zufällige Werte befinden, wird meistens als erstes, der benutzte Bereich des RAMs (z.B. 20h bis 7Fh) gelöscht. Es wird einfach und sparsam mit einer Schleife, die indirekte Adressierung verwendet, gemacht:
V Adresse des ersten Registers in FSR laden (20h) .-------------------->V RAMClr |Indirekt adressierter Register löschen (INDF) | V | Adresse erhöhen | V | 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 eingeschrieben werden:
movlw 0x20 movwf FSR ,->clrf INDF | incf FSR,1 | btfss FSR,7 `-<goto $-3 ; springe zur aktueller Adresse -3 return
Danach können den benötigtenen Variablen die gewünschte Werte angegeben 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 Eischreiben in entsprechenden Register (CMCON bzw. ADCON1) des Wertes 7:
movlw 7 b.z.w. movlw 7 movwf CMCON movwf ADCON1
Wenn einige als Analoge Eingänge benutzt werden sollen, mussen die entsprechende Werte dem Datenblatt des jeweiligen PICs entnommen werden.
Danach werden alle Ports nacheinander gelöscht und die gewünschte Werte die an den Pins vor dem Start des Hauptprogramms ausgegeben werden sollen, geschrieben:
clrf PORTA movlw 0x37 movwf PORTA usw.
Anchliessend werden für jeden Port die Werte in TRIS Register eingeschrieben, wobei ein Bit einem Pin entspricht. Ein Pin wird in TRIS 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 TRIS Register sich in der Bank1 befinden, muss im STATUS-Register auf Bank1 und danach zurück auf Bank 0 umgeschaltet werden:
bsf STATUS,RP0 movlw 0xA8 movwf TRISB bcf STATUS,RP0
Bei einem Umschalten der Bank können selbstverständlich alle TRIS Register nacheinander beschrieben werden.
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, u.s.w.), müssen entsprechende SFRs (Spezial Function Registers) laut Datenblatt des PICs definiert werden.
Die externe Hardware muss nach Datenblättern der Herstellern 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 Bit im Zielregister löschen V Quellbit = 0 ? J >------. N | 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:
bcf Tasten,1 btfsc PORTA,3 bsf Tasten,1
Natürlich wenn ein ganzer Byte vom Port in das W-Register eingelesen wird, kann mann den gleich in das Zielregister schreiben.
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 er einfach zuerst in das W-Register geladen und danach an Port übergeben, z.B.:
movlw 0x12 movwf PORTA
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 aktueller 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 V P0 laden V<---------. P0 decrementieren | V | 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 ~ P0 * ( 12 / 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 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 V P1 laden Warte1 V<-------------. P0 laden | Warte0 V<---------. | P0 decrementieren | | V | | P0 = 0 ? N >--´ | J | V | P1 dekrementieren | V | 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 ~ P1 * P0 * ( 12 / 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 V P2 laden Warte2 V<-----------------. P1 laden | Warte1 V<-------------. | P0 laden | | Warte0 V<---------. | | P0 decrementieren | | | V | | | P0 = 0 ? N >--´ | | J | | V | | P1 dekrementieren | | V | | P1 = 0 ? N >------´ | J | V | P2 dekrementieren | V | 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 ~ P2 * P1 * P0 * ( 12 / 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 in 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.
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.
Schnittstellen und Treiber
Als Schnittstelle wird externe Hadware, die zum steuern eines an sie angeschlossenes "Gerätes" dient, genannt. Das ASM Programm, das die Steuerung ermöglicht ist ein Treiber. Als Beispiele siehe: [[2]] und[3]
Tabellen
Es gibt zwei Arten von Tabellen: Sprungtabellen (computed goto) die "goto" Befehle enthalten und Wertetabellen (lookup table) in denen feste Werte in "retlw" gespeichert sind. Der wichtigste Unterschied zwischen dennen ist, dass die Sprungtabellen steuern den Programlauf abhängig vom Inhalt des W-Registers und die Wertetabellen liefern abhängig von Inhalt des W-Registers ein Wert an den Aufrufer zurück.
Beide werden in Programmspeicher erstellt. Sie können nur bis zu 256 Speicherstellen belegen, da in den W-Register auch nur so viel veschiedenen Zahlen "passen". Sie Fangen also (fast) immer bei einer Adresse XX00h an und enden bei XXFFh. Der Hochwertige Byte "XX" der Adresse an der sich der Anfang einer Tabelle befindet, muss vor dem Einsprung in die Tabelle ins PCLATH Register eingeschrieben werden, wenn die Tabelle weit vom Aufrufer liegt. In der Praxis werden solche Tabellen am oberen Ende des Programmspeichers angelegt, damit sie den ASM Code nicht unterbrechen.
Eine Sprungtabelle wird so aufgebaut:
ORG (XX-1)FF <--- eine Direktive für Assemblerprogramm, wo es die Tabelle im Programmspeicher plazieren soll Adresse Inhalt ------------------------- Tab1 (XX-1)FF addwf PCL,1 XX00 goto Marke0 XX01 goto Marke1 ....................... XXFE goto Marke254 XXFF goto Marke255
Und so aufgerufen:
movlw 0xXX movwf PCLATH movf TWert,0 call Tab1
Wobei:
0xXX = Hochwertiger Byte der Adresse von Tab1, TWert = ein Wert, der die Wahl wohin gesprungen wird bestimmt
Nach ausführen der obiger Befehlsfolge, wird das ASM Programm z.B. für Twert=0x01 weiter ab Marke1 "laufen" bis es an "return" kommt. Dann springt es zurüch zum Aufrufer der Tabelle.
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, für welchen, an den Aufrufer bestimmter Wert aus der Tabelle im W-Register zurückgeliefert wird
Solche Wertetabellen werden z.B. als Zeichengeneratoren für Grafikdisplays benutzt.
Vorlage für MPASM
Diese Vorlage ist nur für ASM Programme ohne Interrupts.
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 SecondL equ 0x20 ; Variablen definieren (Register benennen) SecondH equ 0x21 MinuteL equ 0x22 MinuteH equ 0x23 StundeL equ 0x24 StundeH equ 0x25 org 0x0000 ; bis da sind MPASM Direktiven ; hier fängt das gesamte ASM Programm an call Init ; rufe UP Init (Initialisierung) auf Haupt ............ ; Hauptprogramm als endlose Schleife Eigener Code ............ goto Haupt ; gehe zum Anfang des Hauptprogramms (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 anderen PIC umschreiben
Die wichtigste Vorraussetzung ist, das der PIC2, auf dem das vorhande ASM Programm (für PIC1) laufen soll, zumindest für das ASM Program nötige interne Hardware hat. Der Code benötigt keine Änderungen.
Wenn der Port vom PIC2 anderen Namen hat, muss man das im Quellcode umdefinieren, z.B.:
#define GPIO equ PORTB #define TRISIO equ TRISB
Dann wird das Assemblerprogramm, wenn es GPIO 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.
Das erste...
Hier wird detailiert das ganze Prozess der Erstellung eines ASM Programms beschrieben.
Die Idee:
Es gibt 4 Leds, 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| | | | | | | | | | | | | | | | | | | | | .-. .-. .-. .-. | | | | | | | | | | | | 470| | 470| | 470| | 470| | | | '-' '-' '-' '-' | | | | | | \ o \ o | | | | \ \ V -> V -> V -> V -> \. \. - - - - T1 o T2 o LED1 | LED2 | LED3 | LED4 | | | | | | | +-------+-------+---+---+-------+-------+ | === 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 V Initialisierung .-------------->V | T1 gedrückt ? N >----. | J | | V | | links->rechts "wandern" | | V<------------´ | T2 gedrückt ? N >-----. | J | | V | | rechts->links "wandern" | | V<------------´ `---------------´
danach detailierter:
Start V Initialisierung V<-----------------------------------------, T1 gedrückt ? N >---+---> T2 gedrückt ? N >---+---´ J A J A V | V | LED1 an | LED4 an | V | V | Warten | Warten | V | V | LED1 aus | LED4 aus | V | V | LED2 an | LED3 an | V | V | Warten | Warten | V | V | LED2 aus | LED3 aus | V | V | LED3 an | LED2 an | V | V | Warten | Warten | V | V | LED3 aus | LED2 aus | V | V | LED4 an | LED1 an | V | V | Warten | Warten | V | V | LED4 aus | LED1 aus | V | V | `------------´ `------------´
Man kann auch, aus Platzgründen, die unbedeutende Richtungspfeilen weg lassen:
Start Initialisierung V<-----------------------------------------, T1 gedrückt ? N >---+---> T2 gedrückt ? N >---+---´ J A J A V | V | LED1 an | LED4 an | Warten | Warten | LED1 aus | LED4 aus | LED2 an | LED3 an | Warten | Warten | LED2 aus | LED3 aus | LED3 an | LED2 an | Warten | Warten | LED3 aus | LED2 aus | LED4 an | LED1 an | Warten | Warten | LED4 aus | LED1 aus | V | V | `------------´ `------------´
Optimierung
Speicherbedarf
Programmspeicher
Die ersten Programme für den PIC sind natürlich einfach und kurz. Doch nach und nach werden die Codes länger und unübersichtlicher, das Brennen dauert auch umso länger. Das Programm ist zu umfangreich. Doch wo soll man etwas kürzen?
Es gibt unzählige Möglichkeiten - hier ein paar Beispiele:
Unterprogramme
Bestimmt wird man im Code immer die selben Abschnitte brauchen, zum Beispiel Warteschleifen oder ADC-Routinen, etc. Wieso sollte man diese also mehrfach (doppelt, dreifach, u.s.w.) schreiben? Die Lösung: Der Programmteil wird als Unterprogramm benutzt. Man erstellt eine Sprungmarke (ab hier beginnt das Unterprogramm) und endet wieder mit einem return- oder retlw-Befehl. Aufgerufen werden die Unterprogramme meistens mit dem Call-Befehl. Ein Goto-Befehl ist zu diesem Zweck geeignet nur, wenn der Prozessor zum letzten Aufrufer von call zurück springen soll, da die Rücksprungadresse vom letzten call auf dem Stapel (stack) abgelegt wurde. Somit kann man mehr als 8 Ebenen von UPs nutzen.
Den Unterprogrammen kann man natürlich auch Werte übergeben. Möchte man z.B. mehrere unterschiedliche Zeiten für Warteschleifen benutzen, so kann man die Parameter vor dem Aufruf des UPs in benötigte Anzahl von Register einschreben und dann das Unterprogramm mit Call aufrufen. Das Unterprogramm verwendet dann die Zahlen von den Register. Siehe: Pause
Um gleiche Vorgänge in mehreren Register durchzuführen (z.B. löschen) geeignet ist Anwendung von Schleifen mit bestimmter Anzahl der Abläufe und indirekter Adressierung. Siehe: Variablen
bsf und bcf
Bestimmt gibt es Situationen, in denen der PIC mehrere Bits an einem Port/Register setzen oder löschen muss. Z.B. wenn mehrere LEDs an einem Port ein- oder ausgeschaltet werden sollen. Werden mehr als 2 Bits verändert, so kann man hier die bsf und bcf-Befehle umgehen und einen kürzeren Code erstellen.
bsf PORTB,0 ;An PORTB sind 8 LEDs angeschlossen. bsf PORTB,1 ;Es sollen die ersten 4 LEDs angeschaltet werden, bsf PORTB,2 ;die anderen sollen ausgeschaltet werden. bsf PORTB,3 ;links steht es mit bcf/bsf bcf PORTB,4 ;ziemlich lang bcf PORTB,5 bcf PORTB,6 bcf PORTB,7 movlw b'00001111' ;Das geht auch kürzer und übersichtlicher: movwf PORTB
Man sieht - der Codeabschnitt umfasst nur noch 2 Zeilen anstatt 8. Somit braucht es weniger Speicher und wird von PIC schneller verarbeitet. Allerdings muss man aufpassen, da durch diese Routine der Inhalt des W-Registers und der Inhalt des gesamten Zielregisters verändert wird. Sollen die anderen Bits unangetastet bleiben, muss man es entweder bei den bcf/bsf-Befehlen belassen oder logische Funktionen (and bzw. or) durchführen.
Bit kopieren
;An den PIC ist ein Taster(RB0) und eine LED(RB1) angeschlossen. ;Taster: High=gedrückt Low=nicht gedrückt ;LED: High=Ein Low=Aus ;Wenn der Taster gedrückt ist, soll die LED leuchten ;wenn er nicht gedrückt ist, soll die LED nicht leuchten. ;1.Vorschlag: btfss PORTB,0 ;ist der taster gedrückt? goto no ;nein bsf PORTB,1 ;ja - LED ein goto endif ;fertig abgefragt - unten weitermachen... no bcf PORTB,1 ;LED aus endif
Der oben aufgeführte Code hat viele Nachteile: er ist relativ lang, braucht zwei Gotos und entspr. Sprungmarken. Ok, das ist vielleicht Erbsenzählerei, aber aus diesen 5 Zeilen kann man 3 machen.
bcf PORTB,1 ;Das Bit wird erst gelöscht btfss 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 abefragt. Siehe hierzu: Einlesen
...
Work in Progress... Wer sonst noch Beispiele hat, bitte hier vervollständigen... BMS ...
RAM
Um Anzahl benötigten Register zu sparen werden vorläufige Register (temporary) definiert, die von mehreren UPs benutzt werden. Als Beispiel, das Register "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").
Für Konstanten (z.B. pi, ln2, Meldungen, u.s.w.), die sich im Laufe des Programms nicht ändern, kann EEPROM oder Programmspeicher als Ablage (z.B. Wertetabelle mit retlw-Befehlen) benutzt werden. Siehe 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 Durchä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 aktuelle 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
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
Mid-Range
Zu Mid-Range gehören alle PICs mit 14-bit langen Befehlen aus den Familien 12FXXX und 16FXXX.
Kurzübersicht Assembler Befehle
|
|
|
Ausführliche Beschreibung zu den Befehlen
Eine ausgegliederte Beschreibung der Befehle findet sich unter PIC Assemblerbefehle
Erklärungen zu den Verwendeten Platzhaltern:
- k stellt einen fest definierten Wert da. z.B. 0x20, d'42' oder b'00101010'
- W steht für das W-Register.
- d steht für destination (Ziel). Im code wird d durch ein w bzw. 0 (der Wert wird in das W-Register gespeichert ) oder f bzw. 1 (der Wert wird in das davor definierte Register gespeichert)
- b steht für Bitnummer im Register (eine Zahl zwischen 0 und 7)
- R steht für ein Register
- fett geschrieben Bedeutet, dass es ein Platzhalter ist und im Quellcode durch eine Registeradresse oder einen Wert ersetzt werden muss
- Schreibmaschinenstil bedeutet, dass es so im Quellcode geschrieben werden kann.
- 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
- Es wird bitweise die logische Funktion [math]W\ and\ k[/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:
- 1100 1010 ---- and 1000
- 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 ANDWF
- 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 dem RETURN-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 Unterprogramm1 ;es wird das Unterprogramm "Unterprogramm1" aufgerufen movwf ergebnis ;das W-Register wird in das Register "ergebnis" kopiert. ;im Register "ergebnis" steht nun 23d Unterprogramm1 ;zählt 10 zum W-Register 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 (W) 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 die 0 mit 1 und 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). Dieser Befehl beeinflusst 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.
- 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). Dieser Befehl beeinflusst 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]W\ or\ k[/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 Ooperation:
- 1100 1010 ---- or 1110
- Es wird bitweise die logische Funktion [math]W\ or\ R[/math] ausgeführt und das Ergebniss entweder in das W-Register (d=W=0) oder in R gespeichert (d=F=1). Vergleiche IORLW
- 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-Regsiter gesetzt wird, falls R Null ist.
- Der festgelegte Wert k wird in das W-Register 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.
- 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. Dieser Befehl wird 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).
- Zur Verdeutlichung:
- |C| |-Register R-| ;C steht für das Carry-bit, STATUS,C c 7 6 5 4 3 2 1 0 ;vor dem Verschieben 7 6 5 4 3 2 1 0 c ;nach dem Verschieben
- 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).
- Zur Verdeutlichung:
- |C| |-Register R-| ;C steht für das Carry-bit, STATUS,C C 7 6 5 4 3 2 1 0 ;vor dem Verschieben 0 C 7 6 5 4 3 2 1 ;nach dem Verschieben
- 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 höheren 4 bit (bit7-bit4) mit den niedrigeren 4 bit (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'
- Es wird bitweise die logische Funktion [math]W\ xor\ k[/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:
- 1100 1010 ---- xor 0110
- 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
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 "Borrowbit" (to borrow = etwas borgen) dient zum erkennen, wenn ein Übertrag einer Rechenoperation exisitiert. 250+10 ergibt zum Beispiel 4, und setzt dabei das Borrowbit auf 1. Damit kann das Programm erkennen, wenn wieder einmal ein Ergebnis größer als 255 herauskam. Bei Subtraktionen (SUBLW und SUBWF) verhält sich das Carry Bit umgekehrt als bei Additionen (ADDWF und ADDLW)!! Zum Beispiel 55-6=49 setzt Carry auf 1 aber 10-25=241 löscht das Carry-Flag.
Überprüfung von Rechenergebnissen mit Hilfe des STATUS-Registers
|
|
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).
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 (falls in den Fusebits gesetzt) 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 "TRISA1"
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 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.
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) oder 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
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 der 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 absolute Adressen sind nicht wichtig). In das Register "A" wird die hex Zahl eingeschrieben und aus dem "D" die umgewandelte dec Zahl ausgelesen. A3,7 und D3,7 sind MSB und A0,0 und D0,0 sind LSB. Die "B" und "C" sind Hifsregister. Das UP kann für kleinere als 32-bittige hex Zahlen modifiziert werden. Zum Beispiel für 16-bittige hex Zahlen bleiben nur Register A1,A0,B1,B0,C1,C0,D1,D0 und in den UPs werden in die Register und als X, folgende Zahlen eingeschrieben: HTmp->0x10 (Anzahl Bits der hex Zahl),ATmp->2=X (Anzahl Bytes der hex Zahl). Es müssen auch die UPs "CClr", "DClr" und "CopyCB" entschprechend 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 durchgeführt:
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 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 in B Register kopiert ("CopyCB") und zu C addiert ("AddCB") wird. Diese dec Zahl wird zum D Register addiert nur wenn entsprechender Bit der hex Zahl gleich 1 ist. Weil im UP "Hex_Dec" immer das bit A0 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 Register 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.
#define _C STATUS,C #define _Z STATUS,Z #define _DC STATUS,DC #define _Fcra Flags,0 #define _Fcrp Flags,1 #define _Fdca Flags,2 #define _Ferr Flags,3 ;Überlauf (Fehler) ;.................................................................................. A3 EQU 0x20 ;Register fürs Rechnen A2 EQU 0x21 A1 EQU 0x22 A0 EQU 0x23 B3 EQU 0x24 B2 EQU 0x25 B1 EQU 0x26 B0 EQU 0x27 C3 EQU 0x28 C2 EQU 0x29 C1 EQU 0x2A C0 EQU 0x2B D3 EQU 0x2C D2 EQU 0x2D D1 EQU 0x2E D0 EQU 0x2F Flags EQU 0x30 ATmp EQU 0x31 ;Schleifenzähler HTmp EQU 0x32 ;Schleifenzähler RTmp EQU 0x33 ;Zwischenspeicher ;.................................................................................. Hex_Dec call CClr ;Hex>A, D>Dec call DClr movlw 1 movwf C0 movlw 0x20 ;32 bit Hex > 32 bit Dec (8 Stellen) movwf HTmp HexDecL btfsc A0,0 call AddDC call CopyCB call AddCB call ARotRb decfsz HTmp,1 goto HexDecL return CClr clrf C0 clrf C1 clrf C2 clrf C3 return DClr clrf D0 clrf D1 clrf D2 clrf D3 return CopyCB movf C0,0 movwf B0 movf C1,0 movwf B1 movf C2,0 movwf B2 movf C3,0 movwf B3 return AddCB movlw B0 ;C+B>C movwf FSR goto AddR AddDC movlw C0 ;D+C>D movwf FSR AddR bcf _Ferr ;Addiere zwei Register bcf _Fcrp movlw 4 ;X=4 Bytes lang movwf ATmp AddRL bcf _Fdca bcf _Fcra movf INDF,0 movwf RTmp movlw 4 ;X=4 addwf FSR,1 btfss _Fcrp goto AddRN movlw 1 addwf INDF,1 call DecCor AddRN movf RTmp,0 addwf INDF,1 call DecCor btfss _Fdca goto AddCor1 movlw 6 addwf INDF,1 AddCor1 btfss _Fcra goto AddCor2 movlw 0x60 addwf INDF,1 btfsc _C bsf _Fcra AddCor2 movf INDF,0 andlw 0xF0 sublw 0xA0 btfss _Z goto AddCor3 movf INDF,0 andlw 0x0F clrf INDF addwf INDF,1 bsf _Fcra AddCor3 bcf _Fcrp btfsc _Fcra bsf _Fcrp movlw 5 ;X+1=5 subwf FSR,1 decfsz ATmp,1 goto AddRL btfsc _Fcra bsf _Ferr return DecCor btfsc _DC ;dezimale Korrektur (equ DAW) 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. Hier werden nur geprüfte UPs kurz erklärt. Sie brauchen nur einen Register "Temp" für Schleifenzähler, dessen Name, falls im Programm schon existiert, geändert werden kann.
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 movf INDF,0 movwf EEDATA bsf EECON1,WREN movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1,WR bcf EECON1,WREN btfsc EECON1,WR goto $-1 ; warten bis WR=0 bcf STATUS,RP0 ; zurück auf Bank 0 umschalten bsf INTCON,GIE ; Interrupts erlauben return
EEPROM lesen und zurück in RAM schreiben:
EERead movlw 0x20 ; ab der Adressse wird aus EEPROM in RAM abgelegt movwf FSR movlw 4 ; soviel Bytes movwf Temp ; Schleifenzähler EERLoop call EERead1 incf FSR,1 ; nächste Adresse decfsz Temp,1 goto EERLoop return EERead1 movf FSR,0 bsf STATUS,RP0 ; auf Bank1 umschalten movwf EEADR bsf EECON1,RD movf EEDATA,0 bcf STATUS,RP0 ; zurück auf Bank 0 umschalten movwf INDF return
Interrupts
Das Programmteil misst eine Frequenz an TOCKI Pin und wurde für PIC16F870 geschrieben.
Benötigte Hardware: Widerstand 470 Ohm zwischen der Frequenzquelle und TOCKI Pin.
Es fehlen UPs "Hex_Dec" Hex Dec Wandlung und Ausgabe auf ein Display "DispFrq". Das HP als leere endlose Schleife macht nichts ausser warten auf ein Interrupt von Timer0 bzw. Timer1.
Der Zähler der Frequenz wurde um ein Zähler aus zwei zusätzlichen Register A2 und A3 erweitert und bei jedem Timer0 überlauf erhöht. Der Prescaler wird ins Register A0 und der TIMER0 ins A1 kopiert. Somit ergibt sich 32-bittiger 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 biliebigen Display gezeigt werden.
Es wird nur noch Register "FTmp" benötigt, da das "ATmp" das gleiche wie im "Hex_Dec" ist. Die Quarzfrequenz wurde gewählt, weil sie am nächsten der Temperaturstabiltesten Frequenz für AT Quarze 7,2 MHz ist. Der PIC16F870 wurde nur wegen nötigen 15 I/O Pins für 5-stelligen 7-segment LCD Display LPH2673-1 ohne Kontroller angewendet. Mit einem üblichen 1x16 Zeichen LCD Display (mit Kontroller) wurde das Programm für Frequenzen bis zum 100 MHZ mit einer Genauigkeit +/- 4 Hz positiv getestet.
Um die Frequenz mit Genauigkeit +/- 1Hz zu messen, muss die Messzeit 1 s von Timer1 erzeugt werden, das nur für Quarz bis 2,048 MHz möglich ist. Mit dem Quarz 7,3728 MHz wird aber die Messzeit 0,25 s genutzt und die gemessene Frequenz durch 4 multipliziert (mittels 2 Additionen), was die Auflösung von +/- 4 Hz ergibt.
Das UP "Init" muss selbstverständlich um Initialisierung des Displays ergänzt werden.
; Programm zum Messen der Frequenz an T0CKI Pin mit 7,3728 MHz Quarz ORG 0x0000 call Init Main goto Main ORG 0x0004 ;ISR (interrupt service routine) bcf INTCON,GIE ;interrupts sperren btfss INTCON,T0IF ;ist Timer0 übelaufen ? goto CheckTMR1 ;nein, dann prüfe Timer1 bcf INTCON,T0IF ;Timer0 interrupt flag löschen movlw 1 addwf A2,1 ;erhöhe A2 btfsc STATUS,C incf A3,1 ;increment A3, wenn A2 übelaufen ist CheckTMR1 btfss PIR1,TMR1IF ;ist Timer1 überlaufen ? retfie ;wenn nicht, springe zurück zu Main bsf STATUS,RP0 ;stopp Timer0 movf TRISA,0 andlw 0xEF movwf TRISA bcf STATUS,RP0 bcf T1CON,TMR1ON ;stopp Timer1 bcf PIR1,TMR1IF ;Timer1 interrupt flag löschen GetFreq movf TMR0,0 ;Prescaler ins A0 Register kopieren movwf A1 movwf FTmp clrf ATmp FToggle incf ATmp,1 bsf STATUS,RP0 bsf OPTION_REG,T0SE bcf OPTION_REG,T0SE bcf STATUS,RP0 movf TMR0,0 subwf FTmp,0 btfsc STATUS,Z goto FToggle comf ATmp,1 incf ATmp,0 movwf A0 call Hex_Dec clrf A3 ;Register A löschen clrf A2 clrf A1 clrf A0 call CopyDC call AddDC ;Frequenz * 2 call CopyDC call AddDC ;Frequenz * 4 call DispFrq ;eigenes UP für benutztes Display clrf TMR0 ;Timer0 löschen movlw 0x34 ;Timer1 movwf T1CON ;konfigurieren movlw 0x1F ;Timer1 laden (für 0,25 s) movwf TMR1H clrf TMR1L bsf STATUS,RP0 movf TRISA,0 ;start Timer0 iorlw 0x10 movwf TRISA bcf STATUS,RP0 bsf T1CON,TMR1ON ;start Timer1 retfie ;erlaube interrupts und springe zurück zu Main CopyDC movf D0,0 movwf C0 movf D1,0 movwf C1 movf D2,0 movwf C2 movf D3,0 movwf C3 return Init clrf PORTA ;Ports löschen clrf PORTB movlw 0x20 ;RAM (20-7Fh) löschen movwf FSR clrf INDF incf FSR,1 btfss FSR,7 goto $-3 clrf ADCON0 ;schalte ADC off movlw 7 ;sperre ADC movwf ADCON1 ;und definiere RA0-7 als digitale I/Os movlw 0xE0 ;GIE, PEIE & TMR0IE erlauben movwf INTCON bsf STATUS,RP0 ;bank 1 movlw 0xEF ;alle PORTA Pins als Eingänge movwf TRISA ;nur A4 als Ausgang (nicht beschaltet) clrf TRISB ;alle PORTB Pins als Ausgänge movlw 0xE7 ;Takt vom T0CKI Pin movwf OPTION_REG ;Timer0 konfigurieren bsf PIE1,TMR1IE ;TMR1 interrupt erlauben bcf STATUS,RP0 ;Bank 0 clrf TMR0 ;Timer0 löschen movlw 0x34 ;Timer1 konfigurieren movwf T1CON ;(prescaler 8) movlw 0x1F ;Timer1 laden (für 0,25 s) movwf TMR1H clrf TMR1L bsf STATUS,RP0 movf TRISA,0 ;start Timer0 iorlw 0x10 movwf TRISA bcf STATUS,RP0 bsf T1CON,TMR1ON ;start Timer1 return end
Mausrad
Das UP wertet ein Mausrad aus und setzt, je nach Drehrichtug und sein Anschluss an PORT (PORTA,0 und PORTA,1), ein Flag "_Finc" bzw. "_Fdec", das vor der nächsten Abfrage, im HP gelöscht werden muss. Zum Auswerten werden 3 Register "MausA", "MausB" und "MausC" benötigt. Als "_Z" wurde "STATUS,Z" definiert. Die Warteschleife "Delay" dient der Entprellung der Kontakte und soll ca. 10ms sein.
Mouse movf PORTA,0 andlw 3 movwf MausB movwf MausC call Delay ; ca. 10 ms movf PORTA,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
Handy Display
Als Beispiel die Hardware und Testprogram für Nokia 3310/3330 Display: [[4]]
Hifsmittel
PIC Miniterminal
Das ist ein klenes Terminal, das nur 2 Leitungen zum Anschluss an PIC braucht. Es ermöglicht Steuern von diversen LCD Displays mit üblichem 14-pin Anschluss und Abfragen von 3 Tasten.
Sein Schaltplan (Skizze): [[5]]