Datum: 06.04.2007 23:31
Hallo, mein aktuelles Hobbyprojekt für einen Mega8 drohte über die Flash-Kapazität hinauszuwachsen, 6 KB in meinem Fall, denn 2 KB gehen für meinen Bootloader drauf. Nach ein paar Maßnamen paßt es nun aber wieder komfortabel hinein, ich will hier mal meine Erfahrungen teilen. Ausganspunkt war "normaler" Embedded-Code, schon ziemlich dicht codiert, das ist nichts Neues für mich, damit verdiene ich auch mein Geld. Nichts speziell auf Größenoptimierung "verunstaltet" (jeden gemeinsamen Fitzel Code in eine Unterfunktion o.Ä.). Das habe ich auch in Folge nicht getan. Keine üblichen "Anfängerfehler" drin, keine floats, printf's, unnötige Libs, malloc, Strings, etc. Immer schön ins map-File gucken, ob man die aufgeführten Library-Funktionen auch wirklich haben wollte. - gcc v4.1.1 versus v3.4.6: meistens ist der alte Compiler besser, in meinem Fall momentan um 40 Byte. Den Bootloader hingegen macht gcc 4.1.1 kleiner. Hilft mir in dem Fall aber nicht, unter die nächste Grenze von 1KB kriege ich den nie. - Nullinitialisierung von statischen Variablen: Bisher hatte ich in meinen Init-Funktionen nicht auf den Startup-Code verlassen, sondern die nötigen Variablen genullt. So kann ich auch eine Re-Initialisierung machen, habe aber keinen Gebrauch davon gemacht. Stattdessen starte ich komplett neu. Entfernung Null-Inits brachte einiges. (OK, kann man doch als Anfängerfehler bezeichnen, ich mußte erst im C-Standard nachlesen daß das BSS-Nullen keine Eigenart von gcc ist.) - statische (globale) Variablen in ein struct sammeln: Das erleichtert dem Compiler die Adressierung, da er den Basiszeiger wiederverwenden kann. Finde ich aber schwach, daß der Compiler sich das nicht selbst passend hinlegt. Die Codegröße kann dann noch von der Reihenfolge der struct-Member abhängen Die häufigst benutzte Variable sollte am Anfang stehen, dann kann sie ohne Offset direkt mit dem Basiszeiger adressiert werden. Ansonsten in Gruppen, wie die Variablen auch gebraucht werden. Hier kann man viel rumprobieren... - Multiplikationen mit Konstanten: der Compiler instanziiert sofort eine teure allgemeine Bibliotheksfunktion, auch wenn es anders ginge. Ich hatte eine einzige 32-bit Multiplikation mit 10 drin, die mir ein mulsi3 beschert hat. Mit a = b<<3 + b<<1 geht es in dem Fall kürzer. Wie gesagt, map-File beobachten. - alle Variablen nur so breit wie nötig: Hatte ich eigentlich schon, nur an einigen wenigen Stellen war ich da etwas nachlässig. Mitunter reicht ein kleinerer Typ doch, wenn man z.B. vorher geeignet skaliert. Am besten nur die skalaren Typen aus <stdint.h> verwenden, das erleichtert auch das Folgende. - logische Operatoren werden auf int-Größe erweitert: Trotz 8-bit Target ist der AVR-gcc 16-bittig drauf. Für a = ~b wird die eigentliche Negationsoperation 16-bittig ausgeführt, obwohl beide als uint8_t definiert sind! Ziemlich blöd, mit einem cast dazwische wird's kleiner: a = (uint8_t)~b. Dasselbe gilt für & und |, habe ich aber nicht mehr mit casts ausprobiert. - Compileroption -mint8 für 8-Bit Arithmetik als Default: Mit obigen casts überall sähe der Code ziemlich schlimm aus. Blöd auch, wenn man mal einen Type ändert, dann muß man sorgsam nach den zugehörigen casts suchen. Mit dem Compilerschalter -mint8 wird das zum Standard. Bei mir hat das etwa 200 Byte gespart! Man sollte dafür aber keine ints mehr im Code haben, nur noch Typen definierter Größe aus <stdint.h>. Literal-Werte muß man ggf. anpassen (z.B. mit postfix L long machen) damit sie nicht überlaufen, Compiler-Warnings beachten. Ist anscheinend noch etwas experimentell(?), mit dem aktuellen gcc 4.1.1 geht es nicht, der kriegt ein Problem mit den 64-bit Typen. Ist aber wohl in Arbeit, ich habe einen Patch gesehen. - ein paar Schlagworte habe ich noch, die aber nicht zur Anwendung kamen: -ffreestanding, soll noch ein pragma für main() geben welches Prolog/Epilog kappt, vielleicht kann man die Vektortabelle beschneiden. Wenn ich noch was vergessen habe schreib' ich ein Follow-Up. Fühlt Euch frei, selbst Tricks und Kommentare anzumerken. ;-)
Datum: 07.04.2007 00:29
Hi, Jörg, Danke. Wolfgang Horn
Datum: 07.04.2007 01:31
> statische (globale) Variablen in ein struct sammeln
wusste ich schon - aber das es auf die Reihenfolge ankommt war mir neu!
Danke! :)
Die Unterschiede vom GCC 3.4.6 zum 4.1.1 kommen oft von inline
Funktionen die eigentlich gar nicht geinlined werden sollen ;)
Mit nem prototyp wie:
void xyz(uint16_t param) __attribute__((noinline));
kann man das manuell verhindern.
Spart oft ein paar Bytes - war bei einem Bootloader von mir extrem
ausgeprägt im Vergleich zur alten GCC Version...
Dann noch der Prolog in der Main Funktion - da kommen seit neuestem ja
ne ganze Menge pushs und anderes dazu... mit einem
__attribute__((noreturn)) kann man zumindest ein wenig davon abschalten.
MfG,
Dominik
Datum: 07.04.2007 07:55
Dominik s. Herwald wrote: > Mit nem prototyp wie: > void xyz(uint16_t param) __attribute__((noinline)); > > kann man das manuell verhindern. > Spart oft ein paar Bytes ... Erstmal als "static" deklarieren, vielleicht spart ja dann das Inlining sogar noch mehr Bytes? Der rechnet sich nämlich eigentlich aus, wie viel es spart. Sofern er sich dabei bei nicht-inline-asm-Code verrechnet, darf man dafür auch gern Bugreports schreiben. Nur bei inline asm ist es sinnlos, da gibt es keine Methode, dem Compiler mitzuteilen, dass sich darin mehr als ein Prozessorbefehl versteckt.
Datum: 07.04.2007 07:56
Jörg wrote: > - statische (globale) Variablen in ein struct sammeln: Das > erleichtert dem Compiler die Adressierung, da er den Basiszeiger > wiederverwenden kann. Finde ich aber schwach, daß der Compiler sich > das nicht selbst passend hinlegt. Wenn du drüber nachdenkst wüsstest du, warum er das nicht kann. Globale Variablen bekommen erst vom Linker eine Zuweisung der Adresse verpasst. Damit ist es erst der, der das sortiert. Folglich kann sich der Compiler nicht einfach die Adresse von irgendeiner Variablen benutzen und dann einen Offset dazu errechnen, um andere Variablen zu erreichen.
Datum: 07.04.2007 10:44
Toent vielleicht trivial, aber irgendwan kann man bei gleicher Groesse die Funktionalitaet nicht mehr verdoppeln, wird einen groesseren Chip benoetigen. Meist gibt es ja Baugleiche. Der Mega8 ist ja eher von der kleinen Sorte.
Datum: 07.04.2007 10:57
@Trog Was willst du uns damit sagen? :D
Datum: 07.04.2007 11:28
Ich habe die Texte mal in einem neuen Wiki-Artikel eingefügt: http://www.mikrocontroller.net/articles/AVR-GCC-Co... Ich finde das schon interessant, und ein Thread verschwindet ja schnell wieder im Archiv.
Datum: 07.04.2007 12:22
Zwischendurch mal danke für die Anmerkungen und den Wiki-Artikel, sowas schwebte mir auch vor, habe ich hier nur noch nie gemacht, bin recht neu hier. ;-) Vielleicht sollte ich mich mal anmelden. Mittlerweile habe ich noch das Compileflag -mtiny-stack entdeckt, das hat nochmal ca. 100 Byte gebracht. (Meine Güte, was fange ich nur an mit all dem freigewordenen Platz... ;-) Mit 256 Bytes Stack sollte ich auskommen, denke ich. Die meisten meiner Variablen sind static und modul-global, statische Zustandsvariablen, nur wenig automatische. Verschachtelungstiefe ist auch nicht so wild, selbst mit Interrupts. Gibt es eine Möglichkeit, das zu testen? Ich habe den Header <stdint.h> für -mint8 und gcc 4.1.1 "repariert", siehe Anhang, damit klappts. Das Flag wird in dem Header zwar schon korrekt berücksichtigt, aber die 64bit-Typen müssen in dem Fall wohl raus, die kann der Compiler dann nicht mehr. __attribute__((noreturn)) für main() habe ich irgendwie nicht hinbekommen. Der Compiler motzt am Funktionsende, ich hätte ein return, habe ich aber auskommentiert. Ein for(;;) am Ende von main() scheint als Hinweis an den Compiler den gleichen Zweck erfüllen, damit wird es jedenfalls 4 Byte kürzer. @dl8dtl: >Wenn du drüber nachdenkst wüsstest du, warum er das nicht kann. >Globale Variablen bekommen erst vom Linker eine Zuweisung >der Adresse verpasst. Schon, aber das waren keine übergreifend globalen Variablen, sondern statics, nur für dieses Modul. Da könnte der Compile vielleicht doch selbst... Naja, ich will mich nicht zu sehr als ahnungslos outen. @Trog: Das Design steht fest, es gibt einige Stück davon. Der Code ist auch praktisch fertig, es kommen keine Features mehr hinzu, nur noch Bugfixes und sonstige Pflege. Es paßt ja auch gut rein, wie man sieht, wenn man sich etwas Mühe gibt. Der Mega8 ist für meine Anwendung prima, alles andere wäre Verschwendung. vorläufige Zusammenfassung: Die bisher genanntem Maßnamen haben meinen Code von 6482 auf 5696 Byte verkleinert, das sind ca. 12%! Ich konnte ein paar Luxusfeatures wieder einschalten, die ich zuvor rausnehmen mußte um unter 6 KB zu kommen. Den Code selbst habe ich kaum verändert, nur die Inits und ein ganz paar zu große Typen. Ist immer noch alles sauber und lesbar, imho. Ich bin zufrieden und habe was gelernt. :-) Noch eine Frage zum Schluß: kann man die .lst Files "schöner" bekommen, möglichst mit eingestreutem C-Code? Mit der neuen global-struct taucht nur noch diese auf, vorher gaben zumindest die Variablennamen einen Hinweis, wo man gerade ist. Ich hätte gern mehr Feedback, was der Compiler im Einzelnen so aus meinen Statements macht.
Datum: 07.04.2007 12:54
> logische Operatoren werden auf int-Größe erweitert: Trotz 8-bit Target > ist der AVR-gcc 16-bittig drauf. Für a = ~b wird die eigentliche > Negationsoperation 16-bittig ausgeführt, obwohl beide als uint8_t > definiert sind! Ziemlich blöd, mit einem cast dazwische wird's kleiner: > a = (uint8_t)~b. Dasselbe gilt für & und |, habe ich aber nicht mehr mit > casts ausprobiert. Habe gerade mal versucht das nachzuvollziehen, konnte jedoch keine Unterschiede feststellen. Der GCC 3.4.6 macht in beiden fällen ein:
**** a = ~b; mov r25,r24 com r25 |
In welchem Fall ist das bei dir aufgetreten?
Datum: 07.04.2007 13:28
>In welchem Fall ist das bei dir aufgetreten?
Hab's grad noch mal ausprobiert, bei fast allen Negationen in meinem
Code ist es tatsächlich egal. Bis auf eine, und da kostet es auf einmal
12 Bytes(?):static void set_output(unsigned char new_lines) { if (new_lines == global_lines) { return; // nothing to do } if (new_lines & (unsigned char)~global_lines) // new set bit(s)? { // do something } // do more stuff } |
Allgemein liegt der Hase wohl schon in derartigem Pfeffer, sonst wäre -mint8 ja nicht so der Bringer bei mir, wo ich alle Typen in der Größe festgenagelt habe.
Datum: 07.04.2007 14:09
Das scheint der Compiler auch nur zu machen, wenn die Verknüpfung in einer if-Abfrage stattfindet. Aber warum? Wenn ich das mit einer zusätzlichen Variable mache wird es auch nicht auf 16-Bit aufgebläht:
void foo(uint8_t a, uint8_t b) { uint8_t x; if (a == b) PORTC = 1; x = ~b; if (a == x) PORTC = 0; } |
Datum: 07.04.2007 14:33
Sieht stark danach aus, daß da eine Artanpassungsregel greift: if erwartet einen Ausdruck vom Typ int, also werden die Operanden, aus denen der Ausdruck berechnet werden soll, auf int erweitert.
Datum: 07.04.2007 14:47
Vielleicht, weil die Bedingung in einem if() als int bewertet wird.
Datum: 07.04.2007 15:06
@Jörg Wunsch: Die Funktionen static zu machen hat nur bei der hier:
void putch(char ch) { while (!(UCSRA & (1<<UDRE))); UDR = ch; } |
8 Bytes gebracht. Bei den delays brachte das nix. noinline brachte aber bei der Funktion oben ganze 46 Bytes für den Bootloader bei -Os. Ähnlich verhält es sich bei einer einfachen delay Funktion - da ist sogar genau ein inline assembler Befehl drin - ein nop weil sonst die ganze delay Schleife wegoptimiert werden würde ;) Die anderen Funktionen die ein bisschen größer sind, werden für -Os richtig behandelt und NICHT geinlined. Passiert eigentlich bei mir bisher nur bei sehr kleinen Funktionen mit zwei drei Zeilen wie die oben. Beim alten GCC 3.4.6 brauchte ich das nicht als noinline zu deklarieren der hat es gleich so gemacht wie vorgesehen. Eine ähnliche Diskussion war ja schonmal vor kurzem hier: http://www.avrfreaks.net/index.php?name=PNphpBB2&a... Naja zumindest bevor der der Thread auf Seite 2 etwas Offtopic wurde ;) MfG, Dominik
Datum: 08.04.2007 00:44
Manchmal ist ein vermeintliches Optimierungsproblem einfach nur ein Programmierfehler. Denn (~b) ist nicht etwa (b ^ 0xFF) sondern ((b & 0x00FF) ^ 0xFFFF) und hat folglich den Wertebereich 0xFF00-0xFFFF. Der Vergleich mit (a) wird also stets falsch sein.
Datum: 08.04.2007 09:28
> Denn (~b) ist nicht etwa (b ^ 0xFF) sondern > ((b & 0x00FF) ^ 0xFFFF) und hat folglich den Wertebereich > 0xFF00-0xFFFF. Der Vergleich mit (a) wird also stets falsch sein. Na, das wäre aber böse, dann würde bei mir wohl etliches nicht funktionieren. Ich hantiere hier viel mit 8bit-Werten und entsprechenden Masken. Werd' aber noch in den Assemblercode gucken, das war bisher noch nicht so mein Ding.
Datum: 09.04.2007 22:08
Dominik s. Herwald wrote: > Ähnlich verhält es sich bei einer einfachen delay Funktion - da ist > sogar genau ein inline assembler Befehl drin - ein nop weil sonst > die ganze delay Schleife wegoptimiert werden würde ;) Nimm <util/delay.h> dafür. > Passiert eigentlich bei mir bisher nur bei sehr kleinen Funktionen > mit zwei drei Zeilen wie die oben. Wie geschrieben, wenn du Fälle hast, wo sich GCC ernsthaft verrechnet und garantiert kein inline-asm drin ist (auch nicht aus der avr-libc), dann schreib einen Bugreport für GCC. Das AVR-Target kann von den GCC-Entwicklern mangels einer geeigneten Testumgebung nicht getestet werden. Damit kann dort keiner merken, wenn auf dem AVR eine bestimmte Optimierung sich in das Gegenteil verkehrt: für die Architekturen, die sie prüfen können, wird nämlich sehr wohl (automatisiert) geprüft, ob eine bestimmte Optimierung Nachteile bringt. Ob da inline-asm-Code drin ist, kannst du im vom Compiler generierten Assemblercode (das ist nicht der Disassembler-Code!) schnell feststellen, indem du nach den Kommentaren /* APP */ ... /* NOAPP */ guckst. Den Assemblercode für eine Datei foo.c erhälst du (bei passend gestaltetem Makefile, z. B. dem von WinAVR) durch »make foo.s«. > Beim alten GCC 3.4.6 brauchte ich das nicht als noinline zu > deklarieren der hat es gleich so gemacht wie vorgesehen. Der konnte automatisches Inlining nur bei Optimierung auf Geschwindigkeit (-O3), nicht bei Optimierung auf Platz -- obwohl es eben auch durchaus Platzeinsparungen bringen kann.
Datum: 09.04.2007 22:14
Hinsichtlich unerwünschter Inlines hat sich bei mir auch schon mal -finline-limit=1 als nützlich erwiesen.
Datum: 09.04.2007 22:29
Jörg wrote: > Mittlerweile habe ich noch das Compileflag -mtiny-stack entdeckt, ... > Mit 256 Bytes Stack sollte ich auskommen, denke ich. ... Du hast weniger Stack. Der Stackpointer wird beim ATmega8 auf 0x45F initialisiert. Da -mtiny-stack nur SPL manipuliert, ist die niedrigstmögliche Adresse, die du auf dem Stack nutzen kannst, 0x400. Du hast also nur 96 Bytes Stack. Eine ziemlich gefährliche Option, wie ich finde. Du könntest dich noch rausretten, indem du den Stack woanders hin verlagerst, aber dann verplemperst du unweigerlich RAM stattdessen. Ich stimme mit denjenigen hier überein, die bei derartig knapper Reserve lieber zum nächstgrößeren Controller raten würden, vor allem, falls spätere Bugfixes ,,im Feld'' notwendig sein könnten. Dann lieber gleich das ganze Design auf einen ATmega168 hochziehen. Aber das ist natürlich deine Entscheidung. > Ich habe den Header <stdint.h> für -mint8 und gcc 4.1.1 "repariert", > siehe Anhang, damit klappts. Das kannst du gern mal als Bugreport für avr-libc einreichen und dann dort einen Patch mit reinlegen. Bitte nicht das ganze File, sondern die Ausgabe von "diff -u" zwischen altem und neuem File. Allerdings hast du noch einen Fehler drin: Typen wie intmax_t und uintmax_t sollte es auch bei -mint8 auf jeden Fall geben. Die müssen dann nur Aliase auf die entsprechenden 32-bit-Typen sein. -mint8 ist aber ansonsten eine mindestens genauso gefährliche Option with -mtiny-stack. Alle Funktionen der Standardbibliothek gehen von 16-bit-int aus, und praktisch können sie auch gar nicht anders. Diese Option hat also nur dann wirklich Sinn, wenn man keinerlei Dinge aus der Bibliothek benutzt.
Datum: 10.04.2007 12:59
> Nimm <util/delay.h> dafür. ja das wäre eine Idee - werde ich wohl auch mal tun. Zu dem Verrechnen - da müsste ich mal nen kleinen Beispielcode zusammenstellen. Aber ob sich das dann noch genauso verhält wie im großen Programm weiss ich nicht. Den Code der betreffenden Applikation darf ich leider so nicht rausgeben... Aber in der kleinen Funktion da oben: Beitrag "Tricks, wie ich mein Compilat kleiner kriege" ist wohl kein inline ASM drin - auch nichts aus der avr-libc - jedenfalls seh ich da nichts ;) MfG, Dominik
Datum: 10.04.2007 13:28
Dominik s. Herwald wrote: > Den Code der betreffenden Applikation darf ich leider so nicht > rausgeben... Dann kürze ihn soweit ein und anonymisiere das entsprechend, dass du ihn rausgeben kannst. Wenn du nachweisen kannst, dass das Inlining überhaupt in einer Konstellation Platz verschwendet trotz -Os, dann ist es den Bugreport wert.
Datum: 10.04.2007 14:59
Hallo Jörg, hmm das habe ich gerade mal mit einem Minimalbeispiel ausprobiert - hätte ich zwar nicht gedacht, aber da passierts auch:
#include <avr/io.h> // void putch(char ch) __attribute__((noinline)); void putch(char ch) { while (!(UCSRA & (1<<UDRE))); UDR = ch; } int main(void) { putch(0); putch(1); putch(2); putch(3); putch(4); putch(5); putch(6); putch(7); putch(8); putch(9); return 0; } |
Mit -Os (makefile von WinAVR... ) sind das 234 Bytes. Wenn man in der Zeile // void putch(char ch) __attribute__((noinline)); die Kommentarzeichen entfernt, sinds nur 216 Bytes! s. Anhang. Oder ist das nur bei mir so? MfG, Dominik
Datum: 10.04.2007 15:26
@Jörg Wunsch: Vielen Dank für die Anmerkungen zu -mint8 und -mtiny-stack. -mtiny-stack Hielt ich bisher für die ungefährlichere Option... Ich wollte auch noch danach fragen, ob der Stack an einer passenden Grenze steht, bzw. stehen muß, nun ist die Antwort schon vorher da. ;-) Wo kommt die 0x45F her, warum so "krumm"? Ich habe in das ldscripts-Verzeichnis mal oberflächlich reingeschaut und nicht viel verstanden. RAM habe ich mehr als genug, von daher wäre es kein Nachteil den Stack umzusetzen. Wie ginge das? -mint8 Ich sehe da 2 "Issues". 1) Die Standardbibliothek ist bereits kompiliert, in den Funktionen sollte es intern also keine Probleme geben, sondern "nur" an den Interfaces; immer dann wenn Typen wie int oder long übergeben werden, die dann in der Größe anders sind. Daran müßte man es doch erkennen können? Ich verwende aus den Libs nur EEPROM- und Watchdogfunktionen, die scheinen in Ordnung zu sein. 2) Das andere Problem ist, daß der Compiler bei Konstanten nur noch 16bittig rechnet, auch wenn sie mit UL Postfix versehen sind. Man müßte sie auf uint32_t casten. F_CPU ist so ein Beispiel, daraus errechnete Werte für Baudraten- oder Timerregister gehen schief. Ich traue mich noch nicht, -mint8 wirklich zu verwenden, müßte zu viel neu testen.
Datum: 10.04.2007 15:42
(Dominiks Beispiel) Ja, das sieht stichhaltig aus. Funktioniert auch noch, wenn man die Funktion static macht. http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31528 Kannst dich ja als Cc drauf setzen, wenn du möchtest (oder mir deine email-Adresse mailen, dann setze ich dich drauf).
Datum: 10.04.2007 15:47
Jörg wrote: > Wo kommt die 0x45F her, warum so "krumm"? Es ist das Ende des RAMs auf deinem ATmega8. Andere AVRs haben andere RAM-Aufteilung, da könntest du mehr Glück haben. > RAM habe ich mehr als genug, von daher wäre es kein Nachteil > den Stack umzusetzen. Wie ginge das? Es müsste genügen, auf der Linker-Kommandozeile den Wert des Symbols __stack zu setzen. Das kann man mit der Option --defsym, der man bei der Übergabe durch den Compilertreiber noch ein -Wl, voranstellen muss. Dabei den Offset 0x800000 für den RAM nicht vergessen, also zum Beispiel
-Wl,--defsym=__stack=0x8003ff |
Der RAM oberhalb 0x400 (bis 0x45f) ist damit praktisch erst einmal nicht mehr benutzbar. > 2) Das andere Problem ist, daß der Compiler bei Konstanten nur noch > 16bittig rechnet, auch wenn sie mit UL Postfix versehen sind. Man > müßte sie auf uint32_t casten. ULL benutzen.
Datum: 10.04.2007 15:54
Super Tipps, probiere ich aus, danke!
Datum: 10.04.2007 16:12
@Jörg Wunsch Du hast Post! Extra anmelden wollte ich mich wegen der "Kleinigkeit" eigentlich nicht. MfG, Dominik
Datum: 10.04.2007 16:19
Dominik s. Herwald wrote:
> Extra anmelden wollte ich mich wegen der "Kleinigkeit" eigentlich nicht.
Hmpf. Man kann da nur die email-Adressen von Leuten eintragen, die
angemeldet sind.
Aber anmelden kost nüscht, und du erwirbst damit das Recht, eigene
Kommentare zu anderen Bugreports hinzuzufügen. ;-)
Datum: 10.04.2007 16:25
OK hast mich überzeugt ;) Habe mich mal angemeldet.
Datum: 10.04.2007 16:45
Dominik s. Herwald wrote:
> Habe mich mal angemeldet.
Ja, hat mir der Bugzilla schon gepetzt. ;-)
Da sich das sogar auf i386 nachweisen lässt, haben wir vielleicht
gar nicht so schlechte Karten, dass sich da einer drum kümmert.
Antwort schreiben
Die Angabe einer Email-Adresse ist freiwillig. Wenn Sie automatisch per Email über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
- Suchfunktion und Betreffsuche benutzen - vielleicht gibt es schon einen ähnlichen Beitrag
- Aussagekräftigen Betreff wählen
- Im Betreff angeben um welchen Controllertyp es geht (AVR, PIC, ...)
- Groß- und Kleinschreibung verwenden
- Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang
- JPEG-Dateien (.jpg) nur für Fotos und Scans verwenden
- Schaltpläne, Screenshots usw. als PNG oder GIF anhängen
Formatierung (mehr Informationen...)
- [c]C-Code[/c]
- [avrasm]AVR-Assembler-Code[/avrasm]
- [pre]vorformatierter Text (z.B. Code in anderen Sprachen)[/pre]
- [math]Formel in LaTeX-Syntax[/math]
- [[Titel]] - Link zu Artikel