AVR: unsigned 24 bit auf 3 Ports verteilen

OP #3878709
Lesenswert?

Hallo,

ich stolpere über ein Laufzeitproblem bei der Verteilung der 3 unteren 
Bytes eines 24-bit Wertes auf 3 Ports.

inline void machmal(uint32_t wert)
{
PORTA = (wert & 0xff);          // ok, OUT PORTA,Rn
PORTB = ((wert >> 8) & 0xff);   // 4 Register werden sinnlos umkopiert
PORTC = ((wert >> 16) & 0xff);  // ok, OUT PORTC, Rn+2
}

machmal(0x00112233UL);

Der gcc läuft mit -o3

Wie gehts besser ?
Union etc. geht nicht, weil ich alles in Registern behalten will.

Gruß,
Michael
#3878716
Lesenswert?

Michael Appelt schrieb:
> Union etc. geht nicht, weil ich alles in Registern behalten will.

???

Solange du in C programmierst, hast du eh keinen Einfluß darauf.

Aber:

Schau dir das ABI an. Dein uint32-Wert steht ja beim Funktionsaufruf in 
4 Registern. Mit ein paar Zeilen inline-Assembler oder einer eigenen 
Assembler-Routine kannst du die Werte direkt aus den Registern in die 
Ports schreiben. Schneller gehst es nicht.

Oliver
OP #3878735
Lesenswert?

Hallo,

@Oliver S:

Union geht über den Spicher und die genannte Sequenz und ein wenig mehr 
sollen unter 1us laufen.

Das ganze muss 6mal aufgerufen werden, und mir gefallen die hart 
kodierten Register nicht. Muss vermutlich tiefer in den inline-Assembler 
reinschauen.

@Jonas:

welche Casts ?

PORTB = (uint8_t) ((uint16_t) wert) >> 8);

Wäre das optimal ? Stimmt, hab ich noch nicht probiert.

Gruß,
Michael
#3878739
Lesenswert?

Michael Appelt schrieb:
> Der gcc läuft mit -o3

Default sollte man -Os nehmen.
Besser gehts nicht:
1
inline void machmal(uint32_t wert)
2
{
3
PORTA = (wert & 0xff);          // ok, OUT PORTA,Rn
4
  d0:  83 e3         ldi  r24, 0x33  ; 51
5
  d2:  82 b9         out  0x02, r24  ; 2
6
PORTB = ((wert >> 8) & 0xff);   // 4 Register werden sinnlos umkopiert
7
  d4:  82 e2         ldi  r24, 0x22  ; 34
8
  d6:  85 b9         out  0x05, r24  ; 5
9
PORTC = ((wert >> 16) & 0xff);  // ok, OUT PORTC, Rn+2
10
  d8:  81 e1         ldi  r24, 0x11  ; 17
11
  da:  88 b9         out  0x08, r24  ; 8
12
}
#3878754
Lesenswert?

Jonas Biensack schrieb:
> Nicht komplett aber man kann mit dem Schlüsselwort register eine
> variable in den Registern halten.

Register allocation ist Sache des Compilers. Daher wird das 
Schlüsselwort register seit vielen Compiler-Generationen vom gcc 
schlicht ignoriert.

Peter Dannegger schrieb:
> Besser gehts nicht:

In reinem Assembler wären es nur die 3 out-Befehle und return.

Oliver
OP #3878826
Lesenswert?

Peter II schrieb:
> Michael Appelt schrieb:
>> naja, bei einem Call stehen diese Parameter bei meinem Programm immer in
>> Registern.
>
> es gibt aber bei inline keinen call mehr.

Aber auf die drüberliegenden Functions, die sind nicht inline.

Es sind 6 Functions f1....f6. Jede hat leicht unterschiedliche 
Parameter, d.h. es werden möglicherweise unterschiedliche Register 
benutzt. Jede ruft am Anfang die genannte inline function auf.

Wegen der Registerzuweisung bin ich mit inline asm nicht glücklich.
Wenn es so jetzt funktioniert, dann löst das mein Problem.

Gruß,
Michael
OP #3878858
Lesenswert?

Hallo,

