Forum: Compiler & IDEs AVR Studio 4.19: Daten dynamisch erzeugen


von Horst M. (horst)


Lesenswert?

Hallo zusammen,

sorry für den Threadtitel, mir ist nix anderes eingefallen...

Ich habe hier eine Tabelle und möchte daraus Datendefinitionen erzeugen.
1
.equ A0=2
2
.equ A1=5
3
.equ A2=7
4
.equ A3=4
5
.equ A4=1
6
.equ A5=0
7
.equ A6=3
8
.equ A7=8
9
.equ A8=9
10
.equ A9=6

Dazu habe ich zunächst folgendes Macro definiert:
1
#define b(x) A##x
2
.macro c
3
  .db b(@0),@1
4
.endmacro

Solange ich das statisch verwende, klappt es wie vorgesehen.
1
  c 0,0
2
  c 1,0
3
  c 2,0
4
5
Listfile:
6
000000 0002        c 0,0
7
000001 0005        c 1,0
8
000002 0007        c 2,0

Man sieht, es wurden die Definitionen aus den Tabellenplätzen 0 bis 2 
(2,5 und 7) eingesetzt.

Nun hätte ich das aber gern flexibel, d.h. relativ zu einer 
Tabellenstartposition.

So funktioniert's schonmal nicht:
1
.set d=0
2
  c d,0
3
  c d+1,0
4
  c d+2,0
5
6
Errors:
7
Tabtest.asm(27): error: Undefined symbol: Ad
8
Tabtest.asm(35): info: macro 'c' called here
9
Tabtest.asm(27): error: Undefined symbol: Ad
10
Tabtest.asm(36): info: macro 'c' called here
11
Tabtest.asm(27): error: Undefined symbol: Ad
12
Tabtest.asm(37): info: macro 'c' called here

Aber selbst so geht's nicht wie benötigt:
1
  c 0,0
2
  c 0+1,0
3
  c 0+2,0
4
5
Listfile:
6
000000 0002        c 0,0
7
000001 0003        c 0+1,0
8
000002 0004        c 0+2,0

Es wird stets die Definition aus Tabellenplatz 0 verwendet und danach 
noch der Offset dazuaddiert.

OK, also habe ich's auch mal so probiert:
1
#define A0 2
2
#define A1 5
3
#define A2 7
4
#define A3 4
5
#define A4 1
6
#define A5 0
7
#define A6 3
8
#define A7 8
9
#define A8 9
10
#define A9 6
11
12
#define b(x) A##x
13
.macro c
14
  .db b(@0),@1
15
.endmacro
16
17
  c 0,0
18
  c 1,0
19
  c 2,0
20
21
Errors:
22
Tabtest.asm(27): error: Invalid symbol in this context: A0(11)
23
Tabtest.asm(30): info: macro 'c' called here
24
Tabtest.asm(27): error: Invalid symbol in this context: A1(11)
25
Tabtest.asm(31): info: macro 'c' called here
26
Tabtest.asm(27): error: Invalid symbol in this context: A2(11)
27
Tabtest.asm(32): info: macro 'c' called here

Hier geht schon die statische Definition in die Hose.

Ziel soll sein, mit dem Macro die Daten der Tabelle in gewisser Weise 
dynamisch zu verwenden, z.B.:
1
.set d=0
2
  c d,0
3
  c d+1,0
4
  c d+2,0
5
.set d=3
6
  c d,1
7
  c d+1,1
8
  c d+2,1
9
10
Oder noch besser:
11
12
.macro e
13
.set d=@0
14
  c d,@1
15
  c d+1,@1
16
  c d+2,@1
17
.endmacro
18
19
  e 0,0
20
  e 3,1

Kann ich das Ziel überhaupt mit den Mitteln des AVRASM 2.1.42 erreichen?

von c-hater (Gast)


Lesenswert?

Horst M. schrieb:

> Ich habe hier eine Tabelle und möchte daraus Datendefinitionen erzeugen.

