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
Bernhard und Bernd, danke für eure Antworten. Habe mir die .lss Datei angeschaut und die ISR für den AC Interrupt an erste Stelle gesetzt, jetzt ist die Sprungzeit etwas kürzer. Es werden insgesamt 7 Zyklen für Registersicherungen benötigt. Und ca. 6 für den Sprung von dem Interruptvektor zur ISR. Plus 4 Zyklen standardmäßige Reaktion auf einen Interrupt vom uC sind insgesamt 17 Zyklen plus einen für das Setzen des Ausgangs. Sind also 1,2us@15MHz. Habe mal den Assembler-Inhalt angefügt. Hoffe ich habe mich nicht verzählt. Danke nochmal für eure klasse Antworten. 00000000 <__vectors>: 0: 19 c0 rjmp .+50 ; 0x34 <__ctors_end> 2: 42 c0 rjmp .+132 ; 0x88 <__vector_1> 4: 27 c0 rjmp .+78 ; 0x54 <__bad_interrupt> 6: 26 c0 rjmp .+76 ; 0x54 <__bad_interrupt> 8: 25 c0 rjmp .+74 ; 0x54 <__bad_interrupt> a: 24 c0 rjmp .+72 ; 0x54 <__bad_interrupt> c: 23 c0 rjmp .+70 ; 0x54 <__bad_interrupt> e: 22 c0 rjmp .+68 ; 0x54 <__bad_interrupt> 10: 21 c0 rjmp .+66 ; 0x54 <__bad_interrupt> 12: 20 c0 rjmp .+64 ; 0x54 <__bad_interrupt> 14: 1f c0 rjmp .+62 ; 0x54 <__bad_interrupt> 16: 94 c0 rjmp .+296 ; 0x140 <__vector_11> 18: a0 c0 rjmp .+320 ; 0x15a <__vector_12> 1a: ac c0 rjmp .+344 ; 0x174 <__vector_13> 1c: 53 c0 rjmp .+166 ; 0xc4 <__vector_14> 1e: 5c c0 rjmp .+184 ; 0xd8 <__vector_15> 20: 19 c0 rjmp .+50 ; 0x54 <__bad_interrupt> 22: 18 c0 rjmp .+48 ; 0x54 <__bad_interrupt> 24: 17 c0 rjmp .+46 ; 0x54 <__bad_interrupt> 26: 16 c0 rjmp .+44 ; 0x54 <__bad_interrupt> 28: 15 c0 rjmp .+42 ; 0x54 <__bad_interrupt> 2a: 14 c0 rjmp .+40 ; 0x54 <__bad_interrupt> 2c: 13 c0 rjmp .+38 ; 0x54 <__bad_interrupt> 2e: 13 c0 rjmp .+38 ; 0x56 <__vector_23> 30: 11 c0 rjmp .+34 ; 0x54 <__bad_interrupt> 32: 10 c0 rjmp .+32 ; 0x54 <__bad_interrupt> 00000034 <__ctors_end>: 34: 11 24 eor r1, r1 36: 1f be out 0x3f, r1 ; 63 38: cf ef ldi r28, 0xFF ; 255 3a: d2 e0 ldi r29, 0x02 ; 2 3c: de bf out 0x3e, r29 ; 62 3e: cd bf out 0x3d, r28 ; 61 00000040 <__do_clear_bss>: 40: 11 e0 ldi r17, 0x01 ; 1 42: a0 e0 ldi r26, 0x00 ; 0 44: b1 e0 ldi r27, 0x01 ; 1 46: 01 c0 rjmp .+2 ; 0x4a <.do_clear_bss_start> 00000048 <.do_clear_bss_loop>: 48: 1d 92 st X+, r1 0000004a <.do_clear_bss_start>: 4a: a5 30 cpi r26, 0x05 ; 5 4c: b1 07 cpc r27, r17 4e: e1 f7 brne .-8 ; 0x48 <.do_clear_bss_loop> 50: 04 d1 rcall .+520 ; 0x25a <main> 52: 79 c1 rjmp .+754 ; 0x346 <_exit> 00000054 <__bad_interrupt>: 54: d5 cf rjmp .-86 ; 0x0 <__vectors> 00000056 <__vector_23>: unsigned char bit6:1; unsigned char bit7:1; } flags asm("GPIOR1"); // Interruptroutine für den Analog Comparator ISR(ANALOG_COMP_vect) { 56: 1f 92 push r1 58: 0f 92 push r0 5a: 0f b6 in r0, 0x3f ; 63 5c: 0f 92 push r0 5e: 11 24 eor r1, r1 60: 8f 93 push r24 cli(); 62: f8 94 cli PORTB |= (1 << PB0); 64: 28 9a sbi 0x05, 0 ; 5 // Analog Comparator Interrupt Flag zurücksetzen durch 1 schreiben //ACSR |= (1 << ACI); // Analog Comparator Interrupt Disable ACSR &= ~(1 << ACIE); 66: 80 b7 in r24, 0x30 ; 48 68: 87 7f andi r24, 0xF7 ; 247 6a: 80 bf out 0x30, r24 ; 48 // Analog Comparator ausschalten ACSR |= (1 << ACD); 6c: 80 b7 in r24, 0x30 ; 48 6e: 80 68 ori r24, 0x80 ; 128 70: 80 bf out 0x30, r24 ; 48 flags.flanke = 1; 72: 80 91 03 01 lds r24, 0x0103 76: 81 60 ori r24, 0x01 ; 1 78: 80 93 03 01 sts 0x0103, r24 //sei(); } 7c: 8f 91 pop r24 7e: 0f 90 pop r0 80: 0f be out 0x3f, r0 ; 63 82: 0f 90 pop r0 84: 1f 90 pop r1 86: 18 95 reti
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.
Das cli() kann schon mal weg.
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!
Etwas vergessen, im Timer0 Compare Match wird der MOSFET wieder abgeschaltet... ;)
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
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.