Forum: Mikrocontroller und Digitale Elektronik SPI-Treiber-Entwicklung: Wie macht man es richtig?


von N. G. (newgeneration) Benutzerseite


Lesenswert?

Hallo Forum,

diese Frage stelle ich mir im Zusammenhang mit einem LPC824-Controller 
von NXP, allerdings ist das für die Frage weitgehend irrelevant.

Normalerweise schreibe ich für jedes logische Hardware-Modul einen 
low-level-Treiber, z.B. für UART, I2C, SPI-Master. Diese Treiber stellen 
alle nötigen Funktionen bereit, die die restliche Software braucht, 
sodass außer diesen Treiber-Funktionen niemand anders mit der Hardware 
in Berührung kommt. Das kommt einem auch bei einem Wechsel der 
Mikrocontroller-Architektur zugute, da nur die low-level Treiber 
angepasst werden müssen. Sämtliche andere Funktionalität kann übernommen 
werden (soweit zumindest die Theorie :-) ).
Allgemein sieht das dann zum Beispiel UART so aus:
1
void uart0_init(uint32_t baudrate);
2
// operiert auf einen Ring-Puffer
3
// blockiert nur, wenn der Puffer voll ist 
4
void uart0_putc(char);
5
void uart0_puts(const char*);
6
bool uart0_has_data(void);
7
// operiert auf einen Ring-Puffer
8
// blockiert nur, wenn der Puffer leer ist
9
char uart0_getc(void);
10
//...
Darauf kann man dann weitere Funktionalität aufbauen, wie z.B. das 
Senden und Empfangen von ganzen Frames mit Checksumme usw. (also so wie 
im Schichtenmodell).
Die Anwendung wird aber nicht vom Treiber festgelegt, dieser ist 
lediglich für das Übertragen von einzelnen Bytes verantwortlich.


So, nun zur eigentlichen Frage:
Wie designe ich so einen universellen Treiber für einen SPI-Slave?
Alles, was ich bisher geschrieben habe, war massiv von der Anwendung 
abhängig.
Konkret möchte ich so was:
1
enum spi_data_order {LSB_FISRT, MSB_FIRST};
2
enum spi_mode {SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3};
3
void spi_slave_init(enum spi_data, enum spi_mode);
4
void spi_slave_set_data(const uint8_t*, size_t length);
Dafür müsste ich dann natürlich einen groß genugen Buffer im Treiber 
anlegen, in den dann die Daten von spi_slave_set_data() hineinkopiert 
werden. Eine Möglichkeit wäre natürlich, die Größe des Buffers der 
spi_slave_init()-Methode mitzugeben, aber dass läuft dann auf 
dynamischen Speicher raus.
Während dem Kopieren müsste zumindest der SPI-Interrupt gesperrt werden, 
um konsistente Daten zu haben (sonst wird eventuell die Hälfte von den 
neuen und die Hälfte der alten Daten übertragen). Wobei das 
wahrscheinlich keine so gute Idee ist, wenn man den Interrupt sperrt, 
evtl. wären zwei Buffer, zwischen denen dann immer gewechselt wird 
besser...
Des weiteren möchte man eventuell DMA (soweit auf dem Controller 
verfügbar) verwenden. Wie implementiert man das in dem Treiber? Oder 
würde man dort einen extra Treiber schreiben, der dann ausschließlich 
mit DMA operiert?
Fragen über Fragen also.

Also nochmal in aller Kürze:
Wie würdet ihr einen universellen SPI-Slave-Treiber programmieren?

Es geht bei dieser Frage nicht um die Implementierung an sich, sondern 
um das Design der "API", also wie die Funktionen im Header-File 
aussehen.

Mit freundlichen Grüßen,
N.G.

von Simon (Gast)


Lesenswert?

Meiner Meinung nach stellen sich die Entwickler von STM Cube da ganz 
geschickt an. Die HAL ist zwar nicht Mega effizient aber einfach zu 
bedienen.

Ganz wichtig für eine gute API sind vor allem aber gute Dokumentation 
und Beispiele, damit es dem Anwender auch leicht fällt.

von Tobias K. (kurzschluss81)


Lesenswert?

