AVR Interrupt Zeit

#7659256
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

Angehängte Dateien:
#7659269
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

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

#7659312
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

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

Angehängte Dateien:
#7659319
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.

#7659323
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!

[C] ISR(PCINT2_vect) { PIND = 0b00000100; // toggle PIND = 0b00000100; // toggle } [/C] 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?

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

Persönliche Seite #7659368
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
#7659375
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.

(Firma: Gast) #7659466
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++

(Firma: 1984now) #7659699
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.

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

#7659727
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)

(Firma: 1984now) #7659734
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.

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

#7659809
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 ;-)

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren