AVR-Tutorial: SRAM: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
K (Vorlink korrigiert)
Zeile 144: Zeile 144:
==Spezielle Register==
==Spezielle Register==
===Der Z-Pointer (R30 und R31)===
===Der Z-Pointer (R30 und R31)===
Das Registerpärchen '''R30''' und '''R31''' kann zu einem einzigen logischen Register zusammengefasst werden und heisst dann '''Z-Pointer'''. Diesem kann eine spezielle Aufgabe zukommen, indem er als Adressangabe fungieren kann, von welcher Speicherzelle im SRAM ein Ladevorgang (bzw. Speichervorgang) durchgeführt werden soll. Anstatt die Speicheradresse wie beim '''LDS''' bzw. '''STS''' direkt im Programmcode anzugeben, kann diese Speicheradresse zunächst in den '''Z-Pointer''' geladen werden und der Lesevorgang (Schreibvorgang) über diesen '''Z-Pointer''' abgewickelt werden. Besonders komfortabel ist dies, da im Ladebefehl noch zusätzliche Manipulationen angegeben werden können.
Das Registerpärchen '''R30''' und '''R31''' kann zu einem einzigen logischen Register zusammengefasst werden und heisst dann '''Z-Pointer'''. Diesem kann eine spezielle Aufgabe zukommen, indem er als Adressangabe fungieren kann, von welcher Speicherzelle im SRAM ein Ladevorgang (bzw. Speichervorgang) durchgeführt werden soll. Anstatt die Speicheradresse wie beim '''LDS''' bzw. '''STS''' direkt im Programmcode anzugeben, kann diese Speicheradresse zunächst in den '''Z-Pointer''' geladen werden und der Lesevorgang (Schreibvorgang) über diesen '''Z-Pointer''' abgewickelt werden. Dadurch wird aber die '''SRAM'''-Speicheradresse berechenbar, dann natürlich kann mit den Registern '''R30''' und '''R31''', wie mit den anderen Registern auch, Arithmetik betrieben werden. Besonders komfortabel ist dies, da im Ladebefehl noch zusätzliche Manipulationen angegeben werden können, die oft benötigte arithmetische Operationen implementieren.


===LD===
===LD===

Version vom 24. August 2007, 12:27 Uhr

SRAM - Der Speicher des Controllers

Nachdem in einem der vorangegangenen Kapitel eine Software PWM vorgestellt wurde und in einem weiteren Kapitel darüber gesprochen wurde, wie man mit Schieberegistern die Anzahl an I/O Pins erhöhen kann, wäre es naheliegend beides zu kombinieren und den Mega8 mal 20 oder 30 Leds ansteuern zu lassen. Wenn es da nicht ein Problem gäbe: Die Software-PWM hält ihre Daten in Registern, so wie das praktisch alle Programme bisher taten. Nur gibt es dann ein Problem: Während 6 PWM Kanäle noch problemlos in den Registern untergebracht werden konnten, ist dies mit 30 oder noch mehr PWM Kanälen nicht möglich. Es gibt schlicht und ergreifend nicht genug Register!

Es gibt aber einen Ausweg. Der Mega8 verfügt über 1 KByte SRAM (statisches RAM). Dieses RAM wurde bereits indirekt benutzt: Bei jedem Aufruf eines Unterprogrammes, sei es über einen expliziten CALL oder einen Interrupt, wird ja die Rücksprungadresse irgendwo gespeichert. Dies geschieht genau in diesem SRAM. Auch PUSH und POP operieren in diesem Speicher.

Und dasselbe können auch wir tun! Ein Programm darf Speicherzellen im SRAM benutzen und dort Werte speichern bzw. von dort Werte einlesen. Es muss nur darauf geachtet werden, dass es zu keiner Kollision mit dem Stack kommt, in dem Rücksprungadressen für Unterprogramme gespeichert werden. Da viele Programme aber lediglich ein paar Byte SRAM brauchen, der Rücksprungstack von der oberen Grenze des SRAM nach unten wächst und der Mega8 immerhin über 1 KByte SRAM verfügt, ist dies in der Praxis kein all zu großes Problem.

Das .DSEG und .BYTE

