Forum: Mikrocontroller und Digitale Elektronik Fehler bei Assembler (Bin Anfänger)


von Michael D. (etzen_michi)


Lesenswert?

Guten Tag.

Habe das Problem das ich mit bei Assembler daher das ich grad erst 
angefangen habe erstmal ein 64er Lauflicht gebaut habe und er bei diesem 
immer sagt: "AVR Simulator: Stack pointer below start of RAM"

Das Programm lautet:

;Test

.include "m8def.inc"  ; AtMega8 laden
.def temp1 = r16    ; Register r16 als temp1 benennen
.def temp2 = r17    ; Register r17 als temp2 benennen
.def temp3 = r18    ; register r18 als temp3 benennen

  rjmp Reset

Reset:

  ldi temp1, 0xFF    ; Register temp1 mit 0xFF beschreiben
  ldi temp2, 0x01    ; Register temp2 mit 0x01 beschreiben
  ldi temp3, 0xFE    ; Register temp3 mit 0xFE beschreiben
  out DDRB, temp1    ; Ausgangsregister DDRB mit 0xFF
  out DDRD, temp1    ; Ausgangsregister DDRD mit 0xFF

Loop:

  out PORTD, temp3  ; Über PORTD Inhalt von temp3 ausgeben
  out PORTB, temp2  ; Über PORTB Inhalt von temp2 ausgeben
  lsl temp2      ; 0 in Ihnalt von temp2 einschieben
  brcs Zeile      ; wenn C Flag 1, Zu Prog. Zeile springen
  ldi temp1, 0x01    ; Register temp1 mit 0x01 beschreiben
  rcall Warte      ; Zu Prog. Warte springen

Zeile:

  ldi temp2, 0x01    ; Register temp2 mit 0x01 beschreiben
  rol temp3      ; 1 in Inhalt von temp3 einschieben
  brcc Reset      ; wenn C Flag 0, zu Prog. Reset springen

  rjmp Loop      ; Zu Prog. Loop springen

Warte:

  dec temp1      ; Inhalt von temp1 um 1 veringern
  nop          ; einen Tackt nichts tun
  brne Warte      ; wenn Z Flag 0, zu Prog. Warte springen

  rjmp Loop      ; Zu Prog. Loop springen




Und der Curser steht in diesem Fall immer auf "dec temp1"

Währe nett wenn mir jemand helfen könnte.


(Ggf. auch sagen wie ich den Code in eine Extra Box lege, sodass ich es 
nachträglich bearbeiten kann.)

von bitte löschen (Gast)


Lesenswert?

Ich glaube nicht, dass das für so ein Minimalprogramm, was keine 
Interrupts enabled (kein "sei" hinter "Reset:") und keine rcall-Aufrufe 
macht, so etwas braucht, aber jedes Programm, was über ein Ansteuern 
einer LED hinaus geht, muss am Anfang den Stack-Pointer initialisieren:

Reset:
  ldi r16,high(RAMEND); Main program start
  out SPH,r16 ; Set Stack Pointer to top of RAM
  ldi r16,low(RAMEND)
  out SPL,r16

(Mich wundert nur, dass der Simulator da moppert.)

von spess53 (Gast)


Lesenswert?

Hi

Dir fehlt die Stack-Initialisierung:
1
             ldi r16,high(RAMEND)
2
             out SPH,r16
3
             ldi r16,Low(RAMEND)
4
             out SPL,r16

Sollte als erstes gemacht werden.

MfG Spess

von Sascha W. (sascha_w)


Lesenswert?

wenn du WARTE: per rcall aufrufst, so muss dies auch mit RET wieder 
beendet werden, und nicht per RJMP! Sonst ist der STACK schnell voll!

Sascha

von Michael D. (etzen_michi)


Lesenswert?

Reicht es wenn ich diese Initialisierung nur einmal mache oder muss der 
da dauernt drüber laufen?

Verstehe das Kapitel Stack aus dem Tutorial leider nicht so ganz ...


Hab grad auch nochmal ein wenig mit Rumversuchen versucht, kriege es 
aber irgendwie nicht hin ... Habe anstatt r16 r19 genommen, falls das 
wichtig sein sollte.