also man sollte schon in der Lage sein, abstrakt über ein Problem zu 
reden ohne immer gleich ein 8k-Programm im Source anzuhängen.

Ich habe ja nicht gesagt, daß der gcc was falsch macht und die Hinweise 
waren durchaus zutreffend genug, um damit mein Brett vorm Kopp zu 
entfernen.

Gruß,
Michael
OP #3878908
Lesenswert?

Hallo,

nein, habe ich nicht probiert. Es würde auf Union als Parametertyp 
hinauslaufen, und da müsste ich mich ebenfalls erstmal einlesen.

Ich weiß garnicht:

Wo steht, dass man hier nur Fragen stellen darf, wenn man vorher bereits 
alles ausprobiert hat ?

Wo steht, dass man alles ausprobieren muss ?

Gruß,
Michael
#3878928
Lesenswert?

Mit union:
1
void out24(uint32_t val)
2
{
3
  union{
4
    uint32_t u32;
5
    uint8_t u8[4];
6
  } in;
7
  in.u32 = val;
8
  PORTA = in.u8[0]; 
9
  PORTB = in.u8[1]; 
10
  PORTC = in.u8[2]; 
11
}

Das Listing:
1
000000c0 <out24>:
2
  union{
3
    uint32_t u32;
4
    uint8_t u8[4];
5
  } in;
6
  in.u32 = val;
7
  PORTA = in.u8[0]; 
8
  c0:  62 b9         out  0x02, r22  ; 2
9
  PORTB = in.u8[1]; 
10
  c2:  75 b9         out  0x05, r23  ; 5
11
  PORTC = in.u8[2]; 
12
  c4:  88 b9         out  0x08, r24  ; 8
13
}
14
  c6:  08 95         ret
#3878930
Lesenswert?

Wenn der Abstand zwischen den OUT Befehlen zwingend minimiert sein muss:
1
void __attribute__ ((noinline))
2
out24(uint8_t h, uint8_t m, uint8_t l)
3
{
4
        cli();
5
        PORTA = l;
6
        PORTB = m;
7
        PORTC = h;
8
        sei();
9
}
10

11
void f(uint32_t x)
12
{
13
        out24(x >> 16, x >> 8, x);
14
}
So spart man ein wenig an unkritischer Stelle, hat aber mehr Arbeit:
1
        out24(x >> 16, (uint16_t)x >> 8, x);
OP #3878968
Lesenswert?

Hallo,

ok, ich muss zugeben, dass hat was :-))
Die Details des Union bleiben in der function verborgen.

Danke für die Mühe, die Ihr Euch gemacht habt.
Vermutlich bleibt es aber dabei: ohne -Os klappt es auch mit union 
nicht.

BTW die genaue Zeit der out-Befehle ist mir relativ egal, aber mal eben 
zusätzlich 4 register kopieren ging mir irgendwie quer - technisch wäre 
es aber relativ egal.

Gruß,
Michael
Moderator #3879006
Lesenswert?

Michael Appelt schrieb:
> nein, habe ich nicht probiert. Es würde auf Union als Parametertyp
> hinauslaufen, und da müsste ich mich ebenfalls erstmal einlesen.
>
> Ich weiß garnicht:
>
> Wo steht, dass man hier nur Fragen stellen darf, wenn man vorher bereits
> alles ausprobiert hat ?

Nirgends, das ist ganz dir überlassen.

> Wo steht, dass man alles ausprobieren muss ?

Ebenfalls nirgends.

Nur tust du dir dann mit Aussagen wie der folgenden selber keinen
Gefallen:

Michael Appelt schrieb:
> Union geht über den Spicher

Denn das erweckt den Eindruck, dass du dir diesbezüglich sicher bist und
deswegen auch für die Helfenden keinerlei Bedarf siehst, die Unions doch
einmal auszuprobieren.

Ohne die obige Aussage wäre der Vorschlag mit der Union sicher schon in
der ersten oder zweiten Antwort gekommen, so waren aber alle Hirne erst
einmal blockiert.

Ich selber war mir erst auch nicht sicher, ob hier eine Union etwas
bringen würde. Nach einer kurzen Goole-Suche nach

  gcc struct argument register

