Hallo Leute.
Ich habe mal ein paar grundsätzliche Fragen wie man seine Progrämmchen
in ASM am besten strukturiert. Ich komme von Hochsprachen wie C/C++, und
habe es mir dort angewohnt alles schön Modular aufzubauen, in vielen
Funktionen, Prozeduren, auf mehrere Dateien. Viele Probleme werden in
ASM nun sehr schwierig für mich so modular zu lösen.
Beispiele:
Parameterübergabe:
Wie macht man das am Elegantesten? Ich nehme dann einfach Register. Ist
aber manchmal doch etwas unübersichtlich
Interrupt-Handling:
Wenn ein Interrupt auftritt möchte ich das mein Programm das weiß, und
entsprechende Prozeduren abhandelt. Nun könnte ich alle Prozeduren in
die Interrupt Routine stecken, dann kann ich aber den selben Interrupt
nicht währenddessen auslösen. Wie kann ich Programmverzweigungen durch
einen Iinterrupt bewirken nachdem ich die entsprechene Interrupt Routine
bereits beendet habe. Eigene Status-Flags einbauen?
Prozeduren auf mehrere Dateien verteilen:
Wenn ich obiges tun will, wie mache ich das? Der Assembler scheint mir
meine includierten Dateien an den Anfang stellen zu wollen, und ist so
der Meinung die includierten Dateien seien der Anfang des Programms.
Lösung wäre hier eine Klarstellung mit .org direktive. Das ist aber SEHR
umständlich weil ich dann die Codelängen aller Programmteile kennen
muss, und dann entsprechened mit .org die Teile direkt hintereinander
setzen muss. Und sobald ich was ändere is alles wieder für die Katz.
Wie regelt man Dateien inkludieren in ASM??
Was kommt in die Main-Routine??
Ein paradoxes Problem: Der Interruptcharacter der uC's bedingt, das ein
Program ewig läuft, was tu ich ins Main, was tu ich als Interrupte.
Eigentlich kann ich ja alles durch Interrupte realisieren. Aber ich hab
Hemmungen meine Mainroutine aus NOP bestehen zu lassen :)
Was gibts sonst allgemein für Tips zum Programmierstil? Es scheint mir
der Character von ASM bewirkt das man oft sehr schluderich und
unverständlich schreibt, vorallem wenn der Code schnell sein muss. Was
gibts dennoch für Grundregeln? (ähnlich denen der
Prozeduralen/Objektorientierten Programmierung, die aber auf ASM nur
bedingt anwendbar sind). Besonders zwingt einen der ASM-Charakter
geradezu die heiligen Grundsätze des "guten Programmierstils" zu
verletzten, die mir bei Hochsprachen immer eingepredigt wurden.
Gibts vielleicht Links oder gute Bücher die Profitricks mit ASM
verraten?
Danke für alle Antworten!!
Moritz
Die Frage ist gut!
Aber, warum soll man einen MC in Assembler programmieren, wenn man schon
C beherrscht?
Das Problem ist neben dem Programmierstil aber auch das Systemdesign.
Die angesprochene Funktion main() wird eigentlich als Funktion des
Betriebssystems aufgerufen. Wo aber ist beim MC das Betriebssystem?
Parameterübergaben werden beim PC fast immer über den Stack gemacht,
ebenso die lokalen Variablen, laufen alle über den Stack. Nur der ist
nicht so üppig wie beim PC.
Bin mittlerweile der Ansicht, das es bei Kleinserien eher auf die
Entwicklungszeit als auf die optimale Ausnutzung der vorhanden Resourcen
ankommt. Daher würde ich MCs nur noch in C Programmieren.
Beim Interrupthandling würde ich die Behandlung des Interruptereignisses
nur in der Interruptserviceroutine machen. Die Verarbeitung selbst kann
in main() erfolgen.
Was spricht dagegen, Main() ewig laufen zu lassen. In allen anderen
Betriebssystemen dreht der Prozessor auch mit den Daumen, wenn es nichts
zu tun gibt.
Siegfried
Hallo Moritz,
Beim 8051 nehme ich nur noch C, da das der Keil sehr effizient macht
(nur 5..20% Overhead).
Beim AVR gibt es dagegen gute Gründe noch Assembler zu nehmen, da die
C-Compiler doch extrem verschwenderisch mit dem Programmspeicher
umgehen.
Ich will mal versuchen niederzuschreiben, was mir bei der
AVR-Assemblerprogrammierung so aufgefallen ist:
1. Register
Der AVR kann nur mit den Registern richtig arbeiten. Die richtige
Registeraufteilung ist das A und O, wenn man nicht 50% und mehr nur mit
PUSHen und POPen beschäftigt sein will.
Deshalb teile ich die Register immer in 3 Teile:
a) Main-Register:
Damit arbeitet die Main-Loop
b) Interrupt-Register:
Damit arbeiten die Interrupts
c) häufig benutzte Variablen
Das ist der Rest, der nach a) und b) übrig ist
Damit wird das PUSHen und POPen in Interrupts und in den Main-Funktionen
kaum noch benötigt und die Kodeeffizienz ist fast so gut wie beim 8051.
Viele meiner AVR-Programme benutzen PUSH und POP garnicht.
2. Interrupts
Da gelten folgende 3 Hauptregeln:
- schreibe die Interrupts kurz
- schreibe sie kürzer
- schreibe sie so kurz, wie nur irgend möglich
Der Grund, beim AVR gibt es keine verschiedenen Interruptprioritäten,
d.h. kein Interrupt kann einen anderen unterbrechen.
Wenn also nur bei einem Interrupt geschludert wird und der ist dann sehr
langsam, werden dadurch alle anderen genauso langsam.
Also der Interupt darf nur das unbegingt nötigste machen und setzt dann
ein Flag für die Main-Loop. Alles was Zeit hat, macht dann die Main-Loop
3. Parameter
Keine Frage, nur die Übergabe in Registern ist effizient. Und da die
Main-Loop alle Funktionen nacheinander abarbeitet, auch gefahrlos
möglich (Funktionen müssen nicht reentrant sein).
Bei langen Paramterlisten übergibt man nur den Pointer (X, Y oder
Z-Register) darauf (wie in C).
Zu Deiner Frage, die Main-Loop:
Wie der Name schon sagt, ist es eine Endlos-Schleife. Es gibt ja kein
Betriebssystem zu dem man zurückkehren kann.
Diese Main-Loop ruft in der einfachsten Variante alle Funktionen der
Reihe nach auf.
Oder sie pollt die Flags, die in Interrupts gesetzt wurden, um die
weniger zeitkritischen Sachen zuende zu führen.
Oder sie ruft einen Scheduler auf, der sich darum kümmert, wann welche
Funktion ausgeführt werden soll.
Oder sie macht ein SLEEP und wartet somit auf den nächsten Interrupt.
Peter
Nachtrag zu Deinem Include-Problem:
Da hat der Assembler überhaupt keine Wahl. Er muß es genau an der Stelle
einfügen, wo die .include "xxx" Zeile steht.
Ich hänge Unterfunktionen immer hinten ran und schreibe die Funktionen,
an denen ich gerade arbeite, möglichst weit vorne.
Dann erscheinen im Listing auch die Fehlermeldungen vorne.
Und wenn die Funktion dann läuft, kommt sie auch nach hinten und die
nächste wird in Angriff genommen.
Die Include-Directive ist auch die einzige Möglichkeit, um auf AVRs mit
Hardwarestack modular zu programmieren.
Da kann man ja Unterfunktionen nicht beliebig tief CALLen, sondern fügt
sie quasi inline per .include an der Stelle ein, wo sie ausgeführt
werden sollen.
Peter
Da muss ich Peter recht geben. Den 8051 würde ich heute auch nur noch in
C mit der Keil Entwicklungsumgebung programmieren. Wegen des Preises ist
dies aber wohl nur für kommerzielle Projekte interessant. Was man sich
bei Intel damals wohl bei der Syntax für den 8080/8085/8048/8051 gedacht
hat, wird wohl immer ein Rätsel bleiben. Die AVR Familie wurde wohl auch
für ein anderes Marktsegment entwickelt (kleiner kompakter
Prozessorkern, beschränkter I/O-Bereich, hohe Geschwindigkeit bei
niedriger Taktfrequenz).
Für die Übergabe von Parametern ist ja die Prozessor-
architektur von entscheidender Bedeutung. Da bei AVR-Familie der
Stack-Pointer nicht direkt zügänglich ist, scheidet für mich dort die
Übergabe von Parametern via Stack aus. Die Korrektur ist mir viel zu
zeitaufwendig. Der Befehlssatz ist recht übersichtlich, logisch
aufgebaut und schnell erlernbar. Auch ist das ganze Timing recht
übersichtlich. Zeitkritische Anwendungen sind daher sehr leicht
realisierbar. Für Steuerungsaufgaben wo sich der Rechenaufwand in
Grenzen hält, programmiere ich immer noch vorzugsweise in Assembler, da
es hier in vielen Fällen auf das exakte Timing ankommt.
Mit der Zeit sammeln sich bei jedem Assemblerprogrammieren eine Menge
Makros die einem die Arbeit erleichtern aber u.U. auch dazu führen, dass
die Sourcen für andere teilweise schwer verständlich sind.
An sich ist es völlig egal in welcher Sprache eine Anwendung kodiert ist
wenn sie funktioniert und alle Anforderungen erfüllt. Und man sollte
Wert auf eine
vernünftige Dokumentation und aussagekräftige Kommentare legen, damit
die Quelle auch nach 1-2 Jahren noch versteht und evtl. an andere
Hardwaregegebenheiten anpassen kann.
Hi Moritz!
Warum soll man in ASM nicht strukt. progr. können.
Ich fasse vieles in Sub's zusammen bzw. behandle genau eine Aufgabe. Das
schöne an ASM ist aber das man in einer momentanen Aufgabe schnell mal
was ganz anderes machen kann. zB.: Bearbeitung der internen "Uhrzeit" in
einer ISR
da kann man ja schnell mal Nachfragen ob sie eventuell auch gestellt
werden soll(Tastenabfrage)Das ist ja leider bei den höheren Sprachen
verpönt, spart aber manchmal allerhand Speicher.
Mit dem einfügen fertiger Programmteile habe ich eigentlich keine Probl.
Du kannst mitten im Prog.
.include "NAME" eintragen und es wird da eingefügt. Bei mir sind das
lauter kleine ASM-Prog. Zum EE-lesen lautet der Eintrag einfach:
.include "EEL12.asm"
Als Assembler nutze ich auch nur den normalen WAVRASM von Atmel.
Wenn dein Prog. nur aus ISR's besteht kann die Main durchaus so
aussehen:
Main: rjmp Main ;Warten auf neue Aufgaben
Parameterübergabe sollte man bei Atmel über die Reg. machen, da ist der
Zugriff am schnellsten.
Gruss Uwe
Dein Problem mit dem Include kannst Du mit Makros ganz elegant lösen.
Das geht so: Du schreibst die Standardroutinen alle in irgendwelche
Dateien, als Macros. Also zwischen ein .MACRO name und ein .ENDMACRO.
Das Include der Dateien kannst Du hinschreiben, wo Du willst, weil das
Include von reinen Macros noch nicht zu Code führt.
Wann immer in Deinem Hauptprogramm Du das Macro mit dem Namen name
aufrufst, wird der Quellcode dieses Macros genau an diese Stelle
gesetzt. Da brauchst Du auf irgendwelche Adressen nicht zu achten. Macht
auch nichts, wenn Du jedes Macro nur einmal aufrufst (um es in den Code
zu platzieren), und nicht, wie der Erfinder der Macros sich das
ausgedacht hat, mehrmals.
Ein Beispiel. In deine Include-Datei kommt:
; Nur ein Beispiel für ein Unterprogramm
.macro mactest1
ldi zh,high(adr)
ldi zl,low(adr)
lpm
ret
.endmacro
In Deinen Quellcode kommt dann folgende Konstruktion:
test1:
mactest1
[...]
rcall test1
Mit dem Macro-Aufruf mactest1 wird der Macrocode in das Programm an
dieser Stelle eingefügt. Mit rcall label kannst Du diese Subroutine dann
aufrufen.
Wenn Du mit dieser Methode Makros dabei hast, die Du für eine bestimmte
Anwendung gar nicht brauchst, macht das auch nichts. Der Code wird ja
nur dann erzeugt, wenn Du das Macro auch tatsächlich aufrufst. Im
Prinzip kannst Du alle Unterroutinen, die Du je schreiben wirst, als
Macros schreiben und alle mit Include bereitstellen (ich bin aber nicht
sicher, ob handelsübliche Assembler da nicht ausflippen! Mein selber
geschriebener kann das jedenfalls!).
Es ist ein wenig mit den Header-Dateien in C vergleichbar: nur die
tatsächlich aufgerufenen Funktionen werden in den compilierten Code
aufgenommen, der Rest bleibt wirkungslos und außen vor.
Ich schätze, diese Methode ist unter Assembler-Programmierern nicht sehr
üblich, könnte Dir aber vielleicht den Übergang von C in ASM vielleicht
etwas erleichtern.
Deine Hemmung, dein "MAIN" nur aus NOPs bestehen zu lassen, kann ich
nicht ganz nachvollziehen. Fast alle meine Hauptprogramme bestehen aus
einem Loop mit ein paar Flagabfragen (wie beschrieben) und einem SLEEP,
das den Prozessor in den stromsparenden Tiefschlaf versetzt, bis ihn ein
INT wieder aufweckt.
Also etwa so:
loop:
sleep
nop
sbrc flag,bit1
rcall sub1
sbrc flag,bit2
rcall sub2
[...]
rjmp loop
Der NOP hinter dem SLEEP ist übrigens keine Beschäftigungstherapie,
sondern notwendig!
Die Introutinen selbst sind dann extrem kurz, weil sie nur das
entsprechende Bit setzen müssen, um eine Behandlung auszulösen, also
z.B.
irgendeinint:
in R17,SREG
sbr flag,02h
out SREG,R17
reti
Kürzer geht es, glaube ich, nicht.
MfG
Gerd
Als Assemblerprogrammierer finde es gemein, dass Tabulatorzeichen von
diesem Forum nicht mal zu Leerzeichen umgewandelt werden, sondern
einfach verschwinden. Im Editor hat alles richtig ausgesehen. Natürlich
müssen da Tabs oder Leerzeichen im vorhergehenden Posting an alle
Zeilenanfänge mit Code, die nicht mit einem Punkt anfangen.
MfG
Gerd
> Beim AVR gibt es dagegen gute Gründe noch Assembler zu nehmen, da die> C-Compiler doch extrem verschwenderisch mit dem Programmspeicher umgehen.
Diese undifferenzierte Aussage kann man so aber nicht stehen lassen.
Fakt ist, zumindest nach meiner Erfahrung, dass es hier grosse
Unterschiede gibt und dass es sowohl Compiler gibt, die wirklich sehr
kompakten Code produzieren, den man auch mit handcodiertem Assembler
kaum noch zusammenquetschen könnte. Andererseits gibt es auch Compiler,
bei denen die obige Aussage im wahrsten Sinne des Wortes zutrifft. Einen
Compiler, den ich in diese Kategorie einordnen würde, wäre z.B. der
CodeVision. Ich hatte mal verschiedene Sourcecodes im direkten Vergleich
zum avrgcc untersucht und die Unterschiede in der Grösse des generierten
Codes waren tatsächlich extrem.
Ich wollte dies nur mal anmerken, damit hier kein generell schlechtes
Bild über C-Compiler für den AVR entsteht. Dies wäre nicht der Realität
entsprechend.
Gruss,
Peter
Hallo Peter,
wäre echt super, wenn Du dann doch bitte sagen könntest, welches dieser
gute Compiler ist.
Nur den schlechten zu nennen, ist irgendwie nicht gerade hilfreich :-(
Wenn Du dann noch ein Source-Beispiel mit dem Assembler-Listing posten
könntest, damit man mal ein gutes Beispiel sieht.
Irgendwas aus der Praxis mit I/O-Zugriffen, ifs, switches, interrupts
usw..
Also nicht irgendwelche praxisfernen Werbebeispiele, wie Sieve of
Erathostenes oder so.
Meine Einschätzung ist nur deshalb so negativ, weil ich bisher eben nur
schlechte Beispiele gesehen habe.
Peter D.
Sorry, hatte das mit dem avrgcc überlesen.
Ist der also einer dieser guten Compiler und gibt es noch andere ?
Wie gesagt, ein Beispiel dafür wäre echt super.
Peter
Hallo,
also die Atmels sind ja nicht so meine Welt aber oben wurde ja auch kein
Spezificher Controller genannt. Es gibt durchaus auch Controller, die
sowas wie einen Software Interrupt haben. Einfach per INT Befehl.
Parameter über den Stack zu übergeben ist zwar etwas aufwendiger als in
einem Register, dafür kann man die werte aber lokal vermanschen und hat
nicht immer nur Globale variablen. Achja und es gibt durchaus auf
Assemblerebene noch sowas wie einen Linker, da kann man dann Funktionen
aus anderen Quellen aufrufen.
Eckhard
Hallo Eckhard,
ob global oder lokal ist beim Mikrokontroller nur eine unterschiedliche
Betrachtungsweise.
Jedes Register und jede RAM-Adresse ist global von jeder Funktion aus
zugreifbar.
Man kann aber in einer Funktion für ein Register einen Namen vereinbaren
und diesen Namen nur in dieser Funktion verwenden, dann ist diese
Registervaiable lokal.
Und in der nächsten Funktion gibt man dem Register wieder einen anderen
Namen.
Die Parameterübergabe im Stack scheint kein Mikrokontrollerentwickler
vorgesehen zu haben, es gibt z.B. keinen Befehl, eine Konstante zu
PUSHen.
D.h. der Overhead ist daher beträchtlich gegenüber Registern.
Das eine Funktion dann 100..200% mehr Kodespeicher benötigt, ist nicht
ungewöhnlich.
Außerdem wird mehr SRAM benötigt, die Register sind ja immer da und sie
nicht zu verwenden ist Verschwendung.
Noch was interessehalber, wozu kann ein Mikrokontroller einen
Softwareinterrupt sinnvoll einsetzen ?
Peter D.
Gerd, das Problem mit der Formatierung ist gelöst. Tabs werden jetzt in
2 Leerzeichen umgewandelt und Leerzeichen am Zeilenanfang verschwinden
nicht mehr.
Gruß
Andreas
@Peter, kannst du nochmal genauer deine Meinung zu den Unterschieden
zwischen CodeVision und AVRGCC erklären?
Ich habe folgende Erfahrung gemacht:
Es bestand ein Projekt mit einem Infineon C167-Prozessor(16biter).
Das Programm wurde auf Keil µVision2 geschrieben und compiliert.
Es entstand eine .h86-Datei mit 22kB.
Der C167-Prozessor hat u.a. spezielle Befehle die vom
Keil C-Compiler unterstützt werden.
Da die Hardwarekosten mit dem C167er zu teuer wurde, ist ein
neues Design mit dem ATmega128 entstanden.
Das vorhandene Programm vom C167er wurde für mega128 mit
CodeVision(aktuelle Version) modifiziert. Daraus entstand
eine hex-Datei mit 17kB.
Dem Keil-Compiler sagt man eine effiziente Umsetzng nach.
Mein Beispiel zeigt, daß CodeVision mindestens genauso gut ist.
Gruß Norbert
Hallo Peter,
also ein Softwareinterrupt läßt sich für Systemroutinen einsetzen. Sowas
wird manchmal auch Trap genannt. Fast ganz MS-Dos hat so funktioniert,
ob das nun Sinnvoll ist sei mal dahingestellt. Ansonsten haben ja nicht
alle Controller so massig Register wie der AVR, ich benutze nen HC08, da
kann man nicht gerade sonderlich vieler Parameter in Registern
übergeben. Dafür kann man den Stack mit Speziellen Adressierungsmodi
Relativ zum SP sehr gut nutzen.
Eckhard
Hallo Norbert,
ich habe bisher noch keinen AVR in C programmiert, ich habe aber eben
nur Beispiele gesehen, deren Kodeeffizienz einen nicht gerade umhaut.
Deshalb bin ich ja so schar darauf, daß mir endlich mal jemand zeigt,
daß C beim AVR auch effizient sein kann.
Wenn ich einfach nur so einen Compiler bestelle und dann nicht damit
arbeite, weil ständig der Flash dicht ist, dann würde mein Chef doch
nicht gerade erfreut sein, oder ?
Mit dem AVRGCC will ich mal was machen, aber die UNIX-Syntax ist doch
etwas abschreckend und mit Make-Files habe ich mich auch noch nicht
angefreundet.
Ich benutze nur den Keil-C51 und der braucht keine kryptischen
Anweisungen und auch kein Make, das macht ein sogenanntes AMAKE
vollautomatisch.
Der C167 ist mit dem Mega128 überhaupt nicht vergleichbar, der spielt
doch in einer ganz anderen Liga.
Ein 16-Bitter kann nur da punkten, wo auch sehr oft 16-Bit-Operationen
benutzt werden.
Wenn aber die meisten Variablen nur 8-Bit sind, ist doch vollkommen
klar, das der 8-Bitter die Nase vorn hat.
Wenn also auf dem AVR der Kode kleiner wird, müßte er auf dem 8051 noch
weiter schrumpfen.
Hallo Eckhard,
das war mir schon klar.
Nur habe ich noch keinen 8051 mit MS-DOS drauf gesehen (wozu auch).
Und ein Softwareinterrupt ist wieder auch nur reine Ansichtssache. Ob
ich nun einen INT21 aufrufe oder einen CALL 1000h und die entsprechende
Routine an 1000h linke, ist vollkommen gleich.
Der einzige Unterschied, unter DOS kann ich einen Interrupt verbiegen,
aber nur, weil der Kodespeicher im RAM ist.
Aber bei Mikrokontrollern ist der Kodespeicher nur lesbar und deshalb
gibt es auch keinen Softwareinterrupt.
Peter
Die Software-Interrupts wurden ja damals überwiegend aus Platzgründen
(ein INT Aufruf belegt 2 Byte ein Absolut Call 5 Byte Speicher)
missbraucht, obwohl der Prozessorhersteller damit eigentlich anderes im
Sinn hatte. Aber das soll ja hier nicht Gegenstand der Diskussion sein.
Bei einem Mikrocontroller haben ich ja ein festes Programm, dass ich bei
Bedarf durch Neuprogrammierung austauschen kann.
Einen C167 mit einem MEGA128 zu vergleichen halte ich auch für einen
Fehlgriff. Der Unterschied in der Codegrösse ist für mich teilweise auch
nicht ganz nachvollziehbar wenn bei beiden Prozessoren gleiche
Datentypen verwendet wurden.
Allerdings wäre wohl beide Prozessoren für die Anwendung etwas
überdimensioniert.
Das kryptische MAKE beim AVRGCC stört mich auch enorm. Aber vielleicht
ändert sich ja hier was in der Zukunft, da ja Atmel die Schnittstelle
zum AVR Studio 4 offenlegen will. Dann ist ja komplette Integration in
die Entwicklungsoberfläche möglich.
Hallo,
auch wenn das vielleicht ein wenig über das ziel hinausschießt. Es gibt
ja auch relativ fette Controller. Z.B 6833X von Motorola. Auf denen wird
dann gerne ein RTOS gefahren. Diese Dinger kennen einen User und einen
Supervisor Mode. Das OS läuft normalerweise im Supervisormode. Die
restlichen Prozesse im Usermode. Die einzige möglichkeit um das
OS-Funktionen aufzurufen, die den Supervisormode nutzen ist der
Softwareinterrupt.
Nur um nochmal ein Beispiel zu geben. In der 8-Bit wWelt ist das aber
eher nicht so.
Eckhard
@Norbert
Die Grösse des fertig übersetzten Codes für verschiedene Prozessoren
lassen sich wohl kaum miteinander vergleichen, insbesondere nicht
zwischen solchen mit RISC und CISC-Architektur. Da vergleichst du Äpfel
mit Birnen!
@Peter Dannegger
ich arbeite schon lange mit dem gcc. Den kannte ich schon lange, bevor
ich mich überhaupt mit Mikrocontrollern beschäftigte. Den gcc gibt es
für viele verschiedene Prozessoren und er ist sehr ausentwickelt, vor
allem was die Effizienz des erzeugten Codes betrifft. Ich habe früher
auf folgenden Maschinen/Systemen damit programmiert:
Silicon Graphics Indy (Irix)
Hewlett Packard C100 (HPUX, PA-RISC)
Sun Sparcstation 10 (Solaris, SPARC)
Siemens MX100 (SINIX)
und Linux div. Systeme (i486, Pentium)
Und auf allen Maschinen hat der gcc im Vergleich zu kommerziellen
Compilern eine sehr gute Figur gemacht und diese oft sogar übertroffen.
Und im Bereich Atmel bin ich auch recht überzeugt, dass dies hier
genauso ist. Ich habe noch nicht alle AVR-Compiler getestet, aber im
Vergleich zu denen, die ich testen konnte, lag auch der avrgcc mit vorn
an der Spitze.
Beste Grüsse,
Peter