Forum: Compiler & IDEs Ideenfindung für modulares Software Konzept


von Dirk R. (nixtodo)


Lesenswert?

Hallo zusammen,

suche nach einer Umsetzung für eine Art Softwarepool.

Ich habe einen Schwung gleicher Hardwaremodule mit einem ATmege168A.
Diese Module werden auf verschiedenen Erweiterungen eingesetzt 
(sozusagen Adaptionen). Jetzt möchte ich mir Softwaretechnisch das Leben 
natürlich einfach machen und allgemeine Funktionen an einer zentralen 
Stelle sammeln.
Dazu habe ich mir (in AVR Studio 5 mit AVRGCC) eine Solution angelegt 
die einmal eine Static Library enthält und für jede Hardware Erweiterung 
ein weiteres Projekt.

So habe ich z.B. die allgemeinen UART Sende- und Empfangsfunktionen in 
der Lib und nutze sie in den verschiedenen Erweiterungen. Dazu wird beim 
Compilieren als erstes die Lib erstellt welche dann bei dem jeweiligen 
Projekt mit dazu gelinkt wird.

Das funktioniert bisher auch ganz gut. Doch jetzt habe ich z.B. den 
Fall, dass ich bei den UART Funktionen bei einem Modul das XON / XOFF 
Handshaking benötige. Bei allen anderen Modulen nicht.
Klar könnte ich jetzt bei allen Funktionen die es betrifft eine zweite 
Version erstellen und mal diese oder mal jene verwenden. Schwierig wird 
es da dann bei ISR's.

Eine Lösung mit #defines & Co die eine Umbenennung der Funktionsnamen in 
den Projekten durchführen möchte ich möglichst vermeiden, da ich dieses 
getrickse mir für Fälle aufheben möchte wo sonst nichts mehr geht. (z.B. 
wenn in der Zukunft sich die Hardware ändert und für die selbe 
Funktionalität verschiedene Softwaren nötig werden.)

Gibt es sonst noch bekannte / erprobte Lösungen?

Vielen Dank!

von Bege (Gast)


Lesenswert?

Hallo,

du schreibts du erstellst die Lib bei jedem Compilieren neu ?!
Dann kannst Du doch für's Lib erstellen eine Headerdatei einbinden, die 
dir den Code in der Lib configuriert.

In etwa so --> Pseudocode !
1
config.h
2
#define XON_XOFF   1
3
#define FREQUENCY  20

In den Sourcen zur Lib bindest Du das Header-File ein und läßt den 
Pre-Prozessor die Arbeit machen. Du hast also eine Code-Basis die alles 
kann was du brauchst, und schaltest die benötigte Funktionalität über 
die Konfiugration frei.
1
Rs232.c
2
#incude "config.h"
3
...
4
void Send(char x)
5
{
6
#if (XON_XOFF ==1)
7
XOn_Pin = 1;
8
#endif
9
SendRegister = x;
10
#if (XON_XOFF ==1)
11
while (SendBusy)
12
XOn_Pin = 0;
13
#endif
14
}

Bege

von Fabian O. (xfr)


Lesenswert?

Dirk R. schrieb:
> Dazu habe ich mir (in AVR Studio 5 mit AVRGCC) eine Solution angelegt
> die einmal eine Static Library enthält und für jede Hardware Erweiterung
> ein weiteres Projekt.

Würde ich nicht machen, weil Du dann genau das Problem hast, dass die 
Static-Library nur auf dem ATmega168A funktioniert und Du keine 
projektspezifischen Unterscheidungen mehr machen kannst.

Besser ist es, den gemeinsamen C-Code in einem gemeinsamen Ordner 
abzulegen und die Dateien als Links in jedes Projekt einzufügen. Dann 
können sie für jedes Projekt mit den projektspezifischen Optionen 
kompiliert werden.

Schau mal in diesen Artikel, da habe ich eine Möglichkeit zur Aufteilung 
von Code und Konfigurationsdateien vorgestellt:
http://www.mikrocontroller.net/articles/Plattformunabh%C3%A4ngige_Programmierung_in_C#Organisation_der_Quellcode-Dateien

von Stefan E. (sternst)


Lesenswert?

Fabian O. schrieb:
> Würde ich nicht machen, weil Du dann genau das Problem hast, dass die
> Static-Library nur auf dem ATmega168A funktioniert und Du keine
> projektspezifischen Unterscheidungen mehr machen kannst.
>
> Besser ist es, ...

Oder noch besser (IMO), man lässt "on demand" individuelle private 
Libraries generieren. Das Makefile der Library bekommt dazu "von Außen" 
Variablen (mindestens MCU-Typ und Obj-Ordner)(*) mit auf den Weg, mit 
deren Hilfe die Library (und auch die zugrunde liegenden Objekt-Dateien) 
dann innerhalb des jeweiligen Projektes (in einem Unterordner) generiert 
wird.
Letztlich hat das dann den selben Effekt wie
> den gemeinsamen C-Code in einem gemeinsamen Ordner
> abzulegen und die Dateien als Links in jedes Projekt einzufügen. Dann
> können sie für jedes Projekt mit den projektspezifischen Optionen
> kompiliert werden.
nur dass man einem Projekt nicht einen Wust an "externen" C-Files 
hinzufügen muss, sondern nur einen make-Aufruf als Pre-Build-Step.

(*) wenn mehr Individualität nötig ist (wie anscheinend beim OP), dann 
kann man zusätzlich auch den Pfad zu einem Library-Config-Header-File 
innerhalb des Projektes mitgeben, der dann vom Library-Makefile an die 
Sourcen dort weitergereicht wird.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

In meinen Projekten Löse ich das auf Quellebene über ein "Repository", 
das C- und H-Dateien enthält, die je nach Bedarf automatisch ins 
aktuelle Projekt kopiert werden.

Kopiert wird deshalb, weil es Module gibt wie
1
/* modul.c */
2
3
#include "modul.h"
4
5
/* C-Code */

und modul.c unverändert in den Projekten verwendet werden kann, nicht 
jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.

Im Makefile ist das Kopieren nur eine Zeile:
1
$(COMMON_SRC): % : $(COMMON)/%
2
  cp $< $@

Dan Rest regeln die Abhängigkeiten und die Definition von COMMON und 
COMMON_SRC.

Eine eigene Lib dafür zu erstellen erscheint mir da als Overkill, zumal 
man die Bibliothek als Multilib erstellen muß oder jedesmal neu erzeugen 
muß um Derivat-unabhängig zu sein oder nach Makros parametrisieren zu 
können.

Zudem sind manche Optimierungen wie LTO nur schwierig mit 
Bibliothekscode anwendbar, d.h. wenn man Bibliothekscode inlinen will, 
muß sowohl die Bibliothek richtig erzeugt sein als auch ein Linker mit 
Plugin-Support verfügbar sein.

von Stefan E. (sternst)


Lesenswert?

Johann L. schrieb:
> Kopiert wird deshalb, weil es Module gibt wie
1
/* modul.c */
2
3
#include "modul.h"
4
5
/* C-Code */
>
> und modul.c unverändert in den Projekten verwendet werden kann, nicht
> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.

Und bei meinem Konzept würde dort dann
1
#include MODUL_HEADER
stehen, und MODUL_HEADER würde im Library-Makefile definiert werden mit 
einem "von Außen" herein gereichten Projekt spezifischen Pfad.

Johann L. schrieb:
> Eine eigene Lib dafür zu erstellen erscheint mir da als Overkill

Das kann man so pauschal doch gar nicht sagen. Wenn es nur eine C-Datei 
ist, logisch. Und wenn die gemeinsam zu nutzende Code-Basis nun 
irgendein nicht-trivialer Protokoll-Stack ist?


Wie so oft ist das letztlich zu einem großen Teil eine Frage 
persönlicher Vorlieben. ;-)

von Fabian O. (xfr)


Lesenswert?

Johann L. schrieb:
> Kopiert wird deshalb, weil es Module gibt wie/* modul.c */
>
> #include "modul.h"
>
> /* C-Code */
>
> und modul.c unverändert in den Projekten verwendet werden kann, nicht
> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.

