Forum: Mikrocontroller und Digitale Elektronik Inline-Assembler: was mache ich falsch?


von Maxim B. (max182)


Lesenswert?

Guten Abend,
ich lerne Inline-Assembler.
Leider bekomme ich nicht ganz, was ich möchte. Könnten Sie sagen, was 
anders zu machen ist?
1
static inline unsigned int __attribute__((always_inline))
2
sum32 (unsigned long fre, unsigned long phase)
3
{
4
                unsigned int ausgabe;
5
    ("add %A0, %A1"    "\n\t"
6
    "adc %B0, %B1"    "\n\t"
7
    "adc %C0, %C1"    "\n\t"
8
    "adc %D0, %D1"
9
         
10
    : "=&d"(ausgabe)
11
    : "r"(fre), "r"(phase));
12
    return ausgabe;
13
}
1
volatile unsigned long phase[3] = {0};
2
volatile unsigned long freq[3] = {47244640,47244641,47244642 };
3
4
PORTA = sum32(freq[0], phase[0]);

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.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

irgendwie fehlt mir da ein
1
__asm__ __volatile__

vor der Klammer. Außerdem gibt's keine clobber list.

Frißt der Compiler das überhaupt?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1
static inline  __attribute__((always_inline))
2
uint8_t sum32_byte3 (uint32_t fre, uint32_t phase)
3
{
4
    uint8_t ausgabe;
5
    __asm ("mov %0, %D1"
6
           : "=r"(ausgabe)
7
           : "r" (fre + phase));
8
    return ausgabe;
9
}

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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. ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Also ich hab schon verstanden, was der TO will.

Und gleich ob man es verstanden hat oder nicht, ist kein Grund hier 
rumzukotzen.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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"

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Heinz V. (heinz_v)


Lesenswert?

Inline Assembler in Altgriechisch?

von Maxim B. (max182)


Lesenswert?

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...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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).

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

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?

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

Maxim B. schrieb:
> Soll das bedeuten, daß ich einfach __uint800 schreibe und gleich
> automatisch korrekt Code für 100-Bytes-Typ bekomme?

nein

von Maxim B. (max182)


Lesenswert?

Ich habe gleich eine Probe gemacht:
volatile __uint24 phase = 0;
volatile __uint24 freq = 47001642;
AVR Studio sagt: Fehler.

von Carl D. (jcw2)


Lesenswert?

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.

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

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 = (unsigned int)(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
15
347:        buf_phase = (unsigned int)(phase[channel]>>8);
16
+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.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Leider fehlt im Code die Definition der beiden Variablen. Diese sind 
nicht zufällig mit "volatile" gespickt?

: Bearbeitet durch User
von Maxim B. (max182)


Lesenswert?

Ja, volatile. Das ist leider notwendig (da in Funktion).
Aber in Assembler würde ich nach
1
STS       0x0116,R18
2
STS       0x0117,R19
3
STS       0x0118,R20

für das Weitere entweder gleich r18-r20 benutzen, oder, falls diese 
Register anders gebraucht werden, schreiben:
1
mov r6,r18
2
mov r7,r19
3
mov r8,r20

statt
1
LDS       R6,0x0116
2
LDS       R7,0x0117
3
LDS       R8,0x0118
oder sogar, mit Rücksicht auf Folgendes gleich
1
mov r6,r19
2
mov r7,r20

und somit 4 Takte mit lds und clr gespart.

: Bearbeitet durch User
von Nico W. (nico_w)


Lesenswert?

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?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Maxim B. (max182)


Lesenswert?

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
3
#define SCHARF_REP0 12
4
#define SCHARF_REP1 24
5
#define SCHARF_REP2 36
6
#define SCHARF_REP3 48
7
#define ZIMBEL_REP0 12
8
#define ZIMBEL_REP1 18
9
#define ZIMBEL_REP2 24
10
#define ZIMBEL_REP3 30
11
#define ZIMBEL_REP4 36
12
#define ZIMBEL_REP5 42
13
#define ZIMBEL_REP6 48
14
#define MCP4922_GA 5
15
#define MCP4922_SH 4
16
#define KAN 7
17
#define BUF 6
18
19
#define SPI_DDR DDRB
20
#define SPI_PORT PORTB
21
#define SPI_SS PB0
22
#define SPI_MOSI PB3
23
#define SPI_SCK PB5
24
 __uint24 phase[2] = {0};
25
volatile __uint24 freq[2] = {184549,207150}; // Frequenz, Probewert
26
volatile unsigned char amp[] = {150,201}; // Lautstaerke, Probewert
27
volatile unsigned char note[] = {25,33}; // Tastennummer, Probewert
28
volatile unsigned char status[] = {1,1}; /* Kanalstatus , Probewert 0=aus,1=klang,2=ausklang */
29
30
/* Probewerte */
31
volatile unsigned char ein_gedackt16 = 1; 
32
volatile unsigned char ein_ged_pr8 = 1;    // 0=aus, 1=ged, 2=pr
33
volatile unsigned char ein_fl_okt4 = 1;   // 0=aus, 1=fl,2=okt
34
volatile unsigned char ein_fl_okt2 = 1;   // 0=aus, 1=fl, 2=okt
35
36
volatile unsigned char ein_quinte = 1; 
37
volatile unsigned char ein_terzfloete = 1;
38
volatile unsigned char ein_sesquialtera = 1;
39
volatile unsigned char ein_scharf = 1;
40
volatile unsigned char ein_zimbel = 1;
41
42
void spi_init(void)
43
{
44
  SPI_DDR |= (1<<SPI_SS) | (1<<SPI_MOSI) | (1<<SPI_SCK);
45
  SPCR = (1<<SPE) | (1<<MSTR);
46
  SPSR = (1<<SPI2X);
47
}
48
49
inline static void channel(unsigned char channel){
50
unsigned char temp; // fuer MSB von Phase
51
unsigned char tempton; // fuer Byte von Klang aus Flash
52
unsigned int summ = 0; // fuer Summe von allen sig., Buf. fuer DAC
53
unsigned int buf_phase_16; // fuer Berechnung von Obertoenen
54
unsigned int buf_phase_8; // fuer Berechnung von Obertoenen
55
unsigned int buf_phase_4; // fuer Berechnung von Obertoenen
56
unsigned int buf_phase_2; // fuer Berechnung von Obertoenen
57
unsigned int buf_phase_qu; // fuer Berechnung von Obertoenen
58
unsigned int buf_phase_terz; // fuer Berechnung von Obertoenen
59
60
unsigned char buf_16;
61
unsigned char buf_8;
62
unsigned char buf_4;
63
unsigned char buf_2;
64
unsigned char buf_qu;
65
unsigned char buf_terz;
66
unsigned char notebuf = note[channel];
67
68
    /* Phase summieren */
69
70
  phase[channel] += freq[channel];
71
  if(phase[channel]>=PHASE_MODUL << 16){
72
    phase[channel] -= (PHASE_MODUL << 16);    
73
  }
74
75
76
  buf_phase_16 = (unsigned int)(phase[channel]>>8);
77
  buf_phase_8 = buf_phase_16 + buf_phase_16;
78
  buf_phase_4 = buf_phase_8 + buf_phase_8;
79
  buf_phase_2 = buf_phase_4 + buf_phase_4;
80
  buf_phase_qu = buf_phase_8 + buf_phase_4;
81
  buf_phase_terz = buf_phase_2 + buf_phase_8;
82
83
  buf_16 = (unsigned char)(buf_phase_16>>8);
84
  buf_8 = (unsigned char)(buf_phase_8>>8);
85
  buf_4 = (unsigned char)(buf_phase_4>>8);
86
  buf_2 = (unsigned char)(buf_phase_2>>8);
87
  buf_qu = (unsigned char)(buf_phase_qu>>8);
88
  buf_terz = (unsigned char)(buf_phase_terz>>8); 
89
90
91
/* 16' 1Harm */
92
if(ein_gedackt16){
93
  
94
  tempton = gedackt16[buf_16];
95
  summ += tempton;
96
}
97
98
/* 8' 2 Harm */
99
100
if(ein_ged_pr8 == 1){
101
102
  tempton = gedackt8[buf_8];
103
  summ += tempton;
104
}
105
if(ein_ged_pr8 == 2){
106
107
  tempton = principal8[buf_8];
108
  summ += tempton;
109
  }
110
111
/* 4' 4 Harm */
112
113
if(ein_fl_okt4 == 1){
114
115
  tempton = floete4[buf_4];
116
  summ += tempton;
117
}
118
if(ein_fl_okt4 == 2){
119
120
  tempton = oktave4[buf_4];
121
  summ += tempton;
122
}
123
124
/* 2 2/3' 6 Harm */
125
126
if(ein_quinte){
127
128
  tempton = qufloete[buf_qu];
129
  summ += tempton;
130
}
131
132
/* 2' 8 Harm */
133
134
if(ein_fl_okt2 == 1){
135
136
  tempton = floete2[buf_2];
137
  summ += tempton;
138
}
139
if(ein_fl_okt2 == 2){
140
141
  tempton = oktave2[buf_2];
142
  summ += tempton;
143
}
144
145
/* 1 3/5' 10 Harm */
146
147
if(ein_terzfloete){
148
149
  tempton = terzfloete[buf_terz];
150
  summ += tempton;
151
}
152
153
/* Sesquialtera, 1-mal rep. C-c 8' 16' */
154
155
if(ein_sesquialtera){
156
  if(notebuf<SESQ_REP) {
157
  
158
    tempton = sesquialtera0[buf_4];
159
    }
160
    else {
161
    
162
      tempton = sesquialtera1[buf_8];
163
    }
164
  summ += tempton;
165
}
166
167
/* Scharf, 4-mal rep. c0,c1,c2  4' 8' 8' 16' */
168
if(ein_scharf){
169
  
170
  if(notebuf<SCHARF_REP0) {
171
  
172
   // 4'
173
  
174
    tempton = scharf0[buf_4];
175
  }
176
  else if(notebuf<SCHARF_REP1) {
177
    
178
    tempton = scharf1[buf_8];
179
  }
180
  else if(notebuf<SCHARF_REP2)
181
  {
182
    
183
    tempton = scharf2[buf_8];
184
  }
185
  else {
186
  
187
    tempton = scharf3[buf_16];
188
  }
189
  summ += tempton;
190
}
191
192
/* Zimbel 8-mal rep. c0,fs0,c1,fs1,c2,fs2,c3 2',4',4',8',8',16',16',16 */
193
if(ein_zimbel){
194
195
  if(notebuf<ZIMBEL_REP0) {
196
    
197
    
198
     // 2'
199
    
200
    tempton = zimbel0[buf_2];
201
  }
202
  else if(notebuf<ZIMBEL_REP1) {
203
    
204
     // 4'
205
    
206
    tempton = zimbel1[buf_4];
207
    }
208
  else if(notebuf<ZIMBEL_REP2) {
209
    
210
     // 4'
211
    
212
    tempton = zimbel2[buf_4];
213
    }
214
  else if(notebuf<ZIMBEL_REP3) {
215
     // 8'
216
    
217
    tempton = zimbel3[buf_8];
218
    }
219
  else if(notebuf<ZIMBEL_REP4) {
220
     // 8'
221
    
222
    tempton = zimbel4[buf_8];
223
    }
224
  else if(notebuf<ZIMBEL_REP5) {
225
    
226
    tempton = zimbel5[buf_16];
227
    }
228
  else if(notebuf<ZIMBEL_REP6) {
229
    
230
    tempton = zimbel6[buf_16];
231
    }
232
  else {
233
    tempton = zimbel7[buf_16];
234
    }
235
  summ += tempton;
236
237
  summ *= amp[channel];
238
}
239
240
/* Senden summ in DAC */
241
242
  temp = (unsigned char)(summ>>8);
243
  temp &= 0x0f;
244
  temp |= (1<<MCP4922_SH)|(1<<MCP4922_GA)|(1<<BUF);
245
  if(channel) temp |= (1<<KAN);
246
  SPI_PORT &= ~(1<<SPI_SS);
247
  SPDR = temp;
248
  while(!(SPSR & (1<<SPIF)));
249
  temp = (unsigned char)(summ);
250
  SPDR = temp;
251
  while(!(SPSR & (1<<SPIF)));
252
  SPI_PORT |= (1<<SPI_SS);
253
254
}
255
256
257
258
int main(){
259
DDRD = 0xff;
260
DDRB = 0x7f;
261
spi_init();
262
SPDR = 0xff; //dummi
263
264
while(1){
265
266
267
if((status[0]==1)||(status[0]==2)) {
268
  channel(0);
269
}
270
271
if((status[1]==1)||(status[1]==2)) {
272
  channel(1);
273
}
274
275
}
276
return 0;
277
}
Also, unmöglich, zwei Stimmen mit einem ATmega zu schaffen.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

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".

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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]

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ich bin den Code mal durchgegangen und hab die fehlenden Deklarationen 
geraten; vermutlich Tabellen für Hüllkurven im Falsh.  Anmerkungen unten
 
1
#include <avr/io.h>
2
3
#define XX extern const __flash uint8_t
4
5
XX gedackt16[];
6
XX gedackt8[];
7
XX principal8[];
8
XX floete4[];
9
XX oktave4[];
10
XX qufloete[];
11
XX floete2[];
12
XX oktave2[];
13
XX terzfloete[];
14
XX sesquialtera0[];
15
XX sesquialtera1[];
16
XX scharf0[];
17
XX scharf1[];
18
XX scharf2[];
19
XX scharf3[];
20
XX zimbel0[];
21
XX zimbel1[];
22
XX zimbel2[];
23
XX zimbel3[];
24
XX zimbel4[];
25
XX zimbel5[];
26
XX zimbel6[];
27
XX zimbel7[];
28
29
#define PHASE_MODUL 240UL
30
#define SESQ_REP 12 // Rep. Punkt Sesquialtera, c0
31
#define SCHARF_REP0 12
32
#define SCHARF_REP1 24
33
#define SCHARF_REP2 36
34
#define SCHARF_REP3 48
35
#define ZIMBEL_REP0 12
36
#define ZIMBEL_REP1 18
37
#define ZIMBEL_REP2 24
38
#define ZIMBEL_REP3 30
39
#define ZIMBEL_REP4 36
40
#define ZIMBEL_REP5 42
41
#define ZIMBEL_REP6 48
42
#define MCP4922_GA 5
43
#define MCP4922_SH 4
44
#define KAN 7
45
#define BUF 6
46
47
#define SPI_DDR DDRB
48
#define SPI_PORT PORTB
49
#define SPI_SS PB0
50
#define SPI_MOSI PB3
51
#define SPI_SCK PB5
52
53
//__uint24 phase[2] = {0};
54
//volatile __uint24 freq[2] = {184549,207150}; // Frequenz, Probewert
55
//volatile unsigned char amp[] = {150,201}; // Lautstaerke, Probewert
56
//volatile unsigned char note[] = {25,33}; // Tastennummer, Probewert
57
//volatile unsigned char status[] = {1,1}; /* Kanalstatus , Probewert 0=aus,1=klang,2=ausklang */
58
59
typedef struct
60
{
61
    // Phase, Frequenz
62
    __uint24 phase, freq;
63
64
    // Lautstaerke, Tastennummer, Kanalstatus
65
    uint8_t amp, note, status;
66
} channel_t;
67
68
channel_t channel[2] =
69
{
70
    { 0, 184549, 150, 25, 1 },
71
    { 0, 207150, 201, 33, 1 }
72
};
73
74
#define volatile
75
76
/* Probewerte */
77
volatile uint8_t ein_gedackt16 = 1; 
78
volatile uint8_t ein_ged_pr8 = 1;    // 0=aus, 1=ged, 2=pr
79
volatile uint8_t ein_fl_okt4 = 1;   // 0=aus, 1=fl,2=okt
80
volatile uint8_t ein_fl_okt2 = 1;   // 0=aus, 1=fl, 2=okt
81
82
volatile uint8_t ein_quinte = 1; 
83
volatile uint8_t ein_terzfloete = 1;
84
volatile uint8_t ein_sesquialtera = 1;
85
volatile uint8_t ein_scharf = 1;
86
volatile uint8_t ein_zimbel = 1;
87
88
#undef volatile
89
90
static void spi_init (void)
91
{
92
    SPI_DDR |= (1 << SPI_SS) | (1 << SPI_MOSI) | (1 << SPI_SCK);
93
    SPCR = (1 << SPE) | (1 << MSTR);
94
    SPSR = (1 << SPI2X);
95
}
96
97
static inline __attribute__((always_inline))
98
void do_channel (channel_t *ch)
99
{
100
    asm volatile ("" ::: "memory");
101
102
    if (ch->status == 0)
103
        return;
104
105
    uint16_t summ = 0; // fuer Summe von allen sig., Buf. fuer DAC
106
107
    /* Phase summieren */
108
109
    ch->phase += ch->freq;
110
    if (ch->phase >= PHASE_MODUL << 16)
111
    {
112
        ch->phase -= PHASE_MODUL << 16;
113
    }
114
115
    // fuer Berechnung von Obertoenen
116
    uint16_t buf_phase_16 = (unsigned int) (ch->phase >> 8);
117
    uint16_t buf_phase_8 = buf_phase_16 + buf_phase_16;
118
    uint16_t buf_phase_4 = buf_phase_8 + buf_phase_8;
119
    uint16_t buf_phase_2 = buf_phase_4 + buf_phase_4;
120
    uint16_t buf_phase_qu = buf_phase_8 + buf_phase_4;
121
    uint16_t buf_phase_terz = buf_phase_2 + buf_phase_8;
122
123
    uint8_t notebuf = ch->note;
124
    uint8_t buf_16 = (uint8_t) (buf_phase_16 >> 8);
125
    uint8_t buf_8 = (uint8_t) (buf_phase_8 >> 8);
126
    uint8_t buf_4 = (uint8_t) (buf_phase_4 >> 8);
127
    uint8_t buf_2 = (uint8_t) (buf_phase_2 >> 8);
128
    uint8_t buf_qu = (uint8_t) (buf_phase_qu >> 8);
129
    uint8_t buf_terz = (uint8_t) (buf_phase_terz >> 8); 
130
131
132
    /* 16' 1Harm */
133
    if (ein_gedackt16)
134
        summ += gedackt16[buf_16];
135
136
    /* 8' 2 Harm */
137
138
    if (ein_ged_pr8 == 1)
139
        summ += gedackt8[buf_8];
140
    
141
    if (ein_ged_pr8 == 2)
142
        summ += principal8[buf_8];
143
144
    /* 4' 4 Harm */
145
146
    if (ein_fl_okt4 == 1)
147
        summ += floete4[buf_4];
148
149
    if (ein_fl_okt4 == 2)
150
        summ += oktave4[buf_4];
151
152
    /* 2 2/3' 6 Harm */
153
154
    if (ein_quinte)
155
        summ += qufloete[buf_qu];
156
157
    /* 2' 8 Harm */
158
159
    if (ein_fl_okt2 == 1)
160
        summ += floete2[buf_2];
161
162
    if (ein_fl_okt2 == 2)
163
        summ += oktave2[buf_2];
164
165
    /* 1 3/5' 10 Harm */
166
167
    if (ein_terzfloete)
168
        summ += terzfloete[buf_terz];
169
170
    /* Sesquialtera, 1-mal rep. C-c 8' 16' */
171
172
    if (ein_sesquialtera)
173
    {
174
        if (notebuf < SESQ_REP)
175
            summ += sesquialtera0[buf_4];
176
        else
177
            summ += sesquialtera1[buf_8];
178
    }
179
180
    /* Scharf, 4-mal rep. c0,c1,c2  4' 8' 8' 16' */
181
    if (ein_scharf)
182
    {
183
        if (notebuf < SCHARF_REP0)
184
        {
185
            // 4'
186
            summ += scharf0[buf_4];
187
        }
188
        else if (notebuf < SCHARF_REP1)
189
        {
190
            summ += scharf1[buf_8];
191
        }
192
        else if (notebuf < SCHARF_REP2)
193
        {
194
            summ += scharf2[buf_8];
195
        }
196
        else
197
        {
198
            summ += scharf3[buf_16];
199
        }
200
    }
201
202
    /* Zimbel 8-mal rep. c0,fs0,c1,fs1,c2,fs2,c3 2',4',4',8',8',16',16',16 */
203
    if (ein_zimbel)
204
    {
205
        if (notebuf < ZIMBEL_REP0)
206
        {
207
            // 2'
208
            summ += zimbel0[buf_2];
209
        }
210
        else if (notebuf < ZIMBEL_REP1)
211
        {
212
            // 4'
213
            summ += zimbel1[buf_4];
214
        }
215
        else if (notebuf < ZIMBEL_REP2)
216
        {
217
            // 4'
218
            summ += zimbel2[buf_4];
219
        }
220
        else if (notebuf < ZIMBEL_REP3)
221
        {
222
            // 8'
223
            summ += zimbel3[buf_8];
224
        }
225
        else if (notebuf < ZIMBEL_REP4)
226
        {
227
            // 8'
228
            summ += zimbel4[buf_8];
229
        }
230
        else if (notebuf < ZIMBEL_REP5)
231
        {
232
            summ += zimbel5[buf_16];
233
        }
234
        else if (notebuf < ZIMBEL_REP6)
235
        {
236
            summ += zimbel6[buf_16];
237
        }
238
        else
239
        {
240
            summ += zimbel7[buf_16];
241
        }
242
243
        summ *= ch->amp;
244
    }
245
246
    /* Senden summ in DAC */
247
248
    uint8_t spdr = (uint8_t) (summ >> 8);
249
    spdr &= 0x0f;
250
    spdr |= (1 << MCP4922_SH) | (1 << MCP4922_GA) | (1 << BUF);
251
    if (ch == &channel[1])
252
        spdr |= (1 << KAN);
253
    SPI_PORT &= ~(1 << SPI_SS);
254
    SPDR = spdr;
255
    while (!(SPSR & (1 << SPIF)))
256
        ;
257
258
    spdr = (uint8_t) summ;
259
    SPDR = spdr;
260
    while (!(SPSR & (1 << SPIF)))
261
        ;
262
    SPI_PORT |= (1 << SPI_SS);
263
}
264
265
__attribute__((OS_main))
266
int main (void)
267
{
268
    DDRD = 0xff;
269
    DDRB = 0x7f;
270
    spi_init();
271
    SPDR = 0xff; //dummi
272
273
    while (1)
274
    {
275
        do_channel (&channel[0]);
276
        do_channel (&channel[1]);
277
    }
278
    return 0;
279
}
 
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
        asm volatile ("" ::: "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?

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.