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
intmain(){
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
return0;
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:1f92pushr12Takte
3
82:0f92pushr02Takte
4
84:0fb6inr0,0x3f;631Takt
5
86:0f92pushr02Takte
6
88:1124eorr1,r11Takt
7
8a:5a9asbi0x0b,2;112Takte
8
8c:5a98cbi0x0b,2;11
9
8e:0f90popr0
10
90:0fbeout0x3f,r0;63
11
92:0f90popr0
12
94:1f90popr1
13
96:1895reti
Wenn man sich die Vectortabelle anschaut
1
00000000<__vectors>:
2
0:0c943400jmp0x68;0x68<__ctors_end>
3
4:0c943e00jmp0x7c;0x7c<__bad_interrupt>
4
8:0c943e00jmp0x7c;0x7c<__bad_interrupt>
5
c:0c943e00jmp0x7c;0x7c<__bad_interrupt>
6
10:0c943e00jmp0x7c;0x7c<__bad_interrupt>
7
14:0c944000jmp0x80;0x80<__vector_5>
8
18:0c943e00jmp0x7c;0x7c<__bad_interrupt>
9
1c:0c943e00jmp0x7c;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
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
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
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
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.
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?
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
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
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.
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:84e0ldir24,0x04;4
3
82:89b9out0x09,r24;9
4
84:89b9out0x09,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?
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.
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.
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?
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.
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.
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.
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:8f93pushr24
5
PIND=0b00000100;// toggle
6
82:84e0ldir24,0x04;4
7
84:89b9out0x09,r24;9
8
PIND=0b00000100;// toggle
9
86:89b9out0x09,r24;9
10
}
11
88:8f91popr24
12
8a:1895reti
Falls von Interesse: avr-gcc-11.1.0 ArduinoIde UNO C++
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.
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.
7.7.1 Interrupt Response Time
The interrupt execution response for all the enabled AVR interrupts is
four clock cycles minimumAfter 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)
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.
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.
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 ;-)