kam ich zum aber Schluss, dass das zumindest einen Versuch wert ist. Und
siehe da ...
OP #3879428
Lesenswert?

Hallo,

jawoll, Du bist ein ganz schlauer, deshalb darfst Du ja auch Moderator 
sein.

Also ich habe ein Berufsleben, und privat geht mein Ehrgeiz zu einer 
angemessene Lösung in angemessener Zeit.

Aktuell erhalte ich bei -Os

  DATA_OUT = (uint8_t) ((uint16_t) adr >> 8);
 250:  27 2f         mov  r18, r23
 252:  33 27         eor  r19, r19
 254:  28 b9         out  0x08, r18  ; 8

Gruß,
Michael
OP #3879465
Lesenswert?

Sorry, meine Frage war rein rhetorisch.

Ich wunder mich nur, was vom gcc alles optimiert wird und was 
andererseits manchmal auch nicht. Aber die Bewunderung der Entwickler 
überwiegt.

Nachtrag:

  DATA_OUT = (uint8_t) (adr >> 16);
 25a:  aa 27         eor  r26, r26
 25c:  bb 27         eor  r27, r27
 25e:  88 b9         out  0x08, r24  ; 8

Auch nicht schlecht....ich probiers wohl besser mit union.

Gruß,
Michael
#3879667
Lesenswert?

Aus der Praxis.


.h
1
// little trick, to speed up data access
2

3
typedef union {          
4
    uint32_t d32;
5
    uint8_t ary[4];
6
    } ut_t;



.c
1
ISR(TIMER1_COMPA_vect) {
2

3
    static uint8_t pwm_cnt, slow_cnt;
4
    ut_t tmp;
5
    int8_t enc_new, enc_diff;           // encoder stuff
6

7
    OCR1A   += isr_ptr->time[pwm_cnt];
8
    tmp.d32  = isr_ptr->mask[pwm_cnt];
9

10
    if (pwm_cnt == 0) {                 // set ports at begin of PWM
11
        PORTD = tmp.ary[0] | 0x0F;      // PD0-3 no change
12
        PORTC = tmp.ary[1];
13
        PORTB = tmp.ary[2] | 0xF0;      // PB4-7 no change
14
        PORTA = tmp.ary[3];
15
        pwm_cnt++;
16
        slow_cnt++;
17
        if (slow_cnt == 10) {           // 100ms timer
18
            slow_cnt=0;
19
            flag_100ms=1;               // trigger lcd refresh
20
        }
21
    }
22
    else {                              // clear ports
23
        PORTD &= (tmp.ary[0] | 0x0F);       
24
        PORTC &= tmp.ary[1];
25
        PORTB &= tmp.ary[2] | 0xF0;
26
        PORTA &= tmp.ary[3];
27
                                
28
        if (pwm_cnt == pwm_cnt_max) {
29
            pwm_sync = 1;               // update now possible
30
            pwm_cnt=0;
31
        }
32
        else pwm_cnt++;
33
    }
34
    
35
    // decode rotary encoder, setup for DMX address
36
    // only active, when jogdail is pressed
37
    // DMX address stored in eeprom on release in main loop
38
    if (KEY_PRESSED) {
39
        enc_new = 0;
40
        if( PHASE_A ) enc_new = 3;
41
        if( PHASE_B ) enc_new ^= 1;             // convert gray to binary
42
        enc_diff = enc_last - enc_new;          // difference last - new
43
        if( enc_diff & 1 ){                     // bit 0 = value (1)
44
            enc_last   = enc_new;               // store new as next last
45
            enc_delta += (enc_diff & 2) - 1;    // bit 1 = direction (+/-)
46
            if (enc_delta < 0)  enc_delta = 0;
47
            if (enc_delta > 84) enc_delta = 84;
48
        }     
49
    }
50
}

1
// Timer 1 Output COMPARE A Interrupt
2