Um dem Assembler mitzuteilen, dass sich der folgende Abschnitt auf das SRAM bezieht, gibt es die Direktive .DSEG (Data Segment). Alle nach einer .DSEG Direktive folgenden Speicherreservierungen werden vom Assembler im SRAM durchgeführt.

Die Direktive .BYTE stellt dabei eine derartige Speicherreservierung dar. Es ermöglicht, der Speicherreservierung einen Namen zu geben und es erlaubt auch, nicht nur 1 Byte sondern eine ganze Reihe von Bytes unter einem Namen zu reservieren.

<avrasm>

          .DSEG                ; Umschalten auf das SRAM Datensegment

Counter: .BYTE 1  ; 1 Byte unter dem Namen 'Counter' reservieren Test: .BYTE 20  ; 20 Byte unter dem Namen 'Test' reservieren </avrasm>

spezielle Befehle

Für den Zugriff auf den SRAM Speicher gibt es spezielle Befehle. Diese holen den momentanen Inhalt einer Speicherzelle und legen ihn in einem Register ab, bzw. die Umkehrung: Den Inhalt eines Registers in einer SRAM Speicherzelle ablegen.

LDS

Liest die angegebene SRAM Speicherzelle und legt den gelesenen Wert in einem Register ab.

<avrasm>

       LDS  r17, Counter      ; liest die Speicherzelle mit dem Namen 'Counter'
                              ; und legt den gelesenen Wert im Register r17 ab

</avrasm>

STS

Legt den in einem Register gespeicherten Wert in einer SRAM Speicherzelle ab.

<avrasm>

       STS  Counter, r17      ; Speichert den Inhalt von r17 in der
                              ; Speicherzelle 'Counter'

</avrasm>

Beispiel

Eine mögliche Implementierung der Software PWM, die den PWM Zähler sowie die einzelnen OCR Grenzwerte im SRAM anstelle von Registern speichert, könnte zb. so aussehen:

<avrasm> .include "m8def.inc"

.def temp = r16 .def temp1 = r17 .def temp2 = r18

.org 0x0000

       rjmp    main                  ; Reset Handler

.org OVF0addr

       rjmp    timer0_overflow       ; Timer Overflow Handler

main:

       ldi     temp, LOW(RAMEND)     ; Stackpointer initialisieren
       out     SPL, temp
       ldi     temp, HIGH(RAMEND)
       out     SPH, temp
 
       ldi     temp, 0xFF            ; Port B auf Ausgang
       out     DDRB, temp

       ldi     temp2, 0
       sts     OCR_1, temp2
       ldi     temp2, 1
       sts     OCR_2, temp2
       ldi     temp2, 10
       sts     OCR_3, temp2
       ldi     temp2, 20
       sts     OCR_4, temp2
       ldi     temp2, 80
       sts     OCR_5, temp2
       ldi     temp2, 127
       sts     OCR_6, temp2

       ldi     temp, 0b00000001      ; CS00 setzen: Teiler 1
       out     TCCR0, temp

       ldi     temp, 0b00000001      ; TOIE0: Interrupt bei Timer Overflow
       out     TIMSK, temp

       sei

loop: rjmp loop

timer0_overflow:  ; Timer 0 Overflow Handler

       lds     temp1, PWMCount    ; den PWM Zaehler aus dem Speicher holen
       inc     temp1              ; Zaehler erhoehen
       cpi     temp1, 128         ; wurde 128 erreicht ?
       brne    WorkPWM            ; Nein
       clr     temp1              ; Ja: PWM Zaehler wieder auf 0

WorkPWM:

       sts     PWMCount, temp1    ; den PWM Zaehler wieder speichern
       ldi     temp, 0b11000000   ; 0 .. Led an, 1 .. Led aus

       lds     temp2, OCR_1
       cp      temp1, temp2       ; Ist der Grenzwert für Led 1 erreicht
       brlt    OneOn
       ori     temp, $01

OneOn: lds temp2, OCR_2

       cp      temp1, temp2       ; Ist der Grenzwert für Led 2 erreicht
       brlt    TwoOn
       ori     temp, $02

TwoOn: lds temp2, OCR_3

       cp      temp1, temp2       ; Ist der Grenzwert für Led 3 erreicht
       brlt    ThreeOn
       ori     temp, $04

