Forum: Mikrocontroller und Digitale Elektronik AVR Interrupt Zeit


von G. L. (gfl)


Angehängte Dateien:

Lesenswert?

Hallo
Ich versuche gerade eine i2c-Schnittstelle für einen Atmega328P auf 
einem Arduino Uno über Interrups zu programmieren und habe 
Timingprobleme, d.h. die Zeit die ich zwischen den SCL habe, ist 
schrecklich kurz. Deshalb habe ich eine Testroutine geschrieben, um das 
mal zu messen:
1
#include <avr/interrupt.h>
2
3
int main() {
4
    DDRD &= ~(1 << 6);  //D6 (PCINT22) als TEST_IN 100kHz Rechteck 4,6 Vpp
5
    DDRD |= (1 << 2);   //D2 als TEST_ERG-Ausgang
6
    PCMSK2 |= (1 << PCINT22);
7
    PCICR |= (1 << PCIE2);
8
    sei();
9
    while(1) {
10
        __asm__("nop");
11
    }
12
  return 0;
13
}
14
15
ISR(PCINT2_vect) {
16
    PORTD |= 0b00000100;    //D2 HIGH und LOW
17
    PORTD &= 0b11111011;
18
}
Im Bild sieht man dann, das es über 1,3 us dauert, bis eine Reaktion 
erfolgt! Bzw. 21 Takte.
Die Interrup-Routine zeigt aber nur 10 Takte bis HIGH erscheint (und der 
Sprung ausgelöst vom Interrupt kommt ja auch noch dazu!):
1
00000080 <__vector_5>:
2
  80:  1f 92         push  r1                2 Takte
3
  82:  0f 92         push  r0                2 Takte
4
  84:  0f b6         in  r0, 0x3f  ; 63    1 Takt
5
  86:  0f 92         push  r0                2 Takte
6
  88:  11 24         eor  r1, r1              1 Takt
7
  8a:  5a 9a         sbi  0x0b, 2  ; 11      2 Takte
8
  8c:  5a 98         cbi  0x0b, 2  ; 11
9
  8e:  0f 90         pop  r0
10
  90:  0f be         out  0x3f, r0  ; 63
11
  92:  0f 90         pop  r0
12
  94:  1f 90         pop  r1
13
  96:  18 95         reti
Wenn man sich die Vectortabelle anschaut
1
00000000 <__vectors>:
2
   0:  0c 94 34 00   jmp  0x68  ; 0x68 <__ctors_end>
3
   4:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
4
   8:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
5
   c:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
6
  10:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
7
  14:  0c 94 40 00   jmp  0x80  ; 0x80 <__vector_5>
8
  18:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
9
  1c:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
10
...
dann frag ich mich, ob der Vektor-Zeiger von Zeile 0 beginnend, Zeile 
für Zeile durchjuckelt bis er vector_5 erreicht hat. Dann wäre es ja 
besser PCINT0 zu benutzen, weil der früher dran kommt. Stimmen meine 
Überlegungen?
Und kann man dies push/pop-Sachen unterbinden, die da automatisch mit 
eingebaut werden?
Liebe Grüße

von Steve van de Grens (roehrmond)


Lesenswert?

Das TWI Interface gibt es aus gutem Grund.

von Norbert (der_norbert)


Lesenswert?

Wenn's doch nur so einfach wäre… ;-)

multi-cycle instruction must end first: 1…3
interrupt execution response: >= 5
push PC -> stack: 2
jmp to vector: 3

von Falk B. (falk)


Lesenswert?

G. L. schrieb:
> Ich versuche gerade eine i2c-Schnittstelle für einen Atmega328P auf
> einem Arduino Uno über Interrups zu programmieren

Dieser AVR hat I2C in Hardware, die sollte man nutzen.

