Forum: Mikrocontroller und Digitale Elektronik Fehlermeldungen beim compilieren von SPI Code


von Steff (Gast)


Lesenswert?

Hallo,
Habe ein Quell Code geschrieben um Daten von einem uC über SPI in ein 
externes Flash zu schreiben.
Benutze den LPC2129 und das flash At45Db161.
Beim compilieren kommen u.a. folgende Fehlermeldungen:

              SPI_CS_FLASH ~= (1<<SPI_CSB);  // CSB auf Null setzen
SPI.c(281,1):  syntax error before '~' token

              S0SPDR = opcode; //MAIN_MEMORY_PAGE_READ;
SPI.c(282,1):  'opcode' undeclared (first use in this function)
SPI.c(282,1):  (Each undeclared identifier is reported only once
SPI.c(282,1):  for each function it appears in.)


for (unsigned char k=0;k<7;k++)
   {
      S0SPDR = adressfeld[k];               // Daten an SPI
      while (!(S0SPSR & (SPI_SPIF)));       // Warten bis Daten gesendet
   }
SPI.c(285,1):  'for' loop initial declaration used outside C99 mode

      *data.pagecounter = (S0SPDR << 8);
SPI.c(296,1):  'data' undeclared (first use in this function)



SPI.c(405,1):  'for' loop initial declaration used outside C99 mode
SPI.c(424,1):  left shift count >= width of type
\SPI.c(424,1):  invalid lvalue in assignment

Weiß jemand evtl was mit denen anzufangen oder soll ich dazu noch den 
passenden Quell Code mitschicken?
Bin halt noch ein Anfänger was dies angeht und weiß nicht wie ich die 
Fehler beseitigen kann.

Bei der fehlermeldung SPI.c(296,1): Hab eine Struktur mit dem Namen 
SPIDataBlock. Wie übergebe ich denn eine Struktur in einer Funktion?

Kann mir da jemand weiterhelfen?Wäre Euch sehr dankbar.
Grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
>               SPI_CS_FLASH ~= (1<<SPI_CSB);  // CSB auf Null setzen
> SPI.c(281,1):  syntax error before '~' token
Versuche stattdessen:
SPI_CS_FLASH = SPI_CS_FLASH & (~(1<<SPI_CSB));
>               S0SPDR = opcode; //MAIN_MEMORY_PAGE_READ;
> SPI.c(282,1):  'opcode' undeclared (first use in this function)
> SPI.c(282,1):  (Each undeclared identifier is reported only once
> SPI.c(282,1):  for each function it appears in.)
Tja, "opcode" kennt der Compiler offenbar nicht. Wo ist denn das 
definiert?

> for (unsigned char k=0;k<7;k++)
>    {
>       S0SPDR = adressfeld[k];               // Daten an SPI
>       while (!(S0SPSR & (SPI_SPIF)));       // Warten bis Daten gesendet
>    }
> SPI.c(285,1):  'for' loop initial declaration used outside C99 mode
Versuche es so:
unsigned char k = 0;
for (; k < 7; k++)
  { ...
Im alten C-Standard, auf den der Compiler offenbar eingestellt ist, darf 
man Variablen nicht so im for-Konstrukt definieren. So wie angegeben 
sollte es gehen.
>       *data.pagecounter = (S0SPDR << 8);
> SPI.c(296,1):  'data' undeclared (first use in this function)
Auch hier: Der Compiler findet "data" nicht. Es ist offenbar nicht oder 
an falscher Stelle deklariert.
> SPI.c(405,1):  'for' loop initial declaration used outside C99 mode
Hier fehlt der dazugehörige Code - aber wahrscheinlich musst du hier 
genauso wie oben den 1. Teil in for() vor die Schleife schreiben.
> SPI.c(424,1):  left shift count >= width of type
Ohne Code... schwierig :)
> \SPI.c(424,1):  invalid lvalue in assignment
dito...
> Weiß jemand evtl was mit denen anzufangen oder soll ich dazu noch den
> passenden Quell Code mitschicken?
Das wäre schon ganz gut.
> Bin halt noch ein Anfänger was dies angeht und weiß nicht wie ich die
> Fehler beseitigen kann.
Tutorials durchgearbeitet ... ?
> Bei der fehlermeldung SPI.c(296,1): Hab eine Struktur mit dem Namen
> SPIDataBlock. Wie übergebe ich denn eine Struktur in einer Funktion?
SPIDataBlock data;
NameDerFunktion (&data);
Ob das & da hin muss, hängt davon ab, ob bei der Deklaration der 
Funktion "SPIDataBlock* data" steht (dann muss das & hin) oder nur 
"SPIDataBlock data" (ohne * => das & weglassen)
Ohne den kompletten Code kann man bei so einzelnen Fragmenten kaum was 
korrigieren.
So wie das aussieht, hast du einen fertigen Code kopiert, und mit einem 
anderen Compiler compiliert. Vor allem als Anfänger ist es ganz gut, 
sich durchzulesen, wie so ein Flash angesprochen wird, und das dann 
selber zu programmieren. So lernt man gut dazu.

von Steff (Gast)


Lesenswert?

Das opcode übergebe ich mit dieser funktion:
Prototyp:
extern void ReadDataFromFlash(unsigned short page,unsigned short 
startbyte, unsigned short opcode,struct SPIDataBlock* data);

void ReadDataFromFlash(unsigned short page,unsigned short startbyte, 
unsigned short opcode,SPIDataBlock* data)
{

Wenn ich es so schreib,dann müsste es doch eigentlich erkannt 
werden,oder?

Das mit der FOR Schleife hat geklappt.

Hab den Code fast komplett alleine geschrieben,deshalb sind da auch so 
viele Fehler drinn. Da wird man echt verrückt wenn nichts klappt.
Was bräuchtest du denn von dem Quellcode, wegen en anschaun?
Wär echt so super nett von dir wenn mir weiterhelfen könntest.

grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> Wenn ich es so schreib,dann müsste es doch eigentlich erkannt
> werden,oder?
Eigentlich ja, das sieht so ziemlich richtig aus.
> Was bräuchtest du denn von dem Quellcode, wegen en anschaun?
Am besten die ganze Funktion inklusive der Stellen, an der die Fehler 
auftreten.
> SPI.c(424,1):  left shift count >= width of type
> \SPI.c(424,1):  invalid lvalue in assignment
Und diese Zeile 424 auch, plus die Stellen, an der die in dieser Zeile 
vorkommenden Variablen deklariert sind.
> Hab den Code fast komplett alleine geschrieben,deshalb sind da auch so
> viele Fehler drinn.
Ups, und ich wollte dich schon des kopierens beschuldigen ^^

von Steff (Gast)


Angehängte Dateien:

Lesenswert?

Also hier wären mal die codes in der reichlich fehler vorkommen.

von Steff (Gast)


Angehängte Dateien:

Lesenswert?

at45db161

von Steff (Gast)


Angehängte Dateien:

Lesenswert?

spi.h

von Steff (Gast)


Angehängte Dateien:

Lesenswert?

spi.c

von Steff (Gast)


Lesenswert?

kannst du damit schon was anfangen?
Meine anderen fehlermeldungen sind u.a.:
Die fehlermeldungen hängen glaub fast alle an dem selben Problem, weiß 
nur nicht an welchem?!
at45db.h(57,1):  useless storage class specifier in empty declaration
at45db.c(6,1):  syntax error before 'data'
at45db.c(6,1):  data definition has no type or storage class
at45db.c(17,1):  syntax error before numeric constant
at45db.c(23,1):  conflicting types for 'PageCounter'
at45db.c(8,1):  previous declaration of 'PageCounter' was here
at45db.c(23,1):  request for member 'pagecounter' in something not a 
structure or union
at45db.c(23,1):  data definition has no type or storage class
at45db.c(24,1):  syntax error before '.' token
at45db.c(25,1):  conflicting types for 'StartByteCounter'
at45db.c(9,1):  previous declaration of 'StartByteCounter' was here
at45db.c(25,1):  data definition has no type or storage class
at45db.c(31,1):  conflicting types for 'StartByteCounter'
at45db.c(9,1):  previous declaration of 'StartByteCounter' was here
at45db.c(31,1):  request for member 'startbytecounter' in something not 
a structure or union
at45db.c(31,1):  data definition has no type or storage class
at45db.c(37,1):  conflicting types for 'PageCounter'
at45db.c(8,1):  previous declaration of 'PageCounter' was here
at45db.c(37,1):  request for member 'pagecounter' in something not a 
structure or union
at45db.c(37,1):  data definition has no type or storage class
at45db.c(38,1):  syntax error before '.' token
at45db.c(39,1):  conflicting types for 'StartByteCounter'
at45db.c(9,1):  previous declaration of 'StartByteCounter' was here
at45db.c(39,1):  data definition has no type or storage class
at45db.c(43,1):  syntax error before numeric constant
at45db.c(43,1):  conflicting types for 'writeBuffer'
at45db.h(64,1):  previous declaration of 'writeBuffer' was here
at45db.c(43,1):  data definition has no type or storage class
at45db.c(47,1):  syntax error before numeric constant
at45db.c(47,1):  data definition has no type or storage class
at45db.c(48,1):  syntax error before numeric constant
at45db.c(48,1):  conflicting types for 'writePage'
at45db.h(63,1):  previous declaration of 'writePage' was here
at45db.c(48,1):  data definition has no type or storage class
at45db.c(54,1):  syntax error before '*' token
at45db.c(57,1):  syntax error before '-' token
at45db.c(57,1):  conflicting types for 'ReadDataFromFlash'
at45db.c(57,1): note: an argument type that has a default promotion 
can't match an empty parameter name list declaration
at45db.h(68,1):  previous declaration of 'ReadDataFromFlash' was here
at45db.o" was not generated.
"
at45db.h(57,1):  useless storage class specifier in empty declaration
SPI.c(31,1):  syntax error before 'SPIDataBlock'
SPI.c(32,1):  number of arguments doesn't match prototype
at45db.h(63,1):  prototype declaration
SPI.c(42,1):  syntax error before '~' token
SPI.c(44,1):  'opcode' undeclared (first use in this function)
SPI.c(44,1):  (Each undeclared identifier is reported only once
SPI.c(44,1):  for each function it appears in.)
SPI.c(53,1):  left shift count >= width of type
SPI.c(53,1):  invalid lvalue in assignment
SPI.c(87,1):  syntax error before 'SPIDataBlock'
SPI.c(88,1):  number of arguments doesn't match prototype
at45db.h(64,1):  prototype declaration
SPI.c(99,1):  syntax error before '~' token
SPI.c(101,1):  'opcode' undeclared (first use in this function)
SPI.c(110,1):  'data' undeclared (first use in this function)
SPI.c(129,1):  'for' loop initial declaration used outside C99 mode
SPI.c(136,1):  left shift count >= width of type
SPI.c(136,1):  invalid lvalue in assignment
SPI.c(140,1):  conflicting types for 'SPI_WriteBuffer'
SPI.c(140,1): note: an argument type that has a default promotion can't

Kannst du damit was anfangen?
grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Kannst du die at45db.h mal posten? (du hast die SPI.h 2x gepostet :)
Ist in der Datei auch die Definition von SPIDataBlock drin? Die bräuchte 
ich auch mal.

von Steff (Gast)


Angehängte Dateien:

Lesenswert?

ja genau, die definition ist da drinn.
kannst du mit dem code irgendwas anfangen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Da haben wir den Übeltäter:
1
typedef struct SPIDataBlock {    // 1Struktur = 44Byte
2
 ... blabla ...
3
};
structs verwendet man so:
1
struct ein_toller_name {
2
  int a; char b;
3
};
Und dann muss man aber immer "struct ein_toller_name" schreiben. Da das 
viel zu umständlich ist, gibt es "typedef foo bar;" - das definiert 
einen neuen Datentyp namens bar, der genau das gleiche enthält wie foo. 
Wenn du also schreibst "typedef struct ein_toller_name blar;" hat danach 
"blar" die gleiche Bedeutung wie "struct ein_toller_name". Du könntest 
also schreiben:
1
struct SPIDataBlock {    // 1Struktur = 44Byte
2
 ... blabla ...
3
};
4
typedef struct SPIDataBlock SPIDataBlock;
Dann kannst du im Code SPIDataBlock schreiben, ohne das struct davor. Da 
das aber immer noch zu kompliziert ist, gibt es eine weitere 
Vereinfachung:
1
typedef struct SPIDataBlock {
2
  ... blabla ...
3
} SPIDataBlock;
Bewirkt das gleiche wie oben. Wenn du schreibst:
1
typedef struct {
2
  ... blabla ...
3
} SPIDataBlock;
Kannst du nicht mehr "struct SPIDataBlock" schreiben, sondern nur noch 
das kurze SPIDataBlock.
Vermutlich dürfte das einige der anderne Fehler gleich mit verpuffen 
lassen :)