Also ich habe mir das Zeug wirklich mehrmals aufmerksam durchgelesen, 
aber mir ist immer noch nicht ganz klar, was du eigentlich genau 
erreichen willst. Immer wenn ich glaubte, verstanden zu haben, was du 
beabsichtigst, war da wieder etwas, was dagegen sprach.

Vermutlich versuchst du dein eigentliches Problem von hinten durch die 
Brust in's Auge zu lösen. Oder anders ausgedrückt: vermutlich falscher 
Ansatz.

Aber unabhängig davon hast du wohl Probleme mit dem Verständnis dieses 
Designtime-Gelumpes. Dazu muss man mehrere Sachen wissen.

Das Zeug ist längst nicht so einfach zu beherrschen wie etwa der 
C-Präprozessor (und da allein lauern ja schon genug Fallstricke). 
Immerhin kannst du aber, solange du dich auf Macros im C-Stil 
beschränkst (und dazu gehört z.B. weder .equ noch .set oder .macro), 
darauf verlassen, dass sich der Kram auch tatsächlich wie der 
C-Präprozessor verhält. Sprich: reine Textersetzung ist und den 
entsprechenden Gesetzen folgt.

Deutlich schwieriger zu verstehen ist der Assembler-Makrokram. Dabei 
handelt sich NICHT nur um reine Textersetzung, hier wird u.U. 
tatsächlich schon zur Designzeit gerechnet. Das macht diese 
Macro-Sprache sehr viel mächtiger als einen C-Präprozessor, aber leider 
auch nochmal sehr viel fehlerträchtiger. Das scheinbare Chaos entsteht 
durch zwei Sachen. Erstens ist es nicht immer ganz leicht zu erkennen, 
wann genau der Interpreter zur Evaluation greift, also den Wert von 
Ausdrücken schon während des Übersetzens ermittelt. Und zweitens handelt 
es sich um einen Zweipass-Assembler, der ganze Kram wird also zweimal 
interpretiert und zusammen mit erstens sorgt das dafür, dass 
unterschiedliche Ergebnisse im ersten und zweiten Lauf herauskommen 
können. Was in aller Regel Müll ergibt, weil es keinerlei Sprachmittel 
gibt, um im ersten Lauf gezielt etwas andere zu tun als im zweiten.

Und richtig spannend und bezüglich der herrschenden Gesetzmäßigkeiten 
kaum noch überschaubar ist es, wenn du die beiden Sachen auch noch 
mischst. Was wohl auch der Grund ist, dass der Kram nirgendwo 
systematisch logisch konsistent dokumentiert ist. Hier haben ganz 
offensichtlich die Entwickler selber ihr Werk schon nicht mehr 
überblickt...

> Kann ich das Ziel überhaupt mit den Mitteln des AVRASM 2.1.42 erreichen?

Tja, wenn ich verstanden hätte, was dein eigentliches Ziel ist, hätte 
ich dir die Frage vielleicht sogar beantworten können...

von Horst M. (horst)


Lesenswert?

Also gut, ich fange nochmal an.

Gehen wir von zehn Instanzen aus, die in Gruppen gesteuert werden 
sollen.
Das ist natürlich nur ein Beispiel, real geht's um ein, zwei Dutzend mal 
soviel.

Solange die Instanzen z.B. stetig aufsteigend adressiert werden können, 
ist das simpel.
1
--+
2
 1|-> Eins
3
 2|-> Zwei
4
 3|-> Drei
5
 4|-> Vier
6
 5|-> Fünf
7
 6|-> Sechs
8
 7|-> Sieben
9
 8|-> Acht
10
 9|-> Neun
11
10|-> Zehn
12
--+

Es wird ein Macro definiert, mit dem für 5 aufeinanderfolgende Instanzen 
ein Parameter definiert wird.
1
.macro group5
2
  .db @0,@1
3
  .db @0+1,@1
4
  .db @0+2,@1
5
  .db @0+3,@1
6
  .db @0+4,@1
7
.endmacro

Damit lassen sich leicht verschiedene Definitionen erzeugen.
1
variant1:
2
  group5 1,0  ;weise den Instanzen 1...5 den Parameter 0 zu
