Direkte und indirekte Adressierung
des Datenspeichers,
Berechnete Sprungziele im Programmspeicher

bei 14-Bit-Kern-PICs


zurück zu PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage

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

Allgemeines
Direkte Adressierung
Indirekte Adressierung

 
Programmspeicher

Allgemeines
Sprungberechnung mit PCL
CALL und GOTO

zurück: zu PIC-Grundlagen


Allgemeines

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.

nach oben

Direkte 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
 

 
direkte Adressierung 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.

Adressbereich des PIC

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
 

Der Code wird dadurch zwar portierbarer, aber nicht gerade lesbarer. Der Assembler macht daraus dann das folgende Code-Ungetüm mit vielen überflüssigen Zeilen:


    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
    

Dieser aufgeblasene langsame Code konterkariert die Nutzung des schnellen platzsparenden Assemblercodes.

nach oben


Indirekte Adressierung

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).

indirekte Adressierung

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
 

nach oben

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:


nach oben

Sprungberechnung mit PCL

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
 

im Programm um 5 Adressen/Zellen/Befehle vorwärts springen.

Die Sache hat aber einen gewaltigen Haken! Durch so einen Befehl kann nur PCL (also die unteren 8 Bit des PC) verändert werden. Was passiert mit PCH?
Bei jedem ALU-Befehl, der PCL zum Ziel hat, wird automatisch PCH mit den unteren 5 Bit des Registers PCLATCH beschrieben.

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 .....)

Adressberechnung mit PCL

Wer nun mitgerechnet hat, wird sich vielleicht noch wundern, warum das Ergebnis 4 lautet. Wäre 3 nicht korrekt ? (0x0FE+5=0x103). Nein!
Man muss beachten, das der PC unmittelbar nach dem Lesen des Befehls um 1 erhöht wurde. Während der Befehl aus der Zelle 0x00FE abgearbeitet wird, steht in PC schon 0x0FF, und das Rechenergebnis ist  4 (0x0FF+5=0x104).

"Große Sprünge" lassen sich mit PCL-Berechnungen also nicht machen. Vorsichtig eingesetzt bieten sie aber interessante Möglichkeiten. Im folgenden Beispiel sind in einer Tabelle 10 Werte abgelegt. Die lassen sich auslesen, indem man w mit einem Wert zwischen 0 und 9 beschreibt, und dann "CALL Tabelle" benutzt. Durch den ADDWF-Befehl springt man zu einem der RETLW-Befehle und kehrt mit dem jeweiligen Tabellenwert in w zum rufenden Programm zurück. Man muss aber darauf achten, dass innerhalb der Tabelle keine Programmspeicheradresse der Form 0x__FF vorkommt, da ansonsten rückwärts gesprungen wird.

   
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
 

nach oben

CALL und GOTO

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.


Sprung

Speicherseiten
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.

Ob man in einem Programm schreibt:


    bcf    PCLATCH, 4    ;
    bsf    PCLATCH, 3    ; Page1
    CALL   0x810         ; Sprung
    ...

    ORG    0x810
MeinZiel
    ...
 

oder:


    bcf    PCLATCH, 4    ;
    bsf    PCLATCH, 3    ; Page1
    CALL   MeinZiel      ; Sprung
    ...

    ORG    0x810
MeinZiel
    ...
 


oder:


    bcf    PCLATCH, 4    ;
    bsf    PCLATCH, 3    ; Page1
    CALL   0x010         ; Sprung
    ...

    ORG    0x810
MeinZiel
    ...
 


ist eigentlich egal, der erzeugt Code wird identisch sein, und dem unteren Beispiel entsprechen. Man tut trotzdem gut daran, die echte (13-Bit lange) Zieladresse bzw. die richtige Sprungmarke in den Quelltext zu schreiben. In diesem Fall prüft der Linker, ob man in eine andere Page springt und gibt eine entsprechende Warnung aus.

nach oben


zurück: zu PIC-Grundlagen


zurück zu PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage

12.11.2001
letzte Änderung: 12.09.2012