Warum musst Du die deshalb kopieren? Du kannst doch modul.c und modul.h 
in verschiedenen Ordnern liegen haben.

Überhaupt, warum änderst Du modul.h in jedem Projekt? Darin sollten die 
Prototypen passend zu den öffentlichen Funktionen in modul.c stehen. 
Wenn sich in modul.c eine Funktion ändert oder eine neue dazu kommt, 
musst Du sonst ja alle Versionen von modul.h in den Projekten einzeln 
nachziehen ...

Deshalb würde ich die projektspezifischen Einstellungen in eine eigene 
Datei modul_config.h packen und die entweder in modul.c oder modul.h 
inkludieren, je nachdem ob der Benutzer des Moduls diese Einstellungen 
sehen muss oder nicht. Dann muss man modul.h nur anfassen, wenn sich an 
der Schnittstelle etwas ändert, und das gilt dann für alle Projekte. 
Bringt ja nichts, eine modul.c und modul.h zu haben, die nicht 
zusammenpassen.

von Stefan E. (sternst)


Lesenswert?

Fabian O. schrieb:
> Deshalb würde ich die projektspezifischen Einstellungen in eine eigene
> Datei modul_config.h packen

Ich gehe mal davon aus, dass er genau das gemeint hatte, und ihm nur die 
Namensgebung im Beispiel etwas "ausgerutscht" ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:

> Wie so oft ist das letztlich zu einem großen Teil eine Frage
> persönlicher Vorlieben. ;-)

ACK.


Fabian O. schrieb:
> Johann L. schrieb:
>> Kopiert wird deshalb, weil es Module gibt wie/* modul.c */
>>
>> #include "modul.h"
>>
>> /* C-Code */
>>
>> und modul.c unverändert in den Projekten verwendet werden kann, nicht
>> jedoch modul.h, das pro Projekt zur Verfügung gestellt werden muß.
>
> Warum musst Du die deshalb kopieren? Du kannst doch modul.c und modul.h
> in verschiedenen Ordnern liegen haben.

Ja, kann ich.

Ich hab eben nur vogestellt, wie ich das handhabe.  Und dass es auch 
ohne Library geht.  Irgendwann hab ich mich für diesen Ansatz 
entschieden und bislang gab es meinerseitz kein Bedürfnis, das zu 
änderen -- eben weil es für mich problemlos funktioniert und mir ne 
eigene Library zu viel des Wolfes war / ist.

Wie so oft in der Programmiererei gibt es kein "richtig" oder "falsch", 
sindern eben mehrere unterschiedliche Ansätze, denen man nach Gusto 
folgen kann.  Und das ist auch gut so.

Andere sind in der Auswahl der Ansätze vielleicht eingeschränkter, weil 
sie ne bestimmte IDE verwenden, die das Projektmanagement oder den 
Build-Prozess vorgibt.

Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie 
rumspielen und diese ändern kann, weil sie (selten der Fall) eine 
Erweiterung braucht.

Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken 
(svn, git, bazaar, ...)  oder das jeweils benötigt Standard-Geraffel wie 
Taster, DCF-Decoder, UART, Morsen, Timer, Weiß-der-Teufel zu 
Projektbeginn  von Hand zu kopieren.

> Überhaupt, warum änderst Du modul.h in jedem Projekt? Darin sollten die
> Prototypen passend zu den öffentlichen Funktionen in modul.c stehen.

In einem Fall geht es um Countdown-Zähler, mit denen "langsame" Zeiten 
gemacht werden wie: Timeout für einen Bildschirmschoner, Level-abhängige 
Geschwindigkeitssteuerung von Spielen, LED-Blinker, etc. Welche Zähler 
gebraucht werden ist natürlich 100% vom Projekt abhängig, die 
Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h. 
modul.c kann in allen Projekten, die Timer verwenden gleich sein.

Jedenfalls funktionieren diese Zähler so gut und mit so wenig Overhead, 
daß ich bisher in keiner meiner Projekte auch nur ansazuweise das 
Bedürfnis nach Multithreading oder Tasks oder was auch immer hatte.

