Pfad:
Home =>
AVR-Übersicht =>
Software => LPM-Befehl
(This page in English:
)
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.
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.
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.
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.
Nachdem wir die drei Instruktionen ausgeführt haben, sehen wir das hier:
- Das Registerpaar ZH:ZL (R31:R30) steht auf der Adresse 0x0008, dem Doppelten
der Tabellenadresse.
- R0 ist Null, das Byte ist aus dem Programmspeicher gelesen.
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
- zuerst die Tabellenadresse 0x0005 verdoppelt und in Z geschrieben,
- dann die Null aus dem Speicher gelesen und den Zeiger auf die nächste
Adresse erhöht, 0x000B.
- danach die Eins aus dem Speicher gelesen und den Zeiger auf die nächste
Adresse erhöht, 0x000C.
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.
;
;
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