Forum: Mikrocontroller und Digitale Elektronik Erklärung für dieses Programmverhalten, AVR Assembler


von Daniel (Gast)


Lesenswert?

Hallo liebes Forum.

Ich habe mal eine Frage für ein Programmverhalten, wofür ich selbst 
keine Erklärung finde.
Ich war schon länger auf der Suche nach diesem Fehler, nun wurde ich im 
Simulator fündig.
Nun wäre es lieb von euch, mir dieses Verhalten zu erklären.
Hier mal ein Programmausschnitt, bzw. meine Timer1 Overflow ISR.
Es handelt sich um einen ATmega88A, getaktet auf 8MHz, internes Quarz 
(Teiler-Fuse gelöscht).
Timer1 Prescale ist bei 8.
1
Timer1_Overflow:
2
  in  S,SREG
3
  push  Temp0
4
  push  Temp1
5
  push  Temp2
6
  push  Temp3
7
  push  Temp4
8
  push  r3
9
  push  r4
10
  
11
  lds  Temp0,Overflow        
12
  inc  Temp0
13
  cpi  Temp0,30
14
  breq  PC+2
15
  rjmp  PC+7               <---- Dieser Sprung führt nach Sprung 1 
16
  cbr  Flags,(1<<engine_run)
17
  sbr  Flags,(1<<Displaytim)
18
  clr  Temp0
19
  sts  RPM,Temp0
20
  sts  RPM+1,Temp0
21
  sts  Overflow,Temp0     <---- Sprung 1    
22
  
23
  lds  Temp0,Displaytimer      
24
  inc  Temp0
25
  cpi  Temp0,5
26
  breq  PC+2
27
  rjmp  PC+3               <---- Dieser Sprung führt nach Sprung 2 
28
  sbr  Flags,(1<<Displaytim)
29
  clr  Temp0
30
  sts  Displaytimer,Temp0 <---- Sprung 2
31
  
32
EndISR:
33
  pop  r4
34
  pop  r3
35
  pop  Temp4
36
  pop  Temp3
37
  pop  Temp2
38
  pop  Temp1
39
  pop  Temp0
40
  out  SREG,S
41
  reti
Soo... Bei Sprung 2 ist alles klar. Es soll 3 Befehle weiter gesprungen 
werden.
Nur Sprung 1 ist mir nicht klar. Trotz PC+7, also 7 Befehle weiter 
springen, sind es reell nur 6. Drauf gekommen bin ich am Simulator, als 
er mir falsch gesprungen ist. So wie es oben ist, funktioniert alles.
Nur warum ist das so?

Evtl. hat einer eine Idee :-)

Gruß Daniel

: Bearbeitet durch User
von S. Landolt (Gast)


Lesenswert?

pc+7 springt nicht um 7 Befehle, sondern um 7 Worte; sts ist ein 
2-Wort-Befehl.

von c-hater (Gast)


Lesenswert?

Daniel schrieb:

Also, als erstes mal die Anmerkung: man kann mit Assembler natürlich 
auch beliebig ineffezient sein, aber normalerweise ist das Ziel des 
Einsatzes von Assembler aber eigentlich genau das Gegenteil...

Und wenn das dein Ziel war, hast du es grandios verfehlt. Diesen Code 
könnte aus einem C-Compiler stammen (der wäre dann aber wenigstens 
korrekt...).

> Nur Sprung 1 ist mir nicht klar. Trotz PC+7, also 7 Befehle weiter
> springen, sind es reell nur 6.

PC+7 heißt nicht "7 Befehle weiter", sondern 7 Befehlsworte weiter. Ein 
Befehl kann aber eine unterschiedliche Zahl von Worten umfassen. 
Mindestens eins, es können beim AVR aber auch zwei oder drei sein.

Genau deswegen sind Konstrukte mit PC+X als Sprungziel ziemlich übel. 
Ich als passionierter Asm-Programmierer verwende sie (wenn überhaupt) 
nur an Stellen, wo genau eine Instruktion mit einer Wortlänge von 1 
übersprungen wird und wo anhand des Algorithmus absehbar ist, dass sich 
daran auch niemals etwas ändern wird.

