Hallo,
nach langer Zeit habe ich mich endlich dazu entschlossen doch noch in
die Mikrokontrollerprogrammierung einzusteigen. Dabei war diese Web-Site
nicht ganz unschuldig, dank den vielen Beiträgen und Tutorials. Vielen
Dank einmal.
Ich wollte hier zuerst nur mal meine persönlichen Erfahrungen und meine
ersten Erfolge und Misserfolge weitergeben, vielleicht helfen meine
Erkenntnisse anderen beim Einstieg in die wirklich faszinierende Welt
der Mikrokontroller.
Da ich beruflich lange mit Assembler (PDP-11) zu tun hatte und auch zu
Zeiten des Apple II vor allem Assembler benutzte war es für mich am
einfachsten auch hier mit Assembler zu beginnen. Auch wenn meine
Erfahrungen mit Assembler sehr lange her sind, aber in Assembler
programmieren ist wie Schwimmen oder Fahrrad fahren, man vergisst es nie
mehr.
Zuerst startete ich mit dem Evaluations Board 2.01 von Pollin und AVR
Studio. Vielleicht mag es für viele ein günstiger Einstieg sein, aber
das ewige hin und herschalten zwischen AVR Studio und Pony konnte mich
nicht überzeugen. Da der erste ISP MKII den ich bestellte nie den Weg zu
mir gefunden hat und der "noname" ISP MKII aus Hong-Kong von AVR Studio
nicht erkannt wurde (Schuld daran hatte aber wie sich später zeigte
nicht das "noname" Geräte, sondern es lag entweder an der alten AVR
Studio Version oder an meiner Schusslichkeit im Umgang mit Windows, denn
wie sich kürzlich zeigte erkennt AVR Studio 4.18 den ISP und konnte ihn
problemlos auf den neusten Stand bringen) habe ich mich dann nach einer
Frustpause von fast einem halben Jahr entschlossen doch noch etwas Geld
in die Hand zu nehmen und mir ein STK500 gekauft. Damit bin ich nun sehr
zufrieden und denke es ist die einfachste und unkomplizierteste
Einstiegsplattform, wenn auch nicht die günstigste.
Als erstes startete ich wie gesagt mit dem AVR Assembler und hatte auch
schnell Erfolge (Knight Rider LED, UART Programmierung, etc. bis zu
meinem ursprünglichen Grund für den Einstieg in die Mikrokontrollerwelt,
ein PS/2 Interface für meinen alten Apple als alternative zur original
Tastatur, da ich immer noch gerne mit Apple II spiele).
Da aber sehr viele Beispiele im Internet in C geschrieben sind habe ich
begonnen mich mit C auseinanderzusetzen. Nur kann mich C im Zusammenhang
mit hardwarenaher Programmierung einfach nicht überzeugen. Da habe ich
mir gedacht, am besten wäre es wenn man beides haben könnte. Also C dort
zu brauchen wo es der Übersichtlichkeit und dem Verständnis und
Wartbarkeit dient und die hardwarenahen Routinen und die ISR in
Assembler weil hier manchmal jeder uSekunde zählt.
Damit haben aber die Probleme erst richtig begonnen. Der gnu Assembler
hat natürlich eine ganz andere Syntax als der AVR Assembler. Es ist
alles anders auch wenn in AVR Studio gnu Assembler unterstützt wird (in
der Version 4.18 sogar richtig gut, viel Handarbeit wie es früher
anscheinend nötig war wie ich im Internet gesehen habe war nicht nötig)
ist natürlich nichts so wie in einem reinen C oder Assembler Projekt.
Inline Assembler war mir zu mühsam und auch nicht das was mir
vorschwebte. Die Dokumentation von gas gibt da auch nicht viel her. Also
war Probieren angesegt.
Die folgenden Aktionen haben mir dann aber zum Erfolge verholfen
Man muss in den Assembler Sourcen
#include <avr/io.h>
das generische Header File inkluden und ja keines der üblichen.
Header Files muss man 2-teilen eines für Assembler und eines für C
Source.
Wenn man die IO Register symbolisch anspricht muss man jeweils das Macro
_SFR_IO_ADDR bemühen, das jeweils 32 von den Definitions in avr/io.h
abzieht, damit es stimmt. Also etwa
out _SFR_IO_ADDR(OCR1BL),r24
Aber der grösste Frust war F_CPU. AVR Studio übergibt den Wert den man
in den Konfigurations Optionen angibt mit -DF_CPU=16000000UL, nur kann
der Assembler mit dem Wert 16000000UL gar nichts anfangen. Nach langem
suchen bin ich dann per Zufall über folgenden Beitrag im Internet bei
AVR Freaks gestossen.
AVR GCC forum - How to get a constant from C to Asm
Und dort hat dann jemand ohne das es direkt das Ursprungstopik betraff
folgende Antwort auf mein Problem gehabt
.set MY_FREQ,0 //init to 0
.irpc param,F_CPU //go through all 'characters' in F_CPU
.ifnc \param,U //if not a 'U'
.ifnc \param,L //and not an 'L'
.set MY_FREQ,MY_FREQ*10+\param //left shift,then add
.endif
.endif
.endr
der die Definition von F_CPU mit abschliessendem UL in einen Wert ohne
UL am Ende umwandelt der dann vom Assembler wieder als Zahl verstanden
wird.
Netterweise generiert AVR Studio 4.18 ein Listing auch wenn der Suffix
des Assembler Sourcfiles .s und nicht .S (im vom AVR Studio generierten
Makefile steht
ASMFLAGS += -x assembler-with-cpp -Wa,-gdwarf2)
So das wollte ich mitteilen, auch deshalb weil ich zwar im Zusammenhang
mit den Fragen die ich hatte immer wieder mal via Google auf dieses
Forum gestossen bin aber im Zusammenhang mit gemischten C/Assembler
Projekten keine Antworten auf die Fragen bekam, sogar wenn Benutzer vor
langer Zeit die gleichen Fragen wie ich hatte fand ich im Thread keine
Antwort. Vielleicht hilft es dem Einen oder Anderen oder kann mir sonst
nocht Tipps zu gemischten Projekten geben.
Gruss
Peter
Die Hinweise zur Kombination von Assembler und C können sicher dem einen
oder anderen Forumsteilnehmer nützen. Aber, ohne irgendwie den AVR
geringschätzen zu wollen... von jemandem, der PDP-11 Erfahrung hat,
hätte ich eigentlich erwartet, mit dem MSP430 einzusteigen. Warum? Zitat
aus der englischen Wikipedia: "The architecture dates from the 1990s and
is reminiscent of the DEC PDP-11."
;)
Es erschließt sich mir bis auf den heutigen Tag nicht warum man C und
ASM mischen muß. Ich programmiere, wie du auch, in beiden Sprachen aber
nie gemischt. Auch wo ich hier solche Konstrukte gesehen habe stellt
sich am Ende heraus das eine Mixtur aus beiden Sprachen nicht nötig ist.
Kannst du mal ein Beispiel geben ?
> Es erschließt sich mir bis auf den heutigen Tag> nicht warum man C und ASM mischen muß.
Du schreibst nur Kinderprogramme ?
Da, wo's schnell sien muss Assembler,
da, wo's komplex wird C.
Wenn man die KOnstante nicht als DEFINE braucht,
sondern nur als konstanten Wert, kann man sie
einfach zuweisen und in Assembler verwenden.
Bernd N schrieb:> Es erschließt sich mir bis auf den heutigen Tag nicht warum man C und> ASM mischen muß. Ich programmiere, wie du auch, in beiden Sprachen aber> nie gemischt. Auch wo ich hier solche Konstrukte gesehen habe stellt> sich am Ende heraus das eine Mixtur aus beiden Sprachen nicht nötig ist.
Dem kann ich mich nur uneingeschränkt anschließen.
Es sind eigentlich nur Anfänger, die meinen, etwas unbedingt in
Assembler schreiben zu müssen. Und laufen damit in einen Haufen
Probleme.
Die Fortgeschrittenen wüßten zwar, wie es gehen könnte.
Aber sie machen alles in C und haben keine Lust sich diese unnötige
Arbeit aufzubürden.
Peter
Lieber MaWin, ich denke dir fehlt da noch einiges an Erfahrung.
>> Wenn man die KOnstante nicht als DEFINE braucht,>> sondern nur als konstanten Wert, kann man sie>> einfach zuweisen und in Assembler verwenden.
Das ist in C nicht anders, zeig mal ein Beispiel :-)
> Das ist in C nicht anders, zeig mal ein Beispiel :-)
Scherzkeks, du hast überhaupt nicht begriffen daß die Konstante bereits
in C vorliegt.
> Lieber MaWin, ich denke dir fehlt da noch einiges an Erfahrung.
Dir fehlt VERDAMMT VIEL an Erfahrung.
Und ja, ich schreibe öfters mal ein Programm, welches Assemblerbefehle
einsetzt die so in C gar nicht machbar wären (Hauptsächlich die
geschickte Verwendung des Carry), und bei denen diese Form der
Programmierung entscheidend ist damit das Programm schnell genug wird,
denn leider gehen die üblichen Microcontroller nur in die Megahertz und
nicht in die Gigahertz.
Dafür hab ich kein Problem mit einem üblichen 16MHz AVR auf 16 x 16 RGB
LEDs helligkeitsanimierte Filme ablaufen zu lassen, ohne Hilfschips.
MaWin schrieb:> Programmierung entscheidend ist damit das Programm schnell genug wird,> denn leider gehen die üblichen Microcontroller nur in die Megahertz und> nicht in die Gigahertz.
Was für dich die Regel ist, muss für andere noch lange nicht der
Normalfall sein.
Und der NOrmalfall ist nun mal, dass in >90% aller Fälle, in denen
Neulinge denken sie bräuchten unbedingt Assembler, dieser Sachverhalt
ganz einfach nicht zutrifft.
Das es Fälle gibt, in denen jeder Taktzyklus zählt, ist unbestritten.
Aber im Regelfall ist das nicht der Fall.
MaWin schrieb:> Dir fehlt VERDAMMT VIEL an Erfahrung.>>>> Und ja, ich schreibe öfters mal ein Programm, welches Assemblerbefehle>> einsetzt die so in C gar nicht machbar wären (Hauptsächlich die
MaWin ist heute aber nicht sehr entspannt.
Was soll so eine blöde Anmache?
mfg.
Ja die gute alte PDP-11. Ich habe sogar noch eine PDP-11/23, aber leider
keine RSX-11M Lizenz, da viel zu teuer.
Tja, Bernd ich habe nicht gesagt du musst, und ich musste auch nicht ich
wollte einfach. Es ging um ein Projekt mit Hauptprogramm, dass via UART
Befehle entgegennahm, den Input überprüfen, interpretieren und
übersetzen musste um danach Befehle in eine Warteschlange zu stellen die
von einer Interruptroutine in regelmässigen Abständen (im Moment 50
uSekunden) abarbeiten muss. Da ich einerseits immer mehr Logik in das
Hauptprogramm packen muss und ich auch den Zyklus verkleinern will (weil
dann die Schrittmotoren besser, d.h. sanfter und geräuschloser,
arbeiten) kam ich langsam an die Grenzen der Performance des AVR. Eine
kleine Analyze zeigte, der AVR verbraucht ganz schön Zyklen in der in C
gehaltenen Interrupt routine. In Assembler übersetzt braucht die ISR
noch 60% der Zyklen und ist erst noch übersichtlicher geworden. C ist
eben nicht für alles brauchbar. Andererseits wäre der Aufwand das
Hauptprogram in Assembler zu transferieren viel zu gross.
Das mit #define F_CPU 16000000UL , ich brauche den Wert um Konstanten
umzurechnen und da will gas keine U und L am Ende von Zahlen. Etwa
#define t_period_1_l lo8(F_CPU_AS * PERIOD_1 1000000 2)
und das geht mit F_CPU nicht.
Peter du sagts es und um es mit deinen eigenen Worten zu sagen. Ich habe
keine Lust mir diese unnötige Arbeit aufzubürden alles in C zu
schreiben, wenn es nur darum geht mit Bits herumzuschaufeln. In
Assembler gelingt mir das schneller, sicherer und mit viel weniger
Fehlern. Ich kann da nur für mich sprechen und es liegt sicher auch
daran das ich als Echtzeitprogrammierer auf der PDP-11 einiges an
Erfahrung im Schreiben von Interrupt Routinen und Hardwaretreibern
sammeln konnte die mir nun das Leben wesentlich mehr erleichtern als
meine aktuellen Kenntnisse in C. Und glaube mir die Debugmöglichkeiten
auf einer PDP-11 waren lange nicht so ausgereift wie das was heute als
selbstverständlich vorausgesetzt wird. Und so wie es scheint gibt es
auch andere Programmierer die in einem gemischten Projekt Vorteile sehen
;-).
Gruss
Peter
> Was soll so eine blöde Anmache?
Die, die Bernd N an mich geschrieben hat ?
Gute Frage, ihm fehlt halt offensichtlich Erfahrung,
die Erfahrung, daß das was ich schriebe schon richtig
ist und Hand und Fuss hat.
Aber was interessiert dich das ? Hast du womöglich das
geschrieben was unter Bernd N erschien und kommst mit
deinen Pseudonymen durcheinander ?
Peter Schranz schrieb:> uSekunden) abarbeiten muss. Da ich einerseits immer mehr Logik in das> Hauptprogramm packen muss und ich auch den Zyklus verkleinern will (weil> dann die Schrittmotoren besser, d.h. sanfter und geräuschloser,> arbeiten) kam ich langsam an die Grenzen der Performance des AVR. Eine> kleine Analyze zeigte, der AVR verbraucht ganz schön Zyklen in der in C> gehaltenen Interrupt routine.
Du hast doch nicht die Analyse in die ISR gepackt, oder?
> In Assembler übersetzt braucht die ISR> noch 60% der Zyklen und ist erst noch übersichtlicher geworden.
Bist du sicher, dass du den Optimizer eingeschaltet hast?
> C ist> eben nicht für alles brauchbar.
Doch ist es.
So ziemlich.
TV-Signal Timing geht nicht mehr 100% aber ansonsten geht fast alles.
Nur dazu muss man C können und der grundsätzliche Progammaufbau muss
stimmen.
Wenn ich nach dem bischen gehe, was ich deiner Beschreibung entnehmen
kann, klingt das für mich alles nach: Du hast deinen Programmaufbau
vermurkst und jetzt muss es Assembler richten.
>> Tja, Bernd ich habe nicht gesagt du musst, und ich musste auch nicht ich>> wollte einfach.
Das laß ich gelten :-) Solche Dinge lassen sich nur Anhand konkreter
Beispiele diskutieren und ich hab halt Freude daran entwickelt es genau
umgekehrt zu handhaben. Man kann auch in C auf einen Akku zugreifen wenn
man den Compiler und die Architektur gut kennt.
Kleines Beispiel für nen 8x51:
1
voidputchar(uint8_tc){
2
while(!TI);// Transmitter ready/busy ?
3
TI=0;// TI Flag löschen
4
acc=c;// Paritäts BIT prüfen mittels ACC
5
if(P){// Parity BIT = 1 ?
6
TB8=0;// ODD Parity
7
}else{
8
TB8=1;
9
}
10
SBUF=c;// Ausgabe an UART
11
}
Das ist eigentlich Assembler :-) und ich kann dir viele fiese Beispiele
zeigen. Ich würde hier gerne am konkreten Beispielcode diskutieren statt
mich beleidigen zu lassen (MaWin).
MaWin schrieb:> Gute Frage, ihm fehlt halt offensichtlich Erfahrung,>> die Erfahrung, daß das was ich schriebe schon richtig>> ist und Hand und Fuss hat.
Du solltest dich in Gott umbenennen. Aber vorher einen Deutschkurs
machen.
Peter Schranz schrieb:> Das mit #define F_CPU 16000000UL , ich brauche den Wert um Konstanten> umzurechnen und da will gas keine U und L am Ende von Zahlen.
Das L kannst Du einfach weglassen.
Da 16000000 nicht mehr in ein int16_t paßt, ist es implizit int32_t.
Das U kannst Du auch weglassen, wenn Zwischenrechnungen nicht Werte
>2147483647 ergeben.
Du kannst aber auch einfach umgekehrt definieren:
-DF_CPU_AS=16000000
Und im C-Header:
Peter Schranz schrieb:> von einer Interruptroutine in regelmässigen Abständen (im Moment 50> uSekunden) abarbeiten muss.
Das sind bei 16MHz dann 800 Zyklen.
Ein Interrupt in C hat bei mir im Schnitt 50..200 Zyklen, klingt also
problemlos in C machbar.
Allerdings muß ich zugeben, daß der AVR-GCC es einem nicht gerade leicht
macht, optimiert zu schreiben.
Viele Sachen macht er von Haus aus 16-bittig. Also immer schön darauf
achten, daß Variablen und Returnwerte uint8_t sind, wenn ausreichend.
Von daher ist es vorteilhaft, Assembler zu verstehen und mal näher ins
Listing zu schauen, warum er manchmal etwas umständlich macht.
Peter
>> Was soll so eine blöde Anmache?>> Die, die Bernd N an mich geschrieben hat ?
Sollte ich dich beleidigt haben dann entschuldige ich mich. Es gibt
unzählige Threads C vs ASM, ich mag hier den Thread nicht mißbrauchen
aber am Beispiel zu diskutieren hilft. Wie man klar sehen kann rollen
die ersten Tips ein und ich denke das Peter Danegger sowie Karl Heinz
eine Menge Erfahrung haben, du brauchst dich also nicht auf meine
Aussagen zu verlassen.
Nach mehr als 20 Jahren ASM und ca. 5 Jahren C Erfahrung traue ich mir
allerdings ein bischen was zu.
Hallo Peter,
ja ich kann das L und U schon einfach weglassen da hast du natürlich
recht. Aber AVR Studio übergibt es dem avr-gcc mit U und L daran bin ich
gescheitert.
-DF_CPU=16000000UL
auch wenn ich in den Configuration Options zum Projekt bei Frequency nur
16000000 eingebe.
Ich könnte es natürlich auch dort weglassen und in einem globalen Header
File mit
#define F_CPU 16000000
definieren. Das wäre auch gar nie das Problem gewesen, hätte ich von
Anfang an gewusst, dass das UL an den Assembler Fehlermeldungen schuld
war
../timer1isr.s: Assembler messages:
../timer1isr.s:111: Error: `)' required
../timer1isr.s:111: Error: garbage at end of line
../timer1isr.s:149: Error: `)' required
../timer1isr.s:149: Error: garbage at end of line
Aber eben ohne dieses Wissen bin ich gar nicht erst darauf gekommen
F_CPU selbst im Header File ohne UL zu deklarieren.
Gruss
Peter
> Ich würde hier gerne am konkreten Beispielcode diskutieren> statt mich beleidigen zu lassen (MaWin).
Du beleidigst, Bernd, denn es bist du der sich nicht vorstellen
kann, was anderen klar ist.
Einfaches drehen von bits:
unsigned char bisher[8];
unsigned char neu[8];
Was jetzt in einem byte steckt, soll in bit 0 von neu 0..7
verteilt werden.
if C irgendwie so:
for(i=0;i<8;i++)
{
for(j=0;j<8;j++,bisher[i]>>=1)
{
neu[j]=(neu[j]<<1)|(bisher[i]&1);
}
}
und vergleiche die Ausgabe deines Compilers
mit irgendeinem Assembler, ob 6502, PIC oder AVR
ROR bisher0
ROL neu0
ROR bisher1
ROL neu0
ROR bisher2
ROL neu0
ROR bisher3
ROL neu0
ROR bisher4
ROL neu0
ROR bisher5
ROL neu0
ROR bisher6
ROL neu0
ROR bisher7
ROL neu0
8 x mit oder ohne Schleife.
Wer wie Peter Schranz zu Apple ][ Zeiten programmiert hat,
hat ein gutes Gefühl für Microcontroller, schliesslich war
die 6502 damals ähnlich schnell wie Microcontroller heute.
Man konnte (siehe Apple Graphik Spiele) damals viel machen,
aber damit es ausreichend schnell wurde war war Assembler
angesagt. Das begrifft nicht nur Graphik, auch Mathematik
wird ohne Assembler öde langsam.
Ein guter Teil der C-Standardfunktionen die du benutzt aus
der Standardlibrary wird übrigens in Assembler geschrieben
worden sein, also benutzt du auch unter C stets Assemblerstücke.
Also ich beleidige :-)
>> Ein guter Teil der C-Standardfunktionen die du benutzt aus>> der Standardlibrary wird übrigens in Assembler geschrieben>> worden sein, also benutzt du auch unter C stets Assemblerstücke.
Da wäre ich nicht drauf gekommen.
Peter Schranz schrieb:> Aber AVR Studio übergibt es dem avr-gcc mit U und L daran bin ich> gescheitert.
Ich muß zugeben, ich benutze kaum AVRStudio.
Ich nehme meistens ne Batch.
Hier kannst Du mal sehen, wie ich das mache und was dabei rauskommt:
Beitrag "mehrere MC seriell über Datenbus verbinden (1Draht)"
Es ist ein SW-Protokoll und damit auch ein bischen "zeitkritisch".
Die Interrupts sind also möglichst optimiert geschrieben.
Peter
MaWin schrieb:> if C irgendwie so:>> for(i=0;i<8;i++)> {> for(j=0;j<8;j++,bisher[i]>>=1)> {> neu[j]=(neu[j]<<1)|(bisher[i]&1);> }> }
Nö.
In C irgendwie so
Beitrag "Re: Inline Assembler- Bits spiegeln">> ROR bisher0> ROL neu0> ROR bisher1> ROL neu0> ROR bisher2> ROL neu0> ROR bisher3> ROL neu0> ROR bisher4> ROL neu0> ROR bisher5> ROL neu0> ROR bisher6> ROL neu0> ROR bisher7> ROL neu0>> 8 x mit oder ohne Schleife.
macht 16 Zyklen versus 20.
So what?
Hallo Karl-Heinz,
nein der Programaufbau ist nicht vermurkst. Wie gesagt läuft es ja auch
in der ursprünglichen Version in C, mit 50us Interval und dem aktuellen
Befehlssatz den das Hauptprogram interpretieren muss ohne Probleme. 50us
sind zwar bei 16MHz 800 Zyklen, aber das ist auch nicht das Problem der
ISR, sie verbrauchte nicht 800 Zyklen. Ich möchte nur dem Hauptprogram
auch noch ein bisschen Zeit gönnen. Und jetzt stellte sich eben die
Aufgabe, ich will dem Hauptprogram auf alle Fälle genügend Zeit lassen
und den Zyklus würde ich gerne auf 25 oder 20us verkleinern. Einem C
Progammierer mit mehr Erfahrung wäre es sicher auch gelungen die ISR in
C zu beschleunigen. Aber dazu habe ich zuwenig Erfahrung in C.
Ich hatte ja auch nie wirklich Probleme mit dem Program sondern es waren
mehr ärgerliche Problemchen mit der IDE.
Gruss
Peter
Peter Schranz schrieb:> sind zwar bei 16MHz 800 Zyklen, aber das ist auch nicht das Problem der> ISR, sie verbrauchte nicht 800 Zyklen. Ich möchte nur dem Hauptprogram> auch noch ein bisschen Zeit gönnen. Und jetzt stellte sich eben die> Aufgabe, ich will dem Hauptprogram auf alle Fälle genügend Zeit lassen> und den Zyklus würde ich gerne auf 25 oder 20us verkleinern. Einem C> Progammierer mit mehr Erfahrung wäre es sicher auch gelungen die ISR in> C zu beschleunigen.
Was genau machst du alles in der ISR?
Das ist der springende Punkt!
In deiner Problemstellung bieten sich 2 ISR an.
Die eine die 1 Zeichen von der UART holt und in eine FIFO stellt, von wo
aus es dann gemütlich im Hauptprogramm geholt werden kann, wenn Zeit
ist.
Die andere ISR ist der Basistakt, mit dem die Schrittmotoren angesteuert
werden.
Und mit so einem Aufbau kann ich mir ehrlich gesagt nicht wirklich
vorstellen, warum die Zykluszeit der Hauptschleife ein Problem darstellt
(im Rahmen gesehen natürlich).
Es ist schon alles so wie du gesagt hast. Eine ISR für den UART mit FIFO
zum Hauptprogram und eine ISR die zyklisch die Schrittmotoren steuert.
Wenn das Hauptprogram nicht nachkommt setzt die UART ISR das CTS und
wenn es dumm kommt, dann hat die Schrittmotoren ISR nicht genügend
Aufträge mehr und die Maschine wird langsamer. Das ist nicht schlimm
aber erhöht die Verarbeitungszeit. Kam aber schon in der ursprünglichen
Version ab und zu vor. Das wolle ich vermeiden. Und das Hauptprogram hat
doch einiges zu tun, vor allem wenn ich Kurven fahren will.
OK.
Das klingt ja schon mal vernünftig.
Und wie bzw. wo sparst du jetzt in der ISR durch die Verwendung von
Assembler 60% Rechenzeit ein?
Das klingt nämlich überhaupt nicht mehr vernünftig.
Entweder ist dein C-Code extrem ineffizient programmiert oder die ISR
ist so kurz, dass der Callframe Overhead schon signifikant wird.
Das man mit C ein paar Abstriche machen muss ist klar. Aber 60% ist
schon extrem viel.
Edit: Tschuldigung. Umgekehrt. Es waren nur 40%.
Ist aber immer noch viel!
Nein nicht 60% eingespart, sondern er braucht noch 60% der Zyklen. Also
ich habe 40% gespart. Der Original Source Code in C stammt nicht von
mir, ich darf ihn nur brauchen und modifizieren aber nur privat. Auch
habe ich mich jetzt mal auf Grund der vielen Hinweise bezüglich
Effizienz von C nochmals daran gesetzt (die Erfahrungen die ich hier
festgehalten habe sind ja auch schon eine Zeit her) und mir einfach mal
mit -S zeigen lassen was denn gcc aus dem Original Code macht. Was mir
so auf die schnelle auffällt. Wenn ich OCR1A und OCR1B mit den gleichen
Werten bestücke (kommt in diesem Program öfters vor) lädt gcc zweimal
die Register r24 und r25 mit dem gleichen Konstanten bevor er OCR1A und
danach OCR1B schreibt, in Assembler macht man das intuitiv nicht. Auch
memcpy scheint in einer ISR sehr schlecht zu sein, dadurch werden
ziemlich viele Register in der ISR auf den Stack gelegt. Da die Anzahl
der verschobenen bytes jeweils klein ist und auch nicht variabel ist
schreibe ich in Assembler meist eine Kette von lds und sts und brauche
genau ein Register. D.h. ich mache nicht mal in Assembler eine Schlaufe,
Zeit war ja das Kriterium nicht Platz. Im Moment ist der ganze Code
28kbytes, da habe ich noch einiges bis der ATMEGA32 ausgereizt ist und
dann kann ich ja immer noch auf dem ATMEGA64 oder so wechseln (was ich
sowieso wegen dem 2ten UART mal machen will). Es scheint wohl so zu
sein, man hätte mit C Erfahrung ähnlich viel herausholen können.
Auf der anderen Seite muss ich natürlich sagen, mir macht Assembler
Spass, hat es immer gemacht ;-)
Peter Schranz schrieb:> danach OCR1B schreibt, in Assembler macht man das intuitiv nicht. Auch> memcpy scheint in einer ISR sehr schlecht zu sein, dadurch werden> ziemlich viele Register in der ISR auf den Stack gelegt.
Eine Grundregel beim GCC lautet:
In einer ISR keine Funktionen aufrufen!
Dadurch zieht man sich einen Rattenschwanz an Register-Sicherungen rein.
> Da die Anzahl der verschobenen bytes jeweils klein ist und auch> nicht variabel ist schreibe ich in Assembler meist eine Kette von> lds und sts und brauche genau ein Register.
Und warum kannst du dieselbe Strategie nicht auch in C benutzen?
Da hast du natürlich recht. Vergiss nicht, meine C Kenntnisse sind
bezüglich do's und don't's in einer ISR mangels Erfahrung sehr mager.
Und darum bin ich ja dazumals auch dazu übergegangen einfach das ganze
in Assembler zu schreiben. In Assembler habe ich Erfahrung und auch
während meiner Arbeit ziemlich viel programmiert. C kenne ich fast nur
vom Hörensagen. Selbst viel programmiert habe ich in C bis jetzt nicht.
Und der Erfolg den ich mit Assembler habe hat mich natürlich auch nicht
bewogen mich vermehrt mit C zu befassen. Wie heisst es doch so schön,
was der Bauer nicht kennt...
MaWin schrieb:>> Nö.>> In C irgendwie so>> Beitrag "Re: Inline Assembler- Bits spiegeln">> Nein, kein Mirror, sondern im 8 x 8 bitarray um 90 Grad drehen.
Ah.
Jetzt versteh ichs.
Ja, da gbe ich dir schon recht. Wenn man auf Bitebene runter muss, wirds
in C manchmal haarig. C beginngt nun mal erst ab Bytes aufwärts,
eigentlich erst so richtig ab int aufwärts und mit Bytes hat man schon
zeitweise zu kämpfen. Auf Bitebene kanns dann schon mal extrem tricky
werden.
...und sofort gibts wieder einen Flamewar.
2/3 dieses Threads gingen darum, ob ASM oder C BESSER sind.
Dabei sind beide berechtigt am Platz.
Selbst Bascom wird irgendwo seine Berechtigung haben, auch wenn Ich
vielleicht noch nicht sehe wo!
Es gibt auch ein wunderbares Java für die Dinger!
Am besten ist es immer, alles zu mischen, solange man keinen Knoten in
den Kopf kriegt!
@MaWin:
Wenn du so toll in ASM bist,
warum machst du dann noch kein AVR-Brainf*ck?
Nur um noch mal daran zu erinnern:
Der TO hat nicht mal eine Frage gestellt!
Mit freundlichen Grüßen,
Valentin Buck
> @MaWin:> Wenn du so toll in ASM bist,> warum machst du dann noch kein AVR-Brainf*ck?
Weil
> Dabei sind beide berechtigt am Platz.
das meine Aussage war.
Es sind Bernd und Oliver, die keinen Platz für Assembler sahen.
Aber schön, daß du schon weisst, der der Böse ist,
und zum persönlich angreifenden flamewar übergehst.
Denn eine Entschuldung wird von dir nicht kommen.
MaWin schrieb:> Aber schön, daß du schon weisst, der der Böse ist,> und zum persönlich angreifenden flamewar übergehst.
Wieso Flamewar?
Ich habe dich indirekt gefragt (zugegeben, nicht im allernettesten
Tonfall), warum du eher aggressiv auf die Beiträge geantwortet hast.
Ich finde Kritik nicht schlimm, nur man kann sie doch auch auf
verschiedene Arten ausdrücken (wie einen Schwamm!).
Wenn du sie psychisch nicht handhaben kannst und nun verzweifelst, so
tut es mir leid.
Mensch, entspann dich!
Im Grunde genommen ist alles doch nur ein Weg, um ans Ziel zu kommen.
Und da kann man doch alles machen.
Es ginge doch auch, alles analog zu machen, nur ist C bequem und ASM
auch einfacher als das!
Und meine Frage war ernst gemeint!
Mit freundlichen Grüßen,
Valentin Buck
Win* vs. Linux
Radfahrer gegen Autofahrer
Assembler vs. C
Immer diegleichen Diskussionen, ich meine, das eine tun und das andere
nicht lassen:
- Von (AVR-) Assembler kann man problemlos auf C-Variablen zugreifen.
- Man (Ich ;-/ ) ist beim Optimieren nicht viel besser als der Compiler
- Wenns wirklich auf maximale Leistung bei kurzem Programmstück ankommt
kann man durch Ausnutzen processorspezifischer Befehle (AVR 'swap')
schon was rausholen.
- Besser nen schnelleren Quartz nehmen!
- Wer keine Reserven im System hat, hat falsch geplant.
- Inline Assembler in gcc ist gewöhnungsbedürftig, geht aber.
- Was Ernsthaftes nur in Assembler ist sehr schwer (64k Assembleroutput
will debuggt sein)
- Mit C kann man sich an den Assemblerstil ranrobben, umgekehrt geht das
durch Makros auch.
Ja, soweit mein Beitrag zur Diskussion
Cheers
Detlef
Peter Schranz schrieb:> Was mir> so auf die schnelle auffällt. Wenn ich OCR1A und OCR1B mit den gleichen> Werten bestücke (kommt in diesem Program öfters vor) lädt gcc zweimal> die Register r24 und r25 mit dem gleichen Konstanten bevor er OCR1A und> danach OCR1B schreibt
Nö, tut er nicht:
1
void test_io()
2
{
3
OCR1A = 0xa020;
4
OCR1B = 0xa020;
5
}
1
Disassembly of section .text:
2
3
00000000 <test_io>:
4
0: 80 e2 ldi r24, 0x20 ; 32
5
2: 90 ea ldi r25, 0xA0 ; 160
6
4: 9b bd out 0x2b, r25 ; 43
7
6: 8a bd out 0x2a, r24 ; 42
8
8: 99 bd out 0x29, r25 ; 41
9
a: 88 bd out 0x28, r24 ; 40
10
c: 08 95 ret
Wieviel Zyklen kann man dann noch sparen, wenn man das von Hand in
Assembler schreibt?
Peter Schranz schrieb:> In Assembler habe ich Erfahrung und auch> während meiner Arbeit ziemlich viel programmiert. C kenne ich fast nur> vom Hörensagen. Selbst viel programmiert habe ich in C bis jetzt nicht.
Ja das kenne ich. Du wirst viele C-Programmierer finden, die auch mal
Assembler heiß geliebt haben und sich nichts anderes vorstellen konnten.
Ich gehöre auch dazu.
Aber der Appetit kommt beim Essen. Mit steigender C-Erfahrung wirst Du
Assembler immer weniger mögen und irgendwann aufgeben, ihn mit C zu
mixen.
C macht es auch einfacher, verschiedene AVRs zu verwenden.
Man kann sich natürlich auch in Assembler Macros schreiben, die
automatisch zwischen IN/LDS, OUT/STS, RCALL/CALL usw. auswählen. Aber
angenehmer ists schon, sich darum nicht kümmern zu müssen.
Ich schreibe auch oft Funktionen, die nur einmal aufgerufen werden.
Einfach, um das Problem in Module aufzuteilen und die Übersicht zu
behalten.
Das merkt dann der Compiler und fügt sie inline ein.
Peter
Hallo Peter,
du hast ziemlich sicher recht und ich sehe das ähnlich. Ich stehe ja
aber auch erst am Anfang meiner MCU und C Erfahrung. Assembler war der
einfachste Einstieg für mich und half mir die MCU Welt zu erfahren. Ich
war vielleicht einfach zu faul mich in C zu vertiefen nur um meine
ersten Ideen umzusetzen. Ich wollte auch nie einen Glaubenskrieg
zwischen den Programmiersprachen beginnen noch will ich jemanden sagen
in welcher Sprache er am besten programmieren soll. Um C komme ich ja
sowieso nicht herum. Das war mir schon immer klar. Nur hätte ich mir
einen weicheren Übergang gewünscht.
Gruss
Peter
Peter Schranz schrieb:> ldi r24,0x40> out 0x2xb,r24> ldi 0x07> out 0x2a,r24> out 0x28,r24
Die zweite Zeile sollten wohl zwei Zeilen out sein und du hast das ret
vergessen. Insgesamt also genau 0 Zyklen und 0 Bytes gespart, oder wie?
Davon abgesehen wollte ich ja hauptsächlich die Aussage "gcc lädt
identische Konstanten doppelt" richtigstellen.
Hallo,
naja, welche Sprache ist im Grunde bei Kleinigkeiten oftmals egal. Ich
habe nur die Erfahrung gemacht (Programmiere fast alles in C), dass man
in C das wesentlich besser verstehen und lesen kann, insbesondere, wenn
das ganze eine gewisse kritische Masse überschreitet. Ich denke, das
kommt einfach drauf an, was man machen will und welche Anforderungen
vorhanden sind. Die ein oder andere Interruptroutine habe ich auch schon
in Assembler gehackt, weil sie einfach zu viel Zeit gefresen hat, aber
wie war das mit 80/20, man kann sich also 80% sparen in Assembler
umzusetzen, weil es eh nicht Geschwindigkeitsrelevant ist.
Ganz nebenbei, nimm mal nen Assemblercode von einem Fujitsu Controller
und lass den dann auf einem AVR oder MSP430 laufen :) Da war ich schon
oft froh, dass man sowas in C mit wesentlich weniger Änderungen machen
kann.
Viele Grüße
Hallo Andreas,
das High-Byte geht in einen zwischenspeicher und wird erst mit dem Laden
des Low-Byte in das 16-bit Register übernommen. Ok ich hatte noch einen
Typo in der 3ten Zeile
ldi r24,0x40
out 0x2xb,r24
ldi r24,0x07
out 0x2a,r24
out 0x28,r24
das erste out setzt das tmp und das wird bei beiden anderen out
mitverwendet.
Der gcc macht bei mir leider folgendes
OCR1A = F_CPU * TIMER1_0 2 1000000L; //1856
55c: 80 e4 ldi r24, 0x40 ; 64
55e: 97 e0 ldi r25, 0x07 ; 7
560: 9b bd out 0x2b, r25 ; 43
562: 8a bd out 0x2a, r24 ; 42
OCR1B = F_CPU * TIMER1_0 2 1000000L; //1856
564: 80 e4 ldi r24, 0x40 ; 64
566: 97 e0 ldi r25, 0x07 ; 7
568: 9b bd out 0x2b, r25 ; 43
56a: 8a bd out 0x2a, r24 ; 42
wenn ich nur wüsste warum, irgendwas habe ich da wohl man unbewusst
verbockt. Hat einer einen Tipp?
Gruss
Peter
Peter Schranz schrieb:> das erste out setzt das tmp und das wird bei beiden anderen out> mitverwendet.
Ich vergaß diese Möglichkeit. Dann ist natürlich auch klar, warum der
Compiler das eine out nicht einsparen kann: Er weiß ja nicht, dass das
TEMP Register nicht in einem Interrupt genutzt wird.
In deinem Beispiel werden auch nirgends die Interrupts abgeschaltet oder
sonstwie gesichert, also funktioniert dieses Fragment auch nur mit
impliziten Annahmen.
Peter Schranz schrieb:> wenn ich nur wüsste warum, irgendwas habe ich da wohl man unbewusst> verbockt. Hat einer einen Tipp?
Optimierung einschalten vielleicht? Ich habe mein Beispiel mit -O2
übersetzt. Ohne Optimierung arbeitet der gcc halt stur nach den
Anweisungen, wie sie im Quellcode vorkommen. Schön fürs Debugging,
weniger für Code-Größe und Performance.
Hallo Andreas,
ich muss mal nachschauen was da AVR Studio ein- ausschaltet und warum
oder warum nicht, das werd ich noch herausfinden, werde mal auf Grund
deines Hinweises nach -O suchen und die Doku nochmals gezielt nach
Hinweisen durchforsten.
Das mit meiner Annahme ist ganz einfach, das mache ich nur in der ISR,
also kein Risiko für weitere Interrupts, aber sonst würde ich natürlich
von solchen Tricks auch abraten. In der Zwischenzeit bekomme ich mit den
Hinweisen auch mit C immer kürzeren und schnelleren Code. Nur eine Zeile
macht mir echt sorgen, weil sie ist jetzt noch der Grund das der C Code
viel zu viele Register auf den Stack legt.
current_byte = message[i++];
daraus wird dann
59c: 80 91 bf 00 lds r24, 0x00BF
5a0: e8 2f mov r30, r24
5a2: f0 e0 ldi r31, 0x00 ; 0
5a4: e1 54 subi r30, 0x41 ; 65
5a6: ff 4f sbci r31, 0xFF ; 255
5a8: 93 81 ldd r25, Z+3 ; 0x03
5aa: 90 93 c0 00 sts 0x00C0, r25
5ae: 8f 5f subi r24, 0xFF ; 255
5b0: 80 93 bf 00 sts 0x00BF, r24
Es soll mir einfach nur nie mehr jemand sagen C sei viel einfacher als
Assembler, beides hat wohl seine Tücken.
Gruss
Peter
Peter Schranz schrieb:> Nur eine Zeile> macht mir echt sorgen, weil sie ist jetzt noch der Grund das der C Code> viel zu viele Register auf den Stack legt.>> current_byte = message[i++];
Das sieht doch recht optimal aus.
current_byte und i sind bestimmt globale Variablen, also muß er sie aus
dem SRAM holen bzw. dort ablegen.
Peter
Hi
>Es soll mir einfach nur nie mehr jemand sagen C sei viel einfacher als>Assembler, beides hat wohl seine Tücken.
Als notorischer Assemblerprogrammierer muss ich sagen, nach 5min von C
erzeugten Code Ansehen brauche ich eine halbe Stunde um die gesträubten
Nackenhaare wieder in Form zu bringen.
MfG Spess
Geht mir manchmal genauso. Und ich kann mich nicht daran anfreunden C so
zu "ordnen" und zu "verwenden" dass daraus schöner Assembler Code
entsteht. dann mach ich es lieber gleich direkt in Assembler. Ich denke
in den ISRs werde ich definitv bei Assembler bleiben. Bei mir würde
1
current_byte=message[i++];
in Assembler etwa so umgesetzt
1
lds r30,i
2
inc r30
3
sts i,r30 // Save Next Index
4
clr r31
5
subi r30,lo8(-(message)) // Point to Message Buffer +1
6
sbci r31,hi8(-(massage))
7
ld r0,-Z // Get current Message byte
8
9
sts doi.current_byte,r0 // Put into current byte buffer
ich verbrauche wesentlich weniger Register (wir sind hier in der ISR,
daher ist jedes Register das man nicht braucht gesparte Zeit) und es ist
knapp kürzer. Schlimm wird der erzeugte Code aber vor allem dann wenn es
um Bitmanipulationen geht ;-)
Peter Schranz schrieb:> ich verbrauche wesentlich weniger Register
Na komm, 3 ist nicht wesentlich weniger als 4.
Auch ist Dein Assembler nicht identisch zu der C-Zeile.
Du machst pre-increment, im C-Code hast Du aber geschrieben
post-increment (i++).
Der Compiler muß immer genau das tun, was Du hinschreibst.
Es stimmt allerdings, beim Register-Renaming ist der AVR-GCC recht faul.
Er MOVed gerne mal unnütz in seine Lieblings-Register.
Peter
Ja für diese Zeile stimmts. Das mit den Registern muss man über die
ganze ISR betrachten, in C wurden dort r0,r1,r18,r19,r20,r21,r24,r25,r30
und r31 gesichert, in Assembler sind es noch r0,r1,r30 und r31, aber ich
denke r1 kann ich auch noch eliminieren, so dass noch r0, r30 und r31
bleiben.
Und mein Assembler Code macht schon das was das C Statement macht,
nämlich das Byte an der Position i nehmen und i inkrementieren.
Hi
>...in Assembler sind es noch r0,r1,r30 und r31, aber ich>denke r1 kann ich auch noch eliminieren, so dass noch r0, r30 und r31>bleiben.
In deinem Code sehe ich kein r1. Aber r0 kannst du einsparen wenn du das
ld r0,-Z
durch z.B.
sbiw ZH:ZL,1
ld ZL,Z
ersetzt.
MfG Spess
Nein das r1 kommt an anderen Stellen der ISR noch vor, mein "eliminieren
von r1" muss man im ganzen ISR Kontext sehen. Ja deinen Trick habe ich
vergessen.
Peter Schranz schrieb:> Und mein Assembler Code macht schon das was das C Statement macht,> nämlich das Byte an der Position i nehmen und i inkrementieren.
Ja aber vorher!
Das i++ bedeutet aber nachher
Willst Du es vorher, mußt Du ++i schreiben.
Der Compiler kann nicht wissen, daß das für Dich egal ist, er muß die
Reihenfolge daher beibehalten.
Er darf das Ergebnis von i++ erst abspeichern, nachdem alles andere
erledigt ist. Es könnte ja sein, daß der Rest des Ausdrucks das alte i
noch benötigt.
Peter