3
  group5 6,1  ;weise den Instanzen 6...10 den Parameter 1 zu
4
5
variant2:
6
  group5 1,3  ;weise den Instanzen 1...5 den Parameter 3 zu
7
  group5 6,2  ;weise den Instanzen 6...10 den Parameter 2 zu
8
9
variant3:
10
  group5 3,4  ;weise den Instanzen 3...7 den Parameter 4 zu
11
12
Listfile:
13
                 variant1:
14
000000 0001
15
000001 0002
16
000002 0003
17
000003 0004
18
000004 0005        group5 1,0
19
000005 0106
20
000006 0107
21
000007 0108
22
000008 0109
23
000009 010a        group5 6,1
24
                 
25
                 variant2:
26
00000a 0301
27
00000b 0302
28
00000c 0303
29
00000d 0304
30
00000e 0305        group5 1,3
31
00000f 0206
32
000010 0207
33
000011 0208
34
000012 0209
35
000013 020a        group5 6,2
36
                 
37
                 variant3:
38
000014 0403
39
000015 0404
40
000016 0405
41
000017 0406
42
000018 0407        group5 3,4

Ich frage nun nach einer Möglichkeit, die Instanzen weiterhin in der 
gezeigten Form zu steuern (also gruppenweise aufsteigend), obwohl sie 
NICHT mehr stetig addressiert werden können.
Die Zuordnung der Adresse zur Instanz soll beim Assemblieren definiert 
werden, nicht zur Laufzeit.
1
--+
2
 3|-> Eins
3
 6|-> Zwei
4
 8|-> Drei
5
 5|-> Vier
6
 2|-> Fünf
7
 1|-> Sechs
8
 4|-> Sieben
9
 9|-> Acht
10
10|-> Neun
11
 7|-> Zehn
12
--+

Die Adressen der Instanzen werden in einer Art Tabelle definiert.
1
.equ A1=3
2
.equ A2=6
3
.equ A3=8
4
.equ A4=5
5
.equ A5=2
6
.equ A6=1
7
.equ A7=4
8
.equ A8=9
9
.equ A9=10
10
.equ A10=7

Für die Umsetzung der "logischen" in die "physische" Adresse der Instanz 
wird das folgende Define benutzt.
1
#define b(x) A##x

Damit kann zumindest bei statischen Definitionen mit der logischen 
Adresse gearbeitet werden.
1
.macro c
2
  .db b(@0),@1
3
.endmacro
4
5
variant1:
6
  c 1,0
7
  c 2,0
8
  c 3,0
9
  c 4,0
10
  c 5,0
11
  c 6,1
12
  c 7,1
13
  c 8,1
14
  c 9,1
15
  c 10,1
16
17
variant2:
18
  c 1,3
19
  c 2,3
20
  c 3,3
21
  c 4,3
22
  c 5,3
23
  c 6,2
24
  c 7,2
25
  c 8,2
26
  c 9,2
27
  c 10,2
28
29
variant3:
30
  c 3,4
31
  c 4,4
32
  c 5,4
33
  c 6,4
34
  c 7,4
35
36
Listfile:
37
                 variant1:
38
000000 0003        c 1,0
39
000001 0006        c 2,0
40
000002 0008        c 3,0
41
000003 0005        c 4,0
42
000004 0002        c 5,0
43
000005 0101        c 6,1
44
000006 0104        c 7,1
45
000007 0109        c 8,1
46
000008 010a        c 9,1
47
000009 0107        c 10,1
48
                 
49
                 variant2:
50
00000a 0303        c 1,3
51
00000b 0306        c 2,3
52
00000c 0308        c 3,3
53
00000d 0305        c 4,3
54
00000e 0302        c 5,3
55
00000f 0201        c 6,2
56
000010 0204        c 7,2
57
000011 0209        c 8,2
58
000012 020a        c 9,2
59
000013 0207        c 10,2
60
                 
61
                 variant3:
62
000014 0408        c 3,4
63
000015 0405        c 4,4
64
000016 0402        c 5,4
65
000017 0401        c 6,4
66
000018 0404        c 7,4

