Hallo,
Ich hätte eine Frage zu Assembler. Und zwar habe ich eine zeitkritische
Interrupt-Routine in C geschrieben. Ich hab es jetzt so weit optimiert,
dass im Assemblercode knapp 100 'Befehle' stehen (hab mir die .lss-Datei
angeschaut).
Im AVR-Tutorial steht, dass sogut wie jeder Assembler-Befehl einen Takt
benötigt, also braucht der µC bei einem Takt von 20MHz dann knapp 5µs
(100 / 20000000) für die Abarbeitung dieser Routine. In der
Interrupt-Routine sind keine If-Verzeigungen, sie müsste also in
konstanter Zeit ausgeführt werden. Kann ich von dieser Rechnung
ausgehen?
Der µC macht eigentlich nichts anderes, als gerade diese
Interrupt-Routine abzuarbeiten (natürlich sobald sich was am Pin tut).
Von welcher Zeitverzögerung kann ich ausgehen, bis der µC wirklich die
Interrupt-Routine abarbeitet (passiert vermutlich nicht augenblicklich
oder?)?
Vermutlich wäre der Einsatz eines FPGAs oder ähnliches (damit kenn ich
mich nämlich gar nicht aus) logischer als der Einsatz eines
Microkontrollers in diesem Fall, aber die meisten FPGAs haben definitiv
zu viele Pins und sind ein wenig überdimensioniert. Der Microkontroller
wandelt nämlich nur Signale um...
Ich würde ungern eine Simulation durchführen, da ich nicht die Software
hier habe... mir wäre eine möglichst exakte Aussage lieber, sofern das
überhaupt möglich ist?!
Ich kenne mich in Assembler außerdem nur in soweit aus, dass ich den
Code lesen kann, aber ihn auch nur verstehe, weil ich weiß was da
gemacht werden soll. Ist die lss-Datei denn überhaupt das fertige
Programm in Maschinensprache (ist ja dokumentiert, aber ohne Kommentare
ist es doch das richtige Programm?)?
Danke für eure Zeit ;-)
Ich habe zwar eine Seite über die Ausführungszeit gefunden, weiß aber
nicht wie aktuell und 'seriös' die Seite ist, also habe ich hier
nachgefragt ;-).
Paul
Paul W. schrieb:
> Ich habe zwar eine Seite über die Ausführungszeit gefunden, weiß aber> nicht wie aktuell und 'seriös' die Seite ist, also habe ich hier> nachgefragt ;-).
Im Zweifelsfall gehen wir zum Schmid und nicht zum Schmidl.
Wenn auf diesem Planeten wohl jemand weiß, wieviele Zyklen jeder
einzelne Befehl tatsächlich dauert, dann wird das wohl Atmel sein. Und
tatsächlich gibt es bei Atmel das "Instruction Set Summary", in dem
unter anderem auch die Taktzyklen jedes Befehls aufgeführt ist.
PS: Auch AVR-Studio kennt diese Anzahl.
Einfach mal den Cursor im Textfenster auf einen Befehl stellen und F1
drücken :-)
Du kannst jetzt natürlich hergehen und händisch alle Befehle addieren.
Du kannst aber auch im Simulator einen Breakpoint auf den Anfang der ISR
setzen, einen auf das Ende, die ISR durchlaufen lassen und im AVR-Studio
die Anzahl der vergangenen Takte ablesen.
> Von welcher Zeitverzögerung kann ich ausgehen, bis der µC wirklich> die Interrupt-Routine abarbeitet (passiert vermutlich nicht> augenblicklich oder?)?
Das kann man so pauschal nicht sagen. Das hängt auch vom Befehl ab, der
gerade unterbrochen wird. Dieser Befehl wird natürlich noch fertig
abgearbeitet. Details dazu finden sich im Datenblatt. Das wiederrum gibt
es, wie könnte es anders sein, bei Atmel.
> Von welcher Zeitverzögerung kann ich ausgehen, bis der µC wirklich die> Interrupt-Routine abarbeitet (passiert vermutlich nicht augenblicklich> oder?)?
Ziemlich augenblicklich, der laufende Befehl wird noch zu Ende
abgearbeitet, der Interrupt-Call ausgelöst, und los geht's,
wenn, ja wenn er nicht die Interrupts gesperrt hat weil er noch
in der Bearbeitung des vorherigen Pinwechsels steckte, also
wenn er immer noch in der vorher aufgerufenen Interrupt-Routine
steckt.
Wenn die Interrupt-Routine zu langsam sein sollte:
Neu schreiben in Assembler wird die Laufzeit auf 25% kürzen,
vor allem weil du sagst, daß der uC ansonsten nichts zu tun
hat, also auch keine Register gesetzt, geändert etc. werden
müssen.
Noch ein bischen schneller geht es, wenn du aktiv auf einen
Flanenwechsel wartest, also gar keine Interrupts, sondern
das Hauptprogramm einfach so schreiben, dass es in einer
kleinen engen Schleife wartet, bis sich am Eingang was tut.
Hi
>Von welcher Zeitverzögerung kann ich ausgehen, bis der µC wirklich die>Interrupt-Routine abarbeitet (passiert vermutlich nicht augenblicklich>oder?)?
Ist im Datenblatt unter 'Interrupt response time' zu finden.
MfG Spess
Danke für eure Antworten.
Wenn man nicht weiß, wie das heißt, dann kann man auch nicht danach
suchen :-/. Aber jetzt weiß ich ja wie es heißt: "Instruction Set
Summary". Ich habe bei der Rechnung aber einen Fehler gemacht: Um zu
bestimmen, wie viele Assembler-Befehle es sind habe ich einfach die
Anfangsadresse der Interruptroutine von der Endadresse abgezogen. Was
ich nicht bedacht habe ist, dass ein Befehl ja aus mehreren Bytes
besteht. Ich habe jetzt händisch nochmal die Ausführungszeit addiert und
komme auf 65 Zyklen. Das ist doch recht ordentlich und reicht für meine
Anwendung vollkommen.
Ich habe es per Hand ausgerechnet, weil ich mit diesem AVR-Studio echt
meine Probleme habe... es will nie so wie ich will (ich behaupte mal,
ich kenne mich schon ein wenig mit Compilern und Debuggern aus, aber das
AVR-Studio ist echt gewöhnungsbedürftig, oder ich checks einfach nicht,
also benutz ich zum simulieren VMLab, weil das auch noch einige externe
Beschaltungen ermöglicht. Habe ich bisher bei AVR-Studio nicht
gesehen?).
Das mit dem aktiven Warten auf den Flankenwechsel: Ich glaube das macht
es nur schlimmer... meine main:
while(1);
;-)... die Interrupt-Routine macht es meiner Meinung nach wesentlich
übersichtlicher.
Außerdem hatte ich mal eine ähnliche Anwendung und die Interrupt-Methode
hat eine höhere Frequenzen mitgemacht als die andere Methode (ständiges
Abfragen in der main).
Und wieder einmal merke ich, dass echt jedes kleinste Details im
Datenblatt steht... wenn man es denn auch mal zur Hand nimmt... was ich
ihn diesem Fall dummerweise nicht getan habe...
Nächstes mal geb ich mir mal mehr Mühe mit der Suche...
Danke nochmal für eure Antworten, meine Fragen sind alle beantwortet
:-).
Hi
>Ich habe es per Hand ausgerechnet, weil ich mit diesem AVR-Studio echt>meine Probleme habe...
Aber im Simulator vom AVR_Studio hättest du das einfach ablesen können.
MfG Spess
Wie gesagt: Ich stehe in Konflikt mit dem AVR-Studio... wenn du mir
sagst wo, dann schau ich nochmal nach :-)... ich weiß es echt nicht, F1
bewirkt bei mir gar nichts.
Zitat von MaWin:
"Wenn die Interrupt-Routine zu langsam sein sollte:
Neu schreiben in Assembler wird die Laufzeit auf 25% kürzen,
vor allem weil du sagst, daß der uC ansonsten nichts zu tun
hat, also auch keine Register gesetzt, geändert etc. werden
müssen."
Du bringst mich auf eine Idee...
Wenn ich mir den Assemblercode anschaue, fällt mir auf, dass beim Aufruf
der Interrupt-Routine erstmal 10 oder mehr Register auf den Stack gelegt
werden (weil sie in der Interrupt-Routine benutzt werden) und beim
beenden logischerweise wieder vom Stack geholt werden (was letztendlich
wahrscheinlich mehr Zyklen in Anspruch nimmt als die Routine selber!).
Aber eigentlich ist das total überflüssig, weil die Register nirgendwo
sonst benutzt werden. Dann mach ich mal an Assembler ran...
Danke für den Tipp!
dafür gibts Compileranweisungen, dass bitte zu unterlassen (allerdings
sollte man dann schon wissen, was man tut)
#pragma savereg-
.
.
.
#pragma savereg+
oder so ähnlich :-)
Heutzutage sollte man aber von einem Compiler erwarten, dass er nur das
Nötige sichert. Wenn er nicht komplett stur ist, hängt das natürlich von
den eingestellten Optimierungen ab. Optimieren auf Codesize bedeutet
dann i.a. auch, dass für alle ISR die gleiche Sicherungs- und
Wiederherstellungsroutine benutzt wird.
>> Von welcher Zeitverzögerung kann ich ausgehen, bis der µC wirklich die>> Interrupt-Routine abarbeitet (passiert vermutlich nicht augenblicklich>> oder?)?>>Ziemlich augenblicklich, der laufende Befehl wird noch zu Ende> abgearbeitet,
Der bis zu 5 Taktzyklen brauchen kann.
> der Interrupt-Call ausgelöst, und los geht's,
Naja, nicht ganz. Zuerst wird mal der Program-Counter auf den Stack
gelegt und die Interrupts deaktiviert. Dann wird in die
interrupt-Vektor-Tabelle gesprungen. Kostet zusammen 4 Taktzyklen. Dort
steht dann je nach AVR-Modell ein rjmp oder ein jmp, der dann nochmal 2
bzw. 3 Taktzyklen braucht. Dann sind wir am Anfang der ISR. Da muß dann
erstmal das SREG gesichert werden, wozu es aber davor noch ein Register
braucht, das auch gesichert werden muß. Sowas wie:
1
push r16
2
in r16, SREG
3
push r16
Das bringt nochmal 5 Taktzyklen dazu. Also sind hier schon bis zu 16
Taktzyklen vergangen. Erst dann kann man sich mal damit beschäftigen,
ggf. noch weitere Register zu sichern und dann den eigentlichen Code der
ISR auszuführen.
> Heutzutage sollte man aber von einem Compiler erwarten, dass> er nur das Nötige sichert.
Prust.
> Zuerst wird mal der Program-Counter auf den Stack gelegt und> die Interrupts deaktiviert. Dann wird in die interrupt-Vektor-> Tabelle gesprungen. Kostet zusammen 4 Taktzyklen. Dort steht> dann je nach AVR-Modell ein rjmp oder ein jmp, der dann nochmal> 2 bzw. 3 Taktzyklen braucht.
Das ist der Interrupt-Call von dem ich sprach.
Das Sichern der Register wurde danach noch mal extra erwähnt.
> Das mit dem aktiven Warten auf den Flankenwechsel:> Ich glaube das macht es nur schlimmer... meine main:> while(1);
Wie gesagt, es spart noch ein paar Taktzyklen ein.
loop:
SBIC port,bit
RJMP -2
:
deine Routine bei fallender Flanke
:
SBIS port,bit
RJMP -2
:
deine Routine bei steigende Flanke
:
RJMP loop
Paul W. schrieb:
> Das mit dem aktiven Warten auf den Flankenwechsel: Ich glaube das macht> es nur schlimmer... meine main:> while(1);
Wenn Du die Ausführungsebene Main nicht nutzt, verschenkst Du CPU-Zeit.
Wenn es nur ein Interrupt ist, dann poll einfach und schon entfällt das
ganze CALL, PUSH, POP, RETI-Geraffel.
Pseudocode:
So, da bin ich wieder...
Nachdem meine ersten Schritte in Assembler eher eine Qual waren, habe
ich es dann doch noch geschafft den C-Code nach Assembler zu portieren
und komme auf unschlagbare Größen (also schom im positiven Sinn :-) ).
Natürlich nur mit Einschränkungen:
- Die verwendeten Register brauchen nicht auf den Stack gelegt werden
und anschließend wieder von ihm runter
- Durch einen anderen Anschluss der Hardware fallen einige
bit-Operationen weg
Ich habe im Assembler-Code vom C-Compiler außerdem einigen Code
entdeckt, den ich vom Sinn her nicht verstehe:
1
ldi r30, 0
2
... ;(nix mit Z und auch nix mit r30)
3
andi r30, 0
Mit dem Optimierungsgrad 3 kommt schon wesentlich weniger Code raus,
aber den sollte man ja nicht immer benutzen (kenne mich da zu wenig
aus)?! Habe mich damit auch nicht weiter beschäftigt, tat mir mal ganz
gut mich ein wenig mit Assembler auseinander zu setzen.
Ich habe das ganze in AVR-Studio getestet (weil VMLab irgendwie einen
Bug hat, aber wird ja leider nicht mehr weiter entwickelt)... hab mich
da auch erstmal mit rumgeschlagen, bis ich das ganze verstanden habe.
Aber es funktioniert :D!
Falls der Autor des ASM-Tutorials das hier liest: Ein wenig mehr mit dem
Z-Pointer und Arrays im Tutorial wäre toll. Wenn man z.B. nicht auf ein
konstantes Array-Element zugreifen will, sondern auf ein variables. Da
das ja nicht im Tutorial steht, und es mich interessiert, frage ich
hier, ohne ein neues Topic alleine dafür zu erstellen: Angenommen die
Nummber des Elements steht schon im Register r16. Wäre das hier richtig?
1
ldi r31, HIGH(Array)
2
ldi r30, LOW(Array)
3
add r30, r16
4
adc r31, 0 ;Im Fall, dass ein Stapelüberlauf auftritt
Ich entschuldige mich schonmal bei euch dafür, dass ich hier schon
wieder Fragen stelle, die eigentlich in ein neues Topic gehören.
>>Naja, nicht ganz. Zuerst wird mal der Program-Counter auf den Stack>>gelegt und die Interrupts deaktiviert. Dann wird in die>>interrupt-Vektor-Tabelle gesprungen. Kostet zusammen 4 Taktzyklen. Dort>>steht dann je nach AVR-Modell ein rjmp oder ein jmp, der dann nochmal 2>>bzw. 3 Taktzyklen braucht. Dann sind wir am Anfang der ISR. Da muß dann>>erstmal das SREG gesichert werden, wozu es aber davor noch ein Register>>braucht, das auch gesichert werden muß.
Das SREG speichere ich nicht, muss man das? Habe ich im Tutorial was
überlesen? Die restliche Sicherung der verwendeten Register findet jetzt
auch nicht mehr statt.
So, nachdem ich das fürs erste geschafft habe, wage ich mich (da hier ja
doch viele dazu raten) an die ständige Abfrage der Pins ran...
Danke für eure Antworten, ist echt ein tolles Forum (ja, ich sollte mich
mal anmelden ^^)
Paul
Ja, gleich melde ich mich mal wirklich an... kann nämlich meinen Beitrag
nicht editieren.
1
SBIC port,bit
2
RJMP -2
3
:
4
deine Routine bei fallender Flanke
5
:
6
SBIS port,bit
7
RJMP -2
8
:
9
deine Routine bei steigende Flanke
10
:
11
RJMP loop
Routine bei fallender oder steigender Flanke wäre doch nicht ganz
richtig. Routine bei LOW- oder HIGH-Pegel wäre das doch? Das will ich ja
nicht ^^...
Da kommt dann noch ein xor hinzu + Speicherung eines zusätzlichen
Registers und dann hab ich doch auch fast wieder nix gewonnen?
Egal, ich setz mich mal morgen an die hier vorgeschlagenen Möglichkeiten
ran und sehe es dann vermutlich selber.