Mit leidgeborenen Erfahrung, das "niemals" niemals wirklich niemals ist 
also also: nie mehr.

von Daniel (Gast)


Lesenswert?

Bessten Dank für diese Hilfreiche Antwort. :-)
Habs an anderer Stelle im Programm nochmal probiert, nun ist es dort 
auch besser geworden :-)

Danke

von Daniel (Gast)


Lesenswert?

Ist das soo schlecht, mein Programm? :-( Hatte mich so gefreut das alles 
nun funtioniert, nun bekomm ich eine Übergebraten. Naja :-(
Wie wäre es denn besser, bzw. effizienter, außer das PC+/- zu streichen?

von Peter D. (peda)


Lesenswert?

Sprungweiten selber abzuzählen ist Pfui bäh.
Man benutzt Label und überläßt die Arbeit dem Assembler, der kann das 
nämlich besser. Auch wird dadurch das Programm deutlich lesbarer.
Und selbst im Macros kann ein ordentlicher Assembler lokale Labels 
verwenden.

von Daniel (Gast)


Lesenswert?

Peter D. schrieb:
> Sprungweiten selber abzuzählen ist Pfui bäh.
> Man benutzt Label und überläßt die Arbeit dem Assembler, der kann das
> nämlich besser. Auch wird dadurch das Programm deutlich lesbarer.
> Und selbst im Macros kann ein ordentlicher Assembler lokale Labels
> verwenden.

Hatte ich zu Anfang meiner Programmiererei auch gemacht, bis ich mal 
diese Methode rausgefunden habe. Ich finde es so aufgeräumter, da nicht 
ständig labels irgendwo stehen, wo ich immer keine Ahnung habe, wie ich 
die benenne.
Ich weiß das es egal ist, wie die heißen... bin beim Benennen aber 
einfallslos :-(

Hatte sonst immer "weiter", "weiter2", und andere stehen.
Also ist es so dann doch besser und ich sollte mir das wieder 
angewöhnen?

von Dieter F. (Gast)


Lesenswert?

Daniel schrieb:
> Hatte sonst immer "weiter", "weiter2", und andere stehen.
> Also ist es so dann doch besser und ich sollte mir das wieder
> angewöhnen?

Ja, überlasse das stumpfsinnige Zählen dem Rechenknecht.

von S. Landolt (Gast)


Lesenswert?

> Wie wäre es denn besser, bzw. effizienter ...?

Wenn das Gezeigte wirklich die ganze ISR ist, stellt sich die Frage, 
wozu die vielen push/pop dienen sollen, es reichte doch das Sichern von 
SREG sowie Temp0.
Statt
  breq  PC+2
  rjmp  PC+7
könnte man einfach brne label1 nehmen.
Statt inc mit anschließendem cpi bei Overflow und Displaytimer 
könnte man dec nehmen und direkt mit brne abfragen.

Was nun pc +/- n betrifft, so verlangt dies bei größeren n, sobald 
Programmänderungen oder -korrekturen nötig werden (und wann werden die 
nicht nötig), ein sehr hohes Maß an Disziplin - das bringen nicht viele 
auf. Die Kosten/Nutzen-Abwägung muss jeder für sich selbst machen.

von c-hater (Gast)


Lesenswert?

Daniel schrieb:

> Ist das soo schlecht, mein Programm?

Den größten Teil der Zeit beschäftigt sie sich deine ISR damit, Register 
zu retten und wiederherzustellen.

Ein (recht wirkungsvoller) Trick bei der Assemblerprogrammierung ist, 
genau dies eben nicht machen zu müssen. Das fällt relativ leicht, wenn 
man nicht an die Konventionen einer Runtime-Umgebung gebunden ist, also 
über die gesamte Laufzeit des Gesamtprogramms die volle Kontrolle über 
die Registernutzung hat.

Es heißt dann abwägen, was effizienter ist: ein paar Register zur 
exklusiven Nutzung in ISRs (allgemein) oder sogar nur für eine spezielle 
ISR freizustellen oder doch möglichst alle Register für den Rest des 
Codes verfügbar zum machen und zwar abhängig davon, wie häufig die 
jeweiligen ISRs durchlaufen werden.

Genau diese Abwägung ist, was ein Assemblerprogrammierer machen kann, 
ein C-Compiler aber nicht. Dafür reicht schlicht sein Input nicht. Armes 
Gesocks, erinnert mich immer wieder an den bemitleidenswerten No5 aus 
dem Film aus den 80ern...

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Im Datenblatt stehen die Längen der einzelnen Befehle.
Ein Blick in die .LST Datei dürfte hier auch Klarheit verschaffen.

MfG

von Daniel (Gast)


Lesenswert?

Ja, ist die ganze ISR. Die push und pops nutze ich in zwei anderen ISRs 
auch.
zu anfang push, am ende der verweis nach "EndISR". Sollte ich wohl auch 
ändern.
Stehe ja noch am Anfang meiner Progrmmierkarriere :-)
Vielen Dank für eure Anregungen. Werde das Programm nun überarbeiten :-)

@Patrick: habe eben auch bei ATMEL auf der Homepage nachgesehen. Jetzt 
weiß ich auch, warum dort 16Bit und 32Bit Opcode steht. Denke das ist 
dann eben dieses 1 Wort, 2 Wort länge.

von Dieter F. (Gast)


Lesenswert?

c-hater schrieb:
> also
> über die gesamte Laufzeit des Gesamtprogramms die volle Kontrolle über
> die Registernutzung hat.

Nix für angehende Alzheimer-Patienten. Für die ist C(++) oder Bascom 
oder ... besser geeignet :-)

c-hater schrieb:
> Genau diese Abwägung ist, was ein Assemblerprogrammierer machen kann,
> ein C-Compiler aber nicht. Dafür reicht schlicht sein Input nicht. Armes
> Gesocks, erinnert mich immer wieder an den bemitleidenswerten No5 aus
> dem Film aus den 80ern...

Tja, C(++)-Compiler etc. haben wirklich ein schweres Los. Die werden 
regelrecht gezwungen ALLE (verwendeten) Register vor einer ISR zu 
sichern -  ein Assembler-Programmierer darf hingegen das Eine oder 
Andere vergessen :-)

von S. Landolt (Gast)


Lesenswert?

> bei ATMEL auf der Homepage nachgesehen

Also ich benutze als Referenz "Atmel AVR 8-bit Instruction Set: 
Instruction Set Manual", und dort steht z.B. bei sts :
"Words: 2 (4 bytes)".

von c-hater (Gast)


Lesenswert?

Dieter F. schrieb:

> Tja, C(++)-Compiler etc. haben wirklich ein schweres Los. Die werden
> regelrecht gezwungen ALLE (verwendeten) Register vor einer ISR zu
> sichern -  ein Assembler-Programmierer darf hingegen das Eine oder
> Andere vergessen :-)

Nein, du begreifst den Trick einfach nicht.

Wenn ich z.B. über den gesamten Code einer Anwendung festlege, dass R15 
nicht zur Verfügung steht (und dementsprechend auch an keiner Stelle 
dieses Register benutzt wird), dann kann ich z.B. in JEDER ISR-Instanz 
R15 dazu benutzen, die Flags zu sichern. Das spart (mindestens) 4 Takte 
pro ISR-Instanz, nämlich den Push und den Pop für das Register, in das 
sonst dieses unverzichtbar wichtige Datum zu sichern wäre.

D.h.: Jede, aber auch wirklich absolut jede ISR im System könnte 
mindestens 4 Takte kürzer sein pro Aufruf.

Allein diese kleine Optimierung dürfte sich schon in praktisch JEDEM 
realexistierenden Programm für einen erheblichen Effizienzgewinn sorgen.

Und das ist nur ein Trick, den zumindest fortschrittliche C-Compiler 
immerhin auch schon beherrschen. Allerdings nur dann, wenn sie wirklich 
über den gesamten Code gebieten und nicht etwa externen Binär-Lib-Kram 
einbinden müssen...

von H.Joachim S. (crazyhorse)


Lesenswert?