Das Ziel ist aber, wie im Beispiel ganz oben gruppenweise zu arbeiten.
1
.macro group5
2
  .db b(@0),@1
3
  .db b(@0+1),@1
4
  .db b(@0+2),@1
5
  .db b(@0+3),@1
6
  .db b(@0+4),@1
7
.endmacro
8
9
variant1:
10
  group5 1,0
11
  group5 6,1
12
13
variant2:
14
  group5 1,3
15
  group5 6,2
16
17
variant3:
18
  group5 3,4
19
20
Listfile:
21
                 variant1:
22
000000 0003
23
000001 0004
24
000002 0005
25
000003 0006
26
000004 0007        group5 1,0
27
000005 0101
28
000006 0102
29
000007 0103
30
000008 0104
31
000009 0105        group5 6,1
32
                 
33
                 variant2:
34
00000a 0303
35
00000b 0304
36
00000c 0305
37
00000d 0306
38
00000e 0307        group5 1,3
39
00000f 0201
40
000010 0202
41
000011 0203
42
000012 0204
43
000013 0205        group5 6,2
44
                 
45
                 variant3:
46
000014 0408
47
000015 0409
48
000016 040a
49
000017 040b
50
000018 040c        group5 3,4

So funktioniert's schonmal nicht (und es ist auch nicht wirklich 
überraschend).
Es wird immer die beim Aufruf des group5-Macros angegebene logische 
Adresse in die physische Adresse umgewandelt und danach der im Macro 
jeweils angegebene Offset addiert.

Zum Beispiel:
1
variant3:
2
  group5 3,4
3
4
000014 0408  ;logische Adresse 3 --> physische Adresse 8 +0
5
000015 0409  ;logische Adresse 3 --> physische Adresse 8 +1
6
000016 040a  ;logische Adresse 3 --> physische Adresse 8 +2
7
000017 040b  ;logische Adresse 3 --> physische Adresse 8 +3
8
000018 040c  ;logische Adresse 3 --> physische Adresse 8 +4

Erforderlich ist aber, daß der Offset zur logischen Adresse addiert und 
das Ergebnis in die physische Adresse umgesetzt wird.

Richtig wäre also:
1
000014 0408    ;logische Adresse 3 +0 --> physische Adresse 8
2
000015 0405    ;logische Adresse 3 +1 --> physische Adresse 5
3
000016 0402    ;logische Adresse 3 +2 --> physische Adresse 2
4
000017 0401    ;logische Adresse 3 +3 --> physische Adresse 1
5
000018 0404    ;logische Adresse 3 +4 --> physische Adresse 4

Ich habe noch ein paar andere Konstrukte getestet.
Etwa:
1
.macro group5
2
  .db A@0,@1
3
  .db A@0+1,@1
4
  .db A@0+2,@1
5
  .db A@0+3,@1
6
  .db A@0+4,@1
7
.endmacro

Geht also auch ohne Define usw. (das "A" und der Macro-Parameter @0 
werden verknüpft), ergibt aber dasselbe wie oben.

1
.macro b
2
  .db A@0,@1
3
.endmacro
4
5
.macro group5
6
  .set c=@0
7
  b c,@1
8
  .set c=c+1
9
  b c,@1
10
  .set c=c+1
11
  b c,@1
12
  .set c=c+1
13
  b c,@1
14
  .set c=c+1
15
  b c,@1
16
.endmacro
17
18
Ergibt Fehler wie:
19
Tabtest.asm(29): error: Undefined symbol: Ac
20
Tabtest.asm(34): info: macro 'b' called here
21
Tabtest.asm(46): info: macro 'group5' called here
22
Tabtest.asm(29): error: Undefined symbol: Ac
23
Tabtest.asm(36): info: macro 'b' called here
24
Tabtest.asm(46): info: macro 'group5' called here

"c" wird hier offensichtlich literal verwendet und nicht als Variable.

Die grundsätzliche Frage ist m.E., ob es überhaupt einen Weg gibt, 
zunächst einen Ausdruck wie "@0+1" oder "c+1" numerisch zu berechnen und 
dann mit dem Ergebnis ein neues Symbol zu basteln, das vom Assembler 
entsprechend verarbeitet werden kann.