von Steff (Gast)


Lesenswert?

vielen viele dank für die super Erklärung.
wenn ich das jetzt so abändere, dann kommt die Fehlermeldung:
request for member 'full_charge_cap' in something not a structure or 
union

Die Fehlermeldung kommt an allen Stellen, die etwas mit der Struktur zu 
tun haben.
Hab ich da was falsch gemacht?

von Steff (Gast)


Lesenswert?

wenn ich in einer funktion die komplette struktur übergeben will, dann 
schreibt man das so,oder?
 writeBuffer(FirstStartByte,BUF2_WRITE,data);


Wie mach ich es aber, wenn ich nur die letzten 4 byte einer struktur 
übergeben will?

Hast du evtl noch was gefunden was du in meinem Coder anderst scheiben 
würdest,weil es viell. so nicht funktioniert oder sowas? Wenn den Code 
anschaust, dann siehst glaub gleich das der nicht irgendwoher kopiert 
wurde*g*

grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> wenn ich das jetzt so abändere, dann kommt die Fehlermeldung:
> request for member 'full_charge_cap' in something not a structure or
> union
Ersetze z.B. in ReadDataFromFlash "*data." durch "data->". Also 
"data->battery_mode = 37;" oder so.
> wenn ich in einer funktion die komplette struktur übergeben will, dann
> schreibt man das so,oder?
>  writeBuffer(FirstStartByte,BUF2_WRITE,data);
Ja. So wird eine Kopie der Original-Struktur im Speicher angelegt; wird 
sie verändert, bleibt das original so, wie es ist.
> Wie mach ich es aber, wenn ich nur die letzten 4 byte einer struktur
> übergeben will?
Dann solltest du diese letzten 4 Bytes in einer eigenen struct 
zusammenfassen und übergeben:
1
struct counters {
2
  unsigned short pagecounter;
3
  unsigned short startbytecounter;
4
};
5
struct SPIDataBlock {
6
 ...
7
  struct counters counters;
8
};
Zugriff dann z.B. über data->counters.pagecounter;
Dieses "struct counters" kannst du dann direkt deiner Funktion 
übergeben.
> Hast du evtl noch was gefunden was du in meinem Coder anderst scheiben
> würdest,weil es viell. so nicht funktioniert oder sowas?
Ich würde statt der endlosen SPI_WriteBuffer-Sequenz in der 
writeBuffer-Funktion (verwirrende Namen übrigens) lieber die 
SPI_WriteBuffer so abwandeln:
1
SPI_WriteBuffer(const void* data, const size_t length) {
2
  size_t n = 0;
3
  for (n = 0; n < length; n++) { // Schleife für jedes byte.
4
    unsigned char b = (unsigned char) (data [n]); // Byte aus Speicher holen
5
    S0SPDR = b;        // Byte an SPI
6
    while (!(S0SPSR & (SPI_SPIF)));  // Warten bis Daten gesendet
7
  }
8
}
void* enthält einfach eine RAM-Adresse, wobei nicht näher bekannt ist, 
was sich dort befindet. Der C-Compiler nimmt an, der Programmierer weiß 
es und wie die Daten korrekt zu verwenden sind. Dieser Funktion kannst 
du also eine Stelle im RAM übergeben, an der sich zu sendende Daten 
befinden, und wie viele Bytes es sind.
In writeBuffer löschst du die ganzen SPI_WriteBuffer-Aufrufe, und erstzt 
sie durch ein schlichtes
1
SPI_WriteBuffer ((void*) &data, sizeof (data));
Das hat den Vorteil, dass du SPIDataBlock beliebig verändern kannst, und 
diese Speicher-Routinen funktionieren trotzdem noch ohne 
AnpassunBeitrag "Fehlermeldungen beim compilieren von SPI Code".
Wenn du dann ein einzelne Variablen (wie int, short etc) versenden 
willst, kannst du das ganz genauso machen:
1
unsigned short eine_variable;
2
SPI_WriteBuffer ((void*) &eine_variable, sizeof (eine_variable));
Auch das geht:
1
SPI_WriteBuffer ((void*) &adressfeld, sizeof (adressfeld));
Das sollte auch automatisch mit dem verschachtelten struct, also mit dem 
eingebauten "struct counters" funktionieren.
So, und du darfst jetzt als Hausaufgabe eine SPI_ReadBuffer (void* data, 
size_t length); schreiben, die das gleiche in umgekehrt macht :)
Es könnte jedoch passieren, dass die alten, im Flash gespeicherten Daten 
nicht mehr mit dem neuen Programm kompatibel sind - deine alte 
SPI_WriteBuffer schreibt ja immer zuerst das höherwertige Byte, es 
könnte aber, abhängig von deinem Controller sein, dass die neue Variante 
erst das niederwertige Byte speichert/liest (das müsste genau dann der 
Fall sein, wenn der Controller Little Endian ist, wenn mich nicht alles 
täuscht.).

von Steff (Gast)


Lesenswert?

hi,
echt super nett von dir das du mir so weiterhilfst.
du hast ja geschreiben:

>Dann solltest du diese letzten 4 Bytes in einer eigenen struct
>zusammenfassen und übergeben:struct counters {
>  unsigned short pagecounter;
>  unsigned short startbytecounter;
>};
>struct SPIDataBlock {
> ...
>  struct counters counters;
>};