Und bei Harten Echtzeitanforderungen wie zB Darstellung eines 
Spielgeschehens auf einer Elektronenstrahlröhre, kann man mit dem 
Overhead eines Taskwechsels echt nix anfangen.

> Wenn sich in modul.c eine Funktion ändert oder eine neue dazu kommt,
> musst Du sonst ja alle Versionen von modul.h in den Projekten einzeln
> nachziehen ...

In dem Fall von obn äbdern sich keine Funktionen, sondern lediglich 
Datenstrukturen.  Ist zwar eher ungewöhnlich das, ist aber so :-)

von Fabian O. (xfr)


Lesenswert?

Johann L. schrieb:
> Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie
> rumspielen und diese ändern kann, weil sie (selten der Fall) eine
> Erweiterung braucht.

Überschreibt Dein Makefile diese Änderungen nicht gleich wieder?

> Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken
> (svn, git, bazaar, ...)

Bei sehr vielen Projekten dürfte das die beste Lösung sein. Wenn alle 
Projekte auf die gleichen Module zugreifen, muss man halt auch immer 
alle ändern, wenn es inkompatible Änderungen in der Schnittstelle gibt. 
Wenn jedes Projekt seine eigenen lokalen Kopien hat, kann man dagegen in 
älteren Projekten einfach die älteren Versionen lassen, mit denen sie 
funktionieren. Andererseits kann man dank Repository bei Bedarf mit 
einem Klick auf die aktuellste (oder jede beliebige andere) Version 
updaten.

> oder das jeweils benötigt Standard-Geraffel wie
> Taster, DCF-Decoder, UART, Morsen, Timer, Weiß-der-Teufel zu
> Projektbeginn  von Hand zu kopieren.

Das wird fürchterlich unübersichtlich, wenn man mit der Zeit überall 
verschiedene Versionen mit verschiedenen Features und Bugs rumfliegen 
hat.

Johann L. schrieb:
> Welche Zähler
> gebraucht werden ist natürlich 100% vom Projekt abhängig, die
> Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h.
> modul.c kann in allen Projekten, die Timer verwenden gleich sein.

Zur Bedienung gehört NUR modul.h. Da steht für mich als Benutzer 
drinnen, wie ich das Modul zu bedienen habe. modul.c hat mich nicht zu 
interessieren. Wenn ich da reinschaun muss, ist schon was falsch 
gelaufen. Dass ich als Benutzer die Bedienungsvorschrift des Moduls 
(modul.h) unter meiner Kontrolle habe (indem ich sie ändern kann/soll), 
ist ziemlich unlogisch und keine saubere Trennung der Zuständigkeiten.

Leider machen das viele C-Module so, die in ihrer (einzigen) Headerdatei 
irgendwelche Einstellungen definieren (Puffergrößen, Ports, ...), die 
der Benutzer ändern soll. Das ist da absolut fehl am Platz. Bei jedem 
Update müsste man die Einstellungen in der Headerdatei wieder 
nachziehen. Oder man läuft der Gefahr, dass sie nicht mehr zur 
Implementierung passt.

Klar, im Hobbybereich für sich selber kann man das machen wie man will. 
Aber wenn der Code auch von anderen verwendet wird, ist das keine Frage 
persönlicher Vorliebe mehr, sondern von sauberem Softwaredesign.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fabian O. schrieb:
> Johann L. schrieb:
>> Kopieren hat auch den Vorteil, daß man mit einer lokelen Kopie
>> rumspielen und diese ändern kann, weil sie (selten der Fall) eine
>> Erweiterung braucht.
>
> Überschreibt Dein Makefile diese Änderungen nicht gleich wieder?

Nein.  Änderungen machen Dateien neuer, nicht älter.

>> Alternative Möglichkeit wäre, direkt ein SCM-Repository einzuklinken
>> (svn, git, bazaar, ...)
>
> Bei sehr vielen Projekten dürfte das die beste Lösung sein. Wenn alle
> Projekte auf die gleichen Module zugreifen, muss man halt auch immer
> alle ändern, wenn es inkompatible Änderungen in der Schnittstelle gibt.

Das ist aber ein generelles Problem von Code-Sharing.  D.h. wenn der 
Code Projektübergreifend verwendet wird, ohne daß er ausgegoren ist, 
dann ist es nicht unwahrscheinlich, daß inkompatible Änderungen 
erforderlich werden.

Der Code sollte also eine gewisse Reife haben, bevor er geminsam 
verwendet wird — was aber unabhängig von der Verwendung per Quelle oder 
Bibliothek ist.  Und auch bei Verwendung eines SCM hat man die 
Problematik.  Zwar hat man damit Versionierung, aber wenn man durch den 
Zoo von Versionen nicht mehr durchblickt wird das auch nix helfen...

> Johann L. schrieb:
>> Welche Zähler
>> gebraucht werden ist natürlich 100% vom Projekt abhängig, die
>> Bedienung der Zähler im zugehörigen C-Modul hingegen nicht, d.h.
>> modul.c kann in allen Projekten, die Timer verwenden gleich sein.
>
> Zur Bedienung gehört NUR modul.h. Da steht für mich als Benutzer
> drinnen, wie ich das Modul zu bedienen habe. modul.c hat mich nicht zu
> interessieren.

modul.c interessiert auch nicht. Aber es wird eben gebraucht und daher 
aus der Codebases genommen — ansonsten gibt's ein Compiler- oder 
Linkerfehler.

> Wenn ich da reinschaun muss, ist schon was falsch gelaufen.
> Dass ich als Benutzer die Bedienungsvorschrift des Moduls
> (modul.h) unter meiner Kontrolle habe (indem ich sie ändern kann/soll),
> ist ziemlich unlogisch und keine saubere Trennung der Zuständigkeiten.

Warum soll ich in einem Modul 10 Timer haben, obwohl nur 2 genraucht 
werden, nur weil ein anderes Projekt 10 Timer hat?

Im o.g. Falle ist das modul.h daher projektabhängig.  Gleichwohl weiß 
modul.c, wie es mit modul.h und den datenstrukturen darin umzugehen hat.

> Leider machen das viele C-Module so, die in ihrer (einzigen) Headerdatei
> irgendwelche Einstellungen definieren (Puffergrößen, Ports, ...), die
> der Benutzer ändern soll. Das ist da absolut fehl am Platz. Bei jedem
> Update müsste man die Einstellungen in der Headerdatei wieder
> nachziehen.

Nein, wie kommst du darauf?

> Oder man läuft der Gefahr, dass sie nicht mehr zur
> Implementierung passt.

Auch nicht der Fall, natürlich passt das.

von Fabian O. (xfr)


Lesenswert?

Deine countdown.h besteht aus zwei Teilen: Ein Teil ist die 
Konfiguration der ganzen Counter, der andere ist die öffentliche 
Schnittstelle von countdown.c.

Gut, in Deinem Fall ist Schnittstelle wirklich nicht groß, aber sie 
gehört imo in eine eigene Datei:
1
// countdown.h
2
3
#include "countdown_config.h"
4
5
static inline
6
void wait_10ms (const uint8_t t)
7
{
8
#ifdef __AVR__
9
    asm volatile (" ; BARRIER" ::: "memory");
10
#endif // AVR
11
    count.ms10.wait_10ms = 1+t;
12
    while (count.ms10.wait_10ms);
13
}
14
15
// Alle 10 ms aufrufen wenn COUNTDOWN_US100 nicht define'd ist. 
16
// Alle 100 µs aufrufen wenn COUNTDOWN_US100 define'd ist.
17
extern void job_countdown (void);
Das sind Sachen, die von countdown.c abhängen bzw. zur Implementierung 
gehören und nicht vom Projekt. Du kopierst sie aber in jedes Projekt. 
Wenn Du daran mal was ändern willst oder was hinzufügen, kannst Du es in 
allen Projekten von Hand nachziehen.

Die Definition von countdown_t ist dagegen projektspezifisch. Wobei 
diese Elemente "für internen Gebrauch" auch nicht in der 
benutzerdefinierten Konfiguration stehen sollten. Wenn Du an der 
Implementierung was änderst und diese internen Elemente deswegen anders 
sein müssen, kannst Du es auch wieder in allen Projekten nachziehen.

