Forum: Mikrocontroller und Digitale Elektronik Zeitabschätzung


von Paul W. (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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 MaWin (Gast)


Lesenswert?

> 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.

von spess53 (Gast)


Lesenswert?

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

von Paul W. (Gast)


Lesenswert?

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 
:-).

von spess53 (Gast)


Lesenswert?

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

von Paul W. (Gast)


Lesenswert?

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.

von Paul W. (Gast)


Lesenswert?

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!

von H.Joachim S. (crazyhorse)


Lesenswert?

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 Rolf Magnus (Gast)


Lesenswert?

>> 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.

von MaWin (Gast)


Lesenswert?

> 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.

von MaWin (Gast)


Lesenswert?

> 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

von Peter D. (peda)


Lesenswert?

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

von Paul W. (Gast)


Lesenswert?

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

von Paul W. (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.