Interessant für Machbarkeitsstudien oder fürs Ego (ich kanns besser als 
alle die bescheuerten Compiler zusammen, ich bin ein geiler Typ. Wenn es 
sonst schon keiner merkt - ich sehe es an meinen 4 gesparten Takten 
ISR). Merkst du eigentlich überhaupt noch in welcher Welt du dich da 
eingenistet hast? Es gibt sicher Extremfälle, wo man Takte zählen/sparen 
muss. >95% aller Anwendungen gehören schon mal von Hause aus nicht dazu, 
den restlichen 5% kann eigentlich immer was leistungsfähigeres angeboten 
werden, was auf deine läppischen Takte scheisst. Die versaubeutelst du 
mit Sicherheit an anderer Stelle oder wartest dann eben länger in der 
main. Von Fehlern bei Assemblerprogrammierung und gewaltig höheren 
Entwicklungszeiten abgesehen, von späteren Änderungen und Erweiterungen 
will ich hier mal gar nicht reden.
Eigentlich hast du ja Ahnung. Und im Gegenzug einen gewaltigen Schuss. 
Ein Psychologe könnte wahrscheinlich besser klären was los ist. Du 
kannst es ja gerne machen wie du willst, aber sich ständig blähend 
hinstellen ist schon ziemlich daneben.

von Carl D. (jcw2)


Lesenswert?

c-hater schrieb:
> Dieter F. schrieb:
>
>> Tja, C(++)-Compiler etc. haben wirklich ein schweres Los. Die werden
>> regelrecht gezwungen ALLE (verwendeten) Register vor einer ISR zu
>> sichern -  ein Assembler-Programmierer darf hingegen das Eine oder
>> Andere vergessen :-)
>
> Nein, du begreifst den Trick einfach nicht.
>
> Wenn ich z.B. über den gesamten Code einer Anwendung festlege, dass R15
> nicht zur Verfügung steht (und dementsprechend auch an keiner Stelle
> dieses Register benutzt wird), dann kann ich z.B. in JEDER ISR-Instanz
> R15 dazu benutzen, die Flags zu sichern. Das spart (mindestens) 4 Takte
> pro ISR-Instanz, nämlich den Push und den Pop für das Register, in das
> sonst dieses unverzichtbar wichtige Datum zu sichern wäre.
>
> D.h.: Jede, aber auch wirklich absolut jede ISR im System könnte
> mindestens 4 Takte kürzer sein pro Aufruf.
>
> Allein diese kleine Optimierung dürfte sich schon in praktisch *JEDEM*
> realexistierenden Programm für einen erheblichen Effizienzgewinn sorgen.
>
> Und das ist nur ein Trick, den zumindest fortschrittliche C-Compiler
> immerhin auch schon beherrschen. Allerdings nur dann, wenn sie wirklich
> über den gesamten Code gebieten und nicht etwa externen Binär-Lib-Kram
> einbinden müssen...

Cooler Trick!

> "z.B. in JEDER ISR-Instanz R15 dazu benutzen, die Flags zu sichern."

Solange man nicht mal auf die Idee kommt, doch irgendwo ein SEI zuviel 
einzustreuen. Aber wenn man die Gesamtheit des Codes in Text und 
Funktion im Kopf hat, dann kann es klappen. Andere wissen mit ihrem Hirn 
halt besseres anzufangen.

von Valentin S. (Gast)


Lesenswert?

Dieter F. schrieb:
> c-hater schrieb:
> also
> über die gesamte Laufzeit des Gesamtprogramms die volle Kontrolle über
> die Registernutzung hat.
>
> Nix für angehende Alzheimer-Patienten. Für die ist C(++) oder Bascom
> oder ... besser geeignet :-)

Ach was. Die paar Register hat man locker im Blick. Am besten mit einem 
immer wieder verwendeten Nutzungsschema. Da ist es viel 
herausfordernder, einen C-Compiler unter Kontrolle halten zu wollen. Mit 
Asm wird man (mit recht einfachem Inventar) immer flexibler sein.

H.Joachim S. schrieb:
> Es gibt sicher Extremfälle, wo man
> Takte zählen/sparen muss.

