Verständnisproblem Assemblercode

Beschäftige mich gerade mit Assemblerprogrammierung.
Dieser Code ist für mich nicht so verständlich.

r17 wird mit 0 geladen, soweit sogut, später wird wird zur ISR gesprungen und da wird´s knifflig.
SREG wird gesichert (weiß nicht warum) und r17 um 1 erhöht. Dann wird r17 mit 1 verglichen und nach PulsOn gesprungen und nachher zu return und die ISR beendet.
Das Programm läuft dann wieder bis main und wartet auf den Interrupt. Dabei wird immer r17 auf 0 gesetzt und kann nie mehr als 1 erreichen und somit kann PulsOff nicht aufgerufen werden.
Hier ist ein Puls von ca. 1,92 ms eingestellt (OCR2-120). Was wird hier für eine Ansteuerfrequenz erreicht?

Der Code ist von hier genommen.
Der Code von Seitenanfang werkelt bereits auf meinem µC.

Danke schonmal für eure Hilfe!
lg, Daniel






.include "m16def.inc"

.equ XTAL = 4000000

rjmp init

.org OC2addr
rjmp Compare_vect

init:
ldi r16, HIGH(RAMEND) ; Stackpointer initialisieren
out SPH, r16
ldi r16, LOW(RAMEND)
out SPL, r16

ldi r16, 0x80
out DDRB, r16 ; Servo Ausgangspin -> Output

ldi r17, 0 ; Software-Zähler

ldi r16, 120
out OCR2, r16 ; OCR2 ist der Servowert

ldi r16, 1<<OCIE2
out TIMSK, r16

ldi r16, (1<<WGM21) | (1<<CS22) ; CTC, Prescaler: 64
out TCCR2, r16

sei

main:
rjmp main

Compare_vect:
in r18, SREG
inc r17
cpi r17, 1
breq PulsOn
cpi r17, 2
breq PulsOff
cpi r17, 10
brne return
ldi r17, 0
return: out SREG, r18
reti

PulsOn: sbi PORTB, 0
rjmp return

PulsOff: cbi PORTB, 0
rjmp return
 
Die ganze Geschichte ist NUR Interrupt gesteuert.
Main ist eine Dauerschleife, da wird nichts gemacht.
main:
rjmp main

Im ISR wird R17 auf 1 verglichen, wenn JA > Port On
Im ISR wird R17 auf 2 verglichen, wenn JA > Port Off
Ein/Aus Dauer ist somit identisch, danach erfolgt eine Pause von 8x Dauer
Im ISR wird R17 auf 10 verglichen, wenn NEIN > warten, das ist das Delay zwischen den PWM Pulsen
Wenn R17 10 > neuer Durchlauf

Das SREG wird übrigens bei ISR Eintritt gesichert und bei jedem Verlassen der Routine wieder rerstauriert
return: out SREG, r18
reti

Compare_vect:
in r18, SREG
inc r17
cpi r17, 1
breq PulsOn
cpi r17, 2
breq PulsOff
cpi r17, 10
brne return
ldi r17, 0
return: out SREG, r18
reti

PulsOn: sbi PORTB, 0
rjmp return

PulsOff: cbi PORTB, 0
rjmp return
 
Naja Knifflig würd ich jetzt nicht sagen.

Sehr viele Operationen verändern das SREG = Statusregister.
Ein Interrupt unterbricht aber das Hauptprogramm an einer "unvorhersehbaren" Stelle.
Das bedeutet das nach Beendigung des Interrupts das SREG wieder genau in dem gleichen Zustand sein muß wie vor der Unterbrechung.

Üblicherweise macht man das über den Stack.
push r18
in r18,sreg
push r18

... Dein Programm

pop r18
out sreg,r18
pop r18
reti

denn dann bleiben auch die Registerinhalte erhalten.
Man kann aber natürlich ein Register auch nur exklusiv für die Interruptroutine verwenden, wie in deinem Fall r17 und r18.

Zum Programm:
Gehen wir mal davon aus, das r17 den Wert 0 hat.
r17 wird mit inc r17 um eins erhöht und hat somit den Wert 1.
Der Befehl
breq PulsOn
lässt den Controller zur subroutine PulsOn springen.
In PulsOn wird der Portb.0 auf 1 gesetzt und dann direkt auf return gesprungen.
return schreibt das sreg zurück und die Routine wird mit reti verlassen.

Beim nächsten Interrupt hat r17 bereits den Wert 1 und wird mit inc r17 auf 2 erhöht.
Dadurch läuft der Controller über PulsOn drüber, aber mit
cpi r17, 2
breq PulsOff
wird die PulsOff Routine angesprungen.
Die setzt den Portb.0 wieder auf 0 und beendet damit den Servopuls.

Danach wird der Interrupt wieder wie vorher verlassen.

Der Interrupt wird dann immer wieder aufgerufen, ohne das Portb.0 wieder angetastet wird.
Bis r17 den Wert 10 erreicht.
Hat r17 den Wert 10, bewirkt der der Teil brne return kein Überspringen des ldi r17,0 mehr und das register r17 wird auf 0 gesetzt.
In der Zwischenzeit wurde die Interruptroutine allerdings 8 mal aufgerufen.
Das entspricht nun die Pause zwischen 2 Impulsen.
Da r17 nun wieder den Wert 0 hat beginnt das Spiel von vorne.

So was kann man sich aber auch wunderbar mit dem Simulator des AVR Studio ansehen.

Ich bin von der Methode nicht besonders Überzeugt, da die Pausenlänge von der Impulslänge abhängig ist.
Ich initialisiere lieber einen 16Bit Timer und arbeite dort mit Comparematch Interrupts.
Zum aktuellen Wert des Comparematch Registers wird die gewünschte Impuls bzw. Pausenlänge dazugezählt und wieder ins Comparematch Register zurück geschrieben.
Diese Methode lässt sich dann auch sehr einfach auf 8 bis 10 Ausgangskanäle erweitern.
 
Ich hatte da einen Denkfehler. Mit reti wird ja die ISR beendet, und ich dachte er geht zurück nach rjmp Compare_vect. Mit ret statt reti würde der Code wohl nicht gehen.
Ich bin von der Methode nicht besonders Überzeugt, da die Pausenlänge von der Impulslänge abhängig ist.

Hab ich mir auch gedacht...

Wie kann man das simulieren? Ist aber nicht die IO View?

Ja, schwierig sind die Anfänge, aber macht irgendwie Spaß :)
 
Wie kann man das simulieren? Ist aber nicht die IO View?

Ja, schwierig sind die Anfänge, aber macht irgendwie Spaß
Den Code Assemblieren und den Simulator starten.
Den richtigen Controller und Taktfrequenz im Simulator einstellen und Du kannst den Code step by step, automatisch oder mit breakpoints ablaufen lassen.
Wie man den Simulator aufruft ist bei AVR Studio 4.x 6.x und 7.x verschieden.
Drin ist aber überall einer.
Damit lassen sich auch Zeitanläufe sehr genau erfassen.
Per JTAG kannst Du sogar den Code im Controller laufen lassen.
Das geht aber nur bei den größeren Controllern ab ATMEGA 16, nur die haben JTAG.
JTAG benötigt auch exklusiv ein paar Pins, meistens auf Port C
 
Ansicht hell / dunkel umschalten
Oben Unten