Forum: Mikrocontroller und Digitale Elektronik I2C Programmierungsproblem


von Christoph M. (chrischu91)


Angehängte Dateien:

Lesenswert?

Ich habe ein problem: ich bekomme beim ausgang von der I2C schnitstelle 
vom PIC16F887 beim SDL ausgang, kein Signal über, wenn ich es mit dem 
Oszilloskop messe...
Ich weiss nicht was ich noch einstellen muss, oder ob ich etwas falsch 
mache oder etwas nicht beachte...
Der SDL ausgang sollte doch immer ein Clock geben oder bin ich da falsch 
informiert? (sofern ich das programm darauf geladen habe das im Anhang 
ist.)
Ich benutze den CCSC Compiler und das MPLAB IDE v8.56
Die RS232 funktioniert ohne Probleme, und es muss mit RS232 laufen!

Könnte mir jemand einen tipp geben oder mir helfen, ich danke schon 
jetzt!

Christoph

von XXX (Gast)


Lesenswert?

Hallo

int  read_i2c(int adresse1, int adresse2)
{
  int  daten;
  disable_interrupts(GLOBAL);
  i2c_start();
  i2c_write(0xA1);
  i2c_write(adresse1);
  i2c_write(adresse2);

Fehlt hier i2c_stop(); ??????

  i2c_start();
  i2c_write(0xA0);
  daten = i2c_read(0);
  i2c_stop();
  return(daten);
  enable_interrupts(GLOBAL);
  delay_us(1);
}


Kann es sein, das dort ein stop-Befehl fehlt??

Gruß
Joachim

von Stefan (Gast)


Lesenswert?

Was hängt denn an dem I2C dran ?

von Christoph M. (chrischu91)


Lesenswert?

@Joachim: ich muss ja da ein restart machen und die Adresse, zu der er 
hingeht, ist im vorherigen teil, das stopbit setze ich doch erst am 
ende, wenn ich alles fertig geschrieben habe nicht?

@Stefan: es hängt ein EEPROM 24WC32 daran.

von Stefan (Gast)


Lesenswert?

Ich kenn den aufruf etwas anders.
Vielleicht hilft es dir ja:

I2C1_Start()           ' issue I2C start signal
    I2C1_Wr(0xA2)          ' send byte via I2C (device address + W)
    I2C1_Wr(2)             ' send byte (address of EEPROM location)
    I2C1_Wr(0xAA)          ' send data (data to be written)
    I2C1_Stop()            ' issue I2C stop signal


    I2C1_Start()           ' issue I2C start signal
    I2C1_Wr(0xA2)          ' send byte via I2C  (device address + W)
    I2C1_Wr(2)             ' send byte (data address)
    I2C1_Repeated_Start()  ' issue I2C signal repeated start
    I2C1_Wr(0xA3)          ' send byte (device address + R)
    PORTB = I2C1_Rd(0)     ' Read the data (NO acknowledge)
    I2C1_Stop()

von Bastler (Gast)


Lesenswert?

Pull-Up Widerstände angeschlossen?

von Nilix (Gast)


Lesenswert?

Wenn du ein 24WC32 dran hast, dann mußt du nach dem Sender der 
Slave-Adresse zuerst das High-Byte und dann das Low-Byte der Adresse 
senden, auf welche Speicherstelle du zugreifen willst. -> see datasheet

von Christoph M. (chrischu91)


Lesenswert?

@Stefan: hmm vielleicht liegt es wirklich daran, dass ich ein
         I2C1_Repeated_Start()
einfügen sollte, ich muss mir das noch einmal genau anschauen.

@Bastler: Ja, an das habe ich gedacht.

@Nilix: Achso danke, ja stimmt, da habe ich etwas falsch verstanden!

ich glaub nach zusätzlichen herumfragen in meiner Firma, bei 
verschiedensten personen, dass ich wohl etwas zu komplex war, und der 
RS232 Port gar nicht nachmag mit der programmierung, oder der fehler, 
der Nilix beschrieben hat, ich werde es morgen in der Firma dann checken 
können :)

von Christoph M. (chrischu91)


Lesenswert?

Es funktioniert auch nicht, aber ich versuche nun das Debugging menu zum 
laufen zu bringen, doch dies scheint viel schwieriger zu sein, als ich 
am anfang dachte :s
Ich soll ein Debugging bit setzen, da ich aber PicKit3 verwende scheint 
dies viel schwieriger zu sein...
kann mir da jemand helfen? ich sollte es mindestens mal debuggen können!
Weil die Funktion
  i2c_start();
führt es noch aus, aber bei
  i2c_write(0xA1);
bleibt es hängen.
Wenn man es mit dem oszilloskop misst, kommt bei einer Tastatureingabe 
auch kein Signal! Ich müsste also auch noch wissen wie man das tris_c 
register setzen sollte, damit ich das SCL und SDA richtig in betrieb 
nehmen kann, denn ich habe immer ein Low Pegel statt ein High Pegel, nur 
bei einem Reset geht es kurz auf den High Pegel!

Ich hoffe jemand kann mir helfen.
Werde jetzt aber für eine Woche in den Ferien sein, und es mir nachher 
anschauen!

Danke schon jetzt!
Gruss Christoph

von Stephan S. (uxdx)


Lesenswert?

Du brauchst beim Lesen über I2C defintiv ein "repeated start", sonst 
kannst Du ja keinen Lesebefehl absetzen.

In Assembler sieht das beim 16F887 so aus (Teil einer Routine, die ich 
immer wieder verwende, z.B. für EEPROMs)
1
; lesen Adresse adr_l/adr_h im I2C-Device adr_e nach W
2
3
read_i2c
4
    call   i2c_on                 ; Bus start
5
    call   i2c_set                ; setze Adress-Pointer
6
    call   i2c_ron                ; Bus repeated start
7
8
    movfw   adr_e                 ; 1010 xxx1   I2C-Adresse für lesen
9
    iorlw   b'00000001'           ; letztes Bit 1 = read Data
10
    call    i2c_tx                ; sende I2C-Adresse
11
    call    i2c_rx                ; lesen Speicherzelle nach W
12
13
    call    i2c_off               ; Bus stop
14
    return
15
16
; setze Adresspointer auf adr_l/adr_h im I2C-Device adr_e
17
18
i2c_set
19
20
    movfw   adr_e                 ; 1010 xxx0   I2C-Adresse für schreiben
21
    andlw   b'11111110'           ; letztes Bit 0 = write Data
22
    call    i2c_tx                ; sende I2C-Adresse
23
    movfw   adr_h
24
    call    i2c_tx                ; sende interne Adresse HI
25
    movfw   adr_l
26
    call    i2c_tx                ; sende interne Adresse LO
27
    return
28
29
; I2C-Bus im Master-Mode übernehmen - START
30
31
i2c_on
32
    bcf     PIR1, SSPIF           ; SSPIF Bit zurücksetzen
33
    bank1
34
    bsf     SSPCON2, SEN          ; start condition
35
    bank0
36
    btfss   PIR1, SSPIF           ; Bus schon frei ?
37
    goto    $-1                   ; 
38
    bcf     PIR1, SSPIF           ; ja, Bus ist übernommen
39
    return
40
41
; I2C-Bus im Master-Mode übernehmen - repeated START
42
43
i2c_ron
44
    bcf     PIR1, SSPIF           ; SSPIF Bit zurücksetzen
45
    bank1
46
    bsf     SSPCON2, RSEN         ; repeated start condition
47
    bank0
48
    btfss   PIR1, SSPIF           ; Bus schon frei ?
49
    goto    $-1                   ; 
50
    bcf     PIR1, SSPIF           ; ja, Bus ist übernommen
51
    return
52
53
; I2C-Bus wieder freigeben - STOP
54
55
i2c_off
56
    bank1
57
    bcf     PIR1, SSPIF           ; SSPIF Bit zurücksetzen
58
    bsf     SSPCON2, PEN          ; Bus Freigabe anweisen
59
    bank0
60
    btfss   PIR1, SSPIF           ; Bus schon frei ?
61
    goto    $-1                   ; 
62
    bcf     PIR1, SSPIF           ; ja, Bus frei
63
    return
64
  
65
; ein Byte aus W auf I2C senden
66
67
i2c_tx
68
    movwf   SSPBUF                ; W auf I2C-Bus senden
69
    btfss   PIR1, SSPIF           ; ACK schon empfangen ?
70
    goto    $-1                   ; 
71
    bcf     PIR1, SSPIF           ; ja
72
    return
73
74
; ein Byte vom I2C empfangen
75
76
i2c_rx
77
    bank1
78
    bsf     SSPCON2, RCEN        ; Daten Empfang einschalten
79
    bank0
80
    btfss   PIR1, SSPIF          ; Daten empfangen ?
81
    goto    $-1                  ; 
82
    bcf     PIR1, SSPIF          ; ja
83
    movf    SSPBUF, W            ; empfangene Daten -> W
84
    return

von Christoph M. (chrischu91)


Lesenswert?

@ Stefan: Danke für deine Rückmeldung, der Code geht trotzdem habe ich 
festgestellt ;), mit dem CCS C-Compiler ist dies möglich, dies habe ich 
nachdem ich den dofen Fehler festgestellt hatte trotzdem noch 
ausprobiert, und es funktionierte einwandfrei :D

Habe mir in den Ferien viel überlegt und auch ein paar mal den Code 
angeschaut, nun habe ich festgestellt, dass ich die 2 x I/O:
1
set_tris_c(0x98);  //  Eingang=1, Ausgang=0 (SCL & SDA sind auf dem C3 und C4 Register und müssen auf 1 gesetzt sein! so wie hier die korrigierte Version)

falsch gesetzt hatte, darum funktionierte auch nichts, ein dummer 
fehler, der mich viel Zeit gekostet hatte, aber ich danke euch trotzdem.

------

andere Frage:

jetzt habe ich aber noch so ein komischer Fehler festgestellt: Wenn ich 
jetzt den Code so anwende, kommt bei mir im Terminal das heraus, dass es 
immer nur jeder 4. Register im EEPROM beschreibt, wenn ich aber zwischen 
jedem Vorgang ein delay von 50ms hineinsetze, schreibt es jedes 
register. Dies trifft bei jeder Baudrate egal ob 115200 oder 9600 auf...
Könnte mir das jemand erklären? Ich verstehe das irgendwie nicht...

Danke
Gruss Christoph

von Unbekannt (Gast)


Lesenswert?

Das liegt daran, weil du wahrscheinlich keine ausreichende 
Fehlerbehandlung in deinen Routinen implementiert hast.

I2C-EEPROMs kannst du byteweise oder auch bis zur nächsten Pagegrenze 
hin in einem Rutsch beschreiben. Ein I2C-EEPROM hat dafür einen internen 
Puffer, der genauso groß ist, wie die Pagegröße. Diese steigt mit der 
EEPROM-Speichergröße an.

WIchtig ist hier aber, dass (siehe Datenblatt!) ein I2C-EEPROM 
(typischerweise) bis maximal 10ms für den internen Schreibvorgang 
braucht.
Das sieht dann SO aus, dass nach der Stop-Sequenz das EEPROM anfängt, 
die Daten aus dem Puffer in die EEPROM-Zellen zu schreiben.

Wichtig: In diesem Zustand meldet sich das EEPROM auf keine Zugriffe 
mehr. Es scheint, als wäre es nicht am Bus! Man kann daher nicht sagen, 
ob überhaupt kein EEPROM am Bus ist oder die falsche I2C-Slave-Adresse 
genutzt wurde oder das EEPROM gerade während des internen 
Schreibvorgangs ist.

Nutzt man ausreichende Fehlerrückgabewerte in seinen Routinen, die die 
entsprechenden Register des I2C-Moduls abfragen, dann wirst du 
wahrscheinlich feststellen, dass du das erste Byte fehlerfrei sendest, 
Bytes 2 und 3 aber mit einem Fehler quittiert werden = EEPROM nicht 
vorhanden/erreichbar. Byte 4 wird wieder einwandfrei geschrieben, da das 
EEPROM in der Zwischenzeit mit dem Schreiben fertig war.

Schreibst du z.B. eine ganze Page auf einmal, ist diese Zeit nach dem 
Stop-Kommando noch länger, als bei einem Byte.

Jetzt gibt es zwei allgemeine Ansätze, das in der Software zu 
bewerkstelligen:

1.) Nach einem Stop-Kommando triggert man das EEPROM einfach nur mit 
seiner Slave-Adresse an, bis es sich wieder meldet, sendet dann noch ein 
STOP hinterher und beendet dann die Schreibroutine. Das EEPROM ist dann 
auf jeden Fall beim nächsten Zugriff wieder bereit. Nachteil: Man wartet 
aktiv, was viel Zeit kostet (bis zu 10ms!). Hier muss auf jeden Fall ein 
Software-Timer genutzt werden, der dich aus der Schleife rettet, falls 
z.B. gar kein EEPROM verbaut ist oder das EEPROM defekt ist!

2.) Man schiebt den Timeout beim Schreiben hinein. Man versucht also 
z.B. mindestens 10ms das EEPROM zu erreichen, bis man (Software-Timer!) 
einen Fehler á la "EEPROM nicht erreichbar!" ausgibt. Vorteil: Man 
musste siehe 1. vorher nicht warten. Außerdem wird man durch 
anderweitige Softwareroutinen sowieso höchstwahrscheinlich zwischen den 
Schreibzugriffen unterbrochen. Dann ist die Gesamtperformance in dieser 
Reihenfolge besser. Bei 1. muss ich halt immer x ms warten, bei 2. kann 
es ja schon sein, dass ich durch meine eigenen Routinen "auch mal was 
anderes zu tun habe". Bei 2. warte ich also statistisch immer weniger, 
als bei 1.

Vorteilhaft ist hier ein Oszilloskop mit I2C-Analyser. Ich habe selbst 
mal stundenlang nach einem Fehler gesucht. Den habe ich dann mit dem 
passenden Oszilloskop in sehr kurzer Zeit gefunden. Ein 
Speicheroszilloskop tuts auch, ist aber unkomfortabler.

von Christoph M. (chrischu91)


Lesenswert?

Ich danke dir herzlich, für deine Bemühungen :)
Hat mir sehr viel gebracht, jetzt verstehe ich es endlich ;)

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.