Forum: Mikrocontroller und Digitale Elektronik Atmega 328p uint16_t auf EEPROM per SPI geht nicht


von Andy (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

bin noch ziemlich neu in der Microcontrollerwelt und möchte ein EEPROM 
(25LC640A) mit einem uint16_t Wert beschreiben und wieder auslesen.

Ich benutzt ein Atmega 328p und das ganze soll über SPI ablaufen. 
Sprache wäre C.

Was bereits funktioniert:

SPI_init klappt...

Kann auch ohne Probleme eine uint8_t Wert auf ein beliebige Adresse 
schreiben und wieder auslesen...

Was nicht funktioniert:

Jedoch sobald ich uin16_t versuche kommt auf dem LCD nur Käse raus. Bin 
schon ziemlich am verzweifeln woran das liegen könnte. Hoffe ihr habt 
eine Idee. Wahrscheinlich ist es nur ein Anfänger oder Verständnis 
Fehler.

Hier mein Code fürs schreiben und lesen:
1
/* einfache Funktion, um via SPI zu lesen und zu schreiben */
2
unsigned char spi_transfer(unsigned char data)
3
{
4
    SPDR = (unsigned char) data;              //SPI Data Register transmit
5
    while (!(SPSR & (1<<SPIF)));              //SPI Status Register, SPI Interrupt Flag wait for transmission complete
6
    return SPDR;                              //SPI Data Register return received data 
7
}
8
9
///////////////////////////////////////
10
// uint_16_t auf eeprom schreiben
11
/////////////////////////////////////////
12
13
// Adresse in die geschrieben werden soll
14
uint16_t adresse = 0;
15
// Daten die geschrieben werden sollen
16
uint16_t data_schreiben = 1234;
17
18
//CS auf low
19
spi_cs(0);
20
// Schreibvorgang einleiten
21
spi_transfer(WREN);
22
// CS auf high
23
spi_cs(1);
24
//wartezeit CS
25
_delay_us(1);
26
//CS auf low
27
spi_cs(0);
28
29
// Adresse fürs schreiben in MSB und LSB
30
//Schreibmodus aktivieren
31
spi_transfer(WRITE);
32
// Schreibadresse für MSB
33
spi_transfer((unsigned char) adresse>>8);
34
// Schreibadresse für LSB
35
spi_transfer((unsigned char) adresse);
36
37
        //transmit data MSB
38
        spi_transfer((unsigned char) data_schreiben>>8);
39
        //transmit data LSB
40
        spi_transfer((unsigned char) data_schreiben);
41
42
// schreibvorgang übermitteln
43
spi_cs(1);
44
// 5 ms Wartezeit für Schreibvorgang
45
_delay_ms(5);
46
// CS auf low
47
spi_cs(0);
48
// Schreibmodus verlassen
49
spi_transfer(WRDI);
50
// CS auf high
51
spi_cs(1);
52
// Wartezeit CS
53
_delay_us(1);
54
55
//schreiben ist ab hier abgeschlossen
56
////////////////////////////////////////////////////
57
/// EEPROM lesen
58
//////////////////////////////////////////////////
59
60
// Adresse aus der gelesen werden soll
61
uint16_t adresse = 0;
62
uint16_t data_auslesen = 0;
63
64
65
/** read msb + lsb**/
66
// CS auf low
67
spi_cs(0);
68
// In Lesemodus wechseln
69
spi_transfer(READ);
70
// Schreibadresse für MSB
71
spi_transfer((unsigned char) adresse>>8);
72
// Schreibadresse für LSB
73
spi_transfer((unsigned char) adresse);
74
// Eine Null wird als Dummy benutzt und der Wert des EEPROMs ausgelesen.
75
// Der wird wird der Variablen Ausgelesener_Wert_EEPROM überschrieben.
76
77
 //read data LSB by dummy transmission
78
data_auslesen = data_auslesen + (spi_transfer(0)<<8); 
79
//read data MSB by dummy transmission
80
data_auslesen = data_auslesen + spi_transfer(0);

Danke schon einmal.

: Bearbeitet durch Moderator
von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Andy schrieb:
> Kann auch ohne Probleme eine uint8_t Wert auf ein beliebige Adresse
> schreiben und wieder auslesen...

... glaube ich nicht, da du nicht das page-weise Write beachtet hast.

Andy schrieb:
> einem uint16_t Wert beschreiben und wieder auslesen

... du must den Datentyp selber unterstützen, also hier 2x read/write 
byte und das int korrekt darauf abbilden via z.B. union Definition.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Andy schrieb:
> Hoffe ihr habt eine Idee.
1
//CS auf low
2
spi_cs(0);
3
4
// Adresse fürs schreiben in MSB und LSB
5
//Schreibmodus aktivieren
6
spi_transfer(WRITE);
7
// Schreibadresse für MSB
8
spi_transfer((unsigned char) adresse>>8);
9
// Schreibadresse für LSB
10
spi_transfer((unsigned char) adresse);
11
12
        //transmit data MSB
13
        spi_transfer((unsigned char) data_schreiben>>8);
14
        //transmit data LSB
15
        spi_transfer((unsigned char) data_schreiben);
16
17
// schreibvorgang übermitteln
18
spi_cs(1);
Kann dein EEPROM diese Kommandofolge?
Ist das Kommando WRITE für mehr als 1 Byte geeignet?

> Hier mein Code fürs schreiben und lesen
Da bitte künftig selber die [c] Tags drumrum machen, wie über jeder 
Eingabebox bei "Wichtige Regeln - erst lesen, dann posten!" beschrieben.

von Andy (Gast)


Lesenswert?

Ok ohne die Page zu beachten, könnte in meinem Fall bei unbedachter Wahl 
der Adresse ein überlauf entstehen. oder?

Aber wie bildet ich das ab mit 2 mal read / write? Muss ich vorher die 
unint16_t in 2 x 8 teilen? und dann erst übertragen und lesen und wieder 
zusammen setzen?

Wenn ja dann wie?

Danke im schon einmal.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Andy schrieb:
> Ok ohne die Page zu beachten, könnte in meinem Fall bei unbedachter Wahl
> der Adresse ein überlauf entstehen. oder?
Ja. Ist beim code hier aber nicht der Fall. Denn du schreibst ja nur 2 
Byte am Anfang von Page 0.

> Aber wie bildet ich das ab mit 2 mal read / write? Muss ich vorher die
> unint16_t in 2 x 8 teilen? und dann erst übertragen und lesen und wieder
> zusammen setzen?
Tust du ja eh' schon, müsste also gehen.

Andy schrieb:
> Kann auch ohne Probleme eine uint8_t Wert auf ein beliebige Adresse
> schreiben und wieder auslesen...
>
> Was nicht funktioniert:
> Jedoch sobald ich uin16_t versuche kommt auf dem LCD nur Käse raus.
Im Code oben ist kein LCD involviert.
Und was kommt heraus, wenn du das 16-Bit Wort so schreibst, wie oben 
dargestellt, dann aber ein Byte von Adresse 0 liest und das als char 
darstellst, und danach das Byte von Adresse 1 liest und als char 
darstellst?

Oder was passiert, wenn du diese beiden Bytes nacheinander einzeln 
schreibst und dann als 16-Bit-Wort ausliest?

von jo mei (Gast)


Lesenswert?

Wenn ich meinen Universal-EEPROM-Code so anschaue dann muss das
Schreiben auf ein solches serielles EEPROM etwas differenzierter
geschehen.

- STATUS lesen
- Wenn Block Protection Bits gesetzt sind, diese löschen
- Wenn nicht dann ohne Löschen weiter

Dann erst Write Enable setzen, dann Daten schreiben.

Die Block Protection Bits muss man wohl nicht immer neu
löschen, möglicherweise würde durch dauerndes Löschen
diese eine Zelle (Register) langsam vorzeitig
verschlissen werden.

Aber mit gesetzten Block Protection Bits wird man das
EEPROM nicht beschreiben können .....

von Andy (Gast)


Lesenswert?

Danke erstmal für eure Antworten!

Ich lese am Anfang das Statusregister aus, um zu schauen ob die 
Anschlüsse alle funktionieren. Das geht also. Was du weiter mit den 
"Block Proctection Bits" meinst weiß ich leider nicht. (-> Verweis auf 
Anfänger)

>Im Code oben ist kein LCD involviert.

Den Part hab ich nicht mit veröffentlich, da ich nicht wollte, dass der 
Code zulange wird den ich hier poste.

>Und was kommt heraus, wenn du das 16-Bit Wort so schreibst, wie oben dargestellt, 
dann aber ein Byte von Adresse 0 liest und das als char darstellst, und danach das 
Byte von Adresse 1 liest und als char  darstellst

ok hab folgendes probiert:

Hab den Wert "1234" als uint16_t auf Adresse (0) geschrieben. Danach 
über meine funktionierende spi_read_uint8_t () Funktion ausgelesen. 
Kommt 210 auf dem LCD raus. Da ja 1234 Dezimal = 0000 0100 1101 0010 
Binär sind und das LSB hier ja genau 210 ist sollte das passen. Der Wert 
wird in Adresse (1) angezeigt

Jedoch steht in Adresse (0) beim auslesen der Wert 0 auf dem LCD.

Beschreibe ich eine uint8_t nochmals auf Adresse (0) mit einem 
beliebigen Wert z.B.: 99 wird dieser auch korrekt ausgelesen.
Dieser müsste ja jetzt auf dem EEPROM dauerhaft gespeichert werden.

Benutzte ich jetzt nochmals meinen wirte uint16_t Code zeigt mir das LCD 
wieder die 210 in Adresse (1) und 0 in Adresse (0) angezeigt.

Sollte aber eigentlich eine 4 rauskommen, da der vordere Teil der 
geteilten 16 Bits ja 0000 0100 = 4 sein sollte.

von jo mei (Gast)


Lesenswert?

Andy schrieb:
> Was du weiter mit den
> "Block Proctection Bits" meinst weiß ich leider nicht.

RTFM!

Einfach mal Datenblatt lesen. Wenn du das nicht willst dann
mach irgendwas anderes. Aber nicht EEPROM programmieren.

von Andy (Gast)


Lesenswert?

Ich weiß was eine Page Protection ist. Jedoch versteh ich nicht was das 
hiermit zu tun hat. (das mein ich mit Anfänger)

Aber Page Protection ist nicht an. Sonst würde das beschreiben mit einem 
uint8_t Wert auf Adresse (0) oder (1) nicht funktionieren.

Oder stehe ich gerade auf dem Schlauch.

Und Ja ich hab das Datenblatt gelesen. Sonst wüsste ich ja nicht wie ich 
CS high oder low setzen sollte etc.

von Andy (Gast)


Lesenswert?

Ssooo danke erstmal an Lothar!

Hast mich auf die richtige Spur gebracht. Konnte den Fehler immer mehr 
eingrenzen. Hat jetzt geklappt.

Lag wirklich am schreiben und nicht an der lese Funktion.

Warum auch immer geht die Bit-Manipulation nicht in der Zeile:
   >     spi_transfer((unsigned char) data_schreiben>>8);

hab somit vorher die "Data" Werte in MSB und LSB aufteilen müssen.
Am Ende alles in eine Funktion / Libarry ausgelagert.
1
uint16_t spi_eeprom_lesen_uint16_t (uint16_t adresse)
2
{
3
4
uint16_t data_auslesen = 0;
5
6
if (adresse < 8192)
7
    {
8
9
// CS auf low
10
spi_cs(0);
11
// In Lesemodus wechseln
12
spi_transfer(READ);
13
// Schreibadresse für MSB
14
spi_transfer((unsigned char) adresse>>8);
15
// Schreibadresse für LSB
16
spi_transfer((unsigned char) adresse);
17
// Eine Null wird als Dummy benutzt und der Wert des EEPROMs ausgelesen.
18
// Der wird der Variablen Ausgelesener_Wert_EEPROM überschrieben.
19
20
 //read data LSB by dummy transmission
21
data_auslesen = data_auslesen + (spi_transfer(0)<<8);
22
//read data MSB by dummy transmission
23
data_auslesen = data_auslesen + spi_transfer(0);
24
25
26
//Um 2 mal hinter einander auszulesen muss cs auf high gesetzt werden
27
    // CS auf high
28
    spi_cs(1);
29
    // Wartezeit CS
30
    _delay_us(1);
31
    }
32
33
    // Der Rückgabewert data_auslesen wird aus der Funktion ausgegeben.
34
    return data_auslesen;
35
36
// auslesen EEPROM abgeschlossen
37
38
}

danke nochmal für die Hilfe!

von Andy (Gast)


Lesenswert?

ups sorry meine Fehler...

Falsche Funktion wollte die write posten. Naja vielleicht helfen der 
Nachwelt somit beide.

Hier nochmal die Schreibfunktion:
1
// einen uint16_t Wert als Daten auf das EEPROM schreiben
2
void spi_eeprom_schreiben_uint16_t (uint16_t adresse, uint16_t daten)
3
{
4
5
    if (adresse < 8192)
6
    {
7
8
    // uint_16_t auf eeprom schreiben
9
10
// Data wird in MSB und LSB aufgteilt
11
// Das MSB von Data
12
uint8_t msb = daten >> 8;
13
// Das LSB von Data
14
uint8_t lsb = daten & 0xFF;
15
16
//CS auf low
17
spi_cs(0);
18
// Schreibvorgang einleiten
19
spi_transfer(WREN);
20
// CS auf high
21
spi_cs(1);
22
//wartezeit CS
23
_delay_us(1);
24
//CS auf low
25
spi_cs(0);
26
27
// Adresse fürs schreiben in MSB und LSB
28
//Schreibmodus aktivieren
29
spi_transfer(WRITE);
30
// Schreibadresse für MSB
31
spi_transfer((unsigned char) adresse>>8);
32
// Schreibadresse für LSB
33
spi_transfer((unsigned char) adresse);
34
35
        //transmit data MSB
36
        spi_transfer((unsigned char) msb);
37
         //transmit data LSB
38
        spi_transfer((unsigned char) lsb);
39
40
// schreibvorgang übermitteln
41
spi_cs(1);
42
// 5 ms Wartezeit für Schreibvorgang
43
_delay_ms(5);
44
// CS auf low
45
spi_cs(0);
46
// Schreibmodus verlassen
47
spi_transfer(WRDI);
48
// CS auf high
49
spi_cs(1);
50
// Wartezeit CS
51
_delay_us(1);
52
}
53
//schreiben ist ab hier abgeschlossen
54
}

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Andy schrieb:
> Ssooo danke erstmal an Lothar!
Keine Ursache.

> Hast mich auf die richtige Spur gebracht.
Wenn immmer möglich sollte man bei der Fehlersuche das "Teile und 
Herrsche" Prinzip anwenden und versuchen, das "große Problem" in 
einzelne "kleine Probleme" zu zerlegen.

> Konnte den Fehler immer mehr eingrenzen.
Aber du hast ihn noch nicht gefunden, sondern einen windigen 
"Workaround" gebastelt. Denn der eigentliche Fehler liegt da:
> Warum auch immer geht die Bit-Manipulation nicht in der Zeile:
1
spi_transfer((unsigned char) data_schreiben>>8);
Das würde ich an deiner Stelle genauer anschauen. Du lernst dabei fürs 
Leben  ;-)