von Dr. Sommer (Gast)


Lesenswert?

Falls ich richtig verstanden habe, was du willst, und du es mit dem 
Assembler nicht hinbekommst, hier ein Vorschlag in C++:
1
#include <cstdint>
2
3
static constexpr uint8_t addresses [] = { 3, 6, 8, 5, 2, 1, 4, 9, 10, 7 };
4
5
#define group5(a,b) addresses[a-1],b,  addresses[a],b, addresses[a+1],b, addresses[a+2],b, addresses[a+3],b
6
7
extern "C" constexpr uint8_t variant1 [] = {  group5 (1, 0),
8
                        group5 (6, 1) };
9
10
extern "C" constexpr uint8_t variant2 [] = {  group5 (1, 3),
11
                        group5 (6, 2) };
12
13
14
extern "C" constexpr uint8_t variant3 [] = {  group5 (3, 4) };
ist auch nicht wirklich schön, aber dafür kurz. Du müsstest vom 
Assembler aus auf die Symbole variant1-variant3 problemlos zugreifen 
können, wenn du den Code einfach in eine .cpp -Datei packst und 
mitkompilierst.

von c-hater (Gast)


Lesenswert?

Horst M. schrieb:

> Die grundsätzliche Frage ist m.E., ob es überhaupt einen Weg gibt,
> zunächst einen Ausdruck wie "@0+1" oder "c+1" numerisch zu berechnen und
> dann mit dem Ergebnis ein neues Symbol zu basteln, das vom Assembler
> entsprechend verarbeitet werden kann.

Wenn das das grundsätzliche Problem sein soll (ich bin nicht sicher, ob 
es das ist): Ja, das geht.

Du musst halt nur den Interpreter dazu zwingen den Ausdruck zu 
evaluieren. Das ist leicht. Du musst ihn nur in einer .set-Direktive 
verwenden und dann das mittels .set zugewiesene Symbol in der Bedingung 
einer .if-Direktive verwenden. Damit zwingst du den Interpreter zur 
Evaluierung des Symbols.

Ab diesem Moment steht das in .set zugewiesene Symbol nicht mehr als 
reine Textersetzung zur Verfügung, sondern als fertig berechneter Wert.

Wenn du jetzt noch dafür sorgst, dass im zweiten Assemblerlauf auch 
wieder genau derselbe Wert herauskommt, sollte nichts mehr schief gehen. 
Das ist eigentlich ziemlich leicht, solange man nicht mit Adressen des 
erzeugten Codes hantiert...

von Horst M. (horst)


Lesenswert?

Mittlerweile hab ich's hinbekommen, allerdings nicht mit irgendwelchen 
String-Substituierungen oder der Zusammensetzung von neuen Symbolen.

Ich verwende ein Monster-Macro (OK, im Beispiel noch nicht ganz so 
monströs, aber die eigentliche Anwendung hat wie erwähnt einen deutlich 
größeren Wertebereich), um jeden Eingangswert in den entsprechenden 
Ausgangswert umzusetzen.
Das ist weder cool noch effizient und skaliert auch nicht wirklich, aber 
es tut genau das, was getan werden soll.
1
.equ A1=3
2
.equ A2=6
3
.equ A3=8
4
.equ A4=5
5
.equ A5=2
6
.equ A6=1
7
.equ A7=4
8
.equ A8=9
9
.equ A9=10
10
.equ A10=7
11
.macro a
12
  .if @0==1
13
  .set _x=A1
14
  .elif @0==2
15
  .set _x=A2
16
  .elif @0==3
17
  .set _x=A3
18
  .elif @0==4
19
  .set _x=A4
20
  .elif @0==5
21
  .set _x=A5
22
  .elif @0==6
23
  .set _x=A6
24
  .elif @0==7
25
  .set _x=A7
26
  .elif @0==8
27
  .set _x=A8
28
  .elif @0==9
29
  .set _x=A9
30
  .elif @0==10
31
  .set _x=A10
32
  .else
33
  .error "Out of range"
