Pfad: Home => AVR-Übersicht => Software => LPM-Befehl    (This page in English: Flag EN) Logo

Der LPM-Befehl und seine Besonderheit

Der LPM-Befehl liest Inhalte aus dem Flash-Speicher in ein Register ein. Sein Mnemonic kommt von Load from Program Memory her.

Organisation des Programmspeichers in AVRs Der Befehlsspeicher bei den AVR ist, im Unterschied zu allen anderen internen Speichern wie den Portregistern, dem Statischen RAM (SRAM) und dem EEPROM, aber nicht 8-bittig, sondern 16-bittig. Alle Instruktionen an jeder einzelnen Adresse des Programmspeichers (0x0000, 0x0001, 0x0002, etc.) bestehen aus 16 Bits gleichzeitig, einem oberen und einem unteren Byte.

Das O/U-Bit in der Zeigeradresse Weil beim Lesen entschieden werden muss, welches der beiden Bytes gelesen werden soll (das untere U oder das obere O), muss ein zusätzliches Bit O/U her, das diese Entscheidung mitteilt. Dieses zusätzliche Bit ist das Bit 0 im Zeigerpaar Z, das aus den beiden Einzelregistern ZH (R31) und ZL (R30) besteht. Um Platz im Registerpaar Z für dieses zusätzliche Bit zu schaffen, muss die 16-bittige Adresse im Zeigerpaar Z um eine Bitposition nach links verschoben werden. Ist das O/U-Bit Null, wird das untere Byte gelesen, ist es Eins wird das Obere gelesen.

Nehmen wir als Beispiel eine Tabelle im Programmspeicher mit der Adresse Tabelle:. Sie soll aus den Zahlen 0 bis 7 bestehen. Die kriegen wir folgendermaßen in den Programmspeicher:

; Tabelle mit den Werten Null bis Sieben im Programmspeicher
Tabelle:
  .db 0, 1, 2, 3, 4, 5, 6, 7

Hier hat der Assembler wieder das gleiche Problem: Jedes Byte, das er mit .db in die Tabelle schreibt, hat 8 Bits. Der Programmspeicher hat aber 16 Bits. Nun, der Assembler platziert die Null in das untere Byte an der Adresse Tabelle:, die Eins in das obere Byte an der gleichen Adresse. Und so weiter bis Sieben. Unsere Tabelle hat daher eine Länge von vier 16-Bit-Worten, Tabelle: zeigt auf Null und Eins, Tabelle + 1: zeigt auf 2 und 3, Tabelle + 2: auf 4 und 5 und Tabelle + 3: auf 6 und 7.

Soll der Zeiger Z jetzt auf die Null gesetzt werden, machen wir das so:

  ldi ZH,High(2 * Tabelle + 0) ; Das MSB des Tabellenzeigers
  ldi ZL,Low(2 * Tabelle + 0) ; Das LSB des Zeigers
  lpm ; Lese das Byte an der Adresse in Z in das Register R0

Nach dem Assemblieren zeigt das Assembler-Listing folgendes an:
    25: ; Die Tabelle
    26: Tabelle:
    27:   .db 0, 1, 2, 3, 4, 5, 6, 7
        000004 0100 0302 0504 0706
Die Tabelle beginnt also bei Adresse 000004 und enthält vier 16-Bit-Worte. Die geraden Zahlen 0, 2, 4 und 6 stehen in den unteren Bytes der vier Worte, die ungeraden 1, 3, 5 und 7 stehen in den oberen Bytes der vier Worte.

Das Multiplizieren der Adresse Tabelle: mit zwei im oberen Programmcode zum Auslesen aus der Tabelle schafft den nötigen Platz für das unterste Bit im Zeiger Z, das die Auswahl von Unten und Oben angibt. Und + 0 setzt dieses Bit auf Null. Hingegen würde + 1 den Zeiger auf die Eins in der Tabelle setzen.

Start des LPM-Programms Simulieren wir den Ablauf mit avr_sim, dann startet unser Leseprogramm zu Beginn so aus. R0 habe ich manuell auf 0xFF eingestellt, so dass wir die Änderung durch LPM auch sehen.

Ausgeführtes LPM-Programm Nachdem wir die drei Instruktionen ausgeführt haben, sehen wir das hier:

Ausgeführtes doppeltes LPM-Programm Formulieren wir das Programm ein wenig um und machen aus LPM die Instruktion LPM R0,Z+, und zwar zwei mal. Das Z+ erhöht den Zeiger Z automatisch nach dem Lesen um Eins. Die Tabelle ist jetzt an der Programmspeicher-Adresse 0x0005. Nun hat das Programm in vier Schritten Die Platzierung des O/U-Bits in das unterste Bit des Zeigers Z hat also den Vorteil, dass die Bytes nacheinander aus der Tabelle herausgelesen werden können. Das vereinfacht auch das Ablegen und Lesen von Texten aus der Tabelle, die wir mit

Text:
  .db "Das ist ein Text.",0x0D,0x0A

zusammen mit einem abschließenden Wagenrücklauf- und einem Zeilenvorschub- Zeichen in den Programmspeicher ablegen können. Bei dieser Zeile gibt der Assembler eine Warnung aus, weil die Anzahl an Bytes ungeradzahlig ist. Da er nach dem .db immer an ganzen Adressen ankommen muss, hat er für das letzte Speicherwort noch ein Null-Byte (0x00) drangehängt. Fortgesetztes LPM R0,Z+ bringt den Text Zeichen für Zeichen nacheinander in der richtigen Reihenfolge in das Register R0.

Die Platzierung des O/U-Bits in das unterste Bit von Z hat übrigens zur Folge, dass der Adresse das höchste Bit fehlt und Null ist. Bei AVR mit ganz viel Programmspeicher müssen daher Tabellen in den untersten 32 kWorten platziert werden. Aber das ist nur selten ein Beinbruch.

Bitte unbedingt beachten: NUR der LPM-Befehl benötigt dieses Extra-Bit, alle anderen Zeiger-Anwendungen brauchen das nicht, weil sie sowieso schon 8-bittig sind.

; Testet den LPM-Befehl zum Auslesen von Bytes aus dem Programmspeicher

; Liest die Tasten und wandelt die Nummer der Taste
; über eine Liste im Programmspeicher in die Anzahl
; an LEDs um und beleuchtet diese. (Taste 0 = 8 Stück)
; Ein ziemlich unnützes Programm, aber es demonstriert
; neben dem LPM-Befehl auch das Rollen und Springen.
;
.NOLIST
.INCLUDE "8515def.inc"
.LIST
;

; Register

;
.DEF   erg=R0 ; Der LPM-Befehl wirkt ausschliesslich auf R0
.DEF   mpr=R16 ; Multifunktionsregister
;
; Verwendet werden auch die Register ZL (R30) und ZH (R31).
; Dies wird im 8515def.inc definiert, daher braucht es hier nicht.
;

; Reset-/Interrupt-Vektor

;
   RJMP   main
;
main:   CLR   mpr ; Lade 0 in Register mpr
   OUT   DDRD,mpr ; alle D-Ports sind Eingang Schalter
   DEC   mpr ; Lade FF in Register B
   OUT   DDRB,mpr ; Alle B-Ports sind Ausgang LEDs
   OUT   PORTD,mpr ; Alle Pullups auf D einschalten

loop:   LDI   ZL,LOW(liste2) ; Registerpaar Z zeigt auf das
   LDI   ZH,HIGH(liste2) ; erste Byte (FF) in der Liste
   IN   mpr,PIND ; Lese Schalter aus
   CPI   mpr,0xFF ; Alle Schalter aus? Alle LEDs aus!
   BREQ   ;lesen
incp:   INC   ZL ;Zeige mit LSB auf nächstes Byte der Liste
   BRNE   rolle ;kein Überlauf des MSB, weiter
   INC   ZH ;MSB übergelaufen, eins weiter
rolle:   ROR   mpr ;schiebe Bit 0 in das Carry-Flag
   BRLO   incp ; Carry=1, nicht gedrückt, weiter in der Liste
lesen:   LPM ; Lese das Byte, auf das Zeiger Z zeigt in Register R0
   OUT   PORTB,erg ; Gib Byte auf LEDs aus
   RJMP   loop ; Im Kreis drehen
;
; Die Liste mit den Lampenkombinationen, jedes Byte entspricht einem
; Schalter.
; Die Werte müssen jeweils wortweise angegeben werden, weil bei der
; Verwendung von .DB xx immer automatisch ein Null-Byte mitgespeichert
; würde! Es geht aber auch .DB xx,yy! Die werden zu einem Wort ver-
; knüpft. Dasselbe gilt bei Texten mit .DB "abcd...": Geradzahlige An-
; zahlen gehen, ungeradzahlige kriegen ein Nullbyte zusätzlich!
;
liste:
.DW   0xFEFF
; 1 Lampe, 0 Lampen (0 ist hinten, 1 vorne!)
.DW   0xF8FC ; 3 Lampen, 2 Lampen
.DW   0xE0F0 ; 5 Lampen, 4 Lampen
.DW   0x80C0 ; 7 Lampen, 6 Lampen
;
.EQU   liste2=liste*2 ; Wird gebraucht, weil die Adresse
;      wortweise organisiert ist, die Werte aber
;      byteweise gelesen werden sollen


©2002 by http://www.avr-asm-tutorial.net