Hallo,
ich steuere mit einem Mega48 einen DCDC-Wandler an. Hierbei möchte ich
mittels Analog Comparator Interrupt eine negative Flanke erkennen. Dies
funktioniert auch sehr gut. Ich schalte am Anfang der Interruptroutine
einen Pin auf High, um am Oszilloskop zu messen, wie lange dies dauert,
wann der Interrupt kommt. Hierbei musste ich allerdings feststellen,
dass der Mega48@15MHz ganze 2us benötigt, bis der Pin eingeschaltet
wird. Dies sollte aber bei ca. 300ns liegen. In der Interruptroutine
wird dann der Analog Comparator ausgeschaltet und ein Flag gesetzt. Im
Hauptprogramm wird dann bei vorhandenem Flag Timer0 gestoppt,
zurückgesetzt und neu gestartet und dann der Pin auf 0 gesetzt. Dies
dauert im Schnitt 3us bis der Pin wieder auf 0 ist.
Ich programmiere in C mit AVR-Studio und dem avr-gcc.
Hat jemand eine Idee, warum der Einsprung in die Interruptroutine so
lange dauert?
Über Antworten wäre ich sehr dankbar.
Karsten
Schuld daran ist c, es erledigt dir vielzzviel für deinen Anwendungsfall
Unnötiges, wie das Sichern und Widerholen des Statusregisters und
anderer verwendeter Register.
Danke schonmal für deine Antwort!
Heißt das, ich müsste das alles in Assembler schreiben? Oder kann man
die Sicherung der Register unterdrücken, wie bei BASCOM mit dem
nosave-statement?
Karsten
Schau in den Maschinencode! Das ist die .LSS-Datei. Dort wirst Du
finden, daß am Anfang der ISR diverse Register gerettet werden.
Im übrigen entsprechen 300 ns ganzen 5 Clockzyklen bei 15 MHz. Das
reicht mit dem unvermeidbaren ISR-Overhead (PC auf Stack sichern ...) in
keiner Weise für weitere Aktionen.
Es wird jetzt also dringend Zeit, erst einmal ein Design zu erstellen.
Bernhard
Hast Du dir mal den Assembler angeschaut und die Zyklen gezählt die vom
ISR Vektor bis zur eigentlichen ISR benötigt werden?
-> Interrupt Latenzzeit + ISR Sprung + Register retten + Code Ausführung
Gruss
Bernd
hallo karsten,
kannst du in C keinen Inline assembler einbauen wie in bascom ?
wenn doch, kannst du natürlich in der art wie "Nosave" operieren.
allerdings wird es nicht gänzlich ohne das sichern von registern
funktionieren. du kannst den overhead lediglich deutlich reduzieren.
auf jeden fall musst du SREG mit push/pop behandeln.
ggf. noch das eine oder andrere register welches du in der int routine
verwenden willst.
gruss klaus
Als allererstes muss man sich die erzeugte ISR einmal im Assemblercode
ansehen. So dämlich ist der Compiler dann auch wieder nicht, dass er
massig unnützes Zeug beim Einsprung einfügt. D.h. ich denke nicht, dass
du da mit einer ISR in Assembler großartig weiterkommst. Denn um das
Sichern einiger Register wirst auch du nicht umhinkommen.
Ehe da jetzt spekuliert wird, muss man die Fakten kennen. Also: Was
treibt der Compiler beim Einsprung in die ISR alles und was davon ist
unnötig bzw. kann mit einer anderen Registerbelegung in der ISR
eingespart werden.
Erst dann kann man eine Aussage treffen ob Assembler das Problem lösen
kann.
300ns sind natürlich auch verdammt wenig Zeit. Eventuell könnte man mit
einer geschachtelten Vorgehensweise etwas reißen. Beim Eintritt in die
ISR nur die absolut wichtigsten Register sichern, dann die tatsächlich
zeitkritischen Dinge erledigen und erst dann die weiteren Register für
den weiteren Ablauf der ISR auf den Stack packen.
Ich habe gelernt, dass man die Interrupts ausschalten muss, während man
in einer ISR ist. Aber eigentlich kann kein weiterer Interrupt
auftreten. Die 300ns sollen jetzt nicht die Zeit sein, die der uC am
Ende benötigt, ich hatte nur erwartet, dass ein ATMega 4 Zyklen benötigt
um in die ISR zu springen. Von daher dachte ich natürlich, dass der dann
auch direkt mit der ISR anfängt. Also nach spätestens 500ns. mir würde
es schon reichen, wenn die Zeit auf 1us reduziert werden kann.
ich werde aber mal das cli() weglassen und schauen, obs noch
funktioniert.
macht der avr von alleine... daher unnötig..
Du kannst auch:
Hast du die Chance vorweg ein anderes Ereignis zu bekommen? wenn ja
triggerst du auf das Ereignis, wartest dann in der ISR per Polling auf
dein jetziges Ereignis und ACTION...das geht wesentlich schneller...
> und die ISR für den AC Interrupt an erste Stelle gesetzt, jetzt ist> die Sprungzeit etwas kürzer.
An erste Stelle ? Wie willst Du dem µC sagen, welche Priorität er haben
soll ?
In diesem Fall wärst Du tatsächlich mit Bascom schneller :D
Was auffält: R0/R1 werden nicht verwendet, aber gesichert, das ist
Zeitverschwendung. Cli stammt aus dem C Code, ist aber unnötig, macht
der µC von selbst.
PORTB |= (1 << PB0);
ACSR &= ~(1 << ACIE);
ACSR |= (1 << ACD);
Jeder dieser Befehle wäre mit SBI/CBI zu ersetzen.
Interessanterweise wird PORTB |= (1 << PB0) mit sbi 0x05, 0 umgesetzt,
die restlichen Befehle dagegen per in/andi/ori/out, und brauchen dafür
jeweils 1 Zyklus länger. Wenn's schnell gehen soll, ist aber auch ein
Zyklus nicht zu verachten.
Karsten schrieb:
> dass der Mega48@15MHz ganze 2us benötigt, bis der Pin eingeschaltet> wird. Dies sollte aber bei ca. 300ns liegen.
D.h. 4 Zyklen, guter Witz, da dürfen wir erstmal ganz laut lachen.
Also ein Interrupt muß erstmal das Befehlsende abwarten, führt das Main
gerade einen 4-Zyklus Befehl aus: 4 Zyklen
Dann kommt der Sprung zur Vektortabelle: 4 Zyklen
Dann der Sprung zum Handler: 2 Zyklen
Sind also schonmal 10 Zyklen, wo noch garnichts gemacht wurde.
Ein nackter Interrupt kostet 10 Zyklen, ein leerer Interrupt unter GCC
kostet 30 Zyklen.
2µs = 30 Zyklen ist also schon ein guter Wert, da kann man kaum noch
dran drehen.
Der Wert gilt aber auch nur ausschließlich dann, wenn es keinerlei
andere Interrupts gibt und auch keine atomic Sequenzen im Main.
Peter
Aha, das war ja schonmal sehr aufschlussreich. Eure Antworten sind
bisher echt spitze! Muss an dieser Stelle auch mal gesagt werden!
Also der Ablauf soll sein:
Freigabesignal von außen -> Timer0 starten und Ausgang einschalten
(MOSFET lässt Strom fließen) -> Timer0 Compare Match -> 2us Wartezeit
einstellen, dann erst AC aktivieren, wegen Schwingungen -> Timer0 läuft
weiter für Maximaldauer bis wieder eingeschaltet wird -> im AC Interrupt
soll dann wieder eingeschaltet werden, Timer0 zurückgesetzt und neu
gestartet werden, damit der Vorgang von vorne weiter läuft, bis das
Freigabesignal von außen weg ist.
Das bedeutet also, wenn ich die 2us im Timer0 Compare Match gewartet
habe, sollte ich einfach ein Flag setzen und in der Main den Analog
Comparator pollen, bis der gesetzt ist und dann direkt loslegen, damit
würde die ISR wegfallen. Gute Idee, ich werde sie mal ausprobieren!
Nachtrag:
Wenn der Compiler die eigentlich unnötige andi/ori Geschichte wegließe,
und unter Verwendung eines der GPIO Register des ATM48 müssten weder
Register noch SReg gesichert werde, da diese nicht verändert würden.
Also gäb's keine Push/Pop's, die Zeit brauchen, das ginge also noch
wesentlich flotter:
Gerade abgearbeiteten Befehl beenden 1-4 Takte + ISR Aufruf 4 Takte +
ISR anspringen 3 Takte + ACSR setzen CBI/SBI 4 Takte + = 12-15 Takte
+ 2 Takte für GPIOX als Ersatz für flags.flanke = 1 + 4 Takte für RETI
= 18-21 Takte für die gesamte ISR, wenn der Code optimal wäre.
Nur, wie Du das C beibringst, kann ich Dir leider nicht sagen, könnte
sein, daß sich C hier stur stellt :D
Hallo,
hab's jetzt mit dem Polling des ACI-Flags gelöst. Klappt jetzt alles
wesentlich schneller. Das schnellste sind 980ns und das langsamste
1,2us. Jetzt werde ich nur noch die Registerverarbeitung mit Assembler
optimieren und dann habe ich gute Zeiten erreicht.
Euch allen Vielen Dank für die super schnelle und vor allem konstruktive
Hilfe! So sollte es immer hier im Forum laufen!
Beste Grüße
Karsten