Erst mal Hallo an Alle!
Ich bechäftige mich momentan mit der Realisierung eines I²C Abhörgerätes
auf einem My AVR- Board welches mit einem ATmega8 @8Mhz ausgestattet
ist. Ich nutze AVR- Studio und programmiere in C.
Weiterhin gelten folgende Voraussetzungen:
Es wird als gegeben angesehen, dass eine Übertragung aus 18 Bit besteht.
(Ich weis, dass es möglich ist mehr zu übertragen aber man muss
schließlich erst flattern bevor man fliegt :) )
Der Bustakt ist auf 100kHz festgelegt
(Standard Takt I²C- Wenn das nicht geschafft wird ist der Gerät für mich
nutzlos.)
Das ganze bin ich folgendermaßen angegangen:
Auf den externen Interrupt 0 also Anschluss PortD.2 habe ich den CLK des
I²C-Bussystemes verbunden. An PortC.0 möchte ich gerne die Datenleitung
bei jeder steigenden Taktflanke einlesen. Generell scheint das zu
funktionieren, nur habe ich enorme Probleme die Interrupt Routine
schnell genug zu bekommen.
Das Erkenne ich daher, dass bei mir wenn ich ein Datum über den Bus
schicke (das macht ein zweites AVR-Board ) die Lämpchen nicht blinken
(siehe c-Code), was ja bedeutet, dass er nicht alle 18 Taktflanken
(8 Adress-Bits + 1 Ack + 8 Datenbits + 1 Ack) mitbekommt.
Hier ist mein C-Code:
1
#define F_CPU 3686400
2
#include<avr/io.h>
3
#include<avr/interrupt.h>
4
#include<stdbool.h>
5
#include"lcd_lib.h"
6
#include<stdint.h>
7
8
#define p8574 0x42
9
#define SCL INT0_vect
10
11
intbuffer;
12
intclk,x;
13
uint_fast32_tdata;
14
15
intmain(void){
16
17
DDRD=0x00;//Port D ist Eingang
18
PORTD=0xff;//Port D wird hochohmig geschaltet
19
20
DDRC=0x00;
21
PORTC=0xff;
22
23
DDRB=0xff;//Port C ist Ausgang
24
PORTB=0xff;//Port C auf High
25
26
MCUCR=0b00001111;//11 Interrupt bei Steigender Flanke
27
//10 Interrupt bei Fallender Flanke
28
//01 Interrupt bei jeder Flanke
29
//00 Interrupt bei Low-Level
30
SREG=0b10000000;//Enable external Interupt Request von INT0 und INT1
31
GICR=0b01000000;//Generally enable external Interupt Request von INT0
32
33
34
sei();//enable Interrupts
35
36
while(1){
37
if(clk>=18)
38
{
39
clk=0;
40
while(1){
41
PORTB^=0b11111111;
42
_delay_ms(1000);
43
if(~PIND&0b10000000)break;
44
}
45
}
46
PORTB^=0b11111111;
47
}
48
49
}
50
51
/*Interrupt service Routine für INT_O=SCL
52
Anmerkung Clock muss mit steigender Flanke
53
getriggert werden! Wurde in MCUCR Register eingestellt.
54
*/
55
ISR(SCL){
56
57
//Datenleitung ist an PINC.0 angeschlossen
58
//und wird zu jedem Clock eingelesen.
59
//data ist vom typ uint_fast32_t
60
//(eine 32 Bit große Variable,
61
// die in stdint.h definiert wird.)
62
data=data|((PINC&0b00000001)<<clk);
63
64
//clk wird hochgezählt,
65
//wird 18 erreicht,
66
//wird es in main() zurückgesetzt.
67
clk++;
68
}
Nun ist meine Frage, wie kann ich die Interrupt Routine effizienter
gestalten, würde es zum Beispiel etwas bringen, wenn man diese in
Assembler schreibt? Ich habe von Assembler relativ wenig Ahnung und
hoffe deshalb auf etwas Hilfe aus dem Forum um das Ding vllt. Doch noch
zum laufen zu bringen. Hier ist der Assembler Code meiner Interrupt
Routine, den AVR-Studio beim compilieren in das .lss file abgelegt hat.
1
ISR(SCL){
2
49c: 1f 92 push r1
3
49e: 0f 92 push r0
4
4a0: 0f b6 in r0, 0x3f ; 63
5
4a2: 0f 92 push r0
6
4a4: 11 24 eor r1, r1
7
4a6: 2f 93 push r18
8
4a8: 3f 93 push r19
9
4aa: 4f 93 push r20
10
4ac: 5f 93 push r21
11
4ae: 6f 93 push r22
12
4b0: 7f 93 push r23
13
4b2: 8f 93 push r24
14
4b4: 9f 93 push r25
15
4b6: af 93 push r26
16
4b8: bf 93 push r27
17
18
19
data=data|((PINC&0b00000001)<<clk);
20
4ba: 23 b3 in r18, 0x13 ; 19
21
4bc: 60 91 69 00 lds r22, 0x0069
22
4c0: 70 91 6a 00 lds r23, 0x006A
23
4c4: 30 e0 ldi r19, 0x00 ; 0
24
4c6: 21 70 andi r18, 0x01 ; 1
25
4c8: 30 70 andi r19, 0x00 ; 0
26
4ca: 06 2e mov r0, r22
27
4cc: 02 c0 rjmp .+4 ; 0x4d2 <__vector_1+0x36>
28
4ce: 22 0f add r18, r18
29
4d0: 33 1f adc r19, r19
30
4d2: 0a 94 dec r0
31
4d4: e2 f7 brpl .-8 ; 0x4ce <__vector_1+0x32>
32
4d6: 44 27 eor r20, r20
33
4d8: 37 fd sbrc r19, 7
34
4da: 40 95 com r20
35
4dc: 54 2f mov r21, r20
36
4de: 80 91 6d 00 lds r24, 0x006D
37
4e2: 90 91 6e 00 lds r25, 0x006E
38
4e6: a0 91 6f 00 lds r26, 0x006F
39
4ea: b0 91 70 00 lds r27, 0x0070
40
4ee: 82 2b or r24, r18
41
4f0: 93 2b or r25, r19
42
4f2: a4 2b or r26, r20
43
4f4: b5 2b or r27, r21
44
4f6: 80 93 6d 00 sts 0x006D, r24
45
4fa: 90 93 6e 00 sts 0x006E, r25
46
4fe: a0 93 6f 00 sts 0x006F, r26
47
502: b0 93 70 00 sts 0x0070, r27
48
clk++;
49
506: 6f 5f subi r22, 0xFF ; 255
50
508: 7f 4f sbci r23, 0xFF ; 255
51
50a: 70 93 6a 00 sts 0x006A, r23
52
50e: 60 93 69 00 sts 0x0069, r22
53
54
55
}
56
512: bf 91 pop r27
57
514: af 91 pop r26
58
516: 9f 91 pop r25
59
518: 8f 91 pop r24
60
51a: 7f 91 pop r23
61
51c: 6f 91 pop r22
62
51e: 5f 91 pop r21
63
520: 4f 91 pop r20
64
522: 3f 91 pop r19
65
524: 2f 91 pop r18
66
526: 0f 90 pop r0
67
528: 0f be out 0x3f, r0 ; 63
68
52a: 0f 90 pop r0
69
52c: 1f 90 pop r1
70
52e: 18 95 reti
So wie ich das verstehe, habe ich bei einem I²C-Bustakt von 100kHz
maximal 10µs Zeit für die Interrupt Routine. Wenn ich richtig denke habe
ich dann bei einem Takt von 8 MHz 10µs*8MHz[Clocks/s]=80 Clocks zur
Verfügung.
Der obige Assembler Code hat ca. 120 Clocks...:( es scheint also
plausibel, dass es so nicht funktioniert.)
Im Endeffekt soll in der Interrupt Routine ja "nur" der Zustand der
Datenleitung eingelesen werden und wohin gespeichert werden wo man ihn
später wieder auffinden kann. Das muss doch mit einer Clockzahl <80 zu
schaffen sein? Oder?
Ich bin dankbar für jede Hilfestellung!
Mit freundlichen Grüßen,
Peacefish
Philipp Dorsch schrieb:> Der obige Assembler Code hat ca. 120 Clocks...:( es scheint also> plausibel, dass es so nicht funktioniert.)
Stimmt, soweit war ich auch schonmal.
Um alle Adressen und alle Ereignisse auf den I2C zu sniffen, braucht man
einen AVR mit USI, z.B. ATtiny85.
Beitrag "I2C (TWI) Sniffer mit AVR"
Peter
Solange du in der ISR das hier machst
uint_fast32_t data;
data=data|((PINC&0b00000001)<<clk);
ist das aber auch kein Wunder, dass du dermassen viele Takte in der ISR
verbrauchst.
Dein AVR arbeitet am schnellsten, wenn du ihn mit 8 Bit werkeln lässt.
Variables schieben ist sowas wie ein Albtraum für deinen AVR.
Setz immer das gleiche Bit und schieb das Byte unter dieser Position
durch
Teil die 18 Bit auf auf 2 Byte und überlass es dem Code ausserhalb die
wieder zusamenzusetzen, falls das überhaut notwendig ist.
volatile uint8_t dataByte1;
volatile uint8_t dataByte2;
if( PINC & 0x01 )
dataByte1 |= 0x80;
else
dataByte1 &= ~(0x80);
dataByte1 >>= 1;
Ob sich das ganze dann Timingmässig ausgeht, ist eine andere Frage. Das
weiß Peter besser als ich. Aber mit der Methode "Fleissaufgabe für den
µC" wird es höchst wahrscheinlich sicher nichts werden.
Hello again,
habe mal wieder etwas zeit gehabt, um mich mit dem Thema
auseinanderzusetzen.
Der Code wurde durch das Weglassen der relativen Shift Operation
wirklich effizienter. Nochmals Vielen Dank!
Also kurzes Update was so alles gelaufen ist:
- Das abhörende AVR Board empfängt jetzt die 18 Taktflanken
- ich habe das abhörende AVR Board über einen MAX 232 Baustein mit der
COM
Schnittstelle meines PCs Verbunden
- Über das Programm "Terminal" kann ich nun Daten Empfangen die das AVR
Board sendet.
- Die Daten werden richtig empfangen und gesendet
Allerdings decken sich die Werte die ich auf dem Terminal angezeigt
bekomme, nicht mit dem was ich erwartet hätte.
Als "Teststrecke" habe ich ein zweites My AVR-Board. Dieses steuert über
I²C einen Port Expander Baustein von Philipps an, an welchem LEDs
angeschlossen sind. Das läuft auch ohne Probleme (Siehe Bild)
Die Übertragung sollte dabei folgendermaßen aussehen:
Adresse des PhilippsBausteins (0x42) +Ack +Daten (0xF7) +Ack
also Bitweise: (0100 0010) (1) (1111 0111) (1)
Allerdings bekomme ich auf dem Terminal folgendes angezeigt:
(0011 1011)(1)(1001 0000)(1)
Ein "gutes" Zeichen dabei ist, dass ich immer die gleichen Werte
bekomme, also scheint es kein Geschwindigkeitsproblem mehr zu geben.
Außerdem bekomme ich ja meine 18 Flanken auch rein.
Ich vermute daher, dass ich irgendwo ein logik Problem im Code habe...
Aus diesem Grund hänge ich mal meine Files an, vllt kann mir ja jemand
weiterhelfen. Ich erwarte natürlich nicht, dass sich jemand hier soviel
Zeit mit meinem Käse macht, dass er den kompletten SourceCode korrigiert
und verbessert. Aber vllt. Sieht ja jemand den Denkfehler den ich dabei
gemacht habe und kann mir sagen wo ich nachbessern muss.
Nochmals vielen Dank für die tolle Hilfe!
Mit freundlichen Grüßen
Peacefish
Hier gebe ich den ganzen Käse falschherum aus.
Jetzt bekomme ich die korrekten Werte angezeigt!
Vielen Dank für Euere Hilfe!
Wenn noch Interesse Besteht werde ich natürlich meinen (aufgeräumten)
Quellcode posten.
Noch einen schönen Sonntag!
Mit freundlichen Grüßen
Peacefish
Hallo Philipp,
eine kleine Anmerkung zu folgendem Quellcodeausschnitt:
Philipp Dorsch schrieb:> DDRD=0x00;//Port D ist Eingang> PORTD=0xff;//Port D wird hochohmig geschaltet>> DDRC=0x00;> PORTC=0xff;>> DDRB=0xff;//Port C ist Ausgang> PORTB=0xff;//Port C auf High
Der erste Teil zum Port D ist klar.
Port C arbeitet auch als Eingang und wird hochohmig geschaltet, hier
fehlt der Kommentar.
Der Port B wird als Ausgang geschaltet und alle Pins auf HIGH gesetzt.
Der Kommentar ist aber auf Port C bezogen!
Viel Spaß mit Deinem I2C Sniffer, freue mich schon auf den aufgeräumten
Quellcode, kann man bestimmt mal gebrauchen... :-)
Viele Grüße
Markus
hey,
leider doch etwas zu früh gefreut,
der Sniffer läuft zwar soweit mit funktionierenden 18 Bit langen
Übertragungen. Aber wenn z.B. ein Slave nicht erreicht werden kann, also
das erste Ack 1 ist, dann sendet der Master ja garnichts mehr. Macht ja
auch irgendwie Sinn.
Nur wenn ich jetzt natürlich den Code anpassen will, dass nach jeden 9
Bit die Übertragung der Daten über USART erfolgt, komme ich natürlich
zeitlich gesehen nicht mehr hin, da mir dann bei einer funktionierenden
Übertragung das erste Datenbit einen Interrupt direkt während meiner
USART Übertragung auslößt :(.
Vllt. hat ja jemand eine kleine Anregung wie man das umgehen könnte.
Mit freundlichen Grüßen
Philipp