> Dafür müsste ich dann natürlich einen groß genugen Buffer im Treiber
> anlegen, in den dann die Daten von spi_slave_set_data() hineinkopiert
> werden. Eine Möglichkeit wäre natürlich, die Größe des Buffers der
> spi_slave_init()-Methode mitzugeben, aber dass läuft dann auf
> dynamischen Speicher raus.
> Während dem Kopieren müsste zumindest der SPI-Interrupt gesperrt werden,


Ich mache sowas immer so das ich dem Treiber den Puffer über einen 
Zeiger (incl. Puffergröße) übergebe. Dann muss ich im Treiber gar nichts 
reservieren. Wenn ich den Puffer dann auslesen will, setze ich im 
Treiber ein Flag, welches den Schreibzugriff des Treibers auf den Puffer 
"sperrt" Dann kann zwar der Interrupt kommen, aber im Interrupt werte 
ich das Flag aus und weiß ob ich die Empfangen Daten in den Puffer 
schreiben darf oder nicht.
Wenn die Zeit zwischen zwei Telegrammen sehr kurz ist, kann man auch mit 
zwei alternierenden Puffern arbeiten, welche wechselseitig beschrieben 
und ausgelesen werden. So kannst du dir mit dem Auslesen des einen 
Puffers Zeit lassen, während der andere beschreiben wird.

von Peter D. (peda)


Lesenswert?

Ich mache da kein extra großes Brimborium um das SPI, sondern füge das 
bisschen SPI-Code direkt in den entsprechenden HW-Treiber ein.
Hier mal ein Beispiel für den DAC8532 auf dem AVR:
1
inline void spi( uint8_t val )
2
{
3
  SPDR = val;
4
  while( (SPSR & 1<<SPIF) == 0 );
5
}
6
7
void dac_set( uint16_t dac_a, uint16_t dac_b )
8
{
9
  SPCR = 0<<SPIE | 1<<SPE | 0<<DORD | 1<<MSTR | 1<<CPOL | 0<<CPHA | 0<<SPR1 | 0<<SPR0;
10
                                                // MSB first, data valid on 1-0 transtion
11
  SPSR = 0<<SPIF | 0<<WCOL | 1<<SPI2X;          // SCK = F_CPU / 2
12
  
13
  xDAC8532 = 0;
14
  spi( 0x00 );                       // write DAC-A
15
  spi( (uint8_t)(dac_a >> 8) );
16
  spi( (uint8_t)dac_a );
17
  xDAC8532 = 1;
18
19
  xDAC8532 = 0;
20
  spi( 0x34 );                            // write DAC-B, load DAC-A, DAC-B
21
  spi( (uint8_t)(dac_b >> 8) );
22
  spi( (uint8_t)dac_b );
23
  xDAC8532 = 1;
24
}

von Walter T. (nicolas)


Lesenswert?

N. G. schrieb:
> void spi_slave_set_data(const uint8_t*, size_t length);

Vielleicht besser spi_read_write ? Und dann überlegen, was in Deinem 
Fall besser funktioniert: Lese- und Schreibpuffer ins gleiche oder in 
zwei unterschiedliche Arrays.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Ich implementiere solche Funktionen immer über einen Ringpuffer mit 
Schreibe- und Lese-Pointer.

Ein ISR schreibt in den Ringpuffer und inkrementiert einen 
32Bit-Counter. Der Zugriff auf das Array wird dann ´ala
1
void ISR_xyz() {
2
  rxdata[wrptr % RXBUFSIZE]=...
3
  wrptr++;
4
}

Dort, wo gelesen wird, gibt es dann sowas wie
1
for (;rdptr < wrptr;rdptr++) {
2
  uint8_t data = rxdata[rdptr % RXBUFSIZE];
3
  ...
4
}

Imho ist es vorteilhaft, einen 32Bit-Zähler kontinuierlich zu 
inkrementieren anstatt den Zähler schon im Kreis laufen zu lassen, da 
man dann einfache Vergleiche der rd- und wr-Zähler machen kann.

Ein einmaliger malloc macht ja nichts kaputt ... Dynamische 
Speicherverwaltung ist es imho erst dann, wenn man auch Speicher wieder 
frei gibt und diesen dann neu nutzen möchte.

von tnsz (Gast)


Lesenswert?

