bei 14-Bit-Kern-PICs
Die hier beschriebenen Probleme treffen
so auf 14-Bit-Kern-PICs zu (PIC16F84, PIC16F876 u.ä.).
Sie
entstehen
aus
den
Beschränkungen der Hardware (nur 14-Bit
Befehlsbreite und nur 8-Bit Datenbreite). Auf andere PIC-Familien
trifft das hier beschriebene in verschärfter (PIC10F...) oder
abgeschwächter (PIC18F...) Form zu.
Modernisierte 14-Bit-PICs der Reichen
PIX16F1xxx (sogenannte "enhanced" Typen) enthalten
einige Verbesserungen, mit denen sich die hier beschriebenen Probleme
leichter umgehen lassen.
Datenspeicher
Programmspeicher
Ein 14-Bit-Kern-PIC (z.B. ein PIC16Fxxx) besitzt je nach Typ bis zu mehreren hundert Byte Datenspeicher (RAM) und diverse Register zur Steuerung der Funktionen des PIC (special function register - SFR). Bereits an anderer Stelle wurde erwähnt, das dieser Speicher in mehreren parallelen Banken organisiert ist. Wie kann man nun auf die Speicherzellen zugreifen? Da gibt es zwei Methoden: die direkte Adressierung und die indirekte Adressierung.
Der einfache Befehl
movwf 0x20 |
kopiert den Wert des Arbeitsregisters w in das Register mit der Adresse 20h (0x20). Die Adresse des Zielregisters steht direkt im Befehl. In diesem Fall redet man von direkter Adressierung.
Problematisch ist allein die Tatsache,
dass im Befehl nur 7-Bit lange Adressen enthalten sein
dürfen.
Schreibt man eine längere Adresse (z.B. 0x120) in den Befehl, so
werden
nur die unteren 7 Bit der Adresse verarbeitet, und der Rest ignoriert.
Die folgenden Befehle sind also völlig identisch:
movwf 0x20 movwf 0xA0 movwf 0x120 movwf 0x1A0 |
Wie man es auch dreht und wendet, mit 7 Bit lassen sich nur 128 Adressen erzeugen. Das reicht nicht. Im PIC werden nämlich 9-Bit lange Adressen verwendet. Um aus den 7-Bit-Adressen des Befehls eine 9-Bit-Adresse zu machen, setzt der Prozessor zwei weitere Bits vor die 7 Bit. Dabei handelt es sich um die Bits RP1 und RP0 des STATUS-Registers. |
Diese beiden Bits dienen zur
Bank-Umschaltung.
Der Adressraum des PIC besteht nämlich aus 4 Bänken zu je 128
Adressen (7 Bit). Innerhalb einer Bank kann man problemlos mit
den
Befehlen arbeiten, will man in eine andere Bank wechseln, setzt man
dazu
die Bits RP1 und RP0 im STATUS-Register.
Alle danach ausgeführten Befehle beziehen sich auf die nun ausgewählte Bank. Glücklicherweise ist STATUS in allen Bänken unter der selben Adresse (0x03) zu erreichen. |
Ein Zugriff auf die Adresse 0xA0 sieht
also wie folgt aus:
bcf STATUS,RP1 ; RP1=0 bsf STATUS,RP0 ; RP0=1 movwf 0x20 ; in der Bank 1 ist das die Adresse A0h ; bzw. 0xA0 |
Wer will, kann in der letzten
Zeile
zur besseren Übersicht auch 'movwf 0xA0' schreiben, das ist
egal.
Wir sind nun in der Bank 1, wenn wir
wieder
in die Bank 0 wollen, muss zuerst RP0 wieder gelöscht
werden.
Was für den
mov-Befehl gilt, gilt
natürlich genauso für alle anderen Befehle in denen Adressen
verwendet werden, wie z.B incf, clrf ...
Es gibt mehrere Ansätze, das zu vereinfachen.
So kann man Makros zur Bankumschaltung verwenden. Oft sieht man auch
die Nutzung von BANKSEL. Wird diese
Pseudobefehl gefolgt von einer Speicheradresse verewendet, dann werden
vom Assembler die korrekten Befehle zum Setzen der STATUS-Bit
eingefügt. Schreibt man konsequent vor jeder Assemblerzeile die
eine Variable enthält eine BANKSEL-Zeile mit diesem
Variablennahmen, braucht man über die Bänke kaum noch
nachzudenken. Es sieht aber nicht immer schön aus, wenn nahezu
jede zweite Assembler-Zeile eine BANKSEL-Zeile ist. Dies hier ist z.B.
ist ein Beispiel aus dem PIC16F88x-Datenblatt und beschreibt die
Initialisierung des ADC:
BANKSEL ADCON1 MOVLW B’10000000’ ;right justify MOVWF ADCON1 ;Vdd and Vss as Vref BANKSEL TRISA BSF TRISA,0 ;Set RA0 to input BANKSEL ANSEL BSF ANSEL,0 ;Set RA0 to analog BANKSEL ADCON0 MOVLW B’11000001’ ;ADC Frc clock, MOVWF ADCON0 ;AN0, On |
BSF STATUS,RP0 BCF STATUS,RP1 MOVLW 0x80 MOVWF 0x1F BSF STATUS,RP0 BCF STATUS,RP1 BSF 0x05,0 BSF STATUS,RP0 BSF STATUS,RP1 BSF 0x08,0 BCF STATUS,RP0 BCF STATUS,RP1 MOVLW 0xC1 MOVWF 0x1F |
Für die meisten Zwecke reicht die direkte Adressierung aus. Es kommt aber vor, dass eine Adresse errechnet wird. Das ist zum Beispiel der Fall, wenn man mit Datentabellen arbeitet, oder wenn man komplette Speicherblöcke löschen oder beschreiben möchte. Dazu dient die indirekte Adressierung.
Zur indirekten Adressierung schreibt man die gewünschte Adresse in das Register FSR, das sich in allen Banken unter der Adresse 0x04 findet. Im folgenden kann man auf das gewünschte Register zugreifen, indem man die auf das virtuelle Register INDF zugreift. Ein Register INDF gibt es physisch gar nicht, der PIC greift in diesem Fall immer auf das Register zu, dessen Adresse in FSR steht.
Der Haken daran ist, dass das
Register FSR
(wie alle Register) nur 8 Bit lang ist, während wir bekanntlich im
PIC mit 9-Bit Adressen arbeiten (2 Bit zur Bankauswahl, 7 Bit zur
Adressierung
innerhalb der Bank). Der PIC ergänzt daher die 8 Bit (aus dem FSR)
zu einer vollständigen Adresse, indem er davor das Bit IRP
aus dem STATUS-Register setzt.
Will man auf Speicher in den Bänken 0 und 1 zugreifen muss also IRP gelöscht (=0) sein, will man dagegen auf die Bänke 2 und 3 zugreifen, muss man vorher IRP setzen (=1). |
Nun ein kleines Beispiel zum
Löschen
der Speicherzellen 40h bis 4Fh
bcf STATUS, IRP ; Bank 0 oder 1 movlw 0x40 movwf FSR ; setzt den Zeiger auf Adresse 40h Loop clrf INDF ; löscht die aktuelle Speicherzelle incf FSR, f ; erhöhen des Adressen-Zeigers btfss FSR, 4 ; schon 4F erreicht? goto Loop ; nein, die nächste löschen |
Allgemeines zum Programmspeicher
Ein 14-Bit-Kern-PIC
(z.B. ein PIC16Fxxx) besitzt je nach Typ bis zu 8kWorte
Programmspeicher. Das sind 8192 Speicherzellen, die jeweils einen
Befehl enthalten können. Um das Programm abzuarbeiten, liest der
Micrcontroller eine Zelle nach der anderen aus, und verarbeitet den
darin enthaltenen Befehl. Um sich zu merken, welche
Programmspeicherzelle als nächstes dran ist, merkt er sich die
Adresse in einem 13 Bit langen Register, dem program counter (PC).
Der Microcontroller liest die
Programmspeicherzelle aus, deren Adresse im PC steht, und erhöht
dann den PC um 1, wodurch er auf die nächste Zelle zeigt. Der
ausgelesene Befehl wird abgearbeitet, und der Vorgang wiederholt.
Durch dieses lineare vorgehen wäre
man nach gut 8000 Schritten am Ende des Programms angelangt. Aber man
kann auch im Programm Sprünge zu anderen Propgrammspeicherstellen
definieren. Dazu ist dann jeweils der PC mit der Zieladresse zu
beschreiben, und der Microcontroller wird als nächstes die Arbeit
an der neuen Stelle im Programmspeicher fortsetzen.
Es gibt generell zwei unterschiedliche
Verfahren, einen Sprung im Programmspeicher vorzunehmen, und beide
haben so ihre Tücken:
Leider passen eine 8-Bit-ALU und ein 13-Bit PC nicht gut zusammen.
Der 13-Bit lange program counter PC besteht aus einem unteren 8 Bit
langen Teil (PCL) und dem
oberen 5 Bit langen Teil (PCH).
PCL ist ein ganz normales Register im PIC. Es kann gelesen und
beschrieben werden. Dadurch lässt sich die Adresse, an der
der Microcontroller den nächsten Befehl liest manipulieren. Man
kann z.B. mit
movlw 5 ; w=5 addwf PCL, f ; PCL = PCL+5 |
Wenn in PCLATCH der Wert '0'
steht, dann landet man mit so einem Befehl immer irgendwo zwischen
0x0000 und 0x00FF. Steht dagegen in PCLATCH
der Wert 3, dann springt
man zu einer Programmspeicherzelle im Bereich 0x0300 ... 0x03FF. Man muss also unbedingt PCLATCH vorab mit dem richtigen PCH-Wert beschreiben. Ein weiteres Problem entsteht, wenn bei der Berechnung des PCL ein Überlauf auftritt. Wenn z.B. im obigen Beispiel der ADDWF-Befehl an der Adresse 0x00FE steht, dann wird die Addition mit 5 zum Ergebnis 4 kommen und das C-Flag wird gesetzt. Das C-Flag hat aber keinerlei Auswirkung auf PCH. Folglich springt man nicht 5 Befehle vorwärts, sonder zurück nach 0x0004. (Gehe nicht über Los, ziehe nicht .....) |
Tabelle addwf PCL, f retlw B'00011000' ; 0 retlw B'11011110' ; 1 retlw B'00110010' ; 2 retlw B'01010010' ; 3 retlw B'11010100' ; 4 retlw B'01010001' ; 5 retlw B'00010001' ; 6 retlw B'11011010' ; 7 retlw B'00010000' ; 8 retlw B'01010000' ; 9 |
Es gibt ja auch richtige Sprungbefehle
wie z.B. CALL und GOTO.
Aber auch die sind nicht gänzlich unproblematisch.
Um
mit einem Sprungbefehl einen Sprung zu
einer beliebigen Stelle im 13-Bit-Adressraum des PIC auszulösen,
müsste
der Befehl eine 13-Bit Adresse enthalten. Die passt aber nicht in den
14-Bit langen Befehlscode. Vielmehr enthält dieser nur eine 11-Bit
lange
Adresse. Die oberen beiden Bits des PC
werden beim Sprung wieder aus
dem PCLATCH-Register genommen. |
Der Programmspeicher besteht
also aus bis zu 4 Speicherseiten. Solange die Bits 3&4 des PCLATCH auf 0 stehen, springt man also immer innerhalb einer 2k-großen Speicherseite (Page0) herum (0x0000 ... 0x07FF) Um in eine andere Speicherseite (Page 1, Page 2, Page3) zu springen, müssen die Bits 3&4 des Registers PCLATCH vorab entsprechend gesetzt werden. Für alle Arten von RETURN-Befehlen gibt es diese Beschränkung zum Glück nicht, da die Return-Adresse vollständig in einem 13-Bit breiten Stackspeicher abgelegt wird. |
bcf PCLATCH, 4 ; bsf PCLATCH, 3 ; Page1 CALL 0x810 ; Sprung ... ORG 0x810 MeinZiel ... |
bcf PCLATCH, 4 ; bsf PCLATCH, 3 ; Page1 CALL MeinZiel ; Sprung ... ORG 0x810 MeinZiel ... |
bcf PCLATCH, 4 ; bsf PCLATCH, 3 ; Page1 CALL 0x010 ; Sprung ... ORG 0x810 MeinZiel ... |
12.11.2001
letzte Änderung: 12.09.2012