ThreeOn:lds temp2, OCR_4

       cp      temp1, temp2       ; Ist der Grenzwert für Led 4 erreicht
       brlt    FourOn
       ori     temp, $08

FourOn: lds temp2, OCR_5

       cp      temp1, temp2       ; Ist der Grenzwert für Led 5 erreicht
       brlt    FiveOn
       ori     temp, $10

FiveOn: lds temp2, OCR_6

       cp      temp1, temp2       ; Ist der Grenzwert für Led 6 erreicht
       brlt    SetBits
       ori     temp, $20

SetBits:  ; Die neue Bitbelegung am Port ausgeben

       out     PORTB, temp

       reti

         .DSEG                       ; das Folgende kommt ins SRAM

PWMCount: .BYTE 1  ; Der PWM Counter (0 bis 127) OCR_1: .BYTE 1  ; 6 Bytes für die OCR Register OCR_2: .BYTE 1 OCR_3: .BYTE 1 OCR_4: .BYTE 1 OCR_5: .BYTE 1 OCR_6: .BYTE 1 </avrasm>

Spezielle Register

Der Z-Pointer (R30 und R31)

Das Registerpärchen R30 und R31 kann zu einem einzigen logischen Register zusammengefasst werden und heisst dann Z-Pointer. Diesem kann eine spezielle Aufgabe zukommen, indem er als Adressangabe fungieren kann, von welcher Speicherzelle im SRAM ein Ladevorgang (bzw. Speichervorgang) durchgeführt werden soll. Anstatt die Speicheradresse wie beim LDS bzw. STS direkt im Programmcode anzugeben, kann diese Speicheradresse zunächst in den Z-Pointer geladen werden und der Lesevorgang (Schreibvorgang) über diesen Z-Pointer abgewickelt werden. Dadurch wird aber die SRAM-Speicheradresse berechenbar, dann natürlich kann mit den Registern R30 und R31, wie mit den anderen Registern auch, Arithmetik betrieben werden. Besonders komfortabel ist dies, da im Ladebefehl noch zusätzliche Manipulationen angegeben werden können, die oft benötigte arithmetische Operationen implementieren.

LD

  • LD rxx, Z
  • LD rxx, Z+
  • LD rxx, -Z

Lädt das Register rxx mit dem Inhalt der Speicherzelle, deren Adresse im Z-Pointer angegeben ist. Bei den Varianten mit Z+ bzw. -Z wird zusätzlich der Z-Pointer nach der Operation um 1 erhöht bzw. vor der Operation um 1 vermindert.

LDD

  • LDD rxx, Z+q

Hier erfolgt der Zugriff wieder über den Z-Pointer wobei vor dem Zugriff zur Adressangabe im Z-Pointer noch das Displacement q addiert wird.

Enthält also der Z-Pointer die Adresse $1000 und sei q der Wert $28, so wird mit einer Ladeanweisung

<avrasm>

       LDD r18, Z + $28

</avrasm>

der Inhalt der Speicherzellen $1000 + $28 = $1028 in das Register r18 geladen.

Der Wertebereich für q erstreckt sich von 0 bis 63.

ST

  • ST Z, rxx
  • ST Z+, rxx
  • ST -Z, rxx

Speichert den Inhalt des Register rxx in der Speicherzelle, deren Adresse im Z-Pointer angegeben ist. Bei den Varianten mit Z+ bzw. -Z wird zusätzlich der Z-Pointer nach der Operation um 1 erhöht bzw. vor der Operation um 1 vermindert.

STD

  • STD Z+q, rxx

Hier erfolgt der Zugriff wieder über den Z-Pointer wobei vor dem Zugriff zur Adressangabe im Z-Pointer noch das Displacement q addiert wird.

Enthält also der Z-Pointer die Adresse $1000 und sei q der Wert $28, so wird mit einer Speicheranweisung <avrasm>

       STD Z + $28, r18

</avrasm>

der Inhalt des Registers r18 in der Speicherzellen $1000 + $28 = $1028 gespeichert.

Der Wertebereich für q erstreckt sich von 0 bis 63.

Beispiel