wirf inen Blick in das LPCopen Paket. Das sind alle lowLevel Funktionen 
drin...

von Stefan F. (Gast)


Lesenswert?

Bei I2C Treibern kenne ich das so, dass man eine Funktion zum 
Datentransfer aufruft, die einen Puffer sendet und in einen anderen 
Empfängt. Als Parameter gibt man zwei Zeiger auf die Puffer an, und zwei 
Längen-Angaben.
1
uint8_t i2c_communicate(uint8_t slave_address, void* send_data, uint8_t send_bytes, void* rcv_data, uint8_t rcv_bytes);

Ich denke, dieses Prinzip müsste auch bei SPI passen. (Natürlich mit 
Chip-Select statt Slave-Adresse).

von W.S. (Gast)


Lesenswert?

N. G. schrieb:
> Normalerweise schreibe ich für jedes logische Hardware-Modul einen
> low-level-Treiber, z.B. für UART, I2C, SPI-Master.

Das ist auch gut so. Die Gründe hast du ja selbst genannt. Bei UART ist 
das relativ leicht, weil sich dort eigentlich immer zwei gleichrangige 
Partner am Interface gegenüber stehen.
Bei den übrigen Interfaces im MASTER Mode sieht das nicht ganz so gut 
aus.

Bei I2C müßte man die folgenden Funktionen implementieren:
1
void InitI2C(void);
2
bool OpenSlave(byte Adresse, bool RW);
3
bool WriteToSlave(byte was);
4
byte ReadFromSlave(bool ACK);
5
void CloseSlave(void);
Dieses Schema ist allerdings bei einigen µC nur schwer hinzukriegen, 
weil nach meiner Erfahrung die I2C-Peripheriecores gar mancher µC 
ziemlich verkorkst sind. So hab ich das (wenn ich mich recht entsinne) 
bei einigen STM32 so erlebt, daß man dort bereits beim OpenSlave 
festlegen muß, wieviel Bytes man später per WriteToSlave oder 
ReadFromSlave zu übertragen gedenkt. Das ist natürlich ein häßliches 
Unding, was einen extrem behindert.

Bei SPI-Master geht das ganze viel einfacher, weil man ja weiß, was man 
übertragen will. Dort fallen dann bei vielen Cores ReadfromSlave und 
WriteToSlave zusammen, man hat nur noch die Unterscheidung nach 
tatsächlicher Datenbreite bei den Übertragungsfunktionen.

Aber eines ist gewiß: Das Modell Algorithmen oder höhere Treiber auf 
höherer Ebene und lowlevel-Treiber darunter ist besser, als alles 
zusammen zu ziehen und miteinander zu vermischen wie es Peter macht.

So: Slaves:
Bei I2C Slave und bei SPI-Slave sieht das Ganze ein bissel wurschtliger 
aus.

N. G. schrieb:
> Konkret möchte ich so was:
> enum spi_data_order {LSB_FISRT, MSB_FIRST};
> enum spi_mode {SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3};
> void spi_slave_init(enum spi_data, enum spi_mode);
> void spi_slave_set_data(const uint8_t*, size_t length);
> Dafür müsste ich dann natürlich einen groß genugen Buffer im Treiber
> anlegen,
Daten in Blöcken setzen? lieber nicht so, sondern einzeln in nen 
Ringpuffer.

Naja, ale Slave muß man sich schon dazu durchringen, schon beim 
Initialisieren festzulegen, wie man sich verhalten will. Also welche 
Datenbreite man akzeptiert, was man sendet, wenn es nix zu senden gibt 
(alles Einsen oder so), welche Endianess man haben will (falls das im 
Core drin ist). Dann ist das Empfangen und Abgefragtwerden ja nicht von 
einem selbst gesteuert, weswegen man notgedrungenerweise mit 
Zwischenpuffern und mit engem Polling oder mit Events im ganzen System 
arbeiten muß.

Das sieht dann nicht so aus, wie du es mit spi_set_data usw. skizziert 
hast, sondern eher wie die Verhältnisse beim UART. Also Sende- und 
Empfangs-Ringpuffer und Einzelzeichen-I/O, dazu entweder Polling in der 
Grundschleife in main oder eben Events (sowas wie "evSPIreceived") in 
einer Event-Warteschlange und wiederum Auswertung in der Grundschleife.