> Die Interrup-Routine zeigt aber nur 10 Takte bis HIGH erscheint (und der
> Sprung ausgelöst vom Interrupt kommt ja auch noch dazu!):00000080
> <__vector_5>:
>   80:  1f 92         push  r1                2 Takte
>   82:  0f 92         push  r0                2 Takte
>   84:  0f b6         in  r0, 0x3f  ; 63    1 Takt
>   86:  0f 92         push  r0                2 Takte
>   88:  11 24         eor  r1, r1              1 Takt
>   8a:  5a 9a         sbi  0x0b, 2  ; 11      2 Takte
>   8c:  5a 98         cbi  0x0b, 2  ; 11
>   8e:  0f 90         pop  r0
>   90:  0f be         out  0x3f, r0  ; 63
>   92:  0f 90         pop  r0
>   94:  1f 90         pop  r1
>   96:  18 95         reti
> Wenn man sich die Vectortabelle anschaut00000000 <__vectors>:
>    0:  0c 94 34 00   jmp  0x68  ; 0x68 <__ctors_end>
>    4:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
>    8:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
>    c:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
>   10:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
>   14:  0c 94 40 00   jmp  0x80  ; 0x80 <__vector_5>
>   18:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
>   1c:  0c 94 3e 00   jmp  0x7c  ; 0x7c <__bad_interrupt>
> ...
> dann frag ich mich, ob der Vektor-Zeiger von Zeile 0 beginnend, Zeile
> für Zeile durchjuckelt bis er vector_5 erreicht hat.

Nö, da wird schon direkt der jeweils aktive Vektor angesprungen, anders 
geht es auch gar nicht.

> Dann wäre es ja
> besser PCINT0 zu benutzen, weil der früher dran kommt. Stimmen meine
> Überlegungen?

Nein.

> Und kann man dies push/pop-Sachen unterbinden, die da automatisch mit
> eingebaut werden?

ISR als Assembler-Datei schreiben.

https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Assembler_und_Inline-Assembler

von Sebastian W. (wangnick)


Lesenswert?

Norbert schrieb:
> multi-cycle instruction must end first: 1…3

Und es dürfen im restlichen Code an keiner Stelle Interrupts gesperrt 
werden, sonst kommen diese Sperrzeiten auch noch dazu.

LG, Sebastian

von (prx) A. K. (prx)


Lesenswert?

Eine reine Software-Implementierung eines I2C Targets ist zeitlich ein 
ziemlich heisses Eisen, wenn man beim Takt nicht passend nachgeben will. 
Deshalb gibt es oft eine Hardware-Unterstützung und darin das für 
µC-Targets wichtige Clock Stretching. Bereits wenn man auf Clock 
Stretching verzichten muss, weil der Initiator damit nicht umgehen kann 
(z.B. ein RasPi), wird es spannend.

von Norbert (der_norbert)


Lesenswert?

Falk B. schrieb:
> ISR als Assembler-Datei schreiben.

oder ›naked‹ deklarieren. (Wenn man wirklich weiß was man tut)

von G. L. (gfl)


Lesenswert?

Falk B. schrieb:
> ISR als Assembler-Datei schreiben.
Das schau ich mir mal an.

Ansonsten: Klar gibt es das auch in Hardware!

(prx) A. K. schrieb:
> wenn man beim Takt nicht passend nachgeben will.

Wie sieht das denn aus mit dem Takt? Verstehen den die meisten Bausteine 
auch I2C bei 80 kHz?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

G. L. schrieb:
> dann frag ich mich, ob der Vektor-Zeiger von Zeile 0 beginnend, Zeile
> für Zeile durchjuckelt bis er vector_5 erreicht hat.

Eine rege Fantasie!
1
ISR(PCINT2_vect) {
2
    PIND = 0b00000100;  // toggle
3
    PIND = 0b00000100;  // toggle
4
}
Könnte das ein oder andere Push Pop einsparen.
Insbesondere mit naked

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

G. L. schrieb:
> dann frag ich mich, ob der Vektor-Zeiger von Zeile 0 beginnend, Zeile
> für Zeile durchjuckelt bis er vector_5 erreicht hat. Dann wäre es ja
> besser PCINT0 zu benutzen, weil der früher dran kommt. Stimmen meine
> Überlegungen?

Nein, das stimmt nicht. Aber in der Vektortabelle muss nicht unbedingt 
ein jmp stehen. Wenn du also einen Vektor benutzt, bei dem einige darauf 
in der Tabelle folgende Vektoren unbenutzt sind, dann könntest du die 
ISR (wenn sie kurz genug ist) direkt in die Vektortabelle schreiben und 
dir so die drei Takte für den jmp sparen.

LG, Sebastian

von G. L. (gfl)


Angehängte Dateien:

Lesenswert?

Hier mit naked:
1
...
2
__attribute__((naked)) ISR(PCINT2_vect) {
3
    PORTD |= 0b00000100;    //D2 HIGH und LOW
4
    PORTD &= 0b11111011;
5
}
6
...
und alles "überflüssige" ist weg:
1
00000080 <__vector_5>:
2
  80:  5a 9a         sbi  0x0b, 2  ; 11