34
  .endif
35
.endmacro
36
37
.macro b
38
  a @0
39
  .db _x,@1
40
.endmacro
41
42
.macro group5
43
  b @0,@1
44
  b @0+1,@1
45
  b @0+2,@1
46
  b @0+3,@1
47
  b @0+4,@1
48
.endmacro
49
50
variant1:
51
  group5 1,0
52
  group5 6,1
53
54
variant2:
55
  group5 1,3
56
  group5 6,2
57
58
variant3:
59
  group5 3,4
60
61
Listfile:
62
                 variant1:
63
000000 0003
64
000001 0006
65
000002 0008
66
000003 0005
67
000004 0002        group5 1,0
68
000005 0101
69
000006 0104
70
000007 0109
71
000008 010a
72
000009 0107        group5 6,1
73
                 
74
                 variant2:
75
00000a 0303
76
00000b 0306
77
00000c 0308
78
00000d 0305
79
00000e 0302        group5 1,3
80
00000f 0201
81
000010 0204
82
000011 0209
83
000012 020a
84
000013 0207        group5 6,2
85
                 
86
                 variant3:
87
000014 0408
88
000015 0405
89
000016 0402
90
000017 0401
91
000018 0404        group5 3,4

Ist ein bißchen retro, weil man damit wieder auf den Assembler warten 
muß.
Die Erzeugung von 25000 Bytes mit dem obigen Beispiel läuft auf einem
3 GHz-PC ca. 10 Sekunden.
Bei der eigentlichen Anwendung habe ich für ca. 45 KByte Code lockere
3 Minuten warten müssen...

Ob die Ausgangswerte vorher mit .equ definiert oder im Macro direkt 
zugewiesen werden, hat auf die Laufzeit keinen Einfluß.
Ich habe auch mal getestet, ob es mit einem binären Verfahren - sowas 
wie
1
.if @0<8
2
 .if @0<4
3
  .if @0<2
4
   .if @0==1
5
    .set _x=A1
6
   .endif
7
  .elif @0==2
8
   .set _x=A2
9
  .else
10
   .set _x=A3
11
  .endif
12
 .elif @0<6
13
  .if @0==4
14
   .set _x=A4
15
  .else
16
   .set _x=A5
17
  .endif
18
 .elif @0==6
19
  .set _x=A6
20
 .else
21
  .set _x=A7
22
 .endif
23
.else
24
...
beschleunigt werden kann - das Gegenteil war der Fall.
Der Assembler kommt offensichtlich mit dem Straight-Forward-Ansatz am 
besten zurecht.

Ein bißchen schneller läuft's, wenn das Macro zur Umsetzung nochmal 
unterteilt wird.
1
.macro a_1
2
  .if @0==1
3
  .set _x=A1
4
  .elif @0==2
5
  .set _x=A2
6
  .elif @0==3
7
  .set _x=A3
8
  .elif @0==4
9
  .set _x=A4
10
  .else
11
  .set _x=A5
12
  .endif
13
.endmacro
14
15
.macro a_2
16
  .if @0==6
17
  .set _x=A6
18
  .elif @0==7
19
  .set _x=A7
20
  .elif @0==8
21
  .set _x=A8
22
  .elif @0==9
23
  .set _x=A9
24
  .else
25
  .set _x=A10
26
  .endif
27
.endmacro
28
29
.macro b
30
  .if @0<1 || @0>10
31
  .error "Out of range."
32
  .endif
33
  .if @0<6
34
  a_1 @0
35
  .else
36
  a_2 @0
37
  .endif
38
  .db _x,@1
39
.endmacro

Mit diesem Prinzip läuft die Erzeugung von 45 KByte Code mit dem 
Originalquelltext auf meinem Rechner knapp eine Minute.
Immer noch recht zäh, aber ist halt so...

von Oliver S. (oliverso)


Lesenswert?

Ungefähr an diesem Punkt müssen die Computerianer gewesen sein, als sie 
im letzten Jahrtausend den Entschluß gefasst haben, höhere 
Programmiersprachen zu erfinden.

Oliver

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.