W.S.

von Peter D. (peda)


Lesenswert?

Stefan U. schrieb:
> Bei I2C Treibern kenne ich das so, dass man eine Funktion zum
> Datentransfer aufruft, die einen Puffer sendet und in einen anderen
> Empfängt.

Nö, I2C kann nicht gleichzeitig senden und empfangen.
Man braucht 2 Funktionen i2c_read() und i2c_write().
Separate Puffer sind also nur verschwendeter RAM.

Aber auch bei SPI interressieren die Sendedaten in der Regel nicht mehr. 
Man kann also ruhig die empfangenen Daten im selben Puffer ablegen.
In der Regel bestimmt außerdem ein Command-Byte, ob nachfolgend Daten 
gelesen oder geschrieben werden sollen. Daher hat man üblicher Weise 
auch fürs SPI separate Funktionen spi_read() und spi_write(), sofern die 
SPI-Peripherie überhaupt einen Blocktransfer unterstützt (z.B. EEPROM).

von Johannes S. (Gast)


Lesenswert?

N. G. schrieb:
> diese Frage stelle ich mir im Zusammenhang mit einem LPC824-Controller
> von NXP,

Dieser µC hat sogar schon Treiber für SPI/I2C und weitere im ROM, incl. 
DMA. Habe die aber selber noch nicht benutzt.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Aber eines ist gewiß: Das Modell Algorithmen oder höhere Treiber auf
> höherer Ebene und lowlevel-Treiber darunter ist besser, als alles
> zusammen zu ziehen und miteinander zu vermischen wie es Peter macht.

Recht unübersichtlich wird es aber, wenn man über den extra SPI Treiber 
andere Interfaces tunneln will, z.B. UART (MAX3100), CAN (MCP2515), PIO 
(MCP23S17) usw.
Für mich ist SPI kein eigenständiger Bus, sondern nur ein Werkzeug, um 
externe Peripherie anzubinden.
Ansonsten müßte man ja auch fürs MMIO-Interface erstmal einen Treiber 
schreiben.
Ich bin zwar für Modularisierung, aber zu kleine Häppchen verwirren 
eher, als daß sie vereinfachen.

von Curby23523 N. (Gast)


Lesenswert?

Peter D. schrieb:
> Daher hat man üblicher Weise
> auch fürs SPI separate Funktionen spi_read() und spi_write(), sofern die
> SPI-Peripherie überhaupt einen Blocktransfer unterstützt (z.B. EEPROM).

Also mein SPI Treiber hat nur eine Funktion, uint8_t SPI_Send(uint8_t 
ucVal);

Wenn ich nichts lesen will, dann ignoriere ich den Rückgabewert, wenn 
ich nur lesen will, sende ich eine "0". Aber da bei SPI soweit ich weiß 
lesen und schreiben immer nur gleichzeitig stattfinden kann, gibt es für 
mich auch nur eine Funktion. Ich schiebe was raus und kriege was rein.

von Stefan F. (Gast)


Lesenswert?

> Nö, I2C kann nicht gleichzeitig senden und empfangen.

Ich wollte nur vorschlagen, das Senden und das Empfangen nicht als 
sequentiell nacheinander auszuführende Operationen zu betrachten, 
sondern als eine Einheit.

I2C sendet eine Registeradresse oder ein Kommando (optional gefolgt von 
Daten), um danach Daten zu empfangen. Beide passiert in einer 
zusammenhängenden Transaktion. Bei SPI können Sendung und Empfang 
zeitlich überlappend stattfinden. Auch dann kann man sagen, dass Sendung 
und Empfang zusammen eine Transaktion bilden.

Deswegen halte ich eine Funktion für Sinnvoll, der man sowohl einen 
Sendepuffer als auch einen Empfangspuffer übergibt.

SPI Funktionen, die nur einzelne Bytes senden und/oder empfangen passen 
nicht zu jeder Hardware. Ein DMA Controller wäre damit z.B. völlig 
nutzlose.

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Für mich ist SPI kein eigenständiger Bus, sondern nur ein Werkzeug, um
> externe Peripherie anzubinden.

