Forum: Mikrocontroller und Digitale Elektronik Realisierung eines I²C-Sniffers mit ATmega8


von Philipp D. (peacefish)


Lesenswert?

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
int buffer;
12
int clk,x;
13
uint_fast32_t data;
14
15
int main(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

von Peter D. (peda)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von holger (Gast)


Lesenswert?

Autsch;) Am besten nicht direkt mit volatile Variablen arbeiten.
1
volatile uint8_t dataByte1;
2
volatile uint8_t dataByte2;
3
4
   if( PINC & 0x01 )
5
     dataByte1 |= 0x80;
6
   else
7
     dataByte1 &= ~(0x80);
8
9
   dataByte1 >>= 1;

So ist es besser
1
volatile uint8_t dataByte1;
2
volatile uint8_t dataByte2;
3
4
   temp1 = dataByte1;
5
6
   if( PINC & 0x01 )
7
     temp1 |= 0x80;
8
   else
9
     temp1 &= ~(0x80);
10
11
   temp1 >>= 1;
12
13
   dataByte1 = temp1;

von Philipp D. (peacefish)


Angehängte Dateien:

Lesenswert?

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

von Philipp D. (peacefish)


Lesenswert?

Hey Leute,

tolle Nachricht! Hab die Fehler entdeckt und es läuft jetzt.

Entdeckte Fehler:
1.
1
data=data>>1;
2
if(PINC &0b00000001)
3
    data |=0x80000000;  
4
  //data=data>>1; 
5
  clk++;

data darf nicht nach der Verundung verschoben werden,
dies muss davor passieren. Da natürlich sonst der zweite Takt den ersten 
überschreibt usw.

2.
1
void USART_Send_data(uint_fast32_t senddata){
2
int iii,i;
3
USART_Transmit('T');
4
iii=0;
5
i=0;
6
7
while(i<2){
8
  while(iii<9)
9
  {
10
11
    if((iii%8==0)&&(iii!=0))USART_Transmit('A');
12
13
    //if(senddata&0x80000000) USART_Transmit('1'); FALSCH
14
if(senddata&0b00000000000000000000010000000000)//Richtig
15
    else USART_Transmit('0');
16
17
    //senddata=senddata<<1; FALSCH
18
senddata=senddata>>1;//Richtig
19
    iii++;
20
  }
21
  USART_Transmit(' ');
22
  i++;
23
  iii=0;
24
}
25
}

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

von Markus G. (thechief)


Lesenswert?

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

von Philipp D. (peacefish)


Lesenswert?

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

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.