Durch Verwendung des Z-Pointers ist es möglich die Interrupt Funktion wesentlich kürzer und vor allem ohne ständige Wiederholung von im Prinzip immer gleichem Code zu formulieren. Man stelle sich nur mal vor wie dieser Code aussehen würde, wenn anstelle von 6 PWM Stufen, deren 40 gebraucht würden. Mit dem Z-Pointer ist es möglich diesen auf das erste der OCR Bytes zu setzen und dann in einer Schleife eines nach dem anderen abzuarbeiten. Nach dem Laden des jeweiligen OCR Wertes, wird der Z-Pointer automatisch durch den LD-Befehl auf das nächste zu verarbeitende OCR Byte weitergezählt.

<avrasm> .include "m8def.inc"

.def temp = r16 .def temp1 = r17 .def temp2 = r18 .def temp3 = r19

.org 0x0000

       rjmp    main                  ; Reset Handler

.org OVF0addr

       rjmp    timer0_overflow       ; Timer Overflow Handler

main:

       ldi     temp, LOW(RAMEND)     ; Stackpointer initialisieren
       out     SPL, temp
       ldi     temp, HIGH(RAMEND)
       out     SPH, temp
 
       ldi     temp, 0xFF            ; Port B auf Ausgang
       out     DDRB, temp
       ldi     r30,LOW(OCR)          ; den Z-Pointer mit dem Start der OCR Bytes laden
       ldi     r31,HIGH(OCR)

       ldi     temp2, 0
       st      Z+, temp2
       ldi     temp2, 1
       st      Z+, temp2
       ldi     temp2, 10
       st      Z+, temp2
       ldi     temp2, 20
       st      Z+, temp2
       ldi     temp2, 80
       st      Z+, temp2
       ldi     temp2, 127
       st      Z+, temp2

       ldi     temp2, 0              ; den PWM Counter auf 0 setzen
       sts     PWMCount, temp2
       ldi     temp, 0b00000001      ; CS00 setzen: Teiler 1
       out     TCCR0, temp

       ldi     temp, 0b00000001      ; TOIE0: Interrupt bei Timer Overflow
       out     TIMSK, temp

       sei

loop: rjmp loop

timer0_overflow:  ; Timer 0 Overflow Handler

       lds     temp1, PWMCount       ; den PWM ZAehler aus dem Speicher holen
       inc     temp1                 ; Zaehler erhoehen
       cpi     temp1, 128            ; wurde 128 erreicht ?
       brne    WorkPWM               ; Nein
       clr     temp1                 ; Ja: PWM Zaehler auf 0 setzen

WorkPWM:

       sts     PWMCount, temp1       ; den PWM Zaehler wieder speichern
       ldi     r30,LOW(OCR)          ; den Z-Pointer mit dem Start der OCR Bytes laden
       ldi     r31,HIGH(OCR)
       ldi     temp3, $01            ; das Bitmuster für PWM Nr. i
       ldi     temp, 0b11000000      ; 0 .. Led an, 1 .. Led aus

pwmloop:

       ld      temp2, Z+             ; den OCR Wert für PWM Nr. i holen und Z-Pointer erhöhen
       cp      temp1, temp2          ; ist der Grenzwert für PWM Nr. i erreicht?
       brne    LedOn
       or      temp, temp3

LedOn:

       lsl     temp3                 ; das Bitmuster schieben
       cpi     temp3, $40            ; alle Bits behandelt ?
       brne    pwmloop               ; nächster Schleifendurchlauf
       out     PORTB, temp           ; Die neue Bitbelegung am Port ausgeben
       reti
       .DSEG                         ; das Folgende kommt ins SRAM

PWMCount: .BYTE 1  ; der PWM Zaehler (0 bis 127) OCR: .BYTE 6  ; 6 Bytes für die OCR Register

</avrasm>

X-Pointer, Y-Pointer

Neben dem Z-Pointer gibt es noch den X-Pointer bzw. Y-Pointer. Sie werden gebildet von den Registerpärchen

  • X-Pointer: r26, r27
  • Y-Pointer: r28, r29
  • Z-Pointer: r30, r31

Alles über den Z-Pointer gesagte gilt sinngemäß auch für den X-Pointer bzw. Y-Pointer mit einer Ausnahme: Mit dem X-Pointer ist kein Zugriff LDD/STD mit einem Displacement möglich.

Siehe auch