heisst das dann, dass ich die eine struktur mit den ganzen messwerten 
schreib(ohne pagecounter und startbytecounter) und dann noch eine extra 
struktur nur mit pagecounter und startbytecounter?oder hast du das 
anderst gemeint?

Was wird denn durch den Pfeil gemacht?
>Ersetze z.B. in ReadDataFromFlash "*data." durch "data->". Also
>"data->battery_mode = 37;" oder so.

Was hast du denn mit den 2 folgenden zeilen gemeint?
>const size_t length
>size_t n = 0;

nochmal ganz herzlichen dank. Bin noch ganz am Anfang was das 
Programmieren angeht und das von dir ist echt so wahnsinnig hilfreich. 
Werd jetzt gleich mal anfangen und hoff,dass ich übers Wochenende fertig 
werde.
Grüße

von Steff (Gast)


Lesenswert?

Ahh, das mit den Strukturen hab ich glaub verstanden. Das ist ja so das 
struct counter im struct SPIDataBlock einfach mit eingebunden wird,oder?

Eine ganz kleine Frage hätte ich noch.
Du hast ja folgende Zeilen geschreiben:
>SPI_WriteBuffer(const void* data, const size_t length) {
>  size_t n = 0;
>  for (n = 0; n < length; n++) { // Schleife für jedes byte.
>    unsigned char b = (unsigned char) (data [n]); // Byte aus Speicher >holen
>    S0SPDR = b;        // Byte an SPI
>    while (!(S0SPSR & (SPI_SPIF)));  // Warten bis Daten gesendet
>  }
>}

Fast alle Daten bestehen ja aus 2 Byte. Wie wird denn das in der 
vorhergehenden Funktion gemacht?Hab mit dem const noch nie gearbeitet?!
grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> hi,
> echt super nett von dir das du mir so weiterhilfst.
Kein Problem :)
> du hast ja geschreiben:
>>Dann solltest du diese letzten 4 Bytes in einer eigenen struct
>>zusammenfassen und übergeben:struct counters {
>>  unsigned short pagecounter;
>>  unsigned short startbytecounter;
>>};
>>struct SPIDataBlock {
>> ...
>>  struct counters counters;
>>};
>
> heisst das dann, dass ich die eine struktur mit den ganzen messwerten
> schreib(ohne pagecounter und startbytecounter) und dann noch eine extra
> struktur nur mit pagecounter und startbytecounter?
> Ahh, das mit den Strukturen hab ich glaub verstanden. Das ist ja so das
> struct counter im struct SPIDataBlock einfach mit eingebunden wird,oder?
Japp, ganz genau.
> Was wird denn durch den Pfeil gemacht?
>>Ersetze z.B. in ReadDataFromFlash "*data." durch "data->". Also
>>"data->battery_mode = 37;" oder so.
Du hast ja im Kopf der Funktion geschrieben "SPIDataBlock* data". Der 
Stern * bewirkt, dass nur die Adresse, an der sich der SPIDataBlock im 
RAM befindet, übergeben wird. So wird eben nicht das ganze struct 
kopiert, sondern nur die Adresse. Diese data ist dann eine Zeiger bzw. 
Pointer - es "zeigt" sozusagen an die Stelle, wo es im Speicher zu 
finden ist.
Dann kannst du aber nicht mehr "data.battery_mode" schreiben, sondern 
"data->battery_mode". Das bewirkt aber im Prinzip das selbe, also der 
Zugriff auf ein Element eines struct's, von dem man einen Pointer hat. 
Du hattest ja geschrieben "*data.battery_mode" - der Fehler waren 
fehlende Klammern - mit "(*data).battery_mode" hätte es funktioniert. 
data->battery_mode bewirkt genau das gleiche, sieht aber viel schöner 
aus.

> Was hast du denn mit den 2 folgenden zeilen gemeint?
>>const size_t length
>>size_t n = 0;
Durch das const teilt man dem Compiler mit, dass man in der Funktion die 
Variable length nicht verändern wird. Der Compiler kann dann ein 
bisschen optimieren, z.B. das kopieren wegoptimieren. Der Datentyp 
size_t ist definiert als "Unsigned integer type of the result of the 
sizeof operator.". Also im Prinzip ein Variablentyp, der groß genug ist, 
um die Größe eines structs zu beschreiben, das den ganzen RAM füllt. Das 
ist meistens ein 32-bit-"unsigned int", kann aber auf einem 
Mikrocontroller vielleicht auch kleiner sein, abhängig vom verfügbaren 
Speicher.
Meine SPI_WriteBuffer-Funktion fasst einfach alle übergebenen Daten als 
ein array von char's (also bytes) auf und versendet die. Übergibt man 
einen 2-byte-Wert (wie short) dann denkt die Funktion, es handele sich 
um ein array aus 2 char's, die einfach nacheinander gesendet werden. Und 
das funktioniert mit allen Datentypen aller Größen.

von Steff (Gast)


Lesenswert?

Hast du das so gemeint mit dem struct?

struct counters
{
  unsigned short pagecounter;
  unsigned short startbytecounter;
};

typedef struct     // 1Struktur = 44Byte
{
  unsigned short battery_mode;
  unsigned short temperature;
  .
  .
  .
  unsigned short year;
  unsigned short reserve[2];
  struct counters counters;
}SPIDataBlock;

oder kommt dann das typedef wieder weg?

Werd glau bei der Programmierung es so schreiben:(*data).battery_mode
also die zweite Möglichkeit. Sonst komm ich bissle durcheinander.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> Hast du das so gemeint mit dem struct?
> [...]
> oder kommt dann das typedef wieder weg?
Nene, so ist das genau richtig.
> Werd glau bei der Programmierung es so schreiben:(*data).battery_mode
> also die zweite Möglichkeit. Sonst komm ich bissle durcheinander.
Naja, das (*data). ist auch verwirrend... Halt bei einem Pointer -> und 
bei direktem Zugriff . das ist die übliche Version, so macht das jeder.

von Steff (Gast)


Lesenswert?

ja ok, du weisst es ja wie es geht, dann mach ich es auch so.
mal schaun ob ich jetzt weiterkomme, aber mit deiner Hilfe dürfte es zu 
schaffen sein(hoffe ich mal*g*).

von Steff (Gast)


Lesenswert?

also so wie ich das struct grad geschreiben hab,kann ich es genau 
übernehmen?also für die große Struktur mit typedef und für die kleine 
ohne?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> also so wie ich das struct grad geschreiben hab,kann ich es genau
> übernehmen?also für die große Struktur mit typedef und für die kleine
> ohne?
Ja, das kannst du so übernehmen. Du kannst, wenn du willst, bei dem 
kleinen auch noch ein typedef einbauen:
1
typedef struct
2
{
3
  unsigned short pagecounter;
4
  unsigned short startbytecounter;
5
} counters;
Dann kannst du dir da unten dass "struct" sparen, also statt "struct 
counters" kannst du dann einfach nur "counters" schreiben:
1
typedef struct     // 1Struktur = 44Byte
2
{
3
  unsigned short battery_mode;
4
  unsigned short temperature;
5
  .
6
  .
7
  .
8
  unsigned short year;
9
  unsigned short reserve[2];
10
  counters counters;
11
}SPIDataBlock;

von Steff (Gast)


Lesenswert?

ok,so werd ich es machen.
vielen herzlichen Dank

von Steff (Gast)


Lesenswert?

Hallo
Eine Frage hätte ich noch.
hab ja dann die eine Struktur mit den Messdaten und die andere Struktur 
mit den Countern.
Schreib die daten in ein Flash mit 4095 pages,wobei die 0.page für die 
counter verwendet wird.
den startbytecounter muss ich nach dem eine Messdatenstruktur in eine 
page geschreiben wurde um die Messdatenstukturgröße erhöhen. Den 
pagecounter aber nur wenn eine page mit 12 messdatenstrukturen voll 
geschreiben wurde. Wie mach ich das denn wenn ich die Messdatenstrukur 
in eine page schreib das die counter nicht mit in die jeweilige page 
geschreiben wird, sondern nur in die 0. page?

Grüße

von holger (Gast)


Lesenswert?

>Wie mach ich das denn wenn ich die Messdatenstrukur
>in eine page schreib das die counter nicht mit in die jeweilige page
>geschreiben wird, sondern nur in die 0. page?

Laß die counter aus der Messdatenstruktur raus?
Wenn du sie nicht mitschreiben willst was sollen sie dann
dort? Oder zieh beim schreiben die Länge der counter
von der Messdatenstruktur ab.

von Steff (Gast)


Lesenswert?

Schlimm wäre es nicht wenn die auch mit hineingeschreiben werden oder 
ist es dann evtl sogar leichter dann wenn ich die daten wieder auslesen 
will und alles komplett mit hinein geschrieben wird?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Um das zu machen, würde ich Messdaten und counter trennen:
1
typedef struct {
2
  unsigned short battery_mode;
3
  unsigned short temperature;
4
  .
5
  .
6
  .
7
  unsigned short year;
8
  unsigned short reserve[2];
9
} measured_values;
10
typedef struct {
11
  unsigned short pagecounter;
12
  unsigned short startbytecounter;
13
} counters;
14
typedef struct {
15
  measured_values values;
16
  counters counters;
17
} SPIDataBlock;
18
// Schreiben des Messdatenblocks mit
19
SPI_WriteBuffer ((void*) &(data.values), sizeof (data.values));
20
// Schreiben der counter
21
SPI_WriteBuffer ((void*) &(data.counters), sizeof (data.counters));

von Steff (Gast)


Lesenswert?

Das Problem ist halt dann, das ich ja immer beide Funktionen in der 
großen Funktion:void writeBuffer(unsigned short startbyte,unsigned short 
opcode,SPIDataBlock data) ausführe. Oder versteh ich da was falsch?

von Steff (Gast)


Lesenswert?

woher kommt denn in der funktion die du oben geschreiben hast das data 
bei data.values her? muss man das nicht erst noch festlegen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Du willst ja offenbar die counter und die Messdaten separat speichern.
Die neue SPI_WriteBuffer-Funktion verschickt einfach nur Daten 
beliebiger Länge über SPI.
Du könntest dir jetzt eine Funktion schreiben, die erst die Ziel-Page 
über SPI verschickt (über SPI_WriteBuffer ((void*) &addressfeld, sizeof 
(addressfeld));), und dann die Messdaten hinterher, und dann die 
Ziel-Page 0 verschickt, und dann die counter. Das Aufrufen dieser 
Funktion würde dann die messwerte und die counter an ihre entsprechenden 
Stellen im Flash speichern.
> woher kommt denn in der funktion die du oben geschreiben hast das data
> bei data.values her? muss man das nicht erst noch festlegen?
Ich hatte jetzt angenommen dass diese Aufrufe nach writeBuffer kommen, 
und das data wird da ja als Parameter übergeben.

von Steff (Gast)


Lesenswert?

>Ich hatte jetzt angenommen dass diese Aufrufe nach writeBuffer kommen,
>und das data wird da ja als Parameter übergeben.

Du hast recht.

Wie spreche ich denn eigentlich eine Struktur an die ineinander 
verschachtelt sind?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> Wie spreche ich denn eigentlich eine Struktur an die ineinander
> verschachtelt sind?
Wie ich oben auch schon geschrieben habe:
1
SPIDataBlock data;
2
data.values.battery_mode = 25;
3
data.counters.pagecounter++;

von Steff (Gast)


Lesenswert?

ah ok danke

>Du könntest dir jetzt eine Funktion schreiben, die erst die Ziel-Page
>über SPI verschickt (über SPI_WriteBuffer ((void*) &addressfeld, sizeof
>(addressfeld));), und dann die Messdaten hinterher, und dann die
>Ziel-Page 0 verschickt, und dann die counter. Das Aufrufen dieser
>Funktion würde dann die messwerte und die counter an ihre entsprechenden
>Stellen im Flash speichern

1.Vor jedem schreiben muss ich ja immer die page0 auslesen zwecks dem 
pagecounter und dem startbytecounter.
mit: void ReadDataFromFlash(unsigned short page,unsigned short 
startbyte, unsigned short opcode,SPIDataBlock* data)

2. auswerten der Counter

