Hi,
ich sucht nach Ideen für Geschwindigkeits-Optimierung des C-Codes für
meine Scope-Clock. Eckdaten:
-- ATmega168 @ 24 MHz
-- IRQ-Rate = 48000/s, d.h. 500 Ticks/IRQ
IdR wird pro IRQ ein Pixel an einen 8-Bit DAC ausgegeben. Weil die
Anzeige eine Röhre ist liegen harte Echtzeit-Bedingungen vor. Wie bei
einem Händi, wo die Möglichkeit damit zu Telefonieren nur Nebensache
ist, so sind auch bei meiner Scope-Clock Spielchen wie Snake oder
Asteroids wichtiger als die UHr-Funktion und die wahren
Ressourcen-Fresser an Flash, RAM und Zeit.
Nach Möglichkeit sind Berechnungen natürlich in die main-Loop
ausgelagert, aber aufgrund des kleinen RAMs von 1k ist nicht alles
vorberechenbar, weil es schlicht und einfach nicht gespeichert werden
kann. Eine FIFO-Lösung habe ich verworfen, weil der Overhead zur
FIFO-Verwaltung zu viel der wertvollen Rechenzeit auffraß.
Dennoch verbleibt in der Pixel-ISR sehr viel zu tun, und die "goldene"
Regel, ISRs möglicht kurz zu halten ist schlichtweg nicht umsetzbar.
Von den 500 Ticks, die eine ISR zur Verfügung hat, entfallen alleine ca.
100 Ticks, also 20% der Zeit, auf ISR-Prolog und -Epilog! Der ISR-Aufbau
sieht momentan so aus (schematisch):
1
ISR
2
{
3
intcount=0;
4
5
do{
6
mach_was();
7
8
if(!IRQ_Flag)
9
break;
10
clear(IRQ_Flag);
11
}while(++count<10);
12
}
Durch die do-Schleife wird ein ISR-Frame gespart, wenn mach_was() lange
gedauert hat und der nächste ISR-Beginn überfällig ist.
Irgendwie will ich nicht so ganz glauben, daß es da keine bessere Lösung
für gibt als 20% der Zeit auf Pro- und Epilog zu verheizen.
Ich hatte schon überlegt, das Design auf den Kopf zu stellen, also
mach_was() als einzige Funktion in der main-Loop zu haben und per
Soft-IRQ die Berechnungen zu machen, die jetzt in der main stehen. Aber
damit kommt man wohl vom Regen in die Traufe...
Kann man das geschickter angehen? mach_was() kann übrigens nicht
geinlinet werden, da es indirekt aufgerufen wird.
Johann
Ganz trivial, bringt auch fast nichts, evtl. gar nichts, wenn gcc klug
ist:
int count = 10;
while (--count);
Ich würde es aber stets so schreiben...
Andere wissen sicher mehr.
Johann L. schrieb:
> Irgendwie will ich nicht so ganz glauben, daß es da keine bessere Lösung> für gibt als 20% der Zeit auf Pro- und Epilog zu verheizen.
Klingt nach Assembler. Mit einem Satz von Registern der ausschliesslich
der ISR zur Verfügung steht. Damit entfallen sämtliche PUSH/POPs und die
Daten stehen ausserdem schon in den Registern. AVR hat ja genug für
solche Methoden.
Das mit C zu kombinieren wird allerdings kritisch. Register
ausschliessen geht ja - im Prinzip. Evtl. möglich, wenn man nur eigenen
Quellcode verwendet und die verbleibenden Runtime-Funktionen draufhin
kontrolliert welche Regs sie verwenden.
> Kann man das geschickter angehen? mach_was() kann übrigens> nicht geinlinet werden, da es indirekt aufgerufen wird.
Muß das so sein? Das wird der Hauptgrund sein, daß Prolog und Epilog so
lange dauern. Weil du einen nicht-inline-Funktions-Aufruf drin hast,
sichert die ISR vorsichtshalber mal alle Register. Das kostet massig
Zeit.
A. K. schrieb:
> Johann L. schrieb:>>> Irgendwie will ich nicht so ganz glauben, daß es da keine bessere Lösung>> für gibt als 20% der Zeit auf Pro- und Epilog zu verheizen.>> Klingt nach Assembler. Mit einem Satz von Registern der ausschliesslich> der ISR zur Verfügung steht. Damit entfallen sämtliche PUSH/POPs und die> Daten stehen ausserdem schon in den Registern. AVR hat ja genug für> solche Methoden.
Nicht wirklich. Zeiger-Register hat man effektiv nur 3, welche über die
man mehr oder weniger effektiv auf Strukturen zugreifen kann nur 2 (Y
und Z) und für Zugriff auf Flash-Tabellen nur eines (Z).
> Das mit C zu kombinieren wird allerdings kritisch. Register> ausschliessen geht ja - im Prinzip. Evtl. möglich, wenn man nur eigenen> Quellcode verwendet und die verbleibenden Runtime-Funktionen draufhin> kontrolliert welche Regs sie verwenden.
Eigentlich bin ich ganz froh, daß ich geschafft hab die Software zu 95%
in C hinzubekommen. Ähnliche Projekte implementieren komplett in
Assembler oder nutzen andere Hardware wie FPGA, mehrere Prozessoren oder
32-Bit Boliden.
In Assembler sind lediglich Teile der Arithmetik, die von avr-gcc nicht
unterstützt werden wie _Sat (Saturierte Operatoren) und _Frac (1.7 und
1.15 Q-Format). Die Q-Format Operatoren brauchen Register der Klasse
"a", also aus R16...R23, und avr-gcc hat sinnvollerweise auch eine
Starke Präferenz für diese Register zur Parameterübergabe. Zudem
erlauben sie LDI, CPI, SBCI, SUBI und CPCI.
Das Programm hat run 10000 Zeilen C-Code. Das in Assembler zu hacken
will ich mir nicht antun.
Fixe Register gibt's schon ne handvoll
-- Eines enthält die Null (ohne daß es durch MUL* zerstört wird)
-- Je eins für die zu setzenden x/y-Koordinaten. Damit kann jede
in 2 Ticks und 2 Befehlen die Koordinaten setzen.
-- 2 Register sind als Zwischenregister bei der Fix-Arithmetik
reserviert und müssen so nicht gesichert werden
-- Zur Kommunikaton gibt's ein Bit-Adressierbares SFR (GPIOR0)
Johann
Johann L. schrieb:
> Das Programm hat run 10000 Zeilen C-Code. Das in Assembler zu hacken> will ich mir nicht antun.
Oha. Kann ich verstehen. Aber ich meinte nicht das gesamte Programm,
sondern den Interrupt-Code.
Wieviele mach_was() Varianten gibt es denn? Wenn es nicht zu viele sind,
kann es sich lohnen, an Stelle der Indirektion ein paar Abfragen zu
setzen. Dann geht Inlining wieder.
Was ist denn die eigentliche Zielrichtung der Optimierung? Die von dir
gestellte Frage ist sehr konkret, aber dass du an der Struktur der ISR
nur begrenzt drehen kannst ohne dem GCC ein neues Registermodell
beizubringen ist dir ja selber schon klar. Insofern vielleicht zu
konkret. Muss es wohl eher darum gehen, die Häufigkeit von Interrupts zu
reduzieren.
Dass du durch den Overhead von Interrupts Zeit verlierst ist kaum zu
vermeiden. Inwieweit sich die Interrupts durch einen anderen Ansatz
reduzieren lassen musst du selber wissen, dazu liegt mir nicht genug
Information vor. Die Umkehrung hast du ja selber schon erwähnt.
Eine rohe Idee will ich noch einbringen, wobei ich mangels Einblick in
die Aufgabe keine Ahnung habe ob das was bringt: Wenn eine
Interrupt-Routine ohnehin alles sichert, dann ist der zeitliche
Unterschied zwischen klassischen Interrupts und Contextswitches in
zeiteffizienten RTOS-Kernels wie AvrX nicht allzu gross.
Das mag zunächst etwas paradox klingen, denn es kommt im Prinzip etwas
Overhead hinzu und das reduziert auch nicht den Zeitverlust von
Interrupts. es könnte aber möglicherweise die Umstellung der
Arbeitsweise auf deutlich andere Ansätze programmiertechnisch
erleichtern um so vielleicht die Anzahl Interrupts/Contextswitches zu
reduzieren. Oder zumindest die verfügbare Rechenzeit auf die vorrangige
Tätigkeit konzentrieren.
Mehr Brainstorming: Eine extremere Form solcher Programmiertechnik sind
Adam Dunkels' protothreads. Reaktion auf Events ohne komplexe
Statemachines, teure Interrupts und teure Contextswitches. Aber
natürlich mit anderen Kehrseiten.
A. K. schrieb:
> Johann L. schrieb:>>> Das Programm hat run 10000 Zeilen C-Code. Das in Assembler zu hacken>> will ich mir nicht antun.>> Oha. Kann ich verstehen. Aber ich meinte nicht das gesamte Programm,> sondern den Interrupt-Code.
Der ISR-Code ist lange
Geschätzte 7000 Zeilen.
> Wieviele mach_was() Varianten gibt es denn? Wenn es nicht zu viele sind,> kann es sich lohnen, an Stelle der Indirektion ein paar Abfragen zu> setzen. Dann geht Inlining wieder.
Inlining ist aber nur dann sinnvoll, wenn in der kompletten Call-Chain
kein einziger Funktionsaufruf mehr überig bleibt!
Die Call-Chain ist zwar recht flach, aber die einzelnen Funktionen
zersplitter in viele Fallunterscheidungen und werden oft mehrfach
verwendet.
A. K. schrieb:
> Was ist denn die eigentliche Zielrichtung der Optimierung? Die von dir> gestellte Frage ist sehr konkret, aber dass du an der Struktur der ISR> nur begrenzt drehen kannst ohne dem GCC ein neues Registermodell> beizubringen ist dir ja selber schon klar. Insofern vielleicht zu> konkret. Muss es wohl eher darum gehen, die Häufigkeit von Interrupts zu> reduzieren.
Die Anzahl der IRQs kann ich zwar runterschrauben, das geht aber auf die
Qualität der Grafik: Die Maximalzahl der Pixel, die gezeichnet werden
können, ohne daß die Röhre flimmert, wird kleiner.
Der GJ-Schirm fängt an zu flimmern bei weniger als ca. 30-35 Frames pro
Sekunde. Pro Frame bleiben also F_CPU/(FRAMES * TICKS-pro-ISR) Pixel,
momentan also ca. 1200 Pixel / Frame. Damit ist SPielgeschehen wie bei
Asteroids oder Snake vernünftig darstellbar.
Bei der Optimierung kämpf ich an allen Fronten: Zeit, Flash und RAM. Um
es etwas konkreter zu machen hab ich mal den aktuellen Snapshot des
Projekts angehängt. Ist viel Code, teilweise unkommentiert Spaghetti,
also net erschrecken ;-) Der grobe Ablauf der Pixel-Erzeugung ist
beschrieben in menu.c:49, wo sich auch die Hauptscheife befindet
(menu.c:254)
Die Pixel-ISR um die es geht steht in main.c (SIG_OUTPUT_COMPARE2A) und
darunter wird zum Pixel-Handler menu.item.onPixel (Zeile main.c:151)
verzweigt.
Jeder Menü-Punkt hat seinen eigenen Pixel-Handler. Menü-Punkte gibt es
jeweils am Ende von uhr.c, snake.c, asteroids.c, text.c, textmenu.c,
testbild.c, schoner.c und dcf-oszi.c. Also 8 Stück.
Diese Routinen verwenden teilweise Unterroutinen wie Bresenham (Linie
zeichnen in bresenham.c) oder Vektor-Font pixeln (in vektor-zeichen.c)
Alleine das Pixeln des Vector-Fonts zu inlinen würde den Code
explodieren und vermutlich sogar langsamer machen. Momentan braucht
keine Funktion im Projekt einen Frame-Pointer (ausser varargs-Ausgabe
aufs Terminal in uput.c)
Die RAM-Optimierung geht über gemeinsam verwendeten Speicher, der in
einer Union in frame.h organisiert ist. Das geht weil immer nur 1
Menüpunkt aktiv sein kann (über Recover nach Bildschirmschoner hab ich
mir noch keine Gedanken gemacht). Ohne diese Technik wäre schon längst
das RAM aus (momentan statisch belegt sing ca 2/3 von 1k)
Hauptziel der Optimierung sind aber Code (momentan belegt 80% von 16k)
und Geschwindigkeit, wobei da vor allem die WCET interessant ist, also
die Maximalzeit (worst code execution time).
onPixel setzt ja immer nur 1 Pixel. mach_was() (bzw. onPixel) stellen
also den Body eine Schleife dar, während die Schleife selbst erst durch
aufeinander folgende ISR-Aufrufe zustande kommt.
> Dass du durch den Overhead von Interrupts Zeit verlierst ist kaum zu> vermeiden. Inwieweit sich die Interrupts durch einen anderen Ansatz> reduzieren lassen musst du selber wissen, dazu liegt mir nicht genug> Information vor. Die Umkehrung hast du ja selber schon erwähnt.
Ob das wirklich klappen könnte...?
Die main wäre dann eine Endlosschleife um mach_was(). An Ende der
Schleife würde man auf den Timer schauen und dann wenn noch genug Zeit
ist, eine Soft-IRQ triggern welche den jetzigen mani-Code mit
Spiel-Logik, DCF-Auswertung, Terminal-Ausgabe, Taster- und
RC5-Auswertung, etc. beinhaltet. Aber wie wird die ISR beendet, wenn es
zeit wird, zurückzukehren? Mir würde da nur ein longjmp ans ISR-Ende
einfallen. Der setjmp wäre am Anfang der Soft-ISR, und würe beim
Anspringen direkt zum ISR-Ende gehen. Der longjmp wäre in einer
Timer-ISR, die über die Soft-ISR wacht.
> Eine rohe Idee will ich noch einbringen, wobei ich mangels Einblick in> die Aufgabe keine Ahnung habe ob das was bringt: Wenn eine> Interrupt-Routine ohnehin alles sichert, dann ist der zeitliche> Unterschied zwischen klassischen Interrupts und Contextswitches in> zeiteffizienten RTOS-Kernels wie AvrX nicht allzu gross.>> Das mag zunächst etwas paradox klingen, denn es kommt im Prinzip etwas> Overhead hinzu und das reduziert auch nicht den Zeitverlust von> Interrupts. es könnte aber möglicherweise die Umstellung der> Arbeitsweise auf deutlich andere Ansätze programmiertechnisch> erleichtern um so vielleicht die Anzahl Interrupts/Contextswitches zu> reduzieren. Oder zumindest die verfügbare Rechenzeit auf die vorrangige> Tätigkeit konzentrieren.
Am wichtigsten ist das Zeichen der Pixel, und das muss in regelmäßigen
Zeitabständen geschehen. Jitter macht sich bemerkbar durch
unterschiedlich helle Pixel bzw. unexakt positionierte Pixel. Daher wird
die Pixelausgabe vor der Berechnung gemacht (main.c:118 bzw. dac.h).
Johann
Achso, Compiler ist übrigens avr-gcc 3.4.6 mit -Os.
avr-gcc 4.x mach um mindestens 500 Byte größeren Code. IdR sind das
unnötige Instruktionen; der Code wird auch etwas langsamer als mit
3.4.6.
Johann
>Autor: Johann L. (gjlayde) Benutzerseite>Datum: 18.07.2009 13:27>Achso, Compiler ist übrigens avr-gcc 3.4.6 mit -Os.>avr-gcc 4.x mach um mindestens 500 Byte größeren Code. IdR sind das>unnötige Instruktionen; der Code wird auch etwas langsamer als mit>3.4.6.>Johann
Das Thema wurde hier vor Jahren ausgiebig diskutiert...
Ergebnis war wohl, dass man mit 4.x zumindest ähnlich guten Code wie mit
3.4.6 bekommen kann, wenn man sich Mühe gibt. Und was heißt schon 4.x,
4.3 sollte man wohl zumindest verwenden, nächste Woche soll ja 4.4.1
erscheinen.
Nur das jetzt nicht wieder der Eindruck entsteht, der alte 3.4.6 wäre
stets die bessere Wahl.
Oder Jörg, was meinst Du?
Stefan Salewski schrieb:
>>Autor: Johann L. (gjlayde) Benutzerseite>>Datum: 18.07.2009 13:27>>>Achso, Compiler ist übrigens avr-gcc 3.4.6 mit -Os.>>>avr-gcc 4.x mach um mindestens 500 Byte größeren Code. IdR sind das>>unnötige Instruktionen; der Code wird auch etwas langsamer als mit>>3.4.6.>>>Johann>> Das Thema wurde hier vor Jahren ausgiebig diskutiert...> Ergebnis war wohl, dass man mit 4.x zumindest ähnlich guten Code wie mit> 3.4.6 bekommen kann, wenn man sich Mühe gibt. Und was heißt schon 4.x,> 4.3 sollte man wohl zumindest verwenden, nächste Woche soll ja 4.4.1> erscheinen.
Ich hab mehr als 1x avr-gcc 4.x angetestet. Nich nur eine Version und
nicht nur einen Satz Optionen (-fno-tree-scev-cprop
-fno-inline-small-functions -fno-move-loop-invariants
-fno-tree-loop-optimize -fno-split-wide-types, -morder1, -morder2, ...)
Ich hab sogar einen "Bastard" aus unterschiedlich erzeugten Objekten
getestet, wo ich für jedes Object-File, das aus einer Quelle hervorging,
nur die kleinste Binärversion gelinkt habe. Selbst damit war 3.4.6
deutlich besser.
Möglicherweise liegt's auch daran, weil ich recht genau weiß wie gcc
arbeitet und Code schreibe, mit dem er gut klarkommt (Ich mach selber
gcc-Entwicklung, momentan porte ich gcc 4.3.3 für nen exotischen 32-Bit
µC).
> Nur das jetzt nicht wieder der Eindruck entsteht, der alte 3.4.6 wäre> stets die bessere Wahl.
Stets wohl nicht, aber bisher hat er in keinem meiner Projekte bessere
Arbeit gemacht als 3.4.6, und bei dem vorliegenden Projekt ist nichts
zu verschenken.
Der neue IRA ab gcc 4.4 bringt in Bezug auf AVR leider Ernüchterung
(4.4.0, 4.4.1, 4.5.0) jedenfalls was ich so gesehen hab.
avr-gcc ist eben ein Waisenkind wie andere 8-Bit Targets auch. A.K.
könnte doch bei der avr-gcc-Entwicklung mitmachen. Jedenfalls hab ich
schon gesehen daß er in avr-gcc reindebuggt fg.
Für das obige ZIP
http://www.mikrocontroller.net/attachment/54523/morpheus_2009-07-18.zip
ist die erzeugte Codegröße mit gcc 3.4.6 genau 13314 Bytes. Mit
1
make all size
bzw.
1
make all fsize
kannst du die Größe einzelner Module/Objekte anzeigen lassen. Ich glaub
nicht, daß du durch Rumklimpern an 4.x-Optionen da was rausholst. Dabei
verwende ich noch nichtmal die Killer eeprom_read_block et al. -- aber
das steht irgendwann zur Speicherung von Systemeinstellungen (oder
HighScores ;-)) noch bevor...
Johann
@Johann L. (gjlayde)
>Möglicherweise liegt's auch daran, weil ich recht genau weiß wie gcc>arbeitet und Code schreibe, mit dem er gut klarkommt (Ich mach selber>gcc-Entwicklung, momentan porte ich gcc 4.3.3 für nen exotischen 32-Bit>µC).
Das wusste ich nicht -- mir fällt eigentlich derzeit nur der Name Jörg
Wunsch ein, wenn ich an noch aktive Leute in diesem Forum denke, die
sich etwas mit avr-gcc auskennen. Es gab wohl mal auch andere, aber die
sind wohl alle von den Dummschwätzern vergrault worden.
Na mal sehen wie lange Du uns erhalten bleibst...
Gruß
Stefan
gcc 4.4 (in Crossworks 2.0) ist ohnehin so eine Sache. Die ARM Version
davon hat vor Freigabe wohl auch niemand getestet, jedenfalls nicht in
den Code reingesehen.
Johann L. schrieb:
> könnte doch bei der avr-gcc-Entwicklung mitmachen. Jedenfalls hab ich> schon gesehen daß er in avr-gcc reindebuggt fg.
Eigentlich nicht. Ein paar Bugmeldungen von mir sind drin. Und auch die
sind nicht alle vor mir, sondern entstammen teilweise dem hiesigen
Forum.
Du hast ja selber schon gemerkt, dass allerlei De-Optimierungen der 4er
Versionen bezogen auch AVR eher vom maschinenunabhängigen Teil
verursacht werden. Da ist recht schwer gegen anzustinken.
A. K. schrieb:
> Johann L. schrieb:>>> könnte doch bei der avr-gcc-Entwicklung mitmachen. Jedenfalls hab ich>> schon gesehen daß er in avr-gcc reindebuggt fg.>> Eigentlich nicht. Ein paar Bugmeldungen von mir sind drin. Und auch die> sind nicht alle vor mir, sondern entstammen teilweise dem hiesigen> Forum.
Ausreden gülden nicht. Beweismittel No 1: ab
Beitrag "Re: C Code Optimierung für LCD (AVR32)"> Du hast ja selber schon gemerkt, dass allerlei De-Optimierungen der 4er> Versionen bezogen auch AVR eher vom maschinenunabhängigen Teil> verursacht werden. Da ist recht schwer gegen anzustinken.
Das liegt aber auch daran, daß die Leute keine gescheiten Bug-Reports
machen (Märchen-Code mit "...", keine richtigen Testfälle, etc.)
Erschwert wird die Sachlage bei PRs, die sich auf WinAVR beziehen, weil
das keine offizielle FSF-Release ist und der avr-gcc "private" Patches
von Eric enthält (Die nicht in der Mainline sind, weil Eric auch kein
FSF-Assignment hat).
Wenn eine GCC-Kapriole im Frontend ist und es sich auf einem gängigen
Target wie i386 nachvollziehen lässt, hat man sehr gute Karten.
Ansonsten ist es natürlich mühsam, weil man erst man nachweisen muss,
daß im Backend nicht was an den Kosten oder mit ungünstigen Expandern
verbockt wurde.
Für avr-gcc 4.4.0 hatte Eric noch nichtmal ne Release gemacht (da er
Bugs befürchtet), die aber m.E. wichtig wäre, weil damit mehr Leute
testen und potentielle Probleme berichten würden. IRA hält bestimmt
einige Überraschungen bereit... Die AVR-Mannen müssten sich eben auch
öfter zu Wort melden, und avr-gcc-Entwickler gibt's momentan ja
praktisch keinen. Anatoly maintaint, Eric flickt neue Derivate nach und
das war's...
Johann
Die ISR hat 7000 Zeilen und Du willst ein paar Taktzyklen im Epilog
sparen? Das passt nicht mal ansatzweise zusammen. Such' in den 7000
Zeilen und verschwende nicht unsere Zeit.
>Die ISR hat 7000 Zeilen und Du willst ein paar Taktzyklen im Epilog>sparen? Das passt nicht mal ansatzweise zusammen. Such' in den 7000>Zeilen und verschwende nicht unsere Zeit.
Johann hat ja nicht geschrieben, dass stets alle 7000 Zeilen ausgeführt
werden. Ich denke er weiß schon was er tut, und der letzte Teilsatz
Deiner obigen Aussage trifft wohl eher auf dich zu.
Ha! Deine Naivität möchte ich haben :-) Aber warten wir ab, was der OP
dazu sagt, denn er war gemeint.
Und ja ne is klar, türlich werden nicht alle ach so 7000 Zeilen
ausgeführt. Es wird sich schon die eine oder andere Monsterverzweigung
oder Monsterswitch finden.
ISR schrieb:
> Die ISR hat 7000 Zeilen und Du willst ein paar Taktzyklen im Epilog> sparen? Das passt nicht mal ansatzweise zusammen.
Die Anzahl Codezeilen gibt einen ungefähre Vorstellung von dem Aufwand,
der es wäre, das alles nach Assembler zu portieren, um ein eigenes
Registerlayout zu implementieren.
Die geschätzen 7000 Zeilen sind natürlich verzweigt, ansonsten wären
sie kaum in ~500 Ticks abzuhandeln. Die erste Verzweigungsebene ist zB
ein indirekter Funktionsaufruf, der abhängig vom gewählten Menüpunkt zum
jeweiligen Handler springt.
Es ging darum evtl. andere Ansätze zu finden, wie zB das Design "auf den
Kopf" zu stellen wie oben skizziert. Vielleicht hat jemand schonmal
sowas versucht und gute Erfahrung gemacht, oder ich überseh was
prinzipielles und es geht nicht durch, oder es wäre viel Arbeit und
bringt nix.
> Such' in den 7000 Zeilen und verschwende nicht unsere Zeit.Deine Zeit kannst nur du selber verschwenden, das übernimmt niemand
sonst für dich. Wenn das Lesen der Beiträge hier für dich eine
Zeitverschwendung ist -- lass es.
Wenn ich Fragen zur effektiveren Umsetzung dieser 7000 Zeilen hätte
würde ich danach gefragt und konkreten Code gepostet haben. Ich hab
immer ein Auge darauf, was avr-gcc so treibt, und wenn er nicht pariert
wie erwartet, kriegt er eben eins auf die Finger ;-)
Johann