> geht die Bit-Manipulation nicht
Klar, denn der Compiler beachtet keine Whitespaces bzw. die 
Textformatierung. Wenn du mit dem Leerzeichen im Code:
1
(unsigned char) data_schreiben>>8
auch gern das gehabt hättest:
1
(unsigned char) (data_schreiben>>8)
also: zuerst wird geschoben und dann abgeschnitten,
sieht der Compiler trotzdem das:
1
((unsigned char)data_schreiben) >> 8
Merkst du was?
Hier werden zuerst die 8 "oberen" Bits von data_schreiben 
abgeschnitten und dann die unteren 8 Bits rausgeschoben.
Übrig bleibt 0.

Und dass genau der selbe Code dort "funktioniert" hat:
1
spi_transfer((unsigned char) adresse>>8);
liegt einfach daran, dass du bisher nur in den unteren 256 Bytes des 
EEPROMs herumwerkelst und die "oberen Bits" sowieso 0 sind.

Also mach mal das, dann geht es auch mit dem Code ganz oben:
1
spi_transfer((unsigned char)(adresse>>8));
2
...
3
spi_transfer((unsigned char)(data_schreiben>>8));

Stichwort dazu: Priorität von Operanden und Casts.

: Bearbeitet durch Moderator
von Andy (Gast)


Lesenswert?

Haaa.... Darauf wäre ich nie gekommen. Im Nachhinein logisch. Vielen 
Dank nochmal!

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.