3
ISR(TIMER1_COMPA_vect) {
4
 77a:  1f 92         push  r1
5
 77c:  0f 92         push  r0
6
 77e:  0f b6         in  r0, 0x3f  ; 63
7
 780:  0f 92         push  r0
8
 782:  11 24         eor  r1, r1
9
 784:  2f 93         push  r18
10
 786:  3f 93         push  r19
11
 788:  4f 93         push  r20
12
 78a:  5f 93         push  r21
13
 78c:  6f 93         push  r22
14
 78e:  8f 93         push  r24
15
 790:  9f 93         push  r25
16
 792:  af 93         push  r26
17
 794:  bf 93         push  r27
18
 796:  ef 93         push  r30
19
 798:  ff 93         push  r31
20

21
    static uint8_t pwm_cnt, slow_cnt;
22
    ut_t tmp;
23
    int8_t enc_new, enc_diff;           // encoder stuff
24

25
    OCR1A   += isr_ptr->time[pwm_cnt];
26
 79a:  40 91 88 00   lds  r20, 0x0088
27
 79e:  50 91 89 00   lds  r21, 0x0089
28
 7a2:  e0 91 01 01   lds  r30, 0x0101
29
 7a6:  f0 91 02 01   lds  r31, 0x0102
30
 7aa:  60 91 09 01   lds  r22, 0x0109
31
 7ae:  26 2f         mov  r18, r22
32
 7b0:  30 e0         ldi  r19, 0x00  ; 0
33
 7b2:  22 0f         add  r18, r18
34
 7b4:  33 1f         adc  r19, r19
35
 7b6:  e2 0f         add  r30, r18
36
 7b8:  f3 1f         adc  r31, r19
37
 7ba:  80 81         ld  r24, Z
38
 7bc:  91 81         ldd  r25, Z+1  ; 0x01
39
 7be:  48 0f         add  r20, r24
40
 7c0:  59 1f         adc  r21, r25
41
 7c2:  50 93 89 00   sts  0x0089, r21
42
 7c6:  40 93 88 00   sts  0x0088, r20
43
    tmp.d32  = isr_ptr->mask[pwm_cnt];
44
 7ca:  e0 91 01 01   lds  r30, 0x0101
45
 7ce:  f0 91 02 01   lds  r31, 0x0102
46
 7d2:  22 0f         add  r18, r18
47
 7d4:  33 1f         adc  r19, r19
48
 7d6:  e2 0f         add  r30, r18
49
 7d8:  f3 1f         adc  r31, r19
50
 7da:  df 01         movw  r26, r30
51
 7dc:  da 96         adiw  r26, 0x3a  ; 58
52
 7de:  92 ad         ldd  r25, Z+58  ; 0x3a
53
 7e0:  11 96         adiw  r26, 0x01  ; 1
54
 7e2:  3c 91         ld  r19, X
55
 7e4:  11 97         sbiw  r26, 0x01  ; 1
56
 7e6:  12 96         adiw  r26, 0x02  ; 2
57
 7e8:  2c 91         ld  r18, X
58
 7ea:  12 97         sbiw  r26, 0x02  ; 2
59
 7ec:  13 96         adiw  r26, 0x03  ; 3
60
 7ee:  4c 91         ld  r20, X
61
 7f0:  9f 60         ori  r25, 0x0F  ; 15
62
 7f2:  20 6f         ori  r18, 0xF0  ; 240
63

64
    if (pwm_cnt == 0) {                 // set ports at begin of PWM
65
 7f4:  66 23         and  r22, r22
66
 7f6:  99 f4         brne  .+38       ; 0x81e <__vector_13+0xa4>
67
        PORTD = tmp.ary[0] | 0x0F;      // PD0-3 no change
68
 7f8:  9b b9         out  0x0b, r25  ; 11
69
        PORTC = tmp.ary[1];
70
 7fa:  38 b9         out  0x08, r19  ; 8
71
        PORTB = tmp.ary[2] | 0xF0;      // PB4-7 no change
72
 7fc:  25 b9         out  0x05, r18  ; 5
73
        PORTA = tmp.ary[3];
74
 7fe:  42 b9         out  0x02, r20  ; 2
75
        pwm_cnt++;
76
 800:  91 e0         ldi  r25, 0x01  ; 1
77
 802:  90 93 09 01   sts  0x0109, r25
78
        slow_cnt++;
79
 806:  80 91 08 01   lds  r24, 0x0108
80
 80a:  8f 5f         subi  r24, 0xFF  ; 255
81
 80c:  80 93 08 01   sts  0x0108, r24
82
        if (slow_cnt == 10) {           // 100ms timer
83
 810:  8a 30         cpi  r24, 0x0A  ; 10
84
 812:  f1 f4         brne  .+60       ; 0x850 <__vector_13+0xd6>
85
            slow_cnt=0;
86
 814:  10 92 08 01   sts  0x0108, r1
87
            flag_100ms=1;               // trigger lcd refresh
88
 818:  90 93 bb 01   sts  0x01BB, r25
89
 81c:  19 c0         rjmp  .+50       ; 0x850 <__vector_13+0xd6>
90
        }
91
    }
92
    else {                              // clear ports
93
        PORTD &= (tmp.ary[0] | 0x0F);       
94
 81e:  8b b1         in  r24, 0x0b  ; 11
95
 820:  98 23         and  r25, r24
96
 822:  9b b9         out  0x0b, r25  ; 11
97
        PORTC &= tmp.ary[1];
98
 824:  88 b1         in  r24, 0x08  ; 8
99
 826:  83 23         and  r24, r19
100
 828:  88 b9         out  0x08, r24  ; 8
101
        PORTB &= tmp.ary[2] | 0xF0;
102
 82a:  85 b1         in  r24, 0x05  ; 5
103
 82c:  28 23         and  r18, r24
104
 82e:  25 b9         out  0x05, r18  ; 5
105
        PORTA &= tmp.ary[3];
106
 830:  82 b1         in  r24, 0x02  ; 2
107
 832:  84 23         and  r24, r20
108
 834:  82 b9         out  0x02, r24  ; 2
109
                                
110
        if (pwm_cnt == pwm_cnt_max) {
111
 836:  80 91 00 01   lds  r24, 0x0100
112
 83a:  68 17         cp  r22, r24
113
 83c:  31 f4         brne  .+12       ; 0x84a <__vector_13+0xd0>
114
            pwm_sync = 1;               // update now possible
115
 83e:  81 e0         ldi  r24, 0x01  ; 1
116
 840:  80 93 ba 01   sts  0x01BA, r24
117
            pwm_cnt=0;
118
 844:  10 92 09 01   sts  0x0109, r1
119
 848:  03 c0         rjmp  .+6        ; 0x850 <__vector_13+0xd6>
120
        }
121
        else pwm_cnt++;
122
 84a:  6f 5f         subi  r22, 0xFF  ; 255
123
 84c:  60 93 09 01   sts  0x0109, r22
124
    }
125
    
126
    // decode rotary encoder, setup for DMX address
127
    // only active, when jogdail is pressed
128
    // DMX address stored in eeprom on release in main loop
129
    if (KEY_PRESSED) {
130
 850:  49 99         sbic  0x09, 1  ; 9
131
 852:  24 c0         rjmp  .+72       ; 0x89c <__vector_13+0x122>
132
        enc_new = 0;
133
        if( PHASE_A ) enc_new = 3;
134
 854:  4b 9b         sbis  0x09, 3  ; 9
135
 856:  02 c0         rjmp  .+4        ; 0x85c <__vector_13+0xe2>
136
 858:  23 e0         ldi  r18, 0x03  ; 3
137
 85a:  01 c0         rjmp  .+2        ; 0x85e <__vector_13+0xe4>
138
 85c:  20 e0         ldi  r18, 0x00  ; 0
139
        if( PHASE_B ) enc_new ^= 1;             // convert gray to binary
140
 85e:  4a 9b         sbis  0x09, 2  ; 9
141
 860:  02 c0         rjmp  .+4        ; 0x866 <__vector_13+0xec>
142
 862:  81 e0         ldi  r24, 0x01  ; 1
143
 864:  28 27         eor  r18, r24
144
        enc_diff = enc_last - enc_new;          // difference last - new
145
 866:  90 91 0a 01   lds  r25, 0x010A
146
 86a:  92 1b         sub  r25, r18
147
        if( enc_diff & 1 ){                     // bit 0 = value (1)
148
 86c:  90 ff         sbrs  r25, 0
149
 86e:  16 c0         rjmp  .+44       ; 0x89c <__vector_13+0x122>
150
            enc_last   = enc_new;               // store new as next last
151
 870:  20 93 0a 01   sts  0x010A, r18
152
            enc_delta += (enc_diff & 2) - 1;    // bit 1 = direction (+/-)
153
 874:  80 91 be 01   lds  r24, 0x01BE
154
 878:  81 50         subi  r24, 0x01  ; 1
155
 87a:  92 70         andi  r25, 0x02  ; 2
156
 87c:  89 0f         add  r24, r25
157
 87e:  80 93 be 01   sts  0x01BE, r24
158
            if (enc_delta < 0)  enc_delta = 0;
159
 882:  80 91 be 01   lds  r24, 0x01BE
160
 886:  87 ff         sbrs  r24, 7
161
 888:  02 c0         rjmp  .+4        ; 0x88e <__vector_13+0x114>
162
 88a:  10 92 be 01   sts  0x01BE, r1
163
            if (enc_delta > 84) enc_delta = 84;
164
 88e:  80 91 be 01   lds  r24, 0x01BE
165
 892:  85 35         cpi  r24, 0x55  ; 85
166
 894:  1c f0         brlt  .+6        ; 0x89c <__vector_13+0x122>
167
 896:  84 e5         ldi  r24, 0x54  ; 84
168
 898:  80 93 be 01   sts  0x01BE, r24
169
        }     
170
    }
171
}
172
 89c:  ff 91         pop  r31
173
 89e:  ef 91         pop  r30
174
 8a0:  bf 91         pop  r27
175
 8a2:  af 91         pop  r26
176
 8a4:  9f 91         pop  r25
177
 8a6:  8f 91         pop  r24
178
 8a8:  6f 91         pop  r22
179
 8aa:  5f 91         pop  r21
180
 8ac:  4f 91         pop  r20
181
 8ae:  3f 91         pop  r19
182
 8b0:  2f 91         pop  r18
183
 8b2:  0f 90         pop  r0
184
 8b4:  0f be         out  0x3f, r0  ; 63
185
 8b6:  0f 90         pop  r0
186
 8b8:  1f 90         pop  r1
187
 8ba:  18 95         reti


Der wesentliche Ausschnitt ist der hier
1
    if (pwm_cnt == 0) {                 // set ports at begin of PWM
2
 7f4:  66 23         and  r22, r22
3
 7f6:  99 f4         brne  .+38       ; 0x81e <__vector_13+0xa4>
4
        PORTD = tmp.ary[0] | 0x0F;      // PD0-3 no change
5
 7f8:  9b b9         out  0x0b, r25  ; 11
6
        PORTC = tmp.ary[1];
7
 7fa:  38 b9         out  0x08, r19  ; 8
8
        PORTB = tmp.ary[2] | 0xF0;      // PB4-7 no change
9
 7fc:  25 b9         out  0x05, r18  ; 5
10
        PORTA = tmp.ary[3];
11
 7fe:  42 b9         out  0x02, r20  ; 2
12
        pwm_cnt++;
13
 800:  91 e0         ldi  r25, 0x01  ; 1
14
 802:  90 93 09 01   sts  0x0109, r25

Sieht recht optimal aus.
OP #3880134
Lesenswert?

A. K. schrieb:
> Peter II schrieb:
>> was macht das
>> eor  r19, r19
>> für einen sinn?
>
> Bei non-volatile lhs entfallen mov und eor.

Das versteh ich nicht.

@Falk: mir fehlt in Deinem Posting noch das Hexfile ;-)

Ok, ich habs verstanden und ausprobiert, das Union hat gewonnen.

Allerdings war -Os nix, weil dann meine inline functions per call 
aufgerufen werden. s=size und nicht s=superefficient.

Zur Zeit nehme ich -Os und #defines - Blöcke statt functions, damit 
läufts wie gewünscht.

Gruß,
Michael

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren