Forum: Compiler & IDEs avr-gcc nutzt kein "Store Indirect and Post-Inc."


von Tuxpilot (Gast)


Lesenswert?

Hallo, ich verstehe gerade den avr-gcc nicht.

Ich habe einen Attiny841 und möchte folgendes ausführen:
1
// highest_byte_in_value wird berechnt, ist dann 0 bis 3.
2
switch (highest_byte_in_value) {
3
    case 3 : base64_buffer[current_buffer_element++] = value >> 24;
4
    case 2 : base64_buffer[current_buffer_element++] = value >> 16;
5
    case 1 : base64_buffer[current_buffer_element++] = value >> 8;
6
    default : base64_buffer[current_buffer_element] = value;
7
}

Ergebnis ist:
1
    switch (highest_byte_in_value) {
2
 342:  84 0f         add  r24, r20
3
 344:  91 1d         adc  r25, r1
4
 346:  43 e0         ldi  r20, 0x03  ; 3
5
 348:  95 95         asr  r25
6
 34a:  87 95         ror  r24
7
 34c:  4a 95         dec  r20
8
 34e:  e1 f7         brne  .-8        ; 0x348 <__stack+0x49>
9
 350:  82 30         cpi  r24, 0x02  ; 2
10
 352:  91 05         cpc  r25, r1
11
 354:  61 f0         breq  .+24       ; 0x36e <__stack+0x6f>
12
 356:  83 30         cpi  r24, 0x03  ; 3
13
 358:  91 05         cpc  r25, r1
14
 35a:  19 f0         breq  .+6        ; 0x362 <__stack+0x63>
15
 35c:  01 97         sbiw  r24, 0x01  ; 1
16
 35e:  99 f4         brne  .+38       ; 0x386 <__stack+0x87>
17
 360:  0c c0         rjmp  .+24       ; 0x37a <__stack+0x7b>
18
        case 3 : base64_buffer[current_buffer_element++] = value >> 24;
19
 362:  ae 2f         mov  r26, r30
20
 364:  b0 e0         ldi  r27, 0x00  ; 0
21
 366:  ac 5d         subi  r26, 0xDC  ; 220
22
 368:  be 4f         sbci  r27, 0xFE  ; 254
23
 36a:  3c 93         st  X, r19
24
 36c:  ef 5f         subi  r30, 0xFF  ; 255
25
        case 2 : base64_buffer[current_buffer_element++] = value >> 16;
26
 36e:  ae 2f         mov  r26, r30
27
 370:  b0 e0         ldi  r27, 0x00  ; 0
28
 372:  ac 5d         subi  r26, 0xDC  ; 220
29
 374:  be 4f         sbci  r27, 0xFE  ; 254
30
 376:  2c 93         st  X, r18
31
 378:  ef 5f         subi  r30, 0xFF  ; 255
32
        case 1 : base64_buffer[current_buffer_element++] = value >> 8;
33
 37a:  ae 2f         mov  r26, r30
34
 37c:  b0 e0         ldi  r27, 0x00  ; 0
35
 37e:  ac 5d         subi  r26, 0xDC  ; 220
36
 380:  be 4f         sbci  r27, 0xFE  ; 254
37
 382:  1c 93         st  X, r17
38
 384:  ef 5f         subi  r30, 0xFF  ; 255
39
        default : base64_buffer[current_buffer_element] = value;
40
 386:  f0 e0         ldi  r31, 0x00  ; 0
41
 388:  ec 5d         subi  r30, 0xDC  ; 220
42
 38a:  fe 4f         sbci  r31, 0xFE  ; 254
43
 38c:  00 83         st  Z, r16
44
    }

Das scheint mir viel zu umständlich, gerade weil die Funkion sehr oft 
ausgeführt werden soll. Besser wäre doch, den Quelltext 1:1 zu 
kompilieren:
1
...
2
st    Z+, r19
3
st    Z+, r18
4
st    Z+, r17
5
st    Z+, r16

Muss ich dieses Feature "Store Indirect and Post-Inc." erst irgendwie 
aktivieren oder so?

von mh (Gast)


Lesenswert?

Wenn du so eine Frage stellst, solltest du auch angeben mit welchen 
Optionen du kompilierst.

von Tuxpilot (Gast)


Lesenswert?

Hmm, ich benutze einfach nur das WinAVR-Makefile.

Bei -O (Optimierung) habe ich alles ausprobiert (0, 1, 2, 3, s), das hat 
aber keinen Einfluss auf den SRAM-Zugriff, nur auf die Verzweigung oben 
im switch. Was ich hier gepostet habe ist -Os, der Rest benutzt im 
wesentlichen mehr Register.

Insgesamt habe ich diese Optionen gefunden, kenne aber die meisten 
Bedeutungen nicht (noch nicht benutzt...):

Im Makefile:
1
CSTANDARD = -std=gnu99
2
3
CFLAGS = -g$(DEBUG)
4
CFLAGS += $(CDEFS)
5
CFLAGS += -O$(OPT)
6
#CFLAGS += -mint8
7
#CFLAGS += -mshort-calls
8
CFLAGS += -funsigned-char
9
CFLAGS += -funsigned-bitfields
10
CFLAGS += -fpack-struct
11
CFLAGS += -fshort-enums
12
#CFLAGS += -fno-unit-at-a-time
13
CFLAGS += -Wall
14
CFLAGS += -Wstrict-prototypes
15
CFLAGS += -Wundef
16
#CFLAGS += -Wunreachable-code
17
#CFLAGS += -Wsign-compare
18
CFLAGS += -Wa,-adhlns=$(<:%.c=$(OBJDIR)/%.lst)
19
CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
20
CFLAGS += $(CSTANDARD)

In der Konsole:
1
Compiling C: base64tx.c
2
avr-gcc -c -mmcu=attiny841 -I. -gdwarf-2 -DF_CPU=14745600UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wundef -Wa,-adhlns=obj/base64tx.lst  -std=gnu99 -Wundef -MD -MP -MF .dep/base64tx.o.d base64tx.c -o obj/base64tx.o

Ich glaube, das Makefile kam damals von hier:
https://www.mikrocontroller.net/articles/Beispiel_Makefile

von Walter T. (nicolas)


Lesenswert?

Tuxpilot schrieb:
> weil die Funkion sehr oft
> ausgeführt werden soll

Die ganze Funktion wäre auch sinnvoll, um den Scope der Variablen zu 
kennen.

Ich habe zwar eine grobe Ahnung, wie der Rückgabewert aussehen soll, 
aber wissen ist besser als raten.

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


Lesenswert?

Was ändert sich, wenn du den Index zu einem 16-Bit Typ machst?

von mh (Gast)


Lesenswert?

Walter T. schrieb:
> Die ganze Funktion wäre auch sinnvoll, um den Scope der Variablen zu
> kennen.

Nicht nur der Scope ist interessant, sondern auch die genauen 
Datentypen.

von Carl D. (jcw2)


Lesenswert?

Tuxpilot schrieb:
> Hmm, ich benutze einfach nur das WinAVR-Makefile.

WinAvr wieviel?   2010....?
Der ist auch steinalt und danach wurde das AVR-Backend deutlich 
verbessert.

von (prx) A. K. (prx)


Lesenswert?

avr-gcc 4.7.2 machts auch schon richtig.
1
extern char a[];
2
3
void f(unsigned char i, unsigned long v)
4
{
5
        a[i++] = v >> 24;
6
        a[i++] = v >> 16;
7
        a[i++] = v >> 8;
8
        a[i] = v;
9
}
10
11
        mov r26,r24
12
        ldi r27,0
13
        subi r26,lo8(-(a))
14
        sbci r27,hi8(-(a))
15
        st X,r23
16
        mov r26,r24
17
        subi r26,lo8(-(1))
18
        ldi r27,0
19
        subi r26,lo8(-(a))
20
        sbci r27,hi8(-(a))
21
        st X,r22
22
        mov r26,r24
23
        subi r26,lo8(-(2))
24
        ldi r27,0
25
        subi r26,lo8(-(a))
26
        sbci r27,hi8(-(a))
27
        st X,r21
28
        subi r24,lo8(-(3))
29
        mov r30,r24
30
        ldi r31,0
31
        subi r30,lo8(-(a))
32
        sbci r31,hi8(-(a))
33
        st Z,r20
Grund: Bei i=255 geht der zweite Store nach a[0]. Bei z++ oder z+1 ging 
er aber nach a[256].

Mit 16-Bit Index ist das kein Problem mehr:
1
void f(unsigned i, unsigned long v)
2
{
3
        a[i++] = v >> 24;
4
        a[i++] = v >> 16;
5
        a[i++] = v >> 8;
6
        a[i] = v;
7
}
8
9
        mov r30,r24
10
        mov r31,r25
11
        subi r30,lo8(-(a))
12
        sbci r31,hi8(-(a))
13
        st Z,r23
14
        std Z+1,r22
15
        std Z+2,r21
16
        std Z+3,r20

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


Lesenswert?

Allgemein: Wenn der Typ vom Index kleiner ist als eine Adressrechnung, 
tritt der beschriebene Effekt auf. Nicht nur beim AVR, auch bei 64-Bit 
x86.

Wenn der Compiler die Bereichsgrenzen vom Index kennt, kann er das 
besser machen. Ich hatte den Code oben aber bewusst so formuliert, dass 
er das nicht kann.

Und da sollte sich schon mancher drüber gewundert haben, denn die 
üblicherweise als Index verwendeten int/unsigned sind 32 Bit breit, 
Adressrechnungen aber 64 Bit. Weshalb das ähnlich mies aussieht wie oben 
beim AVR:
1
        movq    %rsi, %rdx
2
        movl    %edi, %eax
3
        shrq    $24, %rdx
4
        movb    %dl, a(%rax)
5
        leal    1(%rdi), %eax
6
        movq    %rsi, %rdx
7
        shrq    $16, %rdx
8
        movb    %dl, a(%rax)
9
        leal    2(%rdi), %eax
10
        movq    %rsi, %rdx
11
        shrq    $8, %rdx
12
        addl    $3, %edi
13
        movb    %dl, a(%rax)
14
        movb    %sil, a(%rdi)

Macht man den Index 64 Bit breit, sieht es besser aus:
1
        movq    %rsi, %rax
2
        movb    %sil, a+3(%rdi)
3
        shrq    $24, %rax
4
        movb    %al, a(%rdi)
5
        movq    %rsi, %rax
6
        shrq    $16, %rax
7
        movb    %al, a+1(%rdi)
8
        movq    %rsi, %rax
9
        shrq    $8, %rax
10
        movb    %al, a+2(%rdi)

: Bearbeitet durch User
von Tuxpilot (Gast)


Angehängte Dateien:

Lesenswert?

Carl D. schrieb:
> Tuxpilot schrieb:
>> Hmm, ich benutze einfach nur das WinAVR-Makefile.
>
> WinAvr wieviel?   2010....?

Steht da nicht...

> Der ist auch steinalt und danach wurde das AVR-Backend deutlich
> verbessert.

Wo bekomme ich denn neuere Makefiles, ohne sie neu zu erfinden? Im Wiki 
scheinbar nicht:

Tuxpilot schrieb:
> Ich glaube, das Makefile kam damals von hier:
> https://www.mikrocontroller.net/articles/Beispiel_Makefile

A. K. schrieb:
> Was ändert sich, wenn du den Index zu einem 16-Bit Typ machst?

Der Index war uint8_t, ändert aber nichts.
Aber: Wenn der Index uint16_t ist, und der switch komplett fehlt, gibt 
es std Z+1, r17 usw. Auch gut, wenn ich den switch nicht bräuchte.
st Z+, r17 bekomme ich nur mit einer Schleife drumrum, aber eine 
Schleife brauche ich da nicht.

So, der restliche Code funktioniert jetzt, ich habe alles zum .zip 
gemacht und angehängt.

Walter T. schrieb:
> Ich habe zwar eine grobe Ahnung, wie der Rückgabewert aussehen soll,
> aber wissen ist besser als raten.

(Rückgabetyp ist void...)
Die Funktion dient dazu, Daten mit variabler Bitzahl in ein Array zu 
packen, um dieses als Base64 "geschützt" an den PC zu senden. Auf dessen 
AMD64 läuft dann die Umkehrfunktion.
(Wegen diesem schlecht passenden C-'<<' und -'>>' habe ich inzwischen 
Zweifel, dass dies schneller geht, als jedes Byte einzeln per UART zu 
senden, oder gar CSV draus zu machen.)

von Tuxpilot (Gast)


Angehängte Dateien:

Lesenswert?

Ich konnte mit switch und C allgemein nicht mehr viel rausholen, und 
habe die Funktion in Assembler neu geschrieben, mit "Store Indirect and 
Post-Inc." sehr sinnvoll eingesetzt.

Wird später noch wichtig, diese Funktion. Heute nicht mehr.

Hier eine gekürzte Version, bei der man noch geschätzt 5 bis 10 Takte 
sparen kann:
1
push_value:
2
    ; Calculate and store new bits_in_buffer:
3
    lds r18, bits_in_buffer
4
    mov r21, r18
5
    add r18, r20
6
    sts bits_in_buffer, r18
7
    mov r18, r21
8
    
9
    ; Calculate how many bits are free in the last buffer element:
10
    neg r21
11
    andi r21, 0x07
12
    ; Set X to first buffer element with free space
13
    ldi r26, lo8(base64_buffer)
14
    ldi r27, hi8(base64_buffer)
15
    lsr r18
16
    lsr r18
17
    lsr r18
18
    add r26, r18
19
    adc r27, r1
20
    
21
    ; Calculate how many bits our value must be shifted left
22
    ldi r19, 32
23
    sub r19, r20
24
    add r19, r21
25
    
26
    ; Calculate how many bits and bytes this are:
27
    mov r0, r19
28
    lsr r0
29
    lsr r0
30
    lsr r0
31
    andi r19, 0x07
32
    
33
    clr r18
34
    
35
    ; Shift value left by r0 bytes:
36
    rjmp 2f
37
    1:
38
    mov r18, r25
39
    mov r25, r24
40
    mov r24, r23
41
    mov r23, r22
42
    clr r22
43
    2:
44
    dec r0
45
    brpl 1b
46
    
47
    ; Shift value left by r19 bits:
48
    rjmp 7f
49
    6:
50
    lsl r22
51
    rol r23
52
    rol r24
53
    rol r25
54
    rol r18
55
    7:
56
    dec r19
57
    brpl 6b
58
    
59
    ; OR last buffer element with our highest bits:
60
    tst r21
61
    breq 9f
62
    ld  r19, X
63
    or  r19, r18
64
    st  X+, r19
65
    9:
66
    
67
    ; Store all bytes of value in next buffer elements:
68
    st  X+, r25
69
    st  X+, r24
70
    st  X+, r23
71
    st  X+, r22
72
    
73
    ret
Min. 49 Takte mit RCALL und RET, Max. 139 Takte.

Hier wird erstmal nichts mehr verbessert. ;)

von Carl D. (jcw2)


Lesenswert?

Tuxpilot schrieb:
> Carl D. schrieb:
>> Tuxpilot schrieb:
>>> Hmm, ich benutze einfach nur das WinAVR-Makefile.
>>
>> WinAvr wieviel?   2010....?
>
> Steht da nicht...
avr-gcc -v

>> Der ist auch steinalt und danach wurde das AVR-Backend deutlich
>> verbessert.
>
> Wo bekomme ich denn neuere Makefiles, ohne sie neu zu erfinden? Im Wiki
> scheinbar nicht:

Erst mal frischen Compiler besorgen:
http://blog.zakkemble.net/avr-gcc-builds/

Wer Angst vor fremden Binaries hat, hier die Anleitung für diy:
https://www.nongnu.org/avr-libc/user-manual/install_tools.html
(nur Linux, aber "Tuxpilot")

> Tuxpilot schrieb:
>> Ich glaube, das Makefile kam damals von hier:
>> https://www.mikrocontroller.net/articles/Beispiel_Makefile
Eventuell muß man Pfade, etc im makefile anpassen, das stimmt.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Und da sollte sich schon mancher drüber gewundert haben, denn die
> üblicherweise als Index verwendeten int/unsigned sind 32 Bit breit,
> Adressrechnungen aber 64 Bit.

Ist das tatsächlich üblich? Ich verwende auf dem PC als Index 
normalerweise immer size_t.
Aber ist interessant zu sehen, dass es auf einem 8-Bitter durchaus 
effizienter sein kann, den Index nicht 8, sondern 16 Bit breit zu 
machen.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ist das tatsächlich üblich?

In legacy code schon. Ob man heute konsequent xxx_t Typen statt 
Basistypen einsetzt, dafür fehlt mit der Überblick über genug aktuellen 
Code.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Rolf M. schrieb:
>> Ist das tatsächlich üblich?
>
> In legacy code schon. Ob man heute konsequent xxx_t Typen statt
> Basistypen einsetzt, dafür fehlt mit der Überblick über genug aktuellen
> Code.

Naja, size_t ist ja nicht gerade neu. Das gab's auch vor C99 schon.

von Peter D. (peda)


Lesenswert?

Bevor man Micro-Optimization betreibt, sollte man erstmal prüfen, ob 
sich das im realen Programm auch wirklich auswirkt, d.h. ein 
Timingproblem oder zu hohe CPU-Last beseitigt wird.

von Robert L. (lrlr)


Lesenswert?

würde mich jetzt auch interessieren..
es sollen "Joystick" Daten (also von einem Menschen eingegeben)
direkt an eine PC übergeben werden