von spess53 (Gast)


Lesenswert?

Hi

>Reicht es wenn ich diese Initialisierung nur einmal mache oder muss der
>da dauernt drüber laufen?

Nein, nur ein mal. Den Rest macht der Controller.

MfG Spess

von Michael D. (etzen_michi)


Lesenswert?

Hmm .. habe nun das mit dem Stack eingebastelt wie hier geschrieben und 
das rcall Loop durch ret ersetzt. Nur irgendwie funktioniert das 
nichtmehr so wie vorher. Vorher hatte ich es so das ich PORTB die X 
Achse und PORTD die Y Achse einer LED Matrix anschließen konnte und ich 
ein Lauflicht hatte. Nun ist es so das die X Achse Läuft und die Y Achse 
aber immer einer mehr leuchtet ...

;Test

.include "m8def.inc"  ; AtMega8 laden
.def temp1 = r16      ; Register r16 als temp1 benennen
.def temp2 = r17      ; Register r17 als temp2 benennen
.def temp3 = r18      ; Register r18 als temp3 benennen
.def Stack = r19    ; Register r19 als Stack benennen

  ldi Stack,high(RAMEND)
  out SPH,Stack
  ldi Stack,Low(RAMEND)
  out SPL,Stack

  rjmp Reset

Reset:

  ldi temp1, 0xFF      ; Register temp1 mit 0xFF beschreiben
  ldi temp2, 0x01      ; Register temp2 mit 0x01 beschreiben
  ldi temp3, 0xFE      ; Register temp3 mit 0xFE beschreiben
  out DDRB, temp1      ; Ausgangsregister DDRB mit 0xFF
  out DDRD, temp1      ; Ausgangsregister DDRD mit 0xFF

Loop:

  out PORTD, temp3    ; Über PORTD Inhalt von temp3 ausgeben
  out PORTB, temp2    ; Über PORTB Inhalt von temp2 ausgeben
  lsl temp2          ; 0 in Ihnalt von temp2 einschieben
  brcs Zeile          ; wenn C Flag 1, Zu Prog. Zeile springen
  ldi temp1, 0x01      ; Register temp1 mit 0x01 beschreiben
  rcall Warte          ; Zu Prog. Warte springen

Zeile:

  ldi temp2, 0x01      ; Register temp2 mit 0x01 beschreiben
  rol temp3          ; 1 in Inhalt von temp3 einschieben
  brcc Reset          ; wenn C Flag 0, zu Prog. Reset springen

  rjmp Loop          ; Zu Prog. Loop springen

Warte:

  dec temp1          ; Inhalt von temp1 um 1 veringern
  nop                ; einen Tackt nichts tun
  brne Warte          ; wenn Z Flag 0, zu Prog. Warte springen

  ret          ; Zu Prog. Loop springen



Villeicht kann das ja jemand mal Simulieren ... ich nutze AVRStudio4.


Info am Rande: Die Fehler zeigt er nur wenn ich auf "Run" gehe, nicht 
aber wenn ich auf "Auto Step" gehe (ausser bei dem mit ret da zeigt er 
den extra genannten Fehler bei beiden Varianten).

von bitte löschen (Gast)


Lesenswert?

So kannst Du Dir das rjmp Reset auch sparen.
Versuche nicht zu frunzeln bis es geht, sondern versuche erst mal zu 
verstehen, was Du machst. Dieses "rjmp Reset" ist eigentlich nur in 
Programmen nötig, die mit Interrupts arbeiten, weil ganz vorne die 
Interrupt-Tabelle liegt, deren erster Eintrag der Reset-Handler ist.
Normalerweise sieht ein Programmanfang auf einem ATmega8 so aus:
1
.include "m8def.inc"
2
.CSEG
3
 rjmp RESET ; Reset Handler
4
 rjmp EXT_INT0 ; IRQ0 Handler
5
 rjmp EXT_INT1 ; IRQ1 Handler
6
 rjmp TIM2_COMP ; Timer2 Compare Handler
7
 rjmp TIM2_OVF ; Timer2 Overflow Handler