3
  82:  5a 98         cbi  0x0b, 2  ; 11
Das geht schon mal um einiges flotter. Aber ob das im realen Leben auch 
ohne Seitenefekte funktioniert, habe ich noch nicht ausprobiert.

von Sebastian W. (wangnick)


Lesenswert?

G. L. schrieb:
> Das geht schon mal um einiges flotter.

Da fehlt aber der reti, oder?

LG, Sebastian

von Falk B. (falk)


Lesenswert?

G. L. schrieb:
> Das geht schon mal um einiges flotter. Aber ob das im realen Leben auch
> ohne Seitenefekte funktioniert, habe ich noch nicht ausprobiert.

Wenn die ISR SOOO einfach ist, dann ja. Aber sobald auch nur ein 
einziges Register der CPU beschrieben oder die Staus-Flags geändert 
werden, dann nicht. Eine "naked" ISR ist nur dann sinnvoll, wenn man 
dort VOLLSTÄNDIG in Inline-ASM schreibt und weiß was man tut. Mit reinem 
C ist es mehr Glück als Verstand. Inline-ASM ist aber nervig im 
Vergleich zu einer reinen ASM-ISR, die man normal schreiben kann.

von G. L. (gfl)


Lesenswert?

Arduino F. schrieb:
> G. L. schrieb:
>> dann frag ich mich, ob der Vektor-Zeiger von Zeile 0 beginnend, Zeile
>> für Zeile durchjuckelt bis er vector_5 erreicht hat.
>
> Eine rege Fantasie!
>
>
1
> ISR(PCINT2_vect) {
2
>     PIND = 0b00000100;  // toggle
3
>     PIND = 0b00000100;  // toggle
4
> }
5
>
> Könnte das ein oder andere Push Pop einsparen.
> Insbesondere mit naked
1
00000080 <__vector_5>:
2
  80:  84 e0         ldi  r24, 0x04  ; 4
3
  82:  89 b9         out  0x09, r24  ; 9
4
  84:  89 b9         out  0x09, r24  ; 9
braucht leider einen mehr, aber es geht ja auch nicht um das 
Pingewackel.

Falk B. schrieb:
> Wenn die ISR SOOO einfach ist, dann ja.

Ist bei I2C natürlich nicht. Aber eine Frage ist noch nicht beantwortet: 
Erlauben die Bausteine auch z.B. 80 kHz?

von G. L. (gfl)


Lesenswert?

So:

hier https://www.i2c-bus.org/speed/ steht u.a. "This does not imply that 
a transmission may not take place at any lower speed or even at a 
somewhat variable bit rate."
Also mach ich etwas langsammer. Ist ja auch nur intern und für mich.
Vielen Dank für die interessanten Anregungen.

von Falk B. (falk)


Lesenswert?

G. L. schrieb:
>> wenn man beim Takt nicht passend nachgeben will.
>
> Wie sieht das denn aus mit dem Takt? Verstehen den die meisten Bausteine
> auch I2C bei 80 kHz?

Wenn dein AVR der Master ist, kannst du den beliebig langsam takten, das 
machen alle I2C Bausteine als Slave mit. Dann braucht man aber auch kein 
I2C mit Interrupts.

Wenn dein AVR ein Slave ist und ein anderer IC der Master ist, dann geht 
das nur, wenn der Master clock stretching unterstützt. Dann kann ein 
langsamer Slave den Takt ausbremsen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

G. L. schrieb:
1
> 00000080 <__vector_5>:
2
>   80:  1f 92         push  r1                2 Takte
3
>   82:  0f 92         push  r0                2 Takte
4
>   84:  0f b6         in  r0, 0x3f  ; 63    1 Takt
5
>   86:  0f 92         push  r0                2 Takte
6
>   88:  11 24         eor  r1, r1              1 Takt
7
>   8a:  5a 9a         sbi  0x0b, 2  ; 11      2 Takte
8
>   8c:  5a 98         cbi  0x0b, 2  ; 11
9
>   8e:  0f 90         pop  r0
10
>   90:  0f be         out  0x3f, r0  ; 63
11
>   92:  0f 90         pop  r0
12
>   94:  1f 90         pop  r1
13
>   96:  18 95         reti

Sieht nach veraltetem Compiler aus.
1
ISR(PCINT2_vect) {
2
    PORTD |= 0b00000100;    //D2 HIGH und LOW
3
    PORTD &= 0b11111011;
4
}

Compiliert für ATmega328 und avr-gcc v8 zu:
1
000000c2 <__vector_5>:
2
  c2:  5a 9a         sbi  0x0b, 2  ; 11
3
  c4:  5a 98         cbi  0x0b, 2  ; 11
4
  c6:  18 95         reti

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

G. L. schrieb:
> die Zeit die ich zwischen den SCL habe, ist schrecklich kurz.

Hab schon länger nix mehr mit I²C gemacht; aber ein (langsamer) Slave 
darf doch SCL auf low ziehen, um dem Master mitzuteilen, wann er fertig 
ist un dass er langsam ist?

von (prx) A. K. (prx)


Lesenswert?

Das genannte Clock Stretching. Damit kann aber nicht jeder Master 
umgehen. Etwa die Raspberry Pis. Das ist besonders ärgerlich, weil sich 
eine Highlevel/Lowlevel Trennung von Steuerungsfunktionen auf Basis von 
I2C förmlich anbietet, aber genau deshalb problematisch ist.

: Bearbeitet durch User
von Marko ⚠. (mos6502) Benutzerseite Flattr this


Lesenswert?

Eine andere Moeglichkeit waere, den Controller zu wechseln. AVRs sind 
nicht gerade die besten, was Interrupt Latency/Response Time betrifft. 
Zudem haben die AVRs auch noch einen Jitter von mindestens einem 
Taktzyklus. PICs z.B. haben das nicht.

von Thomas (kosmos)


Lesenswert?

leg doch auch gleich deine Vectortabelle mit reti für die IRQs an die du 
nicht verwendest, dann dürfte dort auch nicht mehr bad interrupt 
erscheinen.

von Norbert (der_norbert)


Lesenswert?

Thomas schrieb:
> leg doch auch gleich deine Vectortabelle mit reti für die IRQs an
> die du
> nicht verwendest, dann dürfte dort auch nicht mehr bad interrupt
> erscheinen.

Wozu?
Wenn da sowieso niemand hin hüpft,
dann muss man auch keine Girlanden aufhängen.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

G. L. schrieb:
> braucht leider einen mehr, aber es geht ja auch nicht um das
> Pingewackel.

1. Nein!
2. Ja

Es ist schlanker als das im Eingangsposting.
2 Push, 2 Pop und 2 andere Statements weniger
1
00000080 <__vector_5>:
2
ISR(PCINT2_vect)
3
{
4
  80:  8f 93         push  r24
5
  PIND = 0b00000100;  // toggle
6
  82:  84 e0         ldi  r24, 0x04  ; 4
7
  84:  89 b9         out  0x09, r24  ; 9
8
  PIND = 0b00000100;  // toggle
9
  86:  89 b9         out  0x09, r24  ; 9
10
} 
11
  88:  8f 91         pop  r24
12
  8a:  18 95         reti


Falls von Interesse: avr-gcc-11.1.0 ArduinoIde UNO C++

: Bearbeitet durch User
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Norbert schrieb:
> Wenn's doch nur so einfach wäre… ;-)
>
> multi-cycle instruction must end first: 1…3

Das ist aber keine verlorene Zeit, es wird ja hier noch Nutzcode 
ausgeführt, nur halt nicht der in der ISR. Also nicht schädlich für den 
"Durchsatz", nur schädlich für die Latenz.

Solche Sachen gibt es übrigens noch mehr: Code konkurrierender ISRs und 
unter Interruptsperre laufender code in main(). auch hier wird was 
nützliches gemacht, es verzögert aber halt den Eintritt in die konkrete 
ISR.

Mal mehr, mal weniger, je nach Situation zum Zeitpunkt eines IRQ. 
Deswegen nennt man das alles auch variable Latenz. Im Gegensatz zum 
folgenden Rest, der die statische Latenz darstellt, die immer und unter 
allen Umständen anfällt.

> interrupt execution response: >= 5
> push PC -> stack: 2

Erstens sind es 4 Takte und nicht 5 (außer bei externem RAM und/oder 
22Bit-PC, dafür kommen noch Takte dazu, aber der 328P hat weder das eine 
noch das andere) und zweitens ist in diesen Takten das Sichern des PC 
bereits enthalten. Genau das ist übrigens, was die zusätzlichen Takte 
unter den genannten Bedingungen verursacht.

> jmp to vector: 3

