Also, ich möchte im PORTA höchste byte von phase haben, ohne lange
Verschieben zu machen (reine C macht viel zu viel, kopiert Register,
setzt sie auf Null, was hier gar nicht notwendig ist)
Ich bekomme aber:
1
+00000063: 017C MOVW R14,R24 Copy register pair
2
+00000064: 018D MOVW R16,R26 Copy register pair
3
+00000065: 0F82 ADD R24,R18 Add without carry
4
+00000066: 1F93 ADC R25,R19 Add with carry
5
+00000067: 1FA4 ADC R26,R20 Add with carry
6
+00000068: 1FB5 ADC R27,R21 Add with carry
7
24: PORTA = sum32(freq[0], phase[0]);
8
+00000069: BB8B OUT 0x1B,R24 Out to I/O location
Also, ich bekomme in PORTA kleinste Byte statt höchste.
Wie könnte ich das ändern, in Inline-Assembler?
Und wie wäre es möglich, Kopieren von Registerpaar am Anfang zu
vermeiden? Das ist auch überflüssig hier.
Vielen Dank im voraus.
Die Addition brauch nicht in Inline Asm ausgeführt zu werden, und
volatile ist es auch nicht. Wenn ein >> 24 wirklich zu viel Overhead
erzeugt, dann kommt man um das MOV oben schwerlich rum — es sei denn,
man tut auch das OUT ins asm rein.
Ich muß unserem Hater zugute halten, daß er zumindest in einem Punkt
recht hat: der TE weiß entweder nicht, was er will oder ist zumindest
unfähig, es zu artikulieren. Und wenn ihm das schon Menschen gegenüber
nicht gelingt, dann wird es ihm auch Computern gegenüber nicht gelingen.
Ganz egal ob in C oder Assembler.
Axel S. schrieb:> Und wenn ihm das schon Menschen gegenüber> nicht gelingt, dann wird es ihm auch Computern gegenüber nicht gelingen.
Das Gegenteil davon allerdings eine ziemlich präzise Beschreibung des
sprichwörtlichen Nerds. ;-)
Maxim B. schrieb:> ich lerne Inline-Assembler.
Das ist der Fehler! Entweder Assembler und zum C-Code dazulinken oder
nur C, aber kein Inline-Assembler-Murks ...
Hatten wir die Diskussion neulich nicht schon einmal? <4 Wochen?
Ah, genau da ist er ja ... Doch leicht älter als 4 Wochen, die Essenz
ist die Gleiche:
Beitrag "Assembler - Fehler im Programm"
Mampf F. schrieb:> Entweder Assembler und zum C-Code dazulinken oder> nur C, aber kein Inline-Assembler-Murks ...
Auch wenn oben Inline-Assembler nicht angebracht ist, ist deine
Behauptung schlichtweg nicht zutreffend. Die Folgerung wäre nämlich,
dass Inline-Assembler grundsätzlich überflüssig ist, was nicht der Fall
ist.
Mampf F. schrieb:>> ich lerne Inline-Assembler.>> Das ist der Fehler! Entweder Assembler und zum C-Code dazulinken oder> nur C, aber kein Inline-Assembler-Murks ...
Danke!
Ich komme inzwischen zu ähnlichen Gedanken.
Wahrscheinlich ist Inline-Assembler einfach zu kompliziert
implementiert.
Das Problem habe ich mit Assembler in CodeVisionAVR gelöst. Mag sein,
CodeVisionAVR kann C nicht so gut optimieren wie WinAVR, dafür aber viel
einfacher mit Assembler und Flash.
Sicher gibt es kein Compiler für alles. Für einige Aufgaben ist WinAVR
besser, anderes gelingt mit CodeVisionAVR einfacher...
Maxim B. schrieb:> Mag sein, CodeVisionAVR kann C nicht so gut optimieren wie WinAVR,> dafür aber viel einfacher mit Assembler und Flash.
Lustig.
Der von einer 9 Jahre alten GCC Version (das neueste WinAVR ist GCC v4.3
von 2008) erzeugte Code ist nicht guit genug — so dass Assemnler in
Betracht gezogen wird. Statt auf eine neuere GCC-Version umzusteigen
(aktuell v6) die besseren Code erzeugt und Features wie __flash bringt,
wird auf einen Compiler umgesattelt, der noch schlechteren Code
generiert...
> Wahrscheinlich ist Inline-Assembler einfach zu kompliziert> implementiert.
GCC Inline-Assembler bietet recht feinziselierte Möglichkeiten, wie sie
bei der Systemprogrammierung benötigt und unerlässlich sind.
Wenn die Komplexität von Inline-Assembler nicht gebrauch wird, dann geht
natürlich auch "normaler" Assembler.
Johann L. schrieb:> Der von einer 9 Jahre alten GCC Version (das neueste WinAVR ist GCC v4.3> von 2008) erzeugte Code ist nicht guit genug — so dass Assemnler in> Betracht gezogen wird. Statt auf eine neuere GCC-Version umzusteigen> (aktuell v6) die besseren Code erzeugt und Features wie __flash bringt,> wird auf einen Compiler umgesattelt, der noch schlechteren Code> generiert...
Ich benutze AVR Studio 4.18 und WinAVR 20100110. Die neuere Version mit
__flash bringt wirklich etwas kleineren Code. Aber unbequem, immer in
Projektoptionen zu gehen und dort diese "-gdwarf-2" zu setzen. Das lohnt
sich selten.
>GCC Inline-Assembler bietet recht feinziselierte Möglichkeiten, wie sie>bei der Systemprogrammierung benötigt und unerlässlich sind.
Leider habe ich bisher dafür keine ausführliche deutsche Anleitung
gefunden.
In jedem Fall hat man in C entweder 1 Bytes, oder 2 Bytes, oder 4 Bytes
oder 8 Bytes-Variablen. Aber nichts mit z.B. 3 Bytes. D.h. ab und zu
braucht man Assembler-Funktionen sowieso. Oder man muß für Vergnügen,
ohne Assembler zu programmieren, mit teurerem Mikrocontroller und
höherem Stromverbrauch bezahlen.
In der Anleitung für CodeVisionAVR sind einfach die Register genannt,
die für Ausgabe benutzt werden und auch die für freie Verwendung
freigegeben sind: r0,r1,r22,r23,r24,r25,r26,r27,r30,r31, dabei in r30,
r31, r22 und r23 für return-Ausgabe vorgesehen sind. Einfach und
verständlich.
CodeVisionAVR hat m.E. zwei wesentliche Nachteile:
1. das ist ein kommerzielles Programm, nicht frei wie GCC.
2. double ist wie float, nur 4 Bytes.
So denke ich, jedem das Seine. Für einige Sachen ist GCC besser, für
anderes passt CodeVisionAVR gut.
Maxim B. schrieb:> In jedem Fall hat man in C entweder 1 Bytes, oder 2 Bytes, oder 4 Bytes> oder 8 Bytes-Variablen. Aber nichts mit z.B. 3 Bytes. D.h. ab und zu> braucht man Assembler-Funktionen sowieso.
Oder man verwendet GCC und nimmt "__int24" bzw. "__uint24" (AVR).
A. K. schrieb:>> Oder man verwendet GCC und nimmt "__int24" bzw. "__uint24" (AVR).
Kann man wirklich so machen?
Ich habe immer gedacht, typedef kann nur schon sowieso vorhandene Typen
neu benennen, aber keine neuen von Null aus bilden.
Soll das bedeuten, daß ich einfach __uint800 schreibe und gleich
automatisch korrekt Code für 100-Bytes-Typ bekomme?
Maxim B. schrieb:> Ich habe immer gedacht, typedef kann nur schon sowieso vorhandene Typen> neu benennen, aber keine neuen von Null aus bilden.
int24 ist eine GCC-AVR8-Extension.
Maxim B. schrieb:> Ich habe gleich eine Probe gemacht:> volatile __uint24 phase = 0;> volatile __uint24 freq = 47001642;> AVR Studio sagt: Fehler.
Und welche Compiler-Version hat dem AVR-Studio das übersetzen
abgenommen?
Zu 4.7.2 hat der AVR-GCC __(u)int24 dazubekommen.
Danke!
Ich habe nun mit Toolchain 3.5.0 ausprobiert: das muß ziemlich neue
Version sein, nicht wahr?
Ja, das hat geklappt.
Aber...
Alte Variante: WinAVR20100110 und 32bit Variablen:
mit Qs dauert 695 .
Neue Variante: Toolchain 3.5.0 und 24bit Variablen:
mit Qs dauert 750, mit Q2 ist Dauer 765.
Die Frage: wozu dann 24bit Variablen, wenn dadurch nur langsamer wird?
Ich weiß nicht, warum. Disassembler zeigt dort deutlich mehr
unnützlichen Code, als frühere Version...
Ist neue Compiler wirklich besser geworden?
Wirklich viel zu viel.
Ein Beispiel:
1
phase[channel]+=freq[channel];
2
3
buf_phase=(unsignedint)(phase[channel]>>8);
1
343: phase[channel] += freq[channel];
2
+00000B47: 01F2 MOVW R30,R4 Copy register pair
3
+00000B48: 8120 LDD R18,Z+0 Load indirect with displacement
4
+00000B49: 8131 LDD R19,Z+1 Load indirect with displacement
5
+00000B4A: 8142 LDD R20,Z+2 Load indirect with displacement
6
+00000B4B: 90600116 LDS R6,0x0116 Load direct from data space
7
+00000B4D: 90700117 LDS R7,0x0117 Load direct from data space
8
+00000B4F: 90800118 LDS R8,0x0118 Load direct from data space
9
+00000B51: 0D26 ADD R18,R6 Add without carry
10
+00000B52: 1D37 ADC R19,R7 Add with carry
11
+00000B53: 1D48 ADC R20,R8 Add with carry
12
+00000B54: 93200116 STS 0x0116,R18 Store direct to data space
13
+00000B56: 93300117 STS 0x0117,R19 Store direct to data space
14
+00000B58: 93400118 STS 0x0118,R20 Store direct to data space
+00000B5A: 90600116 LDS R6,0x0116 Load direct from data space
17
+00000B5C: 90700117 LDS R7,0x0117 Load direct from data space
18
+00000B5E: 90800118 LDS R8,0x0118 Load direct from data space
19
+00000B60: 2C67 MOV R6,R7 Copy register
20
+00000B61: 2C78 MOV R7,R8 Copy register
21
+00000B62: 2488 CLR R8 Clear Register
Hier sind die Zeilen +00000B5A: bis +00000B5E: überflüssig, Compiler hat
das nicht erkannt.
So könnte ich ungefähr 1/3 der Zeit sparen (mindestens), wenn ich die
zeitkritische Sachen mit Assembler mache und anderes auf C.
Und das ist, nachdem ich die Funktion als inline static deklariert habe
(was die Zeit bis 590 Cycles reduzierte), früher war Code noch
schlimmer.
Maxim B. schrieb:> Die neuere Version mit __flash bringt wirklich etwas kleineren Code.> Aber unbequem, immer in Projektoptionen zu gehen und dort diese> "-gdwarf-2" zu setzen.
Was hat den das Flag zum debuggen mit __flash zu tun?
Maxim B. schrieb:> Aber in Assembler würde ich nach> STS 0x0116,R18> STS 0x0117,R19> STS 0x0118,R20>> für das Weitere entweder gleich r18-r20 benutzen,
Das ist dem Compiler bei "volatile" verboten.
Es ist etwas unfair, dem Compiler per "volatile" zu sagen, dass er
Zugriffe keinesfalls wegoptimieren darf, und ihm anschliessend
vorzuwerfen, dass er Zugriffe nicht wegoptimiert.
Maxim B. schrieb:> Ja, volatile. Das ist leider notwendig (da in Funktion).
"in Funktion" ??
volatile braucht man, damit man in der Hauptschleife Änderungen an einer
Variable sehen kann, die eine ISR macht.
Ich vermute, hier wird ein DDS-Generator in einer Timer-ISR benutzt.
Variablen, die ausschließlich die ISR benutzt, wie den Phase-Akku, oder
Varablen, die von der ISR ausschließlich gelesen werden, wie z.B.
Phase-Inkrement, brauchen kein volatile. Bei letzteren muß nur
sichergestellt werden, daß sie "atomar" von der Hauptschleife
manipuliert werden. Mit denen rechnet man in der Hauptschleife nicht
rum, sondern setzt sie irgendwann auf den berechneten (neuen) Wert und
sperrt für diese Zuweisung die Interrupts. Wenn man vorher noch per
(idle) sleep einen TimerInterrupt abgewartet hat, dann geht das
womöglich sogar Jitter-frei(/-arm).
Compiler sind heute sehr gut darin, Code zu optimieren. Aber nur
basierend auf den Anforderungen (Source-Code). Alles, was dort nur vage
definiert ist, führt aber dazu, daß der Compier vom Worst-Case ausgehen
muß. "volatile" schmeissen, weil man mit Multithreading (nichts anderes
sind ISRs) nicht zurecht kommt, ist kein Problem des Compilers.
Und wenn der AVR wirklich zu langsam ist, für 2,50 liefert der Chinese
einen "STM32-Arduino", der rechnet gerne mit 32Bit.
Carl D. schrieb:> Maxim B. schrieb:>> Ja, volatile. Das ist leider notwendig (da in Funktion).>> "in Funktion" ??>> volatile braucht man, damit man in der Hauptschleife Änderungen an einer> Variable sehen kann, die eine ISR macht.> Ich vermute, hier wird ein DDS-Generator in einer Timer-ISR benutzt.> Variablen, die ausschließlich die ISR benutzt, wie den Phase-Akku, oder> Varablen, die von der ISR ausschließlich gelesen werden, wie z.B.> Phase-Inkrement, brauchen kein volatile.
Danke!
Ich versuche, Programm anders zu gestalten.
Ich möchte es versuchen, zu erreichen, daß ein ATmega zwei Stimmen
bedient. Für nur eine Stimme reicht die Zeit, für zwei noch nicht.
Carl D. schrieb:> volatile braucht man, damit man in der Hauptschleife Änderungen an einer> Variable sehen kann, die eine ISR macht.
Ich hab schon erlebt daß der gcc beim Optimieren eine kurze Sequenz
inline asm einfach komplett entfernt hat, wahrscheinlich weil er
irgendwie zum Schluss gekommen ist das ganze Konstrukt hätte keine
Seiteneffekte und wäre somit überflüssig. Volatile hat hier geholfen dem
Compiler das etwas eindringlicher klar zu machen.
Na, ich kam bei 2 Stimmen auf 517 Takte, und ich fürchte, das ist die
Grenze für GCC. Weitere Ersparnis (ohne vollständig auf Assembler
umzusteigen, was ich auch für möglich halte), wie ich fürchte, kann nur
auf Kosten von repetierenden Register passieren (was ich nicht möchte).
1
#define PHASE_MODUL 240UL
2
#define SESQ_REP 12 // Rep. Punkt Sesquialtera, c0
Bernd K. schrieb:> Carl D. schrieb:>> volatile braucht man, damit man in der Hauptschleife Änderungen an einer>> Variable sehen kann, die eine ISR macht.>> Ich hab schon erlebt daß der gcc beim Optimieren eine kurze Sequenz> inline asm einfach komplett entfernt hat, wahrscheinlich weil er> irgendwie zum Schluss gekommen ist das ganze Konstrukt hätte keine> Seiteneffekte und wäre somit überflüssig. Volatile hat hier geholfen dem> Compiler das etwas eindringlicher klar zu machen.
"volatile asm" ist etwas anderes als eine volatile Variable.
Bei ersterem sagt man dem Compiler "Finger weg von meinem ASM Code",
bei letzterem "diese Variable kann ihren Inhalt jederzeit ohne
erkennbaren Grund ändern".
Z.B. ein Hardwareregister oder eben eine (globale) Variable, die in
einer ISR geändert wird. Beide Fälle erlauben kein Zwischenspeichern des
Wertes in einem Register, denn der aktuelle Programmfluß läst keinen
Rückschluss auf den aktuellen Wert zu und damit ob der "gepufferte Wert'
noch aktuell ist.
@Maxim B.:
Ich sehe da nirgends eine Timer-Isr, d.h. das ganze läuft
"singlethreaded" und damit ist volatile für die Variablen überflüssig.
Allerdings frage ich mich, wie dann der Takt für die DDS zustande kommt.
Der geht nämlich direkt in die Tonhöhe ein und unterschiedlich lang
laufende Code-Paths (es gibt ja einige if's) sorgen für "jaulen".
Maxim B. schrieb:> volatile __uint24 freq[2] = {184549,207150};> volatile unsigned char amp[] = {150,201};> volatile unsigned char note[] = {25,33};> volatile unsigned char status[] = {1,1};> volatile unsigned char ein_gedackt16 = 1;> volatile unsigned char ein_ged_pr8 = 1;> volatile unsigned char ein_fl_okt4 = 1;> volatile unsigned char ein_fl_okt2 = 1;>> volatile unsigned char ein_quinte = 1;> volatile unsigned char ein_terzfloete = 1;> volatile unsigned char ein_sesquialtera = 1;> volatile unsigned char ein_scharf = 1;> volatile unsigned char ein_zimbel = 1;
Kein einziges dieser "volatile" ist notwendig da die Anwendung weder
Interrupts noch ISRs hat.
Wenn du grundlos volatile mit der Grießkanne verteilst, dann hat ein
optimierender Compiler keine Chance, auch kein CodeVisionAVR — es sei
der ist nicht volatile-korrekt.
Selbtst mit #include <avr/io.h> lässt sich der Code nich übersetzen:
1
foo.c: In function 'channel':
2
foo.c:96:13: error: 'gedackt16' undeclared (first use in this function)
3
foo.c:96:13: note: each undeclared identifier is reported only once for each function it appears in
4
foo.c:104:13: error: 'gedackt8' undeclared (first use in this function)
5
foo.c:109:13: error: 'principal8' undeclared (first use in this function)
6
foo.c:117:13: error: 'floete4' undeclared (first use in this function)
7
foo.c:122:13: error: 'oktave4' undeclared (first use in this function)
8
foo.c:130:13: error: 'qufloete' undeclared (first use in this function)
9
foo.c:138:13: error: 'floete2' undeclared (first use in this function)
10
foo.c:143:13: error: 'oktave2' undeclared (first use in this function)
11
foo.c:151:13: error: 'terzfloete' undeclared (first use in this function)
12
foo.c:160:15: error: 'sesquialtera0' undeclared (first use in this function)
13
foo.c:164:17: error: 'sesquialtera1' undeclared (first use in this function)
14
foo.c:176:15: error: 'scharf0' undeclared (first use in this function)
15
foo.c:180:15: error: 'scharf1' undeclared (first use in this function)
16
foo.c:185:15: error: 'scharf2' undeclared (first use in this function)
17
foo.c:189:15: error: 'scharf3' undeclared (first use in this function)
18
foo.c:202:15: error: 'zimbel0' undeclared (first use in this function)
19
foo.c:208:15: error: 'zimbel1' undeclared (first use in this function)
20
foo.c:214:15: error: 'zimbel2' undeclared (first use in this function)
21
foo.c:219:15: error: 'zimbel3' undeclared (first use in this function)
22
foo.c:224:15: error: 'zimbel4' undeclared (first use in this function)
23
foo.c:228:15: error: 'zimbel5' undeclared (first use in this function)
24
foo.c:232:15: error: 'zimbel6' undeclared (first use in this function)
25
foo.c:235:15: error: 'zimbel7' undeclared (first use in this function)
26
foo.c:67:15: warning: variable 'buf_terz' set but not used [-Wunused-but-set-variable]
27
foo.c:66:15: warning: variable 'buf_qu' set but not used [-Wunused-but-set-variable]
28
foo.c:65:15: warning: variable 'buf_2' set but not used [-Wunused-but-set-variable]
29
foo.c:64:15: warning: variable 'buf_4' set but not used [-Wunused-but-set-variable]
30
foo.c:63:15: warning: variable 'buf_8' set but not used [-Wunused-but-set-variable]
31
foo.c:62:15: warning: variable 'buf_16' set but not used [-Wunused-but-set-variable]
Mit avr-gcc-6 -O2 übersetzt sieht der erzeugte Code ganz vernünftig aus.
Hie und da ließe sich ein Befehl sparen, aber auch mit
(Inline-)Assembler wird das nur marginal besser und nicht den erhofften
Fortschritt bringen.
Die ein_xx Variablen werden im Programm nicht verändert, dher kann ein
COmpiler deren Werte vor der while-Schleife laden und darf annehmen,
dass sie sich in die Schleife nicht mehr ändern. Vermultich willst du
irgendwann die Werte im Betrieb ändern, z.B. per Tastatur. Daher hab
ich ein memory-Clobber an den Anfang von do_channel gesetzt; ein
volatile würde hier etwas schlechteren Code machen, da manche Werte wie
ein_fl_okt2 nur einmal pro Durchlauf gelesen werden müssen. Das
memory-Clobber geht wohl auch 1x in der while-Schleife:
1
while(1)
2
{
3
asmvolatile("":::"memory");
4
do_channel(&channel[0]);
5
do_channel(&channel[1]);
6
}
Da ziemlich viel gelesen, entschieden und "gerechnet" wird, lässt sich
nicht mehr viel abkürzen am Code, und dass CodeVisionAVR krass besser
sein kann bleibt zu bewerten. Die einzige Stelle, wo evtl. merklich
Zeit eingespart werden kann, ist die Ausgabe nach SPI: Dies geschieht
nach dem Schema
1
wert-berechnen
2
wert-an-SPI-ausgeben
3
warten-auf-SPI
Mit folgendem Schema kann mehr parallelisiert werden, weil während des
Wartens bereits neue Werte berechnet werden können:
1
wert-berechnen + SPI-nudelt-vor-sich-hin
2
warten-auf-SPI
3
wert-an-SPI-ausgeben
Die Anwendung von amp scheint an der falschen Stelle zu erfogen, sollte
wohl aus dem if raus?
Mit einem kleinen Tweak wird der Code etwas besser
https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.md?r1=245206&r2=245205&pathrev=245206
Auf den gesamten Code spart das 2%. Das Pattern passt 16x, und
insgesamt belegt der o.g. Code ca 1.5KiB.
Wirklich viel ist an dem Code auch nicht mehr zu optimieren, das meiste
lässt sich wie gesagt durch Umstellen des Codes erreichen, so dass auch
während Warten auf SPI Code ausgeführt wird anstatt die Zeit zu
verplempern.