Also erstmal, SPI ist ja gar kein Bus. Die Sache mit dem /SSEL sollte 
man nicht als wirkliche Adressierung ansehen.

Peter D. schrieb:
> Recht unübersichtlich wird es aber, wenn man über den extra SPI Treiber
> andere Interfaces tunneln will

Das sehe ich aber garnicht so. Erstens ist sowas auf der Master-Seite 
sehr gut getrennt und damit übersichtlich zu handhaben und zweitens ist 
man auf der Slave-Seite (was ja das Anliegen des TO war) ja gar keiner 
der von dir genannten Chips, womit das genannte Tunneln slaveseitig 
ersatzlos entfällt.

W.S.

von W.S. (Gast)


Lesenswert?

Stefan U. schrieb:
> SPI Funktionen, die nur einzelne Bytes senden und/oder empfangen passen
> nicht zu jeder Hardware. Ein DMA Controller wäre damit z.B. völlig
> nutzlose.

Nun ja, DMA ist in sehr vielen Fällen wirklich etwas ganz nutzloses. 
Eben deshalb, weil DMA ja keinerlei Verarbeitungs- oder 
Organisationsfunktionen besitzt, sondern eben nur Daten von A nach B 
schaufeln kann.

Die einzige Ecke, wo ich DMA als nötig sehe, ist I2S bei den STM32Fxxx 
Controllern - weil dort die 64 bit breiten Samples (2x 32 Bit als R/L) 
eben aufgrund der mickrigen 16 Bit Peripherie nicht wohlgepuffert in 
echten Samples zu gepackten und per FIFO gepufferten 64 Bit ankommen, 
sondern zu 4 Häppchen a 16 Bit. Wenn man sich vorstellt, bei 192 kHz 
Samplerate pro Sample jedesmal 4 Interrupts ertragen zu müssen, wird mir 
übel davon. Daher DMA, aber das ist nicht wegen eines Vorteils von DMA 
als solchem, sondern weil diese Peripherie in den STM32 so 
grottenmiserabel ist.

W.S.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Slave-Seite (was ja das Anliegen des TO war)

Ein SPI-Slave ist wirklich sehr tricky, man braucht in jedem Fall 
erstmal ein Protokoll. Ich würde zusätzlich noch mit einer CRC eine 
minimale Absicherung vornehmen.
Ein Protokoll könnte so aussehen, daß der Master erstmal ein Commandbyte 
schickt, was er vom Slave überhaupt will, denn der kann ja nicht 
hellsehen. Dann muß der Master noch ein Dummybyte senden, damit der 
Slave Zeit hat, das Kommando zu parsen und z.B. das erste Byte in den 
Sendepuffer zu stellen. Erst danach kann man Datenbytes lesen oder 
schreiben. Damit kein Transfer verloren geht, braucht das Slave-SPI auch 
die höchste Interruptpriorität.

Wegen des hohen Aufwandes (Interrupt), der fehlenden Absicherung (kein 
Acknowledge) und der hohen Leitungszahl kann ich Slave-SPI nicht 
wirklich empfehlen. Es wird Ärger bereiten.

von Pandur S. (jetztnicht)


Lesenswert?

Was soll eine CRC auf SPI ? Angst dass auf den 3cm Leitung ein Bit 
verloren geht ?
Zum synchronisieren hat man ja den CS. Es geht erst mit CS bei Zustand 
Null los.

Aber ja. SPI Slave sollten in Hardware sein. zB als Schieberegister oder 
mit Schieberegister Eingang, ADC, DAC, Flasch, ..
.. zwischen 2 Controllern ohne Buffer muss der Slave immer pollen. 
Ausser man denkt sich etwas Starres als Protokoll aus.

von Peter D. (peda)


Lesenswert?

Sabberalot W. schrieb:
> Was soll eine CRC auf SPI ? Angst dass auf den 3cm Leitung ein Bit
> verloren geht ?

Ja natürlich.
Wenn der Slave mal etwas beschäftig ist und nicht schnell genug das Byte 
in den Sendepuffer stellen kann, dann liest der Master Mumpitz und 
kriegt es nicht mit. Daher die CRC als Notbehelf.

