Forum: Mikrocontroller und Digitale Elektronik Einsprung in Analog Comparator Interruptroutine bei Mega48 zu langsam


von Karsten (Gast)


Lesenswert?

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

von Flo (Gast)


Lesenswert?

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.

von Karsten (Gast)


Lesenswert?

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

von Bernhard R. (barnyhh)


Lesenswert?

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

von Bernd M. (bernd_m)


Lesenswert?

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

von Klaus D. (kolisson)


Lesenswert?

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

von Karsten (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Das cli() kann schon mal weg.

von Karsten (Gast)


Lesenswert?

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.

von ... .. (docean) Benutzerseite


Lesenswert?

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

von MWS (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Karsten (Gast)


Lesenswert?

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!

von Karsten (Gast)


Lesenswert?

Etwas vergessen, im Timer0 Compare Match wird der MOSFET wieder 
abgeschaltet... ;)

von MWS (Gast)


Lesenswert?

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

von Karsten (Gast)


Lesenswert?

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