Hallo liebe Leute, mein Name ist Olli und ich programmiere nun schon seid ein paar Jahren Mikrocontroller (zurzeit hauptsächlich XMEGA) in C. Nun versuche ich immer wieder meine eigene Programmierweise zu hinterfragen und könnte dabei eure Hilfe gebrauchen. Es geht um das Thema Effizienz vs. "Usability". Was meine ich: Wenn ich zum Beispiel einen Beschleunigungssensor, wie den LSM303D, an meinen Controller anschließe, so schreibe ich meist einen "Treiber", also ein Software-Modul, welches die notwendigen Funktionen zur Verwendung des Sensors bereitstellt. Der Sensor wird per TWI/I2C angesteuert und bietet eine Reihe von Registern für Einstellungen oder die Messdaten. Nun kann eine der Funktionen des "Treibers" sein, dass zum Beispiel das 8bit-Register CTRL1 des Sensors ausgelesen wird. Ich habe im Anhang zwei Ansätze, die die beiden Begriffe Effizienz und "Usability" erklären sollen. Der erste Ansatz ist der effizienteste, da dort der Funktion nur der Registerinhalt vom Nutzer übergeben und dann an den Sensor übermittelt wird. Beim zweiten Ansatz werden an die Funktion verschiedene Parameter übergeben, die den Inhalt des Registers darstellen. Hier also die Datenrate, die Achsen, welche verwendet werden sollen, und ob ein "Block Update" erfolgen soll. Das Problem: Der zweite Ansatz ist besser zu verwenden, da man sich nicht mit dem Inhalt der Register auseinandersetzen muss. Dafür ist er aber nicht sehr effizient, da zum einen mehrere Variablen für die Datenübergabe notwendig sind und zum anderen die Daten erst "zusammengepackt" werden müssen. Das Problem verstärkt sich dann, wenn zum Beispiel in einem Register die 8bit je andere Funktionen haben. Dann müsste man beim zweiten Ansatz 8 Bool-Variablen übergeben (oder eine struct mit 8 Bool-Variablen). Die Fragen: Wie handhabt ihr das bei euren Programmen? Welche Erfahrungen habt ihr gemacht? Ich bin der Meinung, dass man speziell in der Mikrocontroller-Programmierung auf die Effizienz achten muss, da ja meist die Ressourcen knapp sind ;) (im Vergleich zur Programmierung auf z.B. einem Linux-Board oder PC). Trotzdem ist es schon sinnvoll, wenn man auch auf die Verwendbarkeit achtet, und wenn es nur die eigene ist, da man auch so Zeit sparen kann. Ich hoffe ich konnte mein anliegen gut rüberbringen =) Gruß Olli
Wie wäre es mit einer verODERung beim Funktionsaufruf? also bspw: lsm303d_chg_ctrl1(LSM303D_AODR_200HZ | LSM303D_AXIS_SEL_XY); das wird zur Compilezeit aufgelöst und verschwendet daher keine Ressourcen.
Du siehst da nur ein Teilproblem. Die Frage, ob man da nur ein komplettes Byte rüberschiebt, oder das Byte zunächst aus Parametern passend zusammenbauen muss, hängt ja zu allererst mal von der Anwendung ab. Musst du das Byte parametrieren, dann ist das halt so. Ob das nun in der Kommunikationsfunktion passiert, oder außerhalb da von, ist fast egal, machen musst du es trotzdem. Oliver
Oliver S. schrieb: > Ob das nun in der Kommunikationsfunktion passiert, > oder außerhalb da von, ist fast egal Taktzyklen-mäßig fast, ja, deshalb kann die Funktion auch ruhig getrennte Parameter bekommen, es einfach schöner. Noch dazu bei der initialisierung über I2C...
Ich hab einen Initialisierungsbefehl und dann Datenbefehle. Alles auf Lowlevel. Das hat vielleicht staerker Restriktionen, ist aber ok, da immer genau so angewendet, und transparenter.
Wenn's um Effizienz geht, wäre dann nicht einmal darüber nachzudenken ob die Übergabe der Pointer der richtige Wert ist. Das schränkt meiner Meinung nach auch die Usability ein.
1 | int lsm303d_chg_ctrl1(uint8_t* register_data, bool write) |
...
1 | lsm_data[1] = register_data; |
Was sagt eigentlich der Compiler dazu? Und wie sieht der Aufruf aus?
Usability steht bei mir im Vordergrund. So, dass ich den Code leicht warten und portieren kann. Meist muss man den Code erst 2 Jahre später wieder ändern. Und dann freust du dich, dass das einfach geht und dass du deinen eigenen Code noch verstehst. Ich nehme sogar C++ mit Klassen und Konstruktoren, kapsel die Daten und baue Zugriffsfunktionen. Zu 99% geht auch alles auf den kleinen AVRs. Und ein gekapselter Sensorzugriff portiert sich auch gut in ein neues Programm, welches zufällig den gleichen Sensor benutzt. Nur wenn die Performance mal wirklich nicht reicht, dann müssen ein paar Hacks rein. Aber das ist sehr selten.
Wichtig für eine effiziente Programmierung und spätere Portierung ist mMn. dass es definierte schnittstellen gibt, also z.B. TWI_send_data(data, kanal) und diese auf jedem System zur verfügung stehen und eine definierte Aufgabe haben. Denn die braucht man immer wieder.
Oliver Lubritz schrieb: > Ich bin der Meinung, dass man speziell in der > Mikrocontroller-Programmierung auf die Effizienz achten muss, da ja > meist die Ressourcen knapp sind Auch auf µC muß man nicht überall auf Effizienz achten. Lesbarkeit (und damit Hand in Hand gehend: Wartbarkeit) des Codes sollte immer oberste Prorität haben. Wenn man Lesbarkeit der Effizienz opfert, dann nur nachdem man die Notwendigkeit und(!) Wirksamkeit dieses Ansatzes nachgewiesen hat. Aber heutzutage sind Hochsprachen-Compiler doch viel besser als ihr Ruf. XL
> Aber heutzutage sind Hochsprachen-Compiler doch viel > besser als ihr Ruf. Ein Ruf kommt nie von ungefähr ;-)
Danke erstmal für die Antworten. @Cube_S: Die Übergabe von Pointern ist sicher für die Verwendbarkeit an sich eher unpraktisch, aber hier sinnvoll denke ich, da die Funktion für das Lesen und Schreiben genutzt werden soll. Allgemein war das aber nur ein Beispiel. Deshalb auch leider der Fehler:
1 | lsm_data[1] = register_data; |
Hätte ich drauf achten sollen auch wenn es nur ein Beispiel ist, müsste in diesem Fall natürlich heißen:
1 | lsm_data[1] = *register_data; |
@Phantomix: An die VerODERung habe ich auch schon gedacht. Ein interessanter Ansatz. @Axel: Der Hinweis mit der Wartbarkeit ist gut. Ich hatte eigentlich mehr darauf geachtet, dass andere Menschen den Code wiederverwenden könnten, aber bezüglich Wartbarkeit hab ich eher gedacht ich bekomme das schon hin. Nur nach zwei Jahren ist es sicher schwer auch wenn man es selbst geschrieben hat. Danke auf jeden Fall erstmal für eure Erfahrungen. Ich habe in meinem Umfeld kaum Menschen mit denen ich mich in diesem Bereich austauschen kann, deswegen ist es sehr schön mal Erfahrungen von anderen als Input zu bekommen. Olli
Oliver Lubritz schrieb: > Die Übergabe von Pointern ist sicher für die Verwendbarkeit an sich eher > unpraktisch, aber hier sinnvoll denke ich, da die Funktion für das Lesen > und Schreiben genutzt werden soll. Vielleicht sind an der Stelle zwei getrennte Funktionen sinnvoller, da du innerhalb der Funktion eh zwei Codeblöcke hast. Edith: Das macht zwar vielleicht ein paar Bytes mehr im Flash aus, ist aber für die Laufzeit günstiger.
:
Bearbeitet durch User
Phantomix Ximotnahp schrieb: > Edith: > Das macht zwar vielleicht ein paar Bytes mehr im Flash aus, ist aber für > die Laufzeit günstiger. Ja der eigentliche Hintergrund für die Schreibweise war, den Flash zu schonen. Geht hat auf Kosten der Verwendbarkeit und wie du sagst Laufzeit. Besonders unpraktisch, wenn man die Funktionen nur zum Schreiben nutzt, da man sich dann meist Variablen für die Übergabe an die Funktion sparen kann. Es war mal eine Anmerkung an mich eines Menschen der C-Programmierung im Bereich Embedded-Linux macht. Ist sicher kein schlechter Ansatz, jedoch sollte man das, wie bei allem, wahrscheinlich nicht stur verfolgen, sondern je nach Situation anpassen. Olli
Oliver Lubritz schrieb: > Der Sensor wird per > TWI/I2C angesteuert und bietet eine Reihe von Registern für > Einstellungen oder die Messdaten. Ich mache das meistens so, daß ich einmal die nötigen Einstellungen eines Peripheriebausteins im h-File definiere und die Init-Funktion rasselt das einfach nur runter und schickt es raus. Danach rufe ich nur noch die Read-Funktion auf, die mir den aktuellen Meßwert liefert. Bei nem ADC muß ich vielleicht noch den Meßeingang umschalten. Daß ich zur Laufzeit an sämtlichen Parametern rumspielen muß, kommt eigentlich nie vor.
Peter Dannegger schrieb: > Daß ich zur Laufzeit an sämtlichen Parametern rumspielen muß, kommt > eigentlich nie vor. Richtig. Zur Laufzeit schraubt man wirklich nur relativ selten an irgendwelchen Konfigurationen herum. Aber es kommt, je nach Anwendung, eben doch vor und wenn es vorkommt, ist es relativ häufig auch noch performance-relevant. Dazu kommt noch ein weiteres Problem: Es gibt eigentlich oft keine klare Abgrenzung zwischen "Konfiguration" und "Betrieb", weil das erst durch die Art der Benutzung der Hardware festgelegt wird. Einfaches, aber durchaus typisches Beispiel für das Dilemma: der generische IO-Pin. Normalerweise legt man in der "Konfiguration" fest, ob das Ding als Eingang oder als Ausgang benutzt wird. Aber es gibt auch viele Anwendungen, da benutzt man exakt dieselbe Sache im Betrieb zum Wechsel zwischen einem aktiven Ausgang und dem HighZ-Status. Allein dieses kleine Beispiel zeigt die grundsätztliche Schwäche jeder Abstraktion: sie abstrahiert (oft unter dem Gesichtspunkt einer typischen Art der Nutzung) und verdeckt damit vorhandene Möglichkeiten oder stellt sie zumindest nur suboptimal und damit im allgemeinen auch langsam zur Verfügung. Und dieses Problem ist keinesfalls nur auf den Bereich der µC beschränkt, das findet man auf allen Ebenen der Softwareentwicklung. Ich denke da nur an die "File"-Abstraktion der Unixoide. Klar, es ist wunderschön einfach, eine UART als File zu öffnen, um ein paar Bytes darüber zu verschicken. Aber wenn ich wirklich alle Möglichkeiten nutzen will, die die UART-Hardware bietet, wird es recht kompliziert (und laaangsaaam). Oder, noch deutlich krasser: LPT unter Windows. Eine vollständige Nutzung aller Features der Hardware ist dort überhaupt nur mit eigener Treiberentwicklung möglich. Klarer Fall von Überabstraktion mit dem Focus "Druckerschnittstelle". Abstraktion ist Fluch und Segen zugleich. In kleinen µC hat sie eher nix zu suchen.
Oliver Lubritz schrieb: > Nun kann eine der Funktionen des > "Treibers" sein, dass zum Beispiel das 8bit-Register CTRL1 des Sensors > ausgelesen wird. Jaja, etwa so stellt sich auch diese unsägliche ST-Lib dar, wo um die Hardware herum nur ein Brei aus Software geschmiert wird, der sachlich gar keinen Nutzen bringt, sondern nur das Ganze verkompliziert. Es geht besser, sehr viel besser. Aber dazu muß man sich zunächst die gewünschte Wirkfunktion der Peripherie klarmachen. Im Beispielfall deines Beschleunigungssensors wäre das ja vermutlich, daß dein eigentliches Anwendungsprogramm zum einen den derzeitigen Stand (Richtung&Größe) der vorhandenen Beschleunigung abfragen will - und daß es zum anderen entweder zeitzyklisch oder bei hinreichenden Veränderungen aktiviert werden muß. Also sollte dein "Treiber" sich intern um alles kümmern, was für den Sensor nötig ist und seine Schnittstelle zum übergeordneten Programm sollte eigentlich nur folgendes bieten: - den momentanen Stand ablesen können - in das vorhandene Eventsystem bei entsprechenden Bedingungen eine Botschaft einspeisen. Kurzum, der Treiber soll für den Rest der Firmware eben DAS liefern, was man eigentlich wissen möchte. Alle Interna wie Register etc. sind dem aufrufenden programm doch eigentlich schnurz. Ähnlich sieht es z.B. mit einem virtuellen COM-Port per USB aus. Kein aufrufendes Programm interessiert sich dafür, WIE der Treiber das zu sendende Zeugs über den USB rüberkriegt, oder wie er empfangenen Daten zwischenspeichert, bis man sie sich asynchron und einzeln je nach Gusto abholt. W.S.
Peter Dannegger schrieb: > Ich mache das meistens so, daß ich einmal die nötigen Einstellungen > eines Peripheriebausteins im h-File definiere und die Init-Funktion > rasselt das einfach nur runter und schickt es raus. > Danach rufe ich nur noch die Read-Funktion auf, die mir den aktuellen > Meßwert liefert. Bei nem ADC muß ich vielleicht noch den Meßeingang > umschalten. > Daß ich zur Laufzeit an sämtlichen Parametern rumspielen muß, kommt > eigentlich nie vor. So habe ich das quasi jetzt auch gelöst. Es kann aber schon passieren, dass man Einstellungen ändern muss, zum Beispiel wenn man die Datenrate, mit der der Sensor misst auf "Power-Down" stellen will, ihn also quasi ausschaltet. Bei einigen meiner Projekte schalte ich die Sensorik per Hardware sogar ganz ab zwischendurch. Nur wenn ich Messen will schalte ich sie ein, mache eine Messung und schalte sie dann wieder aus. Man benötigt dann immer wieder eine Neukonfiguration (ok die packt man in eine Funktion und ruft sie einfach immer wieder auf). @c-hater: Eine interessante Anmerkung. Ich denke du hast Recht. Eine Abstraktion kann helfen aber auch hinderlich sein. @W.S.: Ja es ist richtig es geht besser. Eine entsprechende Abstraktion verbessert auch auf jeden Fall die Nutzbarkeit, das stimmt. Aber wie schon geschrieben wurde, kann eine zu starke Abstraktion auch ein Fluch sein. Der Sensor bietet eine Menge von Funktionen und Einstellungsmöglichkeiten, welche man vielleicht nutzen will. Wenn ich nun eine dieser Einstellungen im Betrieb ändern möchte kann ich dies nicht, wenn mir keine entsprechenden Funktionen bereitgestellt werden. Ich denke dort lässt sich ein Mittelweg finden, der mir noch fehlt. Auf jeden Fall danke für den Denkansatz. Olli
Oliver Lubritz schrieb: > Nur wenn ich Messen will schalte > ich sie ein, mache eine Messung und schalte sie dann wieder aus. Man > benötigt dann immer wieder eine Neukonfiguration (ok die packt man in > eine Funktion und ruft sie einfach immer wieder auf). Dann könnte man dem Init einen Parameter übergeben (0 = off, 1 = init). Man kann auch separate Funktionen für selten benötigte Sonderwünsche schreiben. Werden sie nicht aufgerufen, dann kann ja der Linker toten Code entfernen.
Peter Dannegger schrieb: > Oliver Lubritz schrieb: >> Nur wenn ich Messen will schalte >> ich sie ein, mache eine Messung und schalte sie dann wieder aus. Man >> benötigt dann immer wieder eine Neukonfiguration (ok die packt man in >> eine Funktion und ruft sie einfach immer wieder auf). > > Dann könnte man dem Init einen Parameter übergeben (0 = off, 1 = init). > > Man kann auch separate Funktionen für selten benötigte Sonderwünsche > schreiben. Werden sie nicht aufgerufen, dann kann ja der Linker toten > Code entfernen. Stimmt das wäre auch eine Möglichkeit. Danke =) Olli
Sehr gut lässt sich so etwas übrigens in C++ mit Templates erschlagen. Da wird viel zur Compilezeit bereits erledigt :) LG Jan
Hallo, Oliver Lubritz schrieb: > Aber wie > schon geschrieben wurde, kann eine zu starke Abstraktion auch ein Fluch > sein. Der Sensor bietet eine Menge von Funktionen und > Einstellungsmöglichkeiten, welche man vielleicht nutzen will. Wenn ich > nun eine dieser Einstellungen im Betrieb ändern möchte kann ich dies > nicht, wenn mir keine entsprechenden Funktionen bereitgestellt werden. dann stellt man sie halt zur Verfügung. Werden spezielle Funktionen einer konkreten Schnittstellenimplementierung benötigt, kann man in die Schnittstelle ja etwas wie einen "getDelegate()" vorsehen, wie z.B. in der Klasse EntityManager des Java Persistence APIs. Der Pointer, den getDelegate() zurückliefert, muß dann halt gecastet werden. Die eigendliche Domäne der Anwendung braucht mit derartigen Spezialitäten nichts zu tun zu haben, wenn die Zugriffe auf die unter der Schnittstelle liegenden Funktionen entsprechend weggekapselt werden. Es gibt ja genug Design Patterns, die solchen Mechanismen einen Namen geben, hier z.B. Decorator, Wrapper oder Proxy. Grüße, Markus
Jan Berg schrieb: > Sehr gut lässt sich so etwas übrigens in C++ mit Templates erschlagen. > Da wird viel zur Compilezeit bereits erledigt :) > > LG Jan Ich verwende jedoch C ;)
Oliver Lubritz schrieb: > Es geht um das Thema Effizienz vs. "Usability". Effizienz ist einfach: Wenn sich am späteren Programm nichts mehr weglassen lässt, ohne die Funktion (inkl. Fehlerbehandlung) zu kastrieren, dann ist es Code-effizient, und wenn es im verfügbaren uC schnell genug läuft, erfüllt es auch die Anforderungen an speed und space. Effizienz ist also ein hartes Kriterium. Oliver Lubritz schrieb: > Wie handhabt ihr das bei euren Programmen? Welche > Erfahrungen habt ihr gemacht? Daß das, was als angeblich vorausdenkend und erweiterungsfähig bei der Entwicklung angepriesen wurde, immer zu kurz gedacht war und die Handhabbarkeit des Codes massiv verschlechterte, weil man aus cpde nicht mehr ersehen konnte, was benötigt und was überflüssig war (mit dem Effekt daß überflüssiger Code auch oft nie getestet und daher falsch war), und man oft über die Funktion des Codes vollkommen im unklaren war weil man dem generischen code nicht mehr ansehen konnte, wozu er nützlich ist, was er als machen SOLL (Ausnahmen sind da echt generischer Code, beispielsweise ein Datenbankzugriff ohne sich um konkrete Daten/Tabellenstrukturen zu kümmern). Usability ist also bullshit, weil als weiches Kriterium stets Auslegungssache ist und 90% der Coder legen es zu ihren Faulheitsgunsten aus, statt klar zu sagen "ja, ich wusste auch nicht wie ich das Problem lösen sollte, da habe ich mal 200 Zeilen Code geschrieben der das Problem bloss weiterreicht".
Oliver Lubritz schrieb: > Ich bin der Meinung, dass man speziell in der > Mikrocontroller-Programmierung auf die Effizienz achten muss, da ja > meist die Ressourcen knapp sind ;) Ist Dein System (Microcontroller) ausreichend, um Deine Anforderungen (was Du erreichen willst) bei der von Dir gewählten Implementierung (wie Du es programmiert hast) zu erfüllen? Wenn ja, kannst Du so ineffizient sein, wie Du willst, aber es spielt überhaupt keine Rolle, denn Dein System ist ausreichend stark. Wenn nein, kannst Du entweder - ein leistungsstärkeres System nehmen - deine Anforderungen reduzieren - versuchen, durch Optimierung Dein Ziel zu erreichen Die Frage ist nun, ob die Optimierung sinnvoll (wirtschaftlich) ist, denn es kostet Dich Arbeit: 1. Das Optimieren selbst 2. Später, wenn Du Deinen hochoptimierten Code noch mal anfassen mußt, und falls Dich dies mehr Aufwand kostet, als bei einem weniger effizienten, aber besser verständlichen Code Ggf. ist es billiger, einen größeren µC zu kaufen.
Effizienz ist sehr wichtig besondrs bei Mikrocontroller Programierung, weil die Ressourcen knapp sind. Das hängt auch von dem Compiler ab, wenn er gut ist, dann kann er optimierten asm Code erzeugen, und wenn nicht, dann helfen dir deine Optimierungversuche auch nicht.
@MaWin: Danke. Schöner Denkanstoß. Du hast Recht, wenn man mehr Funktionen einbringt als man benötigt dann passiert es oft, dass der nicht benötigte Code nicht richtig getestet ist. Das kann dann echt in die Hose gehen später. @Bronco: Speziell deine Anmerkung zur Wirtschaftlichkeit ist wichtig. Das habe ich mir zwar auch schon gedacht, aber irgendwie noch nicht richtig berücksichtigt. Olli
W.S. schrieb: > Oliver Lubritz schrieb: >> Nun kann eine der Funktionen des >> "Treibers" sein, dass zum Beispiel das 8bit-Register CTRL1 des Sensors >> ausgelesen wird. > > Jaja, etwa so stellt sich auch diese unsägliche ST-Lib dar, wo um die > Hardware herum nur ein Brei aus Software geschmiert wird, der sachlich > gar keinen Nutzen bringt, sondern nur das Ganze verkompliziert. Ja und nein. Habe zwar mit der ST-Lib noch nicht gearbeitet, allerdings haben solche Software-Layer auch einen entscheidenden Vorteil: Sie berücksichtigen die diversen Fehler (sollten dies so weit möglich), die heutige Controller so mit sich rumschleppen. Das für jeden Controller bzw. Variante selbst zu berücksichtigen ist mMn nicht nur fehleranfällig, sondern ebenso verschwendete Zeit, die besser für andere Sachen genutzt werden kann. Probleme an der Sache: - Niemand garantiert wirklich, dass die APIs dieser Libs über einen längeren Zeitraum gleich bleiben oder die nächste Controllergeneration unterstützen - Es kommt ein weiteres Abstraktionslayer hinzu, wenn diese Lib-Funktionen durch eigene Treiber gekapselt werden
Oliver Lubritz schrieb: > Du hast Recht, wenn man mehr > Funktionen einbringt als man benötigt dann passiert es oft, dass der > nicht benötigte Code nicht richtig getestet ist. Das kann dann echt in > die Hose gehen später. Wenn man keine Test schreibt. Was zu MaWins Zeit wohl noch nicht ueblich war - da hielt man Ausprobieren noch fuer Testen. Es fast ist immer sinnvoll, aktuell benoetigte Funktionalitaet nicht im Minimum genau auf den Anwendungszweck zugeschnnitten zu implementieren. Stattdessen wird die Anforderung abstrahiert, die Loesung generalisiert und so entwickelt und getestet. So vermeidet man komplexe, schlecht wart- und erweiterbare Spezialloesungen. So spezifisch wie ein Stueck Code ist, so spezifisch sind dann auch die Tests - eng beschraenkt auf den einen Pfad, der bei der Entwicklung sichtbar war. Test sollen aber grossraeumig sein, damit sie auch Fehler finden, mit denen keiner gerechnet hat. Und an um so mehr Stellen ein Stueck Code verwendet wird, um so eher faellt ein Fehler zeitig auf. Mit spezifischen Code ist das nicht moeglich - da wird dann einfach noch eine Spezialloesung implementiert, die auch wieder genauso lausig getestet wird. Noch schlimmer ist, wenn Jemand nachtraeglich an diesem beschraenkten Stueck Code rumfummeln muss, der gerade ein ganz anderes Problem loesen muss, weil Funktionalitaet fehlt, die gleich von Anfang leicht zusaetzlich haette implementiert werden koennen. Welcher Code ist wohl besser: Der von dem Entwickler, der gerade 100% im Thema ist, oder von jenem, der nebenbei etwas hinzufuegen muss? Synergieeffekte treten nicht auf! Natuerlich kostet das am Anfang was, es wird Code geschrieben, der nicht unmittelbar gebraucht wird. Aber es ist eben so, wie immer im Leben: Gewinn macht nur, wer zeitig investiert. Natuerlich gibt es Gegenbeispiele... endlose Frameworkhackerein, die nie an ein Ziel kommt. Damit wird gerne unterstrichen, wie gefaehrlich Generalisierung und Abstraktion sei - belastbare Zahlen gibt es dafuer aber nicht. Das ist nur eine Ausrede, um den Hackerkeller nicht verlassen zu muessen.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.