8
 rjmp TIM1_CAPT ; Timer1 Capture Handler
9
 rjmp TIM1_COMPA ; Timer1 CompareA Handler
10
 rjmp TIM1_COMPB ; Timer1 CompareB Handler
11
 rjmp TIM1_OVF ; Timer1 Overflow Handler
12
 rjmp TIM0_OVF ; Timer0 Overflow Handler
13
 rjmp SPI_STC ; SPI Transfer Complete Handler
14
 rjmp USART_RXC ; USART RX Complete Handler
15
 rjmp USART_UDRE ; UDR Empty Handler
16
 rjmp USART_TXC ; USART TX Complete Handler
17
 rjmp ADC_COMPLETE ; ADC Conversion Complete Handler
18
 rjmp EE_RDY ; EEPROM Ready Handler
19
 rjmp ANA_COMP ; Analog Comparator Handler
20
 rjmp TWSI ; Two-wire Serial Interface Handler
21
 rjmp SPM_RDY ; Store Program Memory Ready Handler
22
Reset:
23
 ldi r16,high(RAMEND); Main program start
24
 out SPH,r16 ; Set Stack Pointer to top of RAM
25
 ldi r16,low(RAMEND)
26
 out SPL,r16
27
 sei ; Enable interrupts
28
 ...
29
30
EXT_INT0:
31
EXT_INT1:
32
...
33
SPM_RDY:
34
 iret

Du brauchst das alles nicht, da Du kein "sei" brauchst, bis Du anfängst, 
das Lauflicht durch einen der Timer zu steuern.
Solange Du aber keinen Stack hast, kanst Du keine Unterprogramme (Warte) 
benutzen, was in diesem Fall einfach durch Verschieben des 
entsprechenden Codes an die Stelle seines Aufrufs gelöst werden kann.

von Michael D. (etzen_michi)


Lesenswert?

Wie nutze ich so einen Timer?

Habe es so gemacht wie ich es verstanden habe. Und daher das manche 
Dinge häufig und nur in bestimmten Situtationen abgerufen werden habe 
ich mir Unterprogrammen gearbeitet. Bekomme es irgendwie nicht zum 
laufen und zusätzlich hat er plötzlich PIND ein Low wo an PORTD ein Low 
ist .... wie geht das? ^^

von spess53 (Gast)


Lesenswert?

Hi

>SPM_RDY:
> iret

Besser 'reti'

MfG Spess

von Michael D. (etzen_michi)


Lesenswert?

spess53 schrieb:
>>SPM_RDY:
>
>> iret
>
>
>
> Besser 'reti'

Kann zufällig jemand die Begriffe erklären .. ^^ (Bedeutung Funktion). 
Will schon auch verstehen und nicht nur Copy and Paste machen ^^.

Wenn ich alle rcall in meinem Programm durch rjmp ersetze funktinierts 
.. was ist der Unterschied zwischen diesen beiden befehlen?

Zudem wollte ich nochmal gerne fragen ob man während das Prog. auf "run" 
steht irgendwie die Zustände der Aus/Eingänge sehen kann.

von spess53 (Gast)


Lesenswert?

Hi

>Kann zufällig jemand die Begriffe erklären .. ^^ (Bedeutung Funktion).

Welche?

Wenn du

rjmp RESET ; Reset Handler
rjmp EXT_INT0 ; IRQ0 Handler
...

meinst. Das findest du im Datenblatt unter Interrupts.

'reti' und alle anderen Befehle in der Hilfe zum Assembler vom 
AVR-Studio unter 'Instructions' oder hier

www.atmel.com/atmel/acrobat/doc0856.pdf

MfG Spess

von DRJ (Gast)


Lesenswert?

Bei RET wird zur Zeile nach dem RCALL gesprungen.
Das heißt das Programm macht bei ZEILE weiter nicht bei LOOP!

von Michael D. (etzen_michi)


Lesenswert?

Wenn ich ret eingebe springt er irgendwie immer zum ersten rjmp .. Also 
zum Anfang des Programms.

von DRJ (Gast)


Lesenswert?

