Forum: Mikrocontroller und Digitale Elektronik i2c Speicher lesen/schreiben


von Tom R. (der_neue)


Lesenswert?

Hallo
ich habe einen 24LC32A EEPROM, den ich mit einem 16f876a Microkontroller 
beschreiben und auslesen will, allerdings will das einfach nicht 
funktionieren.

hier ist der Code den ich bei jetzt habe:
1
static unsigned char Schreibwert, Lesewert;
2
static bit ack = 0;
3
static int i = 0;
4
5
Schreibwert = 0;
6
InitI2C();
7
8
  while(1){
9
    LcdClr();
10
    LcdPos(0,0);
11
    LcdChar("Schreibwert:", 12);
12
    LcdNumber(Schreibwert);
13
    __delay_ms(1000);
14
15
    i2c_WriteTo(0xA8);
16
    i2c_SendByte(0x00);  //Adresse Highbyte
17
    i2c_SendByte(0x00);  //Adresse Lowbyte
18
    i2c_SendByte(Schreibwert);
19
    i2c_Stop();
20
21
    __delay_ms(50);
22
    
23
    i2c_WriteTo(0xA8);
24
    i2c_SendByte(0x00);  //Adresse Highbyte
25
    i2c_SendByte(0x00);  //Adresse Lowbyte
26
    i2c_ReadFrom(0xA8);
27
    Lesewert = i2c_GetByte(I2C_LAST);
28
    i2c_Stop();
29
30
    LcdPos(0,1);
31
    LcdChar("Lesewert:", 9);
32
    LcdNumber(Lesewert);
33
    __delay_ms(500);
34
35
    Schreibwert++;
36
  }
37
}
und als Initialisierung nutze ich:
1
void InitI2C(void){
2
  SSPADD = 9;
3
  SSPCON = 0b00101000;
4
  SSPSTAT = 0b10100000;
5
  SSPCON2 = 0;  
6
}
Auf dem Display wird als Lesewert immer 255 ausgegeben.

Ich hoffe ihr könnt mir helfen.

Tom

von Max H. (hartl192)


Lesenswert?

Tom R. schrieb:
> i2c_WriteTo(0xA8);
> i2c_ReadFrom(0xA8);
Senden diese Funktionen das Startbit mit?

Setzt die i2c_ReadFrom(0xA8) das LSb der I2C-Adresse automatisch auf 
'1'?

Auf welchen Pegeln sind die Pins A<2:0>?

von Tom R. (der_neue)


Lesenswert?

Der Code
i2c_WriteTo(address)
i2c_WriteTo(address)
 stammt von der HI-TECH compiler Software, wo dies als sample zu finden 
ist

an sich macht das folgendes:
1
 i2c_Start();
2
 i2c_SendByte(address | (rw?1:0))  //0 für write, 1 für read, adresse = 0xA8
3
 if(i2c_ReadAcknowledge()) 
4
   return TRUE;
5
 return FALSE;
Also Startbit wird gesendet und das Lsb wird entsprechend gesetzt.

und PORTA wird ziemlich weit am Anfang auf 0 gesetzt.
aber was hat das mit dem i2c-Bus zu tun?

von Tom R. (der_neue)


Lesenswert?

hat jemand noch eine Idee was ich falsch mache?, bzw hat jemand einen 
funktionierenden Code (zum i2c Speicer beschreiben /lesen), für diesen 
uC?

von Easylife (Gast)


Lesenswert?

Dir fehlt vermutlich eine Stop condition:

    i2c_WriteTo(0xA8);
    i2c_SendByte(0x00);  //Adresse Highbyte
    i2c_SendByte(0x00);  //Adresse Lowbyte

    i2c_Stop(); // <<<--- diese hier
    __delay_ms(50); // <<<--- zur Sicherheit


    i2c_ReadFrom(0xA8);   // <<<- ReadFrom erzeugt vermutlich als erstes 
eine Start-Condition bevor die Addresse gesendet wird

    Lesewert = i2c_GetByte(I2C_LAST);
    i2c_Stop();


Ansonsten würde ich empfehlen dir bei i2c_WriteTo() und i2c_ReadFrom() 
anzugucken, ob das EEProm jeweils ACKed.

von Easylife (Gast)


Lesenswert?

Vergiss das, was ich eben schrieb. Du brauchts hier keine stop 
condition.

Mir fallen 3 Gründe ein:

a) Adresse ist falsch (siehst du daran, dass schon die Adresse bei 
deinem Schreibzugriff nicht ge-ackt wird).

b) EEProm ist schreibgeschützt (WP pin)

c) Die Zeit zwischen Schreiben und Lesen ist zu kurz bemessen.
Eigentlich solltest du hier ein polling implementieren. Solange das 
EEProm seine Adresse nicht acked, ist es noch mit dem Wegschreiben 
deiner Daten beschäftigt.
Testweise kannst du hier ja einfach mal länger warten (1/2 Sekunde z.B)

von Matthias (Gast)


Lesenswert?

Kleiner Tip:
Organisier Dir einen Logic-Analyzer und schau damit mal was auf der 
"LowLevel" vom und zum EEPROM an Daten läuft.

Ein Teil, das ich auch in der Firma einsetzte ist der hier
"www.saleae.com/ -> LOGIC8" (Kostet ca. 150..200 Euro)

Damit hab ich schon einige Probleme mit Schnittstellen I2C, SPI,...
analysiert und festgestellt, wo ich in meinem Code Murks gebaut hab,
oder ob mir da irgendwas anderes in die Suppe spuckt.
Hab mir auch schon ein eigenes Plugin für eine etwas unhandliche 
Schnittstelle geschrieben. Das Teil ist was den Preis und die Funktionen
angeht echt spitze.
Ein Oszi geht zwar auch, aber das raus-picken der Bits ist sehr mühsam.

Einige Probleme bei I2C können sein:
1.
Adresse des Slaves, bzw, wie die angegeben wird. Da ich aber weder deine 
Schaltung kenne, noch die Doku zu den verwendeten Funktionen, kann ich 
da keine konkretere Aussage machen. Manche I2C Funktionen erwarten die 
Slave-Adresse als 1:1 Wert, wie bei deinem Codebeispiel als 0xA8. Andere 
würde das ein Bit nach rechts geschoben erwarten, also 0x54 (als 7-Bit 
Adresse, bei der das R/W-Bit nicht einbezogen ist).

2.
Hardwareprobleme: Bus-Idle Pegel nicht vorhanden - Pullups fehlen,
zu viel Kapazität auf den Busleitungen für die Geschwindigkeit
(sieht man nicht im Logic-Analyzer, aber mit einem Oszi)

Leider weiss ich nicht, ob die Hi-Tech Routinen Bit-Banging oder 
I2C-Hardware verwenden. Allerdings gehe ich in so einem Fall immer so 
vor, dass ich erst die Hardware prüfe (Oszi -> Stimmen die Bus-Pegel und 
tut sich was, LogikAnalyzer -> Was laufen für Daten). Im Analyzer sieht 
man dann recht schnell, ob man z.B. die korrekte Adresse hat und der 
SLave antwortet.

Wenn die Obige I2C-Read-Routine z.B. eine Bit-Banging Routine wäre, dann 
kann da schon 0xFF rauskommen, wenn der Slave nicht antwortet. 
Dummerweise ist auch der Lieferzustand der EEPROM-Zellen 0xFF, so dass 
man hier keine Unterscheidung machen kann.

Wenn die Funktionen einen Rückgabewert haben, z.B. ob vom SLave ein ACK 
kam, oder nicht, dann empfehle ich Dir den auch auszuwerten!

Ich hoffe das hilft ein wenig weiter.

von Matthias (Gast)


Lesenswert?

Misst noch was vergessen:

@Easylife: Laut Datenblatt dauert ein WriteCycle 5ms.

Er hat 50 drin. Wenn das EEPROM nach seinen 50ms noch nicht fertig ist,
dann wäre es reif für die Tonne ;-)
Außerdem sollte ein Faktor 2, also max. 10ms völlig ausreichen.
Mir ist bisher auch noch kein I2C-EEPROM untergekommen, da nicht nach 
spätesten 10ms mit schreiben fertig ist (laut Datenblatt).

von Tom R. (der_neue)


Angehängte Dateien:

Lesenswert?

Danke erstmal für die Ideen.

@ Matthias ... ich bevorzuge lieber das was ich schon habe zu nutzen, 
ein gutes altes Speicheroszi muss dafür also reichen.

Also erstmal zum Schaltungsaufbau was: Der uC ist auf dem Board aus dem 
Buch "Mikrocontroller für Einsteiger" verbaut. Schaltungstechnisch mache 
ich mir also keine Sorgen. In dem Buch gibt es zwar zahlreiche Beispiele 
zu dem Board, allerdings sträube ich mich dagegen etwas mit Assembler zu 
programmieren. (Ich habe also bereits ein funktionierendes Programm zum 
Lesen und Schreiben des EEPROM's, allerdings in Assembler - Anhang 
~Messwertspeicherung).
Die Adresse für den Speicher habe ich aus diesem Programm entnommen.
ABER

Ich bin mal mit nen Oszi dran gehangen und da ist bei meinem Programm 
gar nichts auf SDA oder SDL zu sehen.

Also scheint das Problem schon beim "InitI2C()" zu liegen?!

von Easylife (Gast)


Lesenswert?

"Gar nichts" heist was? Beide Leitungen low oder high.
Hast du an die 2.2k pullups an SDA und SCL gedacht?

von Tom R. (der_neue)


Lesenswert?

@Easylife
wie gesagt ... Schaltungstechnisch mache ich mir keine Sorgen. (gar 
nichts sollte High-Pegel heißen).

Inzwischen funktioniert das Ganze sogar (aber mit einem komplett anderen 
/ neuen Code) nur brauche ich nochmal für eine scheinbar simple Sache 
eure Hilfe:
Ich möchte, wenn ich mehrere Bytes auf einmal auslese ein Array von 
einer Funktion zurückbekommen. Theoretisch scheint mir das recht 
einfach, aber praktisch will der Kompiler das nicht so wie ich will.
1
unsigned char I2CRead[];
2
I2CRead = readEEPROMchar (0xA8, 0x0000, 4);

in der 'readEEPROMchar()' steht:
1
unsigned char* readEEPROMchar (char adr, int sadr, int amount){
2
  unsigned char data[];
3
  
4
  I2CStart();
5
  I2CSend(adr);    //i2c - slave Adresse
6
  
7
  I2CSend(sadr / 256);  //Speiceradresse
8
  I2CSend(sadr % 256);
9
  
10
  I2CRestart();
11
  I2CSend(adr+1);
12
  for (int i = 0; i < amount; i++) {
13
    data [i] = I2CRead();
14
    if (i == (amount-1)) I2CAck();
15
    else I2CNak();
16
  }
17
  I2CStop();
18
  return data;
19
}

und die Fehlermeldung lautet:
"Error   [202] ... 45.51 nur 'lvalues' darf etwas zugewiesen oder dürfen 
geändert werden"

hat jemand eine Idee wie es besser geht?

von Easylife (Gast)


Lesenswert?

1. Du hast in der Funktion readEEPROMchar() ein array data[] ohne 
Größenangabe deklariert.

2. Der Rückgabewert ist ein Pointer auf dieses Array (auf Element 0).
Da das Array aber lokal in der Funktion existiert, ist der Pointer nach 
Beendigung von readEEPROMchar() nicht mehr gültig.

Du könntest data[] entweder global deklarieren (unschön), oder in der 
aufrufenden Funktion, und einen Pointer auf dieses Array an 
readEEPROMchar() übergeben (so macht man das üblicherweise in C).

Also in etwa so:


int readEEPROMchar (unsigned char *data, char adr, int sadr, int amount)
{
  // (...)
  for (int i = 0; i < amount; i++) {

    data[i] = I2CRead();  // store byte in buffer

    if (i == (amount-1)) I2CAck();
    else I2CNak();
  }
  // (...)

  return 1; // bzw eben eine Information, ob der Read-Zugriff 
erfolgreich war
}


#define MAX_I2C_READ_SIZE (100) // an die individuellen Gegebenheiten 
anpassen

main()
{
  unsigned char i2c_read_buf[MAX_I2C_READ_SIZE];
  int result;

  result = readEEPROMchar (i2c_read_buf, 0xA8, 0x0000, 4);
}

von Easylife (Gast)


Lesenswert?

Sollte dein Compiler die Zeile

    data[i] = I2CRead();  // store byte in buffer

nicht fressen, versuche es mit stattdessen mit den folgenden 2 Zeilen:

    *data = I2CRead();  // store byte in buffer
    data++;             // increment pointer


Das 1. Argument (i2c_read_buf) beim Aufruf von readEEPROMchar ist also 
in Wirklichkeit die Adresse des ersten Elementes des Arrays.

Kann man auch so schreiben:

&i2c_read_buf[0]

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.