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
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.
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?
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.
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?
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.
> 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.
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...
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.
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 :-)
> 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)".
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...
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.
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.
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.
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
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.
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
inS,SREG
3
pushTemp0
4
5
ldsTemp0,Overflow
6
incTemp0
7
cpiTemp0,30
8
brlowrtOvFlw
9
10
cbrFlags,(1<<engine_run)
11
sbrFlags,(1<<Displaytim)
12
clrTemp0
13
stsRPM,Temp0
14
stsRPM+1,Temp0
15
wrtOvFlw:
16
stsOverflow,Temp0
17
18
ldsTemp0,Displaytimer
19
incTemp0
20
cpiTemp0,5
21
brlowrtDspTim
22
23
sbrFlags,(1<<Displaytim)
24
clrTemp0
25
wrtDspTim:
26
stsDisplaytimer,Temp0
27
28
EndISR:
29
popTemp0
30
outSREG,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.