Man könnte stattdessen z.B. für jeden Countertyp einen eigenen typedef 
oder ein Makro anlegen, die projektspezifisch definiert werden, und die 
dann in die Gesamtstruktur einbinden. Die Gesamtstruktur würde in 
countdown.c liegen, also dort, wo sie den Benutzer nichts angeht. Dort 
kannst Du den Rahmen dann ändern, wie Du magst, ohne die 
projektspezifischen Konfigurationen anfassen zu müssen.

Projektspezifische Konfiguration:
1
// countdown_config.h
2
3
typedef struct
4
{
5
  // Ab hier die benötigten 8-Bit Countdown-Zähler eintragen
6
} countdown_us100_t;
7
8
typedef struct 
9
{
10
  // Ab hier die benötigten 8-Bit Countdown-Zähler eintragen
11
  uint8_t wait_10ms;
12
  uint8_t blink;
13
  uint8_t morse;
14
  // ...
15
} countdown_ms10_t;

Implementierung:
1
// countdown.c
2
3
#include "counterdown.h"
4
5
typedef struct
6
{
7
#ifdef COUNTDOWN_US100
8
  // Die Kompoinente muss mindestens das erste Elemenet enthalten
9
  struct
10
  {
11
    // Erstes Element ist für internen Gebrauch!
12
    uint8_t timer_10ms;
13
    countdown_100us_t user;
14
  } us100;
15
#endif /* COUNTDOWN_US100 */
16
17
  // Die Kompoinente muss mindestens das erste Elemenet enthalten
18
  struct
19
  {
20
    // Erstes Element ist für internen Gebrauch!
21
    uint8_t timer_1s; 
22
    countdown_ms10_t user;
23
  } ms10;
24
25
  // ...
26
} countdown_t;

In dem Fall ists wahrscheinlich so hochspeziell, dass Du daran nicht oft 
was ändern musst. Allgemein ists aber eben aus meiner Sicht nicht sauber 
getrennt, weil in countdown.h Konfiguration, Schnittstelle und 
Implementierung miteinander gemischt sind.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wie auch immer, mit der ursprünglichen Frage hat das nun überhaupt nix 
mehr zu tun...

von Hans-jürgen H. (hjherbert) Benutzerseite


Lesenswert?

Ich habe für jedes Projekt einen Ordner und darin einen Makefile, der 
definiert, was dieses Projekt besonderes braucht, und zählt auch die 
erforderlichen Quellen aus der Bibliothek auf:
**** Start projekt21/makefile
1
PRG=projekt21
2
3
DEFS = \
4
  -DNPRGPATH=$(NPRGPATH) \
5
  -DF_CPU=16000000UL \
6
  -DNCHAN=2 \
7
8
OBJ = $(PRG).o \
9
  serial.o \
10
  blink.o \
11
12
MCU=t2313
13
include ../Lib/makedefs
**** Ende projekt21/makefile

Dadurch, dass makedefs hier eingebunden ist, werden die .o-Dateien im 
Ordner  projekt21 abgelegt, obwohl die Quellen zum Teil aus ../Lib 
kommen.
Dabei hilft die Regel: %.o : ../Lib/%.cpp

**** Start Lib/makedefs
1
CFLAGS = $DEFS  -mmcu=$(MCU) -Wall -Os 
2
ASFLAGS = -mmcu=$(MCU) $(DEFS)
3
4
all: $(PRG).elf
5
  echo  Makefile of $(PRG) all done
6
7
# Linken
8
$(PRG).elf: $(OBJ)
9
  avr-gcc -mmcu=$(MCU) -Wall -Os -Wl,-Map,$(PRG).map  -o $@ $^ $(LIBS)
10
11
$(PRG).o : $(PRG).cpp
12
  avr-gcc -c  $(CFLAGS) $< -o $@
13
14
%.o : ../Lib/%.s
15
  avr-gcc -c $(ASFLAGS) $< -o $@
16
17
%.o : ../Lib/%.cpp
18
  avr-gcc -c $(CFLAGS) $< -o $@
**** Ende Lib/makedefs

Das Makefile holt dann Quellen aus Lib und speichert die Prozessor

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.