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:
1 | main() |
2 | {
|
3 | init(); |
4 | for(;;){ |
5 | if( Int_Flag == geloescht ) |
6 | continue; |
7 | loesche_int_flag(); |
8 | do_int_stuff(); |
9 | }
|
10 | }
|
Peter
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.