3. und danach würdest du dann die Funktion schreiben, die du oben 
beschrieben hast? Aber was machst denn die Funktion mit dem adressfeld?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> 3. und danach würdest du dann die Funktion schreiben, die du oben
> beschrieben hast? Aber was machst denn die Funktion mit dem adressfeld?
So wie ich das verstanden habe, muss man dem Flash über SPI die Adresse 
mitteilen, an die man schreiben/lesen will, und deswegen hatte ich 
geschrieben, dass du mit dem neuen SPI_WriteBuffer die 
addressfeld-Variable verschicken kannst, die du ja auch schon erstellt 
hast, um die Ziel-Adresse an den Flash zu schicken.

von Steff (Gast)


Lesenswert?

Hab es bis jetzt ja so:
dann müsste ich es ja evtl ao machen.
auslesen, auswerten der counter und dann...

Für die Messdaten:
1. in buffer schreiben an startbyte 0, da ja soweiso immer nur eine 
messdatenstruktur reinkommt
2. kompletter bufferinhalt in die davor ermittelte page und startbyte 
schreiben

Für die Counter:
1. aktuelle counter in buffer scheiben
2. bufferinhalt in page 0 schreiben.

die counter müsste ich ja eigentlich immer an die letzten 4 startbyte 
schreiben, da ich ja nur die startbyte angeben kann, sonst würde er ja 
beim lesen erstmal 500byte umsonst lesen um an die counterstände zu 
kommen,oder?



void writeBuffer(unsigned short startbyte,unsigned short 
opcode,SPIDataBlock data)
{
  unsigned int adressfeld[3];
  //startbyte im buffer
  startbyte &= 0x03FF;                                      // 0x03FF-> 
auf 10 bits beschränkt


  /* in Buffer über SPI*/
  adressfeld[0] = (unsigned char) 0x00;                     // 8 der 14 
don't care bits
  adressfeld[1] = (unsigned char) (startbyte >> 8) | 0x00;  // die 
restlichen 6 don't care bits + 2 der 10 bits des Startbytes
  adressfeld[2] = (unsigned char) (startbyte & 0x00FF);      // die 
restlichen 8 Bits des Startbytes;

  SPI_CS_FLASH ~= (1<<SPI_CSB);                             // CSB auf 
Null setzen

  S0SPDR = opcode; //BUF1_WRITE;                            // Opcode 
(0x84) ausgeben(Kommando an SPI Bus)
  while (!(S0SPSR & (SPI_SPIF)));                           // Warten 
bis Daten gesendet
  unsigned char k;
  for (k=0; k<3; k++)
  {
    S0SPDR = adressfeld[k];                                 // Daten an 
SPI
    while (!(S0SPSR & (SPI_SPIF)));                         // Warten 
bis Daten gesendet
  }
  //Schreiben des Messdatenblocks
  SPI_WriteBuffer((void*) &data.values, sizeof (data.values));
  //Schreiben der Counter
  SPI_WriteBuffer((void*) &data.counters, sizeof (data.counters));

  SPI_CS_FLASH |= (1<<SPI_CSB);             //CSB auf 1 setzen
}


SPI_WriteBuffer(const void* data, const unsigned int length)
{
   unsigned int n = 0;
   for (n = 0; n < length; n++)
   {
     unsigned char byte = (unsigned char) (data [n]);
     SOSPDR = byte;
     while (!(S0SPSR & (SPI_SPIF)));                         // Warten 
bis Daten gesendet
}

von Steff (Gast)


Lesenswert?

Morgen,
könntest du mir evtl sagen ob man das so schreiben könnte.Hab jetzt das 
write buffer gesplittet.
Also das sind die jeweiligen Funktionsaufrufe:

//update von pagecounter und startbytecounter über Buffer2-> in page 0
  writeBuffer(FirstStartByte,BUF2_WRITE,data.counters);
   //Schreiben der Counter
  SPI_WriteBuffer((void*) &data.counters, sizeof (data.counters));
  writePage(FirstPage, 
(MAX_NO_STRUCT-1)*sizeof(SPIDataBlock),BUF2_TO_MAIN_MEMORY_W_ERASE,data) 
;



  //Schreiben der Daten an den ermittelten freien Speicherbereich über 
Buffer1
  writeBuffer(FirstStartByte,BUF1_WRITE,data.values);
  //Schreiben des Messdatenblocks
  SPI_WriteBuffer((void*) &data.values, sizeof (data.values));
  writePage(PageCounter, StartByteCounter, BUF1_TO_MAIN_MEMORY_W_ERASE, 
data);

und so sehen die einzelnen funktionen aus:

void writeBuffer(unsigned short startbyte,unsigned short 
opcode,SPIDataBlock data)
{
  unsigned int adressfeld[3];
  //startbyte im buffer
  startbyte &= 0x03FF;                                      // 0x03FF-> 
auf 10 bits beschränkt


  /* in Buffer über SPI*/
  adressfeld[0] = (unsigned char) 0x00;                     // 8 der 14 
don't care bits
  adressfeld[1] = (unsigned char) (startbyte >> 8) | 0x00;  // die 
restlichen 6 don't care bits + 2 der 10 bits des Startbytes
  adressfeld[2] = (unsigned char) (startbyte & 0x00FF);  // die 
restlichen 8 Bits des Startbytes;

  SPI_CS_FLASH ~= (1<<SPI_CSB);                             // CSB auf

  S0SPDR = opcode; //BUF1_WRITE;                            // Opcode 
(0x84) ausgeben(Kommando an SPI Bus)
  while (!(S0SPSR & (SPI_SPIF)));                           // Warten 
bis Daten gesendet
  unsigned char k;
  for (k=0; k<3; k++)
  {
    S0SPDR = adressfeld[k];                                 // Daten an
    while (!(S0SPSR & (SPI_SPIF)));                         // Warten 
bis
  }
}


SPI_WriteBuffer(const void* data, const unsigned int length)
{
   unsigned int n = 0;
   for (n = 0; n < length; n++)                       //Schleife für
 {
   unsigned char byte = (unsigned char) (data [n]); //Byte aus Speicher
     SOSPDR = byte;
     while (!(S0SPSR & (SPI_SPIF)));                  // Warten bis 
Daten

     SPI_CS_FLASH |= (1<<SPI_CSB);                      //CSB auf 1
}

Hast du das gestern ung so gemeint oder hab ich dich bisschen 
missverstanden?
Grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Am Ende von SPI_WriteBuffer fehlt aber noch eine Klammer zu } . Und wo 
kommt das "SPI_CS_FLASH |= (1<<SPI_CSB);" da her ... ? SPI_WriteBuffer 
sollte ja eigentlich nur Bytes über SPI übermitteln, und keine 
Steuerbefehle, die müssten dann eigentlich in die aufrufenden 
Funktionen.
Die Schleife am Ende von writeBuffer würde ich rausnehmen und durch 
SPI_WriteBuffer ((void*) &addressfeld, sizeof (addressfeld)); ersetzen. 
Und writeBuffer braucht ja den Parameter "SPIDataBlock data" gar nicht 
mehr.
Aber ansonsten sieht das ganz gut aus.
PS: Wenn du C-Code postest, schreib davor
ein [ c ] und danach ein [ /c ] (natürlich ohne die Leerzeichen) - es 
steht auch über dem Text-Eingabefeld unter "Formatierung".

von Steff (Gast)


Lesenswert?

>Und wo kommt das "SPI_CS_FLASH |= (1<<SPI_CSB);" her?

Das soll sagen das dir Übertragung abgeschlossen ist. Ja stimmt, seh 
grad selber das es an der falschen Stelle steht, aber wüsste nicht wohin 
wenn ich das SPI_WriteBuffer ((void*) &addressfeld, sizeof 
(addressfeld)); auch schreibe.

oder müsste das SPI_CS_FLASH |= (1<<SPI_CSB);"  eher nach den 
Funktionsaufrufen stehen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Du könntest ja eine Funktion "endTransfer" machen, die nur "SPI_CS_FLASH 
|= (1<<SPI_CSB);"  enthält; diese kannst du dann immer direkt nach 
SPI_WriteBuffer aufrufen. Oder eben nicht - wenn du die Schleife am Ende 
von writeBuffer ersetzt durch SPI_WriteBuffer, brauchst du es da ja 
offenbar nicht.

von Steff (Gast)


Lesenswert?

Würde es denn so gehn?
1
 //update von pagecounter und startbytecounter über Buffer2-> in page 0
2
  writeBuffer(FirstStartByte,BUF2_WRITE,data.counters);
3
  SPI_WriteBuffer((void*) &data.counters, sizeof (data.counters));
4
  endTransfer();
5
  writePage(FirstPage, (MAX_NO_STRUCT-1)*sizeof(SPIDataBlock),BUF2_TO_MAIN_MEMORY_W_ERASE,data);
6
  endTransfer();
7
  //Schreiben des Messdatenblocks an den ermittelten freien Speicherbereich über Buffer1 
8
  writeBuffer(FirstStartByte,BUF1_WRITE,data.values);
9
  SPI_WriteBuffer((void*) &data.values, sizeof (data.values));
10
  endTransfer();
11
  writePage(PageCounter, StartByteCounter, BUF1_TO_MAIN_MEMORY_W_ERASE, data);
12
  endTransfer();
schreib dann nach jedem Funktionsaufruf die Funktion endTransfer
1
 //adressfeld zusammenstellen und opcode ausgeben
2
void writeBuffer(unsigned short startbyte,unsigned short opcode) 
3
{  
4
  unsigned int adressfeld[3];
5
  //startbyte im buffer
6
  startbyte &= 0x03FF;                                      // 0x03FF-> auf 10 bits beschränkt
7
                                                             
8
  // in Buffer über SPI
9
  adressfeld[0] = (unsigned char) 0x00;                    
10
  adressfeld[1] = (unsigned char) (startbyte >> 8) | 0x00;                 adressfeld[2] = (unsigned char) (startbyte & 0x00FF);      // die restlichen 8 Bits des Startbytes;
11
                                      
12
  SPI_CS_FLASH ~= (1<<SPI_CSB);                              
13
  S0SPDR = opcode; //BUF1_WRITE;                            
14
  while (!(S0SPSR & (SPI_SPIF)));                             SPI_WriteBuffer ((void*) &adressfeld, sizeof (adressfeld)); 
15
}          
16
17
SPI_WriteBuffer(const void* data, const unsigned int length)
18
{
19
   unsigned int n = 0;
20
   for (n = 0; n < length; n++)                          {
21
     unsigned char byte = (unsigned char) (data[n]); holen
22
     SOSPDR = byte;
23
     while (!(S0SPSR & (SPI_SPIF)));                 
24
   }
25
}
26
27
28
endTransfer()
29
{
30
  SPI_CS_FLASH |= (1<<SPI_CSB);                 
31
}

von Steff (Gast)


Lesenswert?

Bei der Funktion endTransfer muss man nicht übergeben oder?Ich glaub 
sonst müsste es mit dem in den Buffer reinschreiben klappen oder siehst 
du noch irgendwelche offensichtliche Fehler?
grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ich bin nicht so der Experte in Sachen Flash, aber dieser Programmaufbau 
erscheint mir einigermaßen sinnvoll. Probier es doch einfach aus.

von Steff (Gast)


Lesenswert?

Hab es eigentlich nicht so egnau auf das flash bezogen, eher auf die 
Schreibweise. Aber das mit dem endTransfer kann ich schon so 
machen,oder?

Muss jetzt mal die read Funktion schreiben. Das mach ich ja dann mit dem
->,oder?Also da wo ich *data.batterymode geschrieben hab, einfach den 
Punkt durch -> ersetzen.
Hat das mit dem pfeil eigentlich irgendwas mit der dynamischen 
Speicherplatz Reservierung zu tun?Oder bin ich da mit meiner Vermutung 
auf dem falschen Weg?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> Aber das mit dem endTransfer kann ich schon so
> machen,oder?
Ja. Probier es einfach aus: Ein Compilerlauf kostet nichts.
> Muss jetzt mal die read Funktion schreiben. Das mach ich ja dann mit dem
> ->,oder?Also da wo ich *data.batterymode geschrieben hab, einfach den
> Punkt durch -> ersetzen.
Ja.
> Hat das mit dem pfeil eigentlich irgendwas mit der dynamischen
> Speicherplatz Reservierung zu tun?Oder bin ich da mit meiner Vermutung
> auf dem falschen Weg?
Nein, hat es nicht. Das ist einfach nur eine Pointerdereferenzierung 
(http://de.wikipedia.org/wiki/Dereferenzieren#Zeigeroperationen).
Bei der dynamischen Speicherreservierung werden Zeiger verwendet, daher 
wird man dort oft auf -> treffen. Aber -> ist nicht darauf beschränkt.

von Steff (Gast)


Lesenswert?

Hab ja die 3 funktionsaufrufe mit:
[c]
// Schreiben des Messdatenblocks
SPI_WriteBuffer((void*) &(data.values), sizeof (data.values));
// Schreiben der counter
SPI_WriteBuffer((void*) &(data.counters), sizeof (data.counters));
// Schreiben der adressfelder
SPI_WriteBuffer((void*) &adressfeld, sizeof (adressfeld))
[c/]

Wie sieht denn dazu der prototyp aus, der in die Header kommt?
Sind beim compilieren schonmal weniger Fehler. Wenn ich das mit den 
Pfeilen noch abändere, dann noch weniger

von Steff (Gast)


Lesenswert?

upps hab da wohl das "/" an der falschen Stelle

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Der Prototyp wofür? Für SPI_WriteBuffer? Na:
void SPI_WriteBuffer (const void* data, const size_t length);

von Steff (Gast)


Lesenswert?

Könnte ich das eigenlich auch so schreiben?
void SPI_WriteBuffer (const void* data, const unsigned int length);

weil es sonst bisschen komisch aussieht, hab sonst noch nirgends das 
const size_t length geschrieben.

Bei den anderen funktionen, wie:
writeBuffer(FirstStartByte,BUF2_WRITE,data.counters);

writePage(FirstPage, 
(MAX_NO_STRUCT-1)*sizeof(SPIDataBlock),BUF2_TO_MAIN_MEMORY_W_ERASE,data) 
;

ists mir auch nicht so ganz klar wie die aussehen sollen.
Weil bei den vielen Strukturen ineinander weiß ich nicht genau wie man 
das dann schreibt

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Steff wrote:
> Könnte ich das eigenlich auch so schreiben?
> void SPI_WriteBuffer (const void* data, const unsigned int length);
>
> weil es sonst bisschen komisch aussieht, hab sonst noch nirgends das
> const size_t length geschrieben.
Wahrscheinlich könntest du, ja. Aber vielleicht weiß dein Compiler ja, 
dass der Controller keine 4 GB RAM hat, und definiert size_t als 2-Byte 
Wert. Das size_t ist auch eher für PC-Anwendungen gedacht; falls das 
Programm mehr als 4 GB ansprechen können soll, definiert man size_t als 
64bit-Wert, und muss nicht alles ändern. Ich hab da jetzt 
gewohnheitsmäßig size_t hingeschrieben, aber wenn es dir nicht gefällt, 
kannst du auch unsigned int oder vermutlich auch unsigned short 
schreiben. size_t dient halt der Portabilität, aber die hast du ja wegen 
des direkten SPI-Register-Zugriffs sowieso nicht.
> Bei den anderen funktionen, wie:
> writeBuffer(FirstStartByte,BUF2_WRITE,data.counters);
>
> writePage(FirstPage,
> (MAX_NO_STRUCT-1)*sizeof(SPIDataBlock),BUF2_TO_MAIN_MEMORY_W_ERASE,data) ;
>
> ists mir auch nicht so ganz klar wie die aussehen sollen.
> Weil bei den vielen Strukturen ineinander weiß ich nicht genau wie man
> das dann schreibt
Genauso wie vorher, nur bei writeBuffer statt SPIDataBlock* musst du 
halt counters* schreiben. Und writePage wurde ja gar nicht verändert.

von Steff (Gast)


Lesenswert?

Du hast ja geschreiben:
>Genauso wie vorher, nur bei writeBuffer statt SPIDataBlock* musst du
>halt counters* schreiben. Und writePage wurde ja gar nicht verändert.

was macht man denn dann mit dem values?
Übergeb ja in writeBuffer nur writeBuffer(FirstStartByte,BUF2_WRITE);


Übergebe in Test.c über die Funktion  writeSPIBlock(spiData); die Daten, 
also ohne die pagecounter und startbytecounter.
spiData beschreibe ich wiefolgt: 
spiData.battery_status=isOverChargedAlarm | isTerminateChargeAlarm
Passt die beschreibung, oder muss da noch .values rein?
In Test.c hab ich die Struktur so definiert:SPIDataBlock spiData;

In at45db.c ruf ich die Funktion wieder auf mit void 
writeSPIBlock(SPIDataBlock data). Das kann aber nicht stimmen,oder?

von Steff (Gast)


Lesenswert?

Hallo
Schreib ja in der Funktion um das Flash auszulesen folgenden code:
void ReadDataFromFlash(unsigned short page,unsigned short startbyte, 
unsigned short opcode,SPIDataBlock* data)
.
.
.

 data->battery_mode = (S0SPDR << 8);
     while (!(S0SPSR & (SPI_SPIF)));
     data->battery_mode |= S0SPDR;
     while (!(S0SPSR & (SPI_SPIF)));

Dieser Block kommt ja dann noch 15 mal für die anderen messwerte. Wie 
kann man das denn so machen wie beim buffer beschreiben das man eine 
allgemeine funktion hat und den Block nur einmal schreiben zu müssen? 
Oder geht das in diesem Fall garnicht?

Grüße

von Steff (Gast)


Lesenswert?

Hallo

wäre echt super nett von dir wenn du mir nochmal weiterhelfen könntest.
Komme bei der Lesefunktion überhaupt nicht mehr weiter.

Grüße

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Mach die SPI_ReadBuffer-Funktion ganz genauso wie die 
SPI_WriteBuffer-Funktion, nur ohne das "const" bei "void* data" und 
natürlich musst du die Daten aus dem SPI-Register nach "data [n]" 
kopieren, anstatt umgekehrt.

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.