Nö. Das kommt gerade dann öfter vor wenn man  den Einsatzbereich seiner 
8Bitter gern auf Bereiche ausdehnen möchte die etwas mehr Performance 
fordern. Wo es sonst schon stärkerer MCs bräuchte kann man noch schön 
deterministisch seine benötigten Zeitabläufe zusammentakten.

von Thomas F. (igel)


Lesenswert?

Daniel schrieb:
> Ich weiß das es egal ist, wie die heißen... bin beim Benennen aber
> einfallslos :-(
>
> Hatte sonst immer "weiter", "weiter2", und andere stehen.

Sprungmarken markieren innerhalb einer Subroutine doch auch 
Programmabschnitte mit unterschiedlichen Aufgaben. Gutes Beispiel in 
deinem obigen Code: "EndISR:".
Jetzt weiß man: Ab hier ist alles berechnet und die Routine wird 
kontrolliert beendet.
Genauso benennt man Sprungmarken nach der Funktion eines 
Programmabschnittes. Für Schleifen benutze ich da z.B. "loop_" und die 
Schleifenaufgabe.

Daniel schrieb:
1
>   breq  PC+2
2
>   rjmp  PC+7               <---- Dieser Sprung führt nach Sprung 1
3
>   cbr  Flags,(1<<engine_run)
4
>   sbr  Flags,(1<<Displaytim)
5
>   clr  Temp0
6
>   sts  RPM,Temp0
7
>   sts  RPM+1,Temp0

PC+2 könnte hier z.B. auch "RPM_abnullen" heißen.
1
>   breq  PC+2
2
>   rjmp  PC+7

könnte man mit
1
>   brne  RPM_abnullen
noch optimieren. (Ok, wurde schon erwähnt)

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Wenn meine Routine 'Timer1_Overflow' heisst, benutze ich halt 
einfallslos Labels innerhalb dieser Routine, die 'TIM1_OV_A', 
'TIM1_OV_B' usw. heissen.
Das ist doch nicht schwer und man weiss bei Routinen, die länger als 
eine Seite auf dem Bildschirm sind, zu welchem Teil das Label gehört.

Abzählen haben wir ganz früher mal gemacht, als wir Programme per Bytes 
in den Speicher schrieben, aber heute doch nicht mehr.

von S. Landolt (Gast)


Lesenswert?

Ein Einwand sei gestattet: in Fällen wie den nachfolgenden ziehe ich die 
Schreibweise "pc +/- n" vor, ich finde es übersichtlicher bzw. lesbarer:
1
    sbic  EECR,EEPE
2
  rjmp    pc-1
3
.
4
.
5
  brtc    pc+3
6
    add   sumL,cntL
7
    adc   sumH,cntH
8
.
9
.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Daniel schrieb:
> Ist das soo schlecht, mein Programm? :-( Hatte mich so gefreut das alles
> nun funtioniert, nun bekomm ich eine Übergebraten. Naja :-(
> Wie wäre es denn besser, bzw. effizienter, außer das PC+/- zu streichen?
1
Timer1_Overflow:
2
  in  S,SREG
3
  push  Temp0
4
  
5
  lds   Temp0, Overflow        
6
  inc   Temp0
7
  cpi   Temp0, 30
8
  brlo  wrtOvFlw
9
10
  cbr   Flags, (1<<engine_run)
11
  sbr   Flags, (1<<Displaytim)
12
  clr   Temp0
13
  sts   RPM, Temp0
14
  sts   RPM+1, Temp0
15
wrtOvFlw:
16
  sts  Overflow, Temp0
17
  
18
  lds   Temp0, Displaytimer      
19
  inc   Temp0
20
  cpi   Temp0, 5
21
  brlo  wrtDspTim
22
23
  sbr   Flags, (1<<Displaytim)
24
  clr   Temp0
25
wrtDspTim:
26
  sts   Displaytimer, Temp0
27
28
EndISR:
29
  pop  Temp0
30
  out  SREG, S
31
  reti

 So ?

Daniel schrieb:
> diese Methode rausgefunden habe. Ich finde es so aufgeräumter, da nicht
> ständig labels irgendwo stehen, wo ich immer keine Ahnung habe, wie ich
> die benenne.

 Das stimmt schon mal nicht, man muss nicht immer sinnvolle Namen 
geben.

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.