Kann man u.U. ganz einsparen, mindestens aber durch rjmp mit nur 2 
Takten ersetzen. Man muss halt ISRs günstig im Flash plazieren, also in 
der Nähe der Vektortabelle.

Und nun der Vollständigkeit halber noch das, was nicht zur statischen 
Latenz gehört, weil es erst nach Durchlaufen des Nutzcodes in der ISR 
passiert, aber zum sog. "minimalen Interruptrahmen", also dem 
unvermeidlichen Overhead einer Interruptverarbeitung. Das ist das reti 
am Ende der ISR, was (beim 328P) mit 4 Takten zu Buche schlägt. Auch 
hier gilt wieder: bei Devices mit externen RAM und/oder 22Bit-PC kommen 
da noch Takte dazu. Schuld ist auch hier wieder der PC, also dessen 
Rückladen aus dem RAM.

von Norbert (der_norbert)


Lesenswert?

Ob S. schrieb:
> nur schädlich für die Latenz.

Genau darum geht es hier.

>> interrupt execution response: >= 5
> Erstens sind es 4 Takte und nicht 5

Ich hatte in das 32U4 Datenblatt hinein geschaut (das war gerade zur 
Hand), da ist explizit von 5 oder mehr die Rede. Mag sein das es für den 
328P anders ist.

>> jmp to vector: 3
> kann man u.U. ganz einsparen, mindestens aber durch rjmp mit nur 2
> Takten ersetzen.

Ja, kann man. Eins gespart.

von Norbert (der_norbert)


Lesenswert?

7.7.1 Interrupt Response Time
The interrupt execution response for all the enabled AVR interrupts is 
four clock cycles minimum

After four clock cycles the program vector address for the actual 
interrupt handling routine is executed.

During this four clock cycle period, the Program Counter is pushed 
onto the Stack.

The vector is normally a jump to the interrupt routine, and this jump 
takes three clock cycles.

If an interrupt occurs during execution of a multi-cycle instruction, 
this instruction is completed before the interrupt is served.
If an interrupt occurs when the MCU is in sleep mode, the interrupt 
execution response time is increased by four clock cycles.

Either: CPU is running, best case 8 cycles, worst case 12 cycles
1  physical INTERRUPT
·  finish single cycle instruction (1 cycle)
·  maybe finish multi-cycle instruction (additional 1…4 cycles)
2   PC->STACK  (4 cycles)
3  jump to the interrupt routine (normally 3 cycles)


Or: CPU is sleeping, 11 cycles
1  physical INTERRUPT
·  wake from sleep mode (4 cycles)
2   PC->STACK  (4 cycles)
3  jump to the interrupt routine (normally 3 cycles)

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Norbert schrieb:

> Ich hatte in das 32U4 Datenblatt hinein geschaut (das war gerade zur
> Hand), da ist explizit von 5 oder mehr die Rede.

Ja, auch RET/RETI dauert hier 5 Takte. Steht auch irgendwo im DB, dass 
drei Byte PC auf dem Stack landen.

Sprich: Das Teil hat offensichtlich einen 22Bit-PC. Warum auch immer, an 
der Größe des Flash, der normalerweise der Grund dafür ist, kann es hier 
ja nicht liegen.

> Mag sein das es für den
> 328P anders ist.

Ist es. Bei den allermeisten Tiny und Mega-Typen gelten die von mir 
genannten 4 Takte.

von Rainer W. (rawi)


Lesenswert?

Norbert schrieb:
> Wozu?
> Wenn da sowieso niemand hin hüpft,
> dann muss man auch keine Girlanden aufhängen.

Besser als ein im Fehlerfall Amok laufendes Programm ist ein RTI 
allemal. Eine Diagnose dran zu hängen, die dann einen Denkanstoß gibt, 
ist der nächste Level.
Oder du schreibst einfach fehlerfreie Programme - dann kann man sich die 
paar Codezeilen natürlich sparen.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Rainer W. schrieb:
> Besser als ein im Fehlerfall Amok laufendes Programm ist ein RTI
> allemal.

Wir reden ja hier von ›C/C++‹.
Wenn da eine ISR erstellt wird, dann wird der Vector vollautomagisch mit 
dem richtigen Wert befruchtet.
Und einen Interrupt zu aktivieren ohne eine ISR, nun ja, was soll ich 
sagen?

> Oder du schreibst einfach fehlerfreie Programme - dann kann man sich die
> paar Codezeilen natürlich sparen.

Sowieso ;-)

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.