Bei rcall wird die Adresse für den Rücksprung in den Stack geschrieben.
Bei ret wird die Adresse vom Stack geholt und dahin Gesprungen.
Dem ret ist es egal ob im Stack an der Position eine Adresse steht,
er versucht trotzdem dahin zu Springen und kann irgendwo im Programm 
Landen!

von Michael D. (etzen_michi)


Lesenswert?

Wenn ich richtig verstanden habe geht dies ret also nur wenn ich "rcall" 
gemacht habe, aber nicht bei "brcc" oder "brne"

von bitte löschen (Gast)


Lesenswert?

spess53 schrieb:
> Hi
>
>>SPM_RDY:
>> iret
>
> Besser 'reti'
>
> MfG Spess

Äh ja. peinlichgrins Das war x86er Assembler.

von Michael D. (etzen_michi)


Lesenswert?

Was war das? .... Meinst jetzt dass das für AVR mit 32Bit war?

von spess53 (Gast)


Lesenswert?

Hi

>Wenn ich richtig verstanden habe geht dies ret also nur wenn ich "rcall"
>gemacht habe, aber nicht bei "brcc" oder "brne"

Richtig. 'rcall' und bei größeren AVRs auch 'call' springen zu dem 
dahinter stehenden Label. Gleichzeitig wird die Adresse des 
nachfolgenden Befehls auf dem Stack gespeichert. Beim Erreichen des 
'ret' holt sich der Controller diese Adresse wieder vom Stack und macht 
an der o.g. Stelle weiter.
Bei 'brxx', 'rjmp' und 'jmp' wird keine Adresse auf dem Stack 
gespeichert. Also kann auch ein 'ret' nichts sinnvolles vom Stack holen.

MfG Spess

von bitte löschen (Gast)


Lesenswert?

> Wenn ich richtig verstanden habe geht dies ret also nur wenn ich "rcall"
> gemacht habe, aber nicht bei "brcc" oder "brne"
Ja, genau.
Ich habe das Gefühl, Dir ist das Konzept eines Stacks noch nicht so ganz 
klar. Der Stack ist ein Stapelspeicher, wie ein Stapel Teller. Du legst 
einen Teller A drauf, dann einen Teller B. Wenn Du die Teller wieder 
herunter nimmst, kommt zuerst Teller B, dann Teller A.
Hier sind es jedoch keine Teller, sondern Bytes, die auf dem Stack 
abgelegt werden. Wenn mit "rcall" ein Unterprogramm aufgerufen wird, 
wird die aktuelle Stelle, an der das Programm gerade ist, also 2 Bytes, 
auf dem Stack abgelegt, und dann wird zu der Adresse des Unterprogramms 
gesprungen. Am Ende des Unterprogramms wird der oberste Teller vom 
Stapel genommen, und dorthin zurück gesprungen. Der Befehl für beide 
Aktionen ist "ret". Ein "reti" macht im Prinzip dasselbe, nur wird das 
Flag, das Interrupts erlaubt, dabei gesetzt.
Der Stack wird repräsentiert durch den Stack-Zeiger, also einem 
Register, in dem die Adresse des nächsten freien Platzes auf dem Stack 
steht. Das bemerkenswerte bei diesem Stack ist, das die Teller quasi an 
der Decke hängen, also der Stack am oberen Ende des Speichers anfängt, 
und nach unten wächst. Deswegen die Initialisierung mit "RAMEND" (Ende 
des RAM) am Anfang.
Der Stack wird aber nicht nur Rücksprungadressen verwendet, sondern 
auch, um Werte zu sichern und sie später zu restaurieren. Mit "push r1" 
legst Du den Inhalt des Wertes vom Register r1 auf dem Stack ab, mit 
"pop r1" kannst Du dann den Wert wieder vom Stack holen. Das macht 
besonders in Unterprogrammen Sinn, die durch Interrupts aufgerufen 
werden, weil diese keine Register verändern dürfen, die woanders, also 
möglicherweise in einem unterbrochenen Programmteil, gerade benutzt 
werden.

