AVR-Tutorial: SRAM: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
K (Syntaxhighlight (asm), R/G, Kleinigkeiten)
 
(25 dazwischenliegende Versionen von 8 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
==SRAM - Der Speicher des Controllers==
== SRAM Der Speicher des Controllers ==


Nachdem in einem der vorangegangenen Kapitel eine [[AVR-Tutorial:_PWM|Software PWM]] vorgestellt wurde und in einem weiteren Kapitel darüber gesprochen wurde, wie man mit [[AVR-Tutorial:_Schieberegister|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!
Nachdem in einem der vorangegangenen Kapitel eine [[AVR-Tutorial: PWM#PWM in Software|Software-PWM]] vorgestellt und in einem weiteren Kapitel darüber gesprochen wurde, wie man mit [[AVR-Tutorial: Schieberegister|Schieberegistern]] die Anzahl an I/O-Pins erhöhen kann, wäre es naheliegend, beides zu kombinieren und den ATmega8 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 machten. Während allerdings 6 PWM-Kanäle noch problemlos in den Registern untergebracht werden konnten, ist dies mit 30 oder noch mehr PWM-Kanälen nicht mehr 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.
Es gibt aber einen Ausweg. Der ATmega8 verfügt über 1&nbsp;KByte '''SRAM''' (statisches [[RAM]]). Dieses RAM wurde bereits indirekt durch den [[AVR-Tutorial: Stack|Stack]] benutzt. Bei jedem Aufruf eines Unterprogrammes, sei es über einen expliziten '''<code>call</code>''' (bzw. '''<code>rcall</code>''') oder einen Interrupt, wird die Rücksprungadresse irgendwo gespeichert. Dies geschieht genau in diesem SRAM. Auch '''<code>push</code>''' und '''<code>pop</code>''' 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.
Ein Programm darf Speicherzellen im SRAM direkt 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 z.&nbsp;B. die erwähnten 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 ATmega8 immerhin über 1&nbsp;KByte (=&nbsp;1.024 Byte) SRAM verfügt, ist dies in der Praxis kein allzu großes Problem.


==Das .DSEG und .BYTE==
== 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.
Um dem Assembler mitzuteilen, dass sich der folgende Abschnitt auf das SRAM bezieht, gibt es die Direktive '''<code>.DSEG</code>''' (''Data Segment''). Alle nach einer <code>.DSEG</code>-Direktive folgenden Speicherreservierungen werden vom Assembler im SRAM durchgeführt.


<avrasm>
Die Direktive '''<code>.BYTE</code>''' stellt dabei eine derartige Speicherreservierung dar. Sie ermöglicht, der Speicherreservierung einen Namen zu geben und erlaubt auch, nicht nur 1&nbsp;Byte, sondern eine ganze Reihe von Bytes unter einem Namen zu reservieren.
           .DSEG                ; Umschalten auf das SRAM Datensegment
 
<syntaxhighlight lang="asm">
           .DSEG                ; Umschalten auf das SRAM-Datensegment
Counter:  .BYTE  1            ; 1 Byte unter dem Namen 'Counter' reservieren
Counter:  .BYTE  1            ; 1 Byte unter dem Namen 'Counter' reservieren
Test:      .BYTE  20            ; 20 Byte unter dem Namen 'Test' reservieren
Test:      .BYTE  20            ; 20 Byte unter dem Namen 'Test' reservieren
</avrasm>
</syntaxhighlight>


==spezielle Befehle==
== 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.
Für den Zugriff auf den SRAM-Speicher gibt es spezielle Befehle. Diese holen entweder den momentanen Inhalt einer Speicherzelle und legen ihn in einem Register ab oder legen den Inhalt eines Registers in einer SRAM-Speicherzelle ab.


===LDS===
=== LDS ===
Liest die angegebene '''SRAM''' Speicherzelle und legt den gelesenen Wert in einem Register ab.
Liest die angegebene SRAM-Speicherzelle und legt den gelesenen Wert in einem Register ab.


<avrasm>
<syntaxhighlight lang="asm">
         LDS  r17, Counter      ; liest die Speicherzelle mit dem Namen 'Counter'
         LDS  r17, Counter      ; Liest die Speicherzelle mit dem Namen 'Counter'
                               ; und legt den gelesenen Wert im Register r17 ab
                               ; und legt den gelesenen Wert im Register r17 ab.
</avrasm>
</syntaxhighlight>


===STS===
=== STS ===
Legt den in einem Register gespeicherten Wert in einer '''SRAM''' Speicherzelle ab.
Legt den in einem Register gespeicherten Wert in einer SRAM-Speicherzelle ab.


<avrasm>
<syntaxhighlight lang="asm">
         STS  Counter, r17      ; Speichert den Inhalt von r17 in der
         STS  Counter, r17      ; Speichert den Inhalt von r17 in der
                               ; Speicherzelle 'Counter'
                               ; Speicherzelle 'Counter'.
</avrasm>
</syntaxhighlight>


===Beispiel===
=== Beispiel ===


Eine mögliche Implementierung der [[AVR-Tutorial:_PWM|Software PWM]], die den PWM Zähler sowie die einzelnen OCR Grenzwerte im '''SRAM''' anstelle von Registern speichert, könnte zb. so aussehen:
Eine mögliche Implementierung der [[AVR-Tutorial: PWM#PWM in Software|Software-PWM]], die den PWM-Zähler sowie die einzelnen OCR-Grenzwerte im SRAM anstelle von Registern speichert, könnte z.&nbsp;B. so aussehen:


<avrasm>
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"
 
.def temp  = r16
.def temp  = r16
.def temp1 = r17
.def temp1 = r17
.def temp2 = r18
.def temp2 = r18
 
 
.org 0x0000
.org 0x0000
         rjmp    main                  ; Reset Handler
         rjmp    main                  ; Reset Handler
.org OVF0addr
.org OVF0addr
         rjmp    timer0_overflow      ; Timer Overflow Handler
         rjmp    timer0_overflow      ; Timer Overflow Handler
 
main:
main:
         ldi    temp, LOW(RAMEND)     ; Stackpointer initialisieren
         ldi    temp, HIGH(RAMEND)   ; Stackpointer initialisieren
        out    SPH, temp
        ldi    temp, LOW(RAMEND)
         out    SPL, temp
         out    SPL, temp
        ldi    temp, HIGH(RAMEND)
 
        out    SPH, temp
 
         ldi    temp, 0xFF            ; Port B auf Ausgang
         ldi    temp, 0xFF            ; Port B auf Ausgang
         out    DDRB, temp
         out    DDRB, temp
 
         ldi    temp2, 0
         ldi    temp2, 0
         sts    OCR_1, temp2
         sts    OCR_1, temp2
Zeile 74: Zeile 75:
         ldi    temp2, 127
         ldi    temp2, 127
         sts    OCR_6, temp2
         sts    OCR_6, temp2
 
         ldi    temp, 0b00000001      ; CS00 setzen: Teiler 1
         ldi    temp, (1<<CS00)      ; CS00 setzen: Teiler 1
         out    TCCR0, temp
         out    TCCR0, temp
 
         ldi    temp, 0b00000001     ; TOIE0: Interrupt bei Timer Overflow
         ldi    temp, (1<<TOIE0)     ; TOIE0: Interrupt bei Timer Overflow
         out    TIMSK, temp
         out    TIMSK, temp
 
         sei
         sei
 
loop:  rjmp    loop
loop:  rjmp    loop
 
; *************************************************************************
; Behandlung des Timer-Overflows
;
; realisiert die PWM auf 6 Kanälen
;
; veränderte Register: keine
;
timer0_overflow:                  ; Timer 0 Overflow Handler
timer0_overflow:                  ; Timer 0 Overflow Handler
         lds    temp1, PWMCount    ; den PWM Zaehler aus dem Speicher holen
        push    temp              ; Alle verwendeten Register sichern
         inc    temp1              ; Zaehler erhoehen
        push    temp1
         cpi    temp1, 128        ; wurde 128 erreicht ?
        push    temp2
        in      temp, SREG
        push    temp
 
         lds    temp1, PWMCount    ; den PWM-Zähler aus dem Speicher holen
         inc    temp1              ; Zähler erhöhen
         cpi    temp1, 128        ; Wurde 128 erreicht?
         brne    WorkPWM            ; Nein
         brne    WorkPWM            ; Nein
         clr    temp1              ; Ja: PWM Zaehler wieder auf 0
         clr    temp1              ; Ja: PWM-Zähler wieder auf 0
 
WorkPWM:
WorkPWM:
         sts    PWMCount, temp1    ; den PWM Zaehler wieder speichern
         sts    PWMCount, temp1    ; den PWM-Zähler wieder speichern
         ldi    temp, 0b11000000  ; 0 .. Led an, 1 .. Led aus
         ldi    temp, 0b11000000  ; 0 .. LED an, 1 .. LED aus
 
         lds    temp2, OCR_1
         lds    temp2, OCR_1
         cp      temp1, temp2      ; Ist der Grenzwert für Led 1 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 1 erreicht
         brlt    OneOn
         brlt    OneOn
         ori    temp, $01
         ori    temp, $01
 
OneOn:  lds    temp2, OCR_2
OneOn:  lds    temp2, OCR_2
         cp      temp1, temp2      ; Ist der Grenzwert für Led 2 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 2 erreicht
         brlt    TwoOn
         brlt    TwoOn
         ori    temp, $02
         ori    temp, $02
 
TwoOn:  lds    temp2, OCR_3
TwoOn:  lds    temp2, OCR_3
         cp      temp1, temp2      ; Ist der Grenzwert für Led 3 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 3 erreicht
         brlt    ThreeOn
         brlt    ThreeOn
         ori    temp, $04
         ori    temp, $04
 
ThreeOn:lds    temp2, OCR_4
ThreeOn:lds    temp2, OCR_4
         cp      temp1, temp2      ; Ist der Grenzwert für Led 4 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 4 erreicht
         brlt    FourOn
         brlt    FourOn
         ori    temp, $08
         ori    temp, $08
 
FourOn: lds    temp2, OCR_5
FourOn: lds    temp2, OCR_5
         cp      temp1, temp2      ; Ist der Grenzwert für Led 5 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 5 erreicht
         brlt    FiveOn
         brlt    FiveOn
         ori    temp, $10
         ori    temp, $10
 
FiveOn: lds    temp2, OCR_6
FiveOn: lds    temp2, OCR_6
         cp      temp1, temp2      ; Ist der Grenzwert für Led 6 erreicht
         cp      temp1, temp2      ; Ist der Grenzwert für LED 6 erreicht
         brlt    SetBits
         brlt    SetBits
         ori    temp, $20
         ori    temp, $20
 
SetBits:                             ; Die neue Bitbelegung am Port ausgeben
SetBits:                           ; Die neue Bitbelegung am Port ausgeben
         out    PORTB, temp
         out    PORTB, temp
 
        pop    temp              ; die gesicherten Register wiederherstellen
        out    SREG, temp
        pop    temp2
        pop    temp1
        pop    temp
 
         reti
         reti
;
; **********************************************
;
           .DSEG                      ; das Folgende kommt ins SRAM
           .DSEG                      ; das Folgende kommt ins SRAM


PWMCount: .BYTE  1                  ; Der PWM Counter (0 bis 127)
PWMCount: .BYTE  1                  ; Der PWM-Counter (0 bis 127)
OCR_1:    .BYTE  1                  ; 6 Bytes für die OCR Register
OCR_1:    .BYTE  1                  ; 6 Bytes für die OCR-Register
OCR_2:    .BYTE  1
OCR_2:    .BYTE  1
OCR_3:    .BYTE  1
OCR_3:    .BYTE  1
Zeile 140: Zeile 162:
OCR_5:    .BYTE  1
OCR_5:    .BYTE  1
OCR_6:    .BYTE  1
OCR_6:    .BYTE  1
</avrasm>
</syntaxhighlight>
 
Die ISR sichert alle verwendeten Register und stellt sie am Ende der ISR wieder her. Dies ist zwar streng genommen in diesem Beispiel nicht notwendig, da das eigentliche Programm in der Hauptschleife ja nichts tut, aber außer einem bisschen Zeit kostet das nichts und es erspart das Kopfkratzen, wenn dann irgendwann in der Hauptschleife Ergänzungen und anderer Code dazu kommen. Man kann natürlich einige Register speziell für die Verwendung ausschließlich in der ISR reservieren und sich so das Sichern/Wiederherstellen ersparen, aber das SREG muss im Normalfall innerhalb einer ISR auf jeden Fall gesichert und wiederhergestellt werden.


==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 heißt 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 <code>lds</code> bzw. <code>sts</code> 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, denn 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 ===


* '''LD rxx, Z'''
* '''<code>LD rxx, Z</code>'''
* '''LD rxx, Z+'''
* '''<code>LD rxx, Z+</code>'''
* '''LD rxx, -Z'''
* '''<code>LD rxx, -Z</code>'''


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.
Lädt das Register '''<code>rxx</code>''' mit dem Inhalt der Speicherzelle, deren Adresse im Z-Pointer angegeben ist. Bei den Varianten mit '''<code>Z+</code>''' bzw. '''<code>-Z</code>''' wird zusätzlich der Z-Pointer ''nach'' der Operation um 1 erhöht bzw. ''vor'' der Operation um 1 vermindert.


===LDD===
=== LDD ===
* '''LDD rxx, Z+q'''
* '''<code>LDD rxx, Z+q</code>'''


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


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


<avrasm>
<syntaxhighlight lang="asm">
         LDD r18, Z + $28
         LDD r18, Z + $28
</avrasm>
</syntaxhighlight>


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


Der Wertebereich für '''q''' erstreckt sich von 0 bis 63.
Der Wertebereich für <code>q</code> erstreckt sich von 0 bis 63.


===ST===
=== ST ===


* '''ST Z, rxx'''
* '''<code>ST Z, rxx</code>'''
* '''ST Z+, rxx'''
* '''<code>ST Z+, rxx</code>'''
* '''ST -Z, rxx'''
* '''<code>ST -Z, rxx</code>'''


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.
Speichert den Inhalt des Registers '''<code>rxx</code>''' in der Speicherzelle, deren Adresse im Z-Pointer angegeben ist. Bei den Varianten mit '''<code>Z+</code>''' bzw. '''<code>-Z</code>''' wird zusätzlich der Z-Pointer ''nach'' der Operation um 1 erhöht bzw. ''vor'' der Operation um 1 vermindert.


===STD===
=== STD ===
* '''STD Z+q, rxx'''
* '''<code>STD Z+q, rxx</code>'''


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


Enthält also der '''Z-Pointer''' die Adresse $1000 und sei '''q''' der Wert $28, so wird mit einer Speicheranweisung
Enthält also der Z-Pointer die Adresse $1000 und sei <code>q</code> der Wert $28, so wird mit einer Speicheranweisung
<avrasm>
<syntaxhighlight lang="asm">
         STD Z + $28, r18
         STD Z + $28, r18
</avrasm>
</syntaxhighlight>


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


Der Wertebereich für '''q''' erstreckt sich von 0 bis 63.
Der Wertebereich für <code>q</code> erstreckt sich von 0 bis 63.


===Beispiel===
=== 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.
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 derer 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 <code>ld</code>-Befehl auf das nächste zu verarbeitende OCR-Byte weitergezählt.


<avrasm>
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"
 
.def temp  = r16
.def temp  = r16
.def temp1 = r17
.def temp1 = r17
.def temp2 = r18
.def temp2 = r18
.def temp3 = r19
.def temp3 = r19
 
 
.org 0x0000
.org 0x0000
         rjmp    main                  ; Reset Handler
         rjmp    main                  ; Reset Handler
.org OVF0addr
.org OVF0addr
         rjmp    timer0_overflow      ; Timer Overflow Handler
         rjmp    timer0_overflow      ; Timer Overflow Handler
 
main:
main:
         ldi    temp, LOW(RAMEND)     ; Stackpointer initialisieren
         ldi    temp, HIGH(RAMEND)   ; Stackpointer initialisieren
        out    SPH, temp
        ldi    temp, LOW(RAMEND)
         out    SPL, temp
         out    SPL, temp
        ldi    temp, HIGH(RAMEND)
 
        out    SPH, temp
 
         ldi    temp, 0xFF            ; Port B auf Ausgang
         ldi    temp, 0xFF            ; Port B auf Ausgang
         out    DDRB, temp
         out    DDRB, temp


         ldi    r30,LOW(OCR)         ; den Z-Pointer mit dem Start der OCR Bytes laden
         ldi    r30, LOW(OCR)         ; den Z-Pointer mit dem Start der OCR-Bytes laden
         ldi    r31,HIGH(OCR)
         ldi    r31, HIGH(OCR)
 
         ldi    temp2, 0
         ldi    temp2, 0
         st      Z+, temp2
         st      Z+, temp2
Zeile 232: Zeile 256:
         ldi    temp2, 127
         ldi    temp2, 127
         st      Z+, temp2
         st      Z+, temp2
 
         ldi    temp2, 0              ; den PWM Counter auf 0 setzen
         ldi    temp2, 0              ; den PWM-Counter auf 0 setzen
         sts    PWMCount, temp2
         sts    PWMCount, temp2


         ldi    temp, 0b00000001      ; CS00 setzen: Teiler 1
         ldi    temp, (1<<CS00)      ; CS00 setzen: Teiler 1
         out    TCCR0, temp
         out    TCCR0, temp
 
         ldi    temp, 0b00000001     ; TOIE0: Interrupt bei Timer Overflow
         ldi    temp, (1<<TOIE0)     ; TOIE0: Interrupt bei Timer Overflow
         out    TIMSK, temp
         out    TIMSK, temp
 
         sei
         sei
 
loop:  rjmp    loop
loop:  rjmp    loop


; *************************************************************************
; Behandlung des Timer-Overflows
;
; realisiert die PWM auf 6 Kanälen
;
; veränderte Register: keine
;
timer0_overflow:                      ; Timer 0 Overflow Handler
timer0_overflow:                      ; Timer 0 Overflow Handler
         lds    temp1, PWMCount      ; den PWM ZAehler aus dem Speicher holen
        push    temp                  ; Alle verwendeten Register sichern
         inc    temp1                ; Zaehler erhoehen
        push    temp1
         cpi    temp1, 128            ; wurde 128 erreicht ?
        push    temp2
        push    R30
        push    R31
        in      temp, SREG
        push    temp
 
         lds    temp1, PWMCount      ; den PWM-Zähler aus dem Speicher holen
         inc    temp1                ; Zähler erhöhen
         cpi    temp1, 128            ; Wurde 128 erreicht?
         brne    WorkPWM              ; Nein
         brne    WorkPWM              ; Nein
         clr    temp1                ; Ja: PWM Zaehler auf 0 setzen
         clr    temp1                ; Ja: PWM-Zähler auf 0 setzen


WorkPWM:
WorkPWM:
         sts    PWMCount, temp1      ; den PWM Zaehler wieder speichern
         sts    PWMCount, temp1      ; den PWM-Zähler wieder speichern


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


pwmloop:
pwmloop:
         ld      temp2, Z+            ; den OCR Wert für PWM Nr. i holen und Z-Pointer erhöhen
         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?
         cp      temp1, temp2          ; Ist der Grenzwert für PWM Nr. i erreicht?
         brne   LedOn
         brlo   LedOn
         or      temp, temp3
         or      temp, temp3
LedOn:
LedOn:
         lsl    temp3                ; das Bitmuster schieben
         lsl    temp3                ; das Bitmuster schieben
         cpi    temp3, $40            ; alle Bits behandelt ?
         cpi    temp3, $40            ; Alle Bits behandelt?
         brne    pwmloop              ; nächster Schleifendurchlauf
         brne    pwmloop              ; nächster Schleifendurchlauf


         out    PORTB, temp          ; Die neue Bitbelegung am Port ausgeben
         out    PORTB, temp          ; Die neue Bitbelegung am Port ausgeben
        pop    temp                  ; die gesicherten Register wiederherstellen
        out    SREG, temp
        pop    R31
        pop    R30
        pop    temp2
        pop    temp1
        pop    temp
         reti
         reti
 
;
; *********************************************************************
;
         .DSEG                        ; das Folgende kommt ins SRAM
         .DSEG                        ; das Folgende kommt ins SRAM


PWMCount: .BYTE  1                    ; der PWM Zaehler (0 bis 127)
PWMCount: .BYTE  1                    ; der PWM-Zähler (0 bis 127)
OCR:      .BYTE  6                    ; 6 Bytes für die OCR Register
OCR:      .BYTE  6                    ; 6 Bytes für die OCR-Register
</syntaxhighlight>


</avrasm>
=== X-Pointer, Y-Pointer ===
 
Neben dem Z-Pointer gibt es noch den '''X-Pointer''' und den '''Y-Pointer'''. Sie werden gebildet von den Registerpärchen
===X-Pointer, Y-Pointer===
* X-Pointer: r26, r27
Neben dem '''Z-Pointer''' gibt es noch den '''X-Pointer''' bzw. '''Y-Pointer'''. Sie werden gebildet von den Registerpärchen
* Y-Pointer: r28, r29
* '''X-Pointer''': r26, r27
* Z-Pointer: r30, r31
* '''Y-Pointer''': r28, r29
Alles über den Z-Pointer gesagte gilt sinngemäß auch für den X- bzw. Y-Pointer mit einer Ausnahme: Mit dem X-Pointer ist kein Zugriff über <code>ldd</code> oder <code>std</code> mit einem Displacement möglich.
* '''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 ==
== Siehe auch ==
* [[Adressierung]]
* [[Adressierung]]
----


{{Navigation_zurückhochvor|
{{Navigation_zurückhochvor|
Zeile 296: Zeile 347:
hochtext=Inhaltsverzeichnis|
hochtext=Inhaltsverzeichnis|
hochlink=AVR-Tutorial|
hochlink=AVR-Tutorial|
vortext=Mehrfachverzweigung|
vortext=7-Segment-Anzeige|
vorlink=AVR-Tutorial: Mehrfachverzweigung}}
vorlink=AVR-Tutorial: 7-Segment-Anzeige}}


[[Category:AVR]][[Category:AVR-Tutorial]]
[[Category:AVR-Tutorial|SRAM]]

Aktuelle Version vom 23. November 2022, 18:28 Uhr

SRAM – Der Speicher des Controllers

Nachdem in einem der vorangegangenen Kapitel eine Software-PWM vorgestellt 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 ATmega8 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 machten. Während allerdings 6 PWM-Kanäle noch problemlos in den Registern untergebracht werden konnten, ist dies mit 30 oder noch mehr PWM-Kanälen nicht mehr möglich. Es gibt schlicht und ergreifend nicht genug Register.

Es gibt aber einen Ausweg. Der ATmega8 verfügt über 1 KByte SRAM (statisches RAM). Dieses RAM wurde bereits indirekt durch den Stack benutzt. Bei jedem Aufruf eines Unterprogrammes, sei es über einen expliziten call (bzw. rcall) oder einen Interrupt, wird die Rücksprungadresse irgendwo gespeichert. Dies geschieht genau in diesem SRAM. Auch push und pop operieren in diesem Speicher.

Ein Programm darf Speicherzellen im SRAM direkt 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 z. B. die erwähnten 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 ATmega8 immerhin über 1 KByte (= 1.024 Byte) SRAM verfügt, ist dies in der Praxis kein allzu 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. Sie ermöglicht, der Speicherreservierung einen Namen zu geben und erlaubt auch, nicht nur 1 Byte, sondern eine ganze Reihe von Bytes unter einem Namen zu reservieren.

           .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

Spezielle Befehle

Für den Zugriff auf den SRAM-Speicher gibt es spezielle Befehle. Diese holen entweder den momentanen Inhalt einer Speicherzelle und legen ihn in einem Register ab oder legen den Inhalt eines Registers in einer SRAM-Speicherzelle ab.

LDS

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

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

STS

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

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

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 z. B. so aussehen:

.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, HIGH(RAMEND)    ; Stackpointer initialisieren
        out     SPH, temp
        ldi     temp, LOW(RAMEND)
        out     SPL, 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, (1<<CS00)       ; CS00 setzen: Teiler 1
        out     TCCR0, temp

        ldi     temp, (1<<TOIE0)      ; TOIE0: Interrupt bei Timer Overflow
        out     TIMSK, temp

        sei

loop:   rjmp    loop

; *************************************************************************
; Behandlung des Timer-Overflows
;
; realisiert die PWM auf 6 Kanälen
;
; veränderte Register: keine
;
timer0_overflow:                   ; Timer 0 Overflow Handler
        push    temp               ; Alle verwendeten Register sichern
        push    temp1
        push    temp2
        in      temp, SREG
        push    temp

        lds     temp1, PWMCount    ; den PWM-Zähler aus dem Speicher holen
        inc     temp1              ; Zähler erhöhen
        cpi     temp1, 128         ; Wurde 128 erreicht?
        brne    WorkPWM            ; Nein
        clr     temp1              ; Ja: PWM-Zähler wieder auf 0

WorkPWM:
        sts     PWMCount, temp1    ; den PWM-Zähler 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

        pop     temp               ; die gesicherten Register wiederherstellen
        out     SREG, temp
        pop     temp2
        pop     temp1
        pop     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

Die ISR sichert alle verwendeten Register und stellt sie am Ende der ISR wieder her. Dies ist zwar streng genommen in diesem Beispiel nicht notwendig, da das eigentliche Programm in der Hauptschleife ja nichts tut, aber außer einem bisschen Zeit kostet das nichts und es erspart das Kopfkratzen, wenn dann irgendwann in der Hauptschleife Ergänzungen und anderer Code dazu kommen. Man kann natürlich einige Register speziell für die Verwendung ausschließlich in der ISR reservieren und sich so das Sichern/Wiederherstellen ersparen, aber das SREG muss im Normalfall innerhalb einer ISR auf jeden Fall gesichert und wiederhergestellt werden.

Spezielle Register

Der Z-Pointer (R30 und R31)

Das Registerpärchen R30 und R31 kann zu einem einzigen logischen Register zusammengefasst werden und heißt 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, denn 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

        LDD r18, Z + $28

der Inhalt der Speicherzelle $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 Registers 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

        STD Z + $28, r18

der Inhalt des Registers r18 in der Speicherzelle $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 derer 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.

.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, HIGH(RAMEND)    ; Stackpointer initialisieren
        out     SPH, temp
        ldi     temp, LOW(RAMEND)
        out     SPL, 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, (1<<CS00)       ; CS00 setzen: Teiler 1
        out     TCCR0, temp

        ldi     temp, (1<<TOIE0)      ; TOIE0: Interrupt bei Timer Overflow
        out     TIMSK, temp

        sei

loop:   rjmp    loop

; *************************************************************************
; Behandlung des Timer-Overflows
;
; realisiert die PWM auf 6 Kanälen
;
; veränderte Register: keine
;
timer0_overflow:                      ; Timer 0 Overflow Handler
        push    temp                  ; Alle verwendeten Register sichern
        push    temp1
        push    temp2
        push    R30
        push    R31
        in      temp, SREG
        push    temp

        lds     temp1, PWMCount       ; den PWM-Zähler aus dem Speicher holen
        inc     temp1                 ; Zähler erhöhen
        cpi     temp1, 128            ; Wurde 128 erreicht?
        brne    WorkPWM               ; Nein
        clr     temp1                 ; Ja: PWM-Zähler auf 0 setzen

WorkPWM:
        sts     PWMCount, temp1       ; den PWM-Zähler 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?
        brlo    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

        pop     temp                  ; die gesicherten Register wiederherstellen
        out     SREG, temp
        pop     R31
        pop     R30
        pop     temp2
        pop     temp1
        pop     temp

        reti
;
; *********************************************************************
;
        .DSEG                         ; das Folgende kommt ins SRAM

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

X-Pointer, Y-Pointer

Neben dem Z-Pointer gibt es noch den X-Pointer und den 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- bzw. Y-Pointer mit einer Ausnahme: Mit dem X-Pointer ist kein Zugriff über ldd oder std mit einem Displacement möglich.

Siehe auch