Einen dummen SPI-Flash kannst Du mit 90MHz auslesen. Aber bei einem MC 
als SPI-Slave kann das in die Hose gehen. Nicht alles, was ein MC kann, 
kann man auch sinnvoll anwenden.

Für 3cm Leitung würde ich auch keinen 2. MC anbinden, sondern einen 
etwas dickeren nehmen.

: Bearbeitet durch User
von Rangi J. (rangi)


Lesenswert?

also ich mach das mit dem SPI-Slave ausschließlich in DMA. Wie Peter 
schon sagt, ist es wahrscheinlich dass Interrupt zu spät kommt. Als 
Slave hat man keine Macht darüber, wie der Master seinen Clock sendet. 
Außerdem gibt es keine Flußkontrolle (Busy). Das folgende Byte muss 
immer schon vorher bereitliegen.
Das in Kombination mit einem passenden Protokoll geht das sehr gut. 
(Erstes Byte sagt wieviele Daten folgen). Die DMA antwortet im 
normalfall immer mit 0 (DMA-Zeiger auf eine 0). DMA zyklisch, länge 1. 
Wenn was gesendet werden soll wird ein Puffer angelegt, mit dem 1.Byte 
länge + Daten + 0. Dann wird die DMA darauf zeigen lassen, Länge um 2 
höher. Somit ist am ende immer wieder eine 0 im Puffer.
Ein weiteres Problem ist das Buffering in der SPI und in der DMA. Beim 
STM vergehen 2 zyklen, bis ein neues Byte rauskommt. Also wenn man die 
DMA verschiebt, kommen vorher noch 2 alte Bytes raus, bevor der Inhalt 
des neuen Buffers kommt.

Das alles macht es sehr schwer einen passenden Treiber für alles bereit 
zustellen.

von N. G. (newgeneration) Benutzerseite


Lesenswert?

Hallo an alle,

zur Zeit befinde ich mich grad im Prüfungs-Stress, wodurch ich mir für 
den Rest der Woche leider keine Zeit für dieses Problem nehmen kann.
Ich werde allerdings alle eure Antworten lesen und später darauf 
eingehen.

Nochmal um es klarzustellen:
Ich bin primär an einem SPI-Slave interessiert. Und vendor-libs kommen 
nicht in Frage, da diese nicht austauschbar sind (vendor-lock), damit 
fliegen Dinge wie die HAL oder lpcopen raus.

Ich freue mich aber auf weitere Antworten.

Mit freundlichen Grüßen,
N.G.

von N. G. (newgeneration) Benutzerseite


Lesenswert?

Hallo an alle,

Weihnachten und vor allem die Klausuren sind rum :-)
Das bedeutet, dass es hier jetzt weiter gehen kann:

Ich würde schon gerne einen low-level-Treiber schreiben (und eben nicht 
die Register in einem "Higher-level"-Treiber beschreiben).
Klar, SPI ist nicht wirklich mit anderen Bussen vergleichbar.

Effektiv wird es also wahrscheinlich auf eine Implementierung mit einem 
Sende- und einem Empfangspuffer hinauslaufen.

Ich denke, es wäre besser, wenn ihr einmal etwas über die aktuelle 
Aufgabe erfahrt:

Der Slave (also der zu programmierende Prozessor) soll nur Daten zum 
Master senden. Ein Paket besteht aus 48bit. Da die meisten SPI-Module 
jeweils 8bit auf einmal schicken könnten wären es also 6 einzelne 
Übertragungen pro Paket. Die Daten des Masters können verworfen werden.
Wenn keine neuen Daten vorliegen, dann dürfen die alten erneut gesendet 
werden. Alle Bits 0 oder 1 ist nicht erwünscht!

Theoretisch würde ich das so umsetzen:
2 Puffer, einer fürs Senden und einer fürs Empfangen, wobei die 
empfangenen Daten zwar in den Puffer geschrieben werden (kompatibilität) 
aber einfach nicht ausgelesen werden.
Der Puffer für die zu sendenden Daten wird bei jedem neuen "Zyklus" zum 
SPI-Modul kopiert (am besten per DMA, sonst per Interrupt).
So wären die Forderungen von oben erfüllt.

Gibt es dazu irgendwelche Einwände? ;-)

Mit freundlichen Grüßen,
N.G.

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.