> Was war das? .... Meinst jetzt dass das für AVR mit 32Bit war?
Nein, das war für 80x86 CPUs, wie sie auf PCs Verwendung finden. Der 
erste Proz, für den ich (in den 80ern) Assembler programmiert habe, war 
ein 8086. Irgendwie bleibt das haften und drängt immer wieder nach 
außen. :-D

von Michael D. (etzen_michi)


Lesenswert?

Ok .. wenn ich das alles nun richtig Verstanden habe war meine Aktion 
das ich alle rcall durch rjmp ersetzt habe richtig, da ich immer am Ende 
der unterprogramm Resete (Ganz von vorne anfange) oder Ein anderes 
Unterprogramm/Hauptprogramm von vorne anfange.

Der Stack speichert bei einem rjmp die aktuelle Position sodass wenn man 
ret eingibt er automatisch zu dieser Position zurückspringt und den 
nächsten Befehl ausführt, welcher danach gekommen währe.

Zum Thema reti: Bei Interrupts bin ich nochnicht ^^.

Und zu dem mit dem pop und Push wollte ich nochmal frgaen: Wenn ich Push 
genutzt habe dann kann ich aber kein ret mehr nutzen bis ich wieder ein 
rcall hatte oder?

von Sascha W. (sascha_w)


Lesenswert?

Michael Dierken schrieb:
> Ok .. wenn ich das alles nun richtig Verstanden habe war meine Aktion
> das ich alle rcall durch rjmp ersetzt habe richtig, da ich immer am Ende
> der unterprogramm Resete (Ganz von vorne anfange) oder Ein anderes
> Unterprogramm/Hauptprogramm von vorne anfange.
ja

> Der Stack speichert bei einem rjmp die aktuelle Position sodass wenn man
> ret eingibt er automatisch zu dieser Position zurückspringt und den
> nächsten Befehl ausführt, welcher danach gekommen währe.
nein der Stack speichert bei einem rcall o. call die 'Position'

> Zum Thema reti: Bei Interrupts bin ich nochnicht ^^.
>
> Und zu dem mit dem pop und Push wollte ich nochmal frgaen: Wenn ich Push
> genutzt habe dann kann ich aber kein ret mehr nutzen bis ich wieder ein
> rcall hatte oder?
ähm?!
du kannst ein ret vor einem rcall/call aufrufen, aber das hat nichts mit 
push/pop zu tun
Wenn du mit rcall in ein Unterprogramm springst und dort push benutzt, 
dann must du vor dem ret erst pop benutzen. Ebensowenig kannst du erst 
push benutzen, dann mit rcall springen und dann pop benutzen.

Du hast die Funktion des Stack definitiv noch nicht verstanden.

Sascha

von spess53 (Gast)


Lesenswert?

Hi

>Ok .. wenn ich das alles nun richtig Verstanden habe war meine Aktion
>das ich alle rcall durch rjmp ersetzt habe richtig, da ich immer am Ende
>der unterprogramm Resete (Ganz von vorne anfange)

Nein. Ein Reset als Folge eines 'ret' ist ein Programmabsturz! Außerdem 
passiert das nur bei einem leeren Stack. Wenn sich etwas auf dem Stack 
befindet geht das irgendwo ins Nirvana. Also:

      rcall label
      ...
      ...

label: ...
       ...
       ...
       ret

>Der Stack speichert bei einem rjmp die aktuelle Position sodass wenn man
>ret eingibt er automatisch zu dieser Position zurückspringt und den
>nächsten Befehl ausführt, welcher danach gekommen währe.

Nein. Das passiert bei rcall/call.

>Und zu dem mit dem pop und Push wollte ich nochmal frgaen: Wenn ich Push
>genutzt habe dann kann ich aber kein ret mehr nutzen bis ich wieder ein
>rcall hatte oder?

Nein. Wenn du in einem Unterprogramm ein oder mehrere Register auf den 
Stack 'gepusht' hast müssen die vor dem 'ret' wieder mit 'pop' vom 
Stack geholt werden:

Label:  push r16
        push r17
        ...
        ...
        pop r17
        pop r16
        ret

MfG Spess

von iche (Gast)


Lesenswert?

Hallo Michael

Ich glaube dir ist am besten geholfen, wenn man dir rät zuerst das 
Tutorial abzuklappern. Mir hat das am Anfang am meisten gebracht, die 
Themen einzeln und möglichst kurzschrittig auf einem Steckbrett 
nachzuvollziehen und dann Änderungen im Code dazuzubasteln. Dadurch 
kannst du selbst sehen welche effekte die änderungen haben - du hast 
aber ein funktionierendes Programm als Ausgangspunkt.
Außerdem steht ganz viel nützliches Zeug im Datenblatt und es gibt auch 
eine asm-Befehlsreferenz von Atmel im Netz (die hatte ich mir damals 
ausgedruckt(!)).

Ich wünsche dir viel Erfolg und Freude beim "lernen"

Robert (seit 2 Jahren kein Anfänger mehr - aber noch lange kein Profi:))

von Michael D. (etzen_michi)


Lesenswert?

Sascha Weber schrieb:
> Du hast die Funktion des Stack definitiv noch nicht verstanden.

Deswegen frage ich ja nochmal ^^.

Habe ausversehen vorhin rjmp und rcall vertauscht .. meinete natürlich 
das er bei rcall speichert.


Zu iche (Gast): Ja ich mache grad das Tutorial, aber überwiegend 
Theoretisch, da die Hardware nochnicht da ist und da habe ich dann 
einfach mal versucht soweit wie ich war mir ein 64Kanal Lauflicht zu 
basteln ^^.

Vielen Dank für eure Geduldigen Antworten.

Auch wenn ich das mit push und pop nochnicht ganz verstehe:

Wenn ich dies Push mache, muss ich danach einfach nur das pop mit dem 
gleichen Register machen?
So wie ich es zuvor verstanden habe kann man mit den push (Wert im 
Register des Stack ablegen) da was reinschreiben was aber ja nicht mit 
pop (Auslesen) wiederkommen würde.

von iche (Gast)


Lesenswert?

push und pop haben nicht viel mit dem register zu tun auf welches sie 
zugreifen/lesen

mit push schiebst du den inhalt des registers R (egal welches - du musst 
halt eins nehmen) oben als letzten wert auf den stack

und mit pop lädst du den letzten wert auf dem stack in das register R". 
R und R" können, müssen aber nicht das gleiche sein.

von iche (Gast)


Lesenswert?

wenn du
1
push r16
2
push r17
3
pop r16
4
pop r17
machst, vertauschst du die inhalte der register r16 und r17 ;-)

von Michael D. (etzen_michi)


Lesenswert?

Meinst du mit R und R" sowas wie z.B. rr16 und r17?

Denke das ich den sinn so langsam kapiert habe .. muss mal ein wenig 
rumprobieren, ggf. kommt das auchnicht im Tutorial ^^.

von iche (Gast)


Lesenswert?

ja genau

von Michael D. (etzen_michi)


Lesenswert?

Eine letzte Frage noch: Sehe ich das richtig das man max. 16Bit auf dem 
Stack ablegen kann?

von iche (Gast)


Lesenswert?

eigentlich kannst du immer nur 8bit (selbst) im sram (der stack befindet 
sich im sram) ablegen. wie der SRAM genau aufgebaut ist, hab ich jetzt 
nicht auf die schnelle gefunden (steht nicht direkt im datenblatt)

So aus dem Tutorial habe ich: SRAM 8bit - bei uC mit mehr als 256Byte 
ram is der adresszähler dann natürlich breiter als 8bit (sonst könnte ja 
die adresse $257 nie erreicht werden.

und wenn du 16/32/64 Bit auf dem Stack/Sram ablegen willst, musst du die 
bytes eben alle nacheinander (in der richtigen reihenfolge) aufn Stack / 
innen Sram packen.

aber bitte: fang klein und vor allem möglichst weit vort im tutorial an 
- dann entstehen solche grundsatzlichen fragen nicht, und ich schreibe 
mich nicht so in rage, dass ich gleich die groß-KLEINschreibung 
vergesse^^

von Karl H. (kbuchegg)


Lesenswert?

Michael Dierken schrieb:
> Eine letzte Frage noch: Sehe ich das richtig das man max. 16Bit auf dem
> Stack ablegen kann?


Du kannst auf dem Stack ablegen soviel du willst und solange der dafür 
notwendige Speicher noch frei ist.


Am einfachsten macht man sich das Konzept eines Stacks (wieder mal) mit 
Papier und Bleistift klar. Das weiter oben genannte Beispiel mit den 
Tellern war schon nicht schlecht, trifft es aber konkret nicht so recht

Angenommen wir haben diesen Code
(die Zahlen in der linken Spalte geben an, wo genau im Speicher diese 
Anweisung durch den Assembler abgelegt wurde. In deinem Programmtext 
sind sie nicht vorhanden. Ich habe sie nur ergänzt um mich im Code auf 
etwas beziehen zu können
1
0100    ldi   r16, 0xAA
2
0102    ldi   r17, 0x55
3
0104    rcall do_it
4
0106    ldi   r18, 0x00
5
6
...
7
8
0200  doit:
9
0200    push  r17
10
0202    push  r16
11
0204    ldi   r18, 0x02
12
0206    push  r18
13
0208    pop   r16
14
020A    pop   r17
15
020C    pop   r18
16
020E    ret


der Stack sei symbolisiert, durch eine Spalte am rechten Bildschirmrand. 
Wir wollen mal sehen, wie sich der entwickelt. Der Stackpointer sei 
dabei ein Pfeil.

das Programm beginnt, der Stack wird initialisiert (nicht weiter 
gezeigt) und irgendwann landet das Programm an der Adresse 0100

Hier ist der Stack
                                                    +------+
                                                 -->|      |
                                                    +------+

die erste Anweisung
1
  ldi  r16, 0xAA

Register 16 wird mit 0xAA geladen, das hat keine Auwirkungen auf den 
Stack, wir merken uns aber, dass in r16 0xAA steht

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                 -->|      |
   | AA   |  |       | |       |                    +------+
   +------+  +-------+ +-------+

1
  ldi   r17, 0x55

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                 -->|      |
   | AA   |  | 55    | |       |                    +------+
   +------+  +-------+ +-------+

1
  rcall  do_it

der Assembler hat schon dafür gesorgt, dass anstelle von do_it die 
Adresse von do_it (0200) eingesetzt wurde. Für den Prozessor steht da 
also ein Aufruf eines Unterprogrammes, das an der Stelle 0200 beginnt. 
Damit beim ret wieder zum Aufrufer zurückgefunden wird, wird die 
aktuelle Adresse der Ausführung (die schon durch die Befehlsverarbeitung 
auf den Befehl nach den rcall gesetzt wurde) auf dem Stack gesichert. 
der rcall steht auf Adresse 0104, der nächste Befehl steht auf 0106

nach Abarbeitung des rcall ist die Situation also so

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | AA   |  | 55    | |       |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                 -->|      |
                                                    +------+

Auf dem Stack wurde die Adresse 0106 (in 2 Happen) abgelegt und die 
Programmausführung geht bei 0200 weiter.
1
0200  doit:
2
0200    push  r17
3
0202    push  r16
4
0204    ldi   r18, 0x02
5
0206    push  r18
6
0208    pop   r16
7
020A    pop   r17
8
020C    pop   r18
9
020E    ret

die nächste Anweisung ist
1
  push r17

r17 hat den Inhalt 55 und der wird als nächstes auf den Stack gepusht.

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | AA   |  | 55    | |       |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                 -->|      |
                                                    +------+
1
  push r16

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | AA   |  | 55    | |       |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                    | AA   |
                                                    +------+
                                                 -->|      |
                                                    +------+
1
  ldi  r18, 0x02

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | AA   |  | 55    | | 02    |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                    | AA   |
                                                    +------+
                                                 -->|      |
                                                    +------+
1
  push r18

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | AA   |  | 55    | | 02    |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                    | AA   |
                                                    +------+
                                                    | 02   |
                                                    +------+
                                                 -->|      |
                                                    +------+
1
  pop  r16

pop ist die Umkehrung vom push. Das unterste (konzeptionell das zuletzt 
abgelegte) Byte wird in das angegebene Register geholt. Hier ist das 
jetzt 02, welches ins Register r16 geholt wird. Gleichzeitig wird dieses 
Element auch vom Stack insofern entfernt, als der Stackzeiger sich 
wieder um 1 Byte zurückbewegt. Dadurch werden zwar die 02 nicht aus dem 
Speicher gelöscht, aber konzeptionell ist es 'nicht mehr auf dem Stack 
vorhanden'.

Nach dem pop r16 sieht die Situation also so aus

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | 02   |  | 55    | | 02    |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                    | AA   |
                                                    +------+
                                                 -->|      |
                                                    +------+
1
  pop  r17

wieder: das letzte Byte am Stack wird nach r17 geholt

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | 02   |  | AA    | | 02    |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                    | 55   |
                                                    +------+
                                                 -->|      |
                                                    +------+

1
  pop r18

     r16       r17      r18                         +------+
   +------+  +-------+ +-------+                    | 06   |
   | 02   |  | AA    | | 55    |                    +------+
   +------+  +-------+ +-------+                    | 01   |
                                                    +------+
                                                 -->|      |
                                                    +------+

und damit ist dann im nächsten Befehl der ret erreicht
1
  ret

ret holt sich die letzten beiden Bytes vom Stack (wie auch immer die 
dorthin gelangt sind) und nimmt sie als Adresse an der die 
Programmausführung fortgesetzt werden soll. In diesem Beispiel sind die 
beiden letzten Bytes 01 und 06. Zusammengesetzt ergibt das die Adresse 
0106 und dort geht dann die Programmausführung nach dem ret weiter.

Und wenn du dir jetzt das ursprüngliche Programm ansiehst
1
0100    ldi   r16, 0xAA
2
0102    ldi   r17, 0x55
3
0104    rcall do_it
4
0106    ldi   r18, 0x00
5
6
...
7
8
0200  doit:
9
0200    push  r17
10
0202    push  r16
11
0204    ldi   r18, 0x02
12
0206    push  r18
13
0208    pop   r16
14
020A    pop   r17
15
020C    pop   r18
16
020E    ret

dann ist 0106 genau die Adresse an der die nächste Anweisung nach dem 
rcall steht, der den Unterprogrammaufruf (und damit das Ablegen von 0106 
auf dem Stack) ausgelöst hat.

Und ja. Wenn wir im Unterprogramm einen pop zuwenig gemacht hätten, dann 
hätte sich der ret ganz andere Bytes vom Stack geholt, weil dann 1 Byte 
am Stack übrig geblieben wäre und damit der ret sich etwas völlig 
Falsches vom Stack geholt hätte.

In Assembler gibt es hier keinerlei Überprüfung. Du pusht etwas auf den 
Stack, du popst etwas vom Stack. Was, wieviel, in welche Register: das 
ist dein Bier. Niemand redet dir da drein. Und schon gar nicht der 
Prozessor. Push, Pop, Rcall, Ret all diese Befehle interessiert nicht 
was vorher war. Ihr Verhalten ist genau festgelegt und dreht sich nur 
darum ob ein Byte auf den Stack kommt, ein Byte vom Stack genommen wird, 
bzw. ob bei einem Aufruf etwas auf den Sttack geschrieben wird oder beim 
Ret wieder vom Stack geholt wird. Das das alles zusammenstimmt ... darum 
musst du dich als Programmierer kümmern.

von Frank (Gast)


Lesenswert?

@Michael

Du kannst das doch alles mal im Simulator (AVR-Studio) nachvollziehen, 
dann hast Du es kappiert.
Mach doch mal ein push und pop im Einelschritt-Modus, schau Dir die 
Register und den Stack an etc. Und dann die Beispiele von K. H. 
Buchegger, spess53 und den anderen.

von Michael Dierken (Gast)


Lesenswert?

Erstmal an Karl Heinz Buchegger: Respekt ... wie lange hast daran 
geschrieben ^^.

Denke das ich es nun nach dem Beispiel von K. H. B. kurz Mod verstanden 
habe und werde es nochmal wie von Frank angesprochen nochmal im 
Einzelschritt anschauen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.