auch wenn man das  (übertrieben) 250 mal in der Sekunden macht, wäre die 
Datenmenge trotzdem noch sehr überschaubar.

und in 1/250 Sekunde kann man schon verdammt viel Code ausführen ..

dass man hier base64 braucht  wäre auch zu bezweifeln.. (oder schickt er 
a e-mail ;-) )

wüsste nicht wo da bit 8 verloren gehen sollte..

von Tuxpilot (Gast)


Lesenswert?

Robert L. schrieb:
> auch wenn man das  (übertrieben) 250 mal in der Sekunden macht, wäre die
> Datenmenge trotzdem noch sehr überschaubar.

Hähä, stimmt.
Ich möchte allerdings später 10.000 Datensätze pro Sekunde übertragen, 
bestehend aus ein paar ADC-Werten (rund 10 Bit) und ein paar internen 
Variablen (rund 1 Bit).

Um sich beim Programmieren daran zu tasten, welche Daten man braucht, 
ist so eine Funktion push_value() sehr praktisch. Wenn es schneller 
werden soll, muss man natürlich den Puffer manuell befüllen, evtl. gar 
nicht linear.

Base64 ist auch nicht nötig, macht aber das überprüfen des Datenstroms 
im Terminal einfacher, da die Datensätze durch \n getrennt sind. Die 
Datenrate ist besser als Hexadezimal, ausserdem werde ich mein 
Plotprogramm noch auf Uuencoding umstellen.

Carl D. schrieb:
> Erst mal frischen Compiler besorgen:
> http://blog.zakkemble.net/avr-gcc-builds/

Gemacht, der Code wird tatsächlich etwas schlanker. Nur das "Store 
Indirect and Post-Inc." wird immer noch unelegant umgangen. Compiliert 
habe ich hiermit:
1
avr-gcc-8.2.0-x64-linux/bin/avr-gcc -c -mmcu=attiny841 -I. -gdwarf-2 -DF_CPU=14745600UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wundef -Wa,-adhlns=obj/base64tx.lst  -std=gnu99 -Wundef -MD -MP -MF .dep/base64tx.o.d base64tx.c -o obj/base64tx.o

von Oliver S. (oliverso)


Lesenswert?

Mit 16-Bit Index?

Oliver

von Tuxpilot (Gast)


Lesenswert?

Ja

von Kaj (Gast)


Lesenswert?

Tuxpilot schrieb:
> Die Datenrate ist besser als Hexadezimal
Kann ich mir nur schwer vorstellen. Denn durch die Codierung musst du 
entweder genau so viele (Bytes/3 = 0) oder sogar mehr Bytes (Bytes/3 != 
0 -> Padding) uebertragen als bei Hex. Die Datenrate kann mit Base64 
also gar nicht besser werden.
Aber vielleicht kannst du mich ja erleuchten, und erklaeren wie das 
funktionieren soll.

von Rentenzahler (Gast)


Lesenswert?

Ich vermute er meint HEX als ASCII übertragen.

von Tuxpilot (Gast)


Lesenswert?

Rentenzahler schrieb:
> Ich vermute er meint HEX als ASCII übertragen.

Ja. Wie kann man es denn sonst noch übertragen? Jeweils zwei Hex-Werte 
in einem Byte, das wäre ja wieder binär, so kann man kein \n senden.

Padding in Form von = oder == braucht man nicht anhängen. Enthält keine 
weitere Information, denn das Format ist ja bekannt. Nur die 2 oder 4 
Bits im letzten Zeichen muss man anhängen.

von Robert L. (lrlr)


Lesenswert?

die \n und Base64 im Terminal anzeigen, macht welchen Sinn?

ist das so wie in dem Matrix Film, wo der grüne Code über den Bildschirm 
läuft?

von Tuxpilot (Gast)


Lesenswert?

Synchronisierung. Eine Zeile ist ein Datensatz.

Wahrscheinlich so wie im Matrix Film, wenn es da darum geht, die 
Verbindung (besonders die Baudrate) zu prüfen. Wie will man das bei 
binären Daten machen? Da hilft nur ein Adapter mit Auto Baud Rate 
Detection, wenn man sich nicht mehr an die Baudrate erinnert.

Wenn du eine Adresse suchst, multiplizierst du die Hausnummer mit 7,62 
Meter und fährst von der Kreuzung aus genau soweit, oder schaust du auf 
die Nummern die an den Häusern stehen?

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.