Forum: Compiler & IDEs Encoder auswerten, Assembler Code unverständlich


von Robert S. (razer) Benutzerseite


Lesenswert?

Hallo an alle,

Ich hab eine Routine für die Auswertung zweier Drehgeber (nach P. 
Danneger) geschrieben.
1
#define INC0_PHASE_A  (1<<PB4)
2
#define INC0_PHASE_B  (1<<PB5)
3
#define INC1_PHASE_A  (1<<PB6)
4
#define INC1_PHASE_B  (1<<PB7)
5
6
void decodeGrayCode(void)
7
{
8
  uint8_t inc0 = 0, inc1 = 0;
9
  static int8_t inc0_old = 0x01, inc1_old = 0x01;
10
  uint8_t tmp = ENCODER_PIN;
11
  
12
  if(tmp & 0x10)
13
    inc0 = 0x01;
14
    
15
  if(tmp & INC0_PHASE_B)
16
    inc0 ^= 0x03;
17
    
18
  if(tmp & INC1_PHASE_A)
19
    inc1 = 0x01;
20
  
21
  if(tmp & INC1_PHASE_B)
22
    inc1 ^= 0x03;
23
    
24
  inc0 -= inc0_old;
25
  inc1 -= inc1_old;
26
  
27
  if(inc0 & 0x01)
28
  {
29
    inc0_old += inc0;
30
    inc0_value += (inc0 & 0x02) - 1;
31
  }
32
  
33
  if(inc1 & 0x01)
34
  {
35
    inc1_old += inc1;
36
    inc1_value += (inc1 & 0x02) - 1;
37
  }
38
}

Nun hab ich mir das Assembler Listing angeschaut. Jedoch versteh ich da 
einen Teil nicht.
1
11c:  53 b1         in  r21, 0x03  ; 3
2
  
3
  if(tmp & INC0_PHASE_A)
4
 11e:  25 2f         mov  r18, r21
5
 120:  30 e0         ldi  r19, 0x00  ; 0
6
 122:  c9 01         movw  r24, r18
7
 124:  44 e0         ldi  r20, 0x04  ; 4
8
 126:  96 95         lsr  r25
9
 128:  87 95         ror  r24
10
 12a:  4a 95         dec  r20
11
 12c:  e1 f7         brne  .-8        ; 0x126 <decodeGrayCode+0xa>
12
 12e:  48 2f         mov  r20, r24
13
 130:  41 70         andi  r20, 0x01  ; 1
14
    inc0 = 0x01;
15
    
16
  if(tmp & INC0_PHASE_B)
17
 132:  55 ff         sbrs  r21, 5
18
 134:  02 c0         rjmp  .+4        ; 0x13a <decodeGrayCode+0x1e>
19
    inc0 ^= 0x03;
20
 136:  83 e0         ldi  r24, 0x03  ; 3
21
 138:  48 27         eor  r20, r24
22
 13a:  86 e0         ldi  r24, 0x06  ; 6
23
 13c:  36 95         lsr  r19
24
 13e:  27 95         ror  r18
25
 140:  8a 95         dec  r24
26
 142:  e1 f7         brne  .-8        ; 0x13c <decodeGrayCode+0x20>
27
 144:  92 2f         mov  r25, r18
28
 146:  91 70         andi  r25, 0x01  ; 1
29
    
30
  if(tmp & INC1_PHASE_A)
31
    inc1 = 0x01;
32
  
33
  if(tmp & INC1_PHASE_B)
34
 148:  57 ff         sbrs  r21, 7
35
 14a:  02 c0         rjmp  .+4        ; 0x150 <decodeGrayCode+0x34>
36
    inc1 ^= 0x03;
37
 14c:  83 e0         ldi  r24, 0x03  ; 3
38
 14e:  98 27         eor  r25, r24

Für beide Encoder wird die Auswertung der Phase A sehr kompliziert 
übersetzt. Woran liegt das?

Da INC0_PHASE_A eine Konstante ist, sollte er nu das eine Bit prüfen, 
wie das bei der Phase B gemacht wird.

Ich verwende WinAVR2008610.

Ich hoffe ihr könnt mir helfen.

Danke im Voraus
Gruß Robert

von yalu (Gast)


Lesenswert?

Ich habe den C-Code mal auf das Wesentliche abgespeckt, um besser zu
erkenn, was der Compiler da für Unfug treibt:
1
#include <stdint.h>
2
3
uint8_t a;
4
5
void func(void) {
6
  uint8_t i = 0;
7
  
8
  if(a & 0x40)
9
    i = 0x01;
10
  a = i;
11
}

Hier ist der von GCC 4.2.3 erzeugte Assemblercode:
1
func:
2
/* prologue: frame size=0 */
3
/* prologue end (size=0) */
4
  lds r24,a
5
  clr r25
6
  clr __tmp_reg__
7
  lsl r24
8
  rol r25
9
  rol __tmp_reg__
10
  lsl r24
11
  rol r25
12
  rol __tmp_reg__
13
  mov r24,r25
14
  mov r25,__tmp_reg__
15
  andi r24,lo8(1)
16
  sts a,r24
17
/* epilogue: frame size=0 */
18
  ret

Der von GCC 4.3.0 generierte Code sieht gleich aus, lediglich 'clr
r25' wird durch 'ldi r25,lo8(0)' ersetzt.

Wie man sieht, hat der Compiler Code erzeugt, der das Eregbnis a
direkt, also ohne Verzweigungen, berechnet. Das ist auch sinnvoll auf
Prozessoren mit schnellen Shiftern und vergleichsweise langsamen
Sprüngen, also bspw. bei PC-Prozessoren. Beim AVR ist das aber genau
anders herum, zumal die Schieberei über drei Register mit
anschließendem Zurückkopieren ziemlich umständlich ist.

Möglicherweise gibt es irgendeine Compileroption, mit der dieses
"Rechnen-statt-Springen"-Feature abgeschaltet werden kann, ich habe
sie allerdings weder im GCC-Manual noch per Google-Suche gefunden.
Vielleicht hat ja jemand anderes mehr Glück bei der Suche.

Als Workaround kannst du die ersten vier If-Anweisungen etwas
umstellen, so dass den Variablen inc0 und inc1 keine Werte direkt
zugewiesen werden, in denen nur ein einzelnes Bit gesetzt ist:
1
  if(tmp & INC0_PHASE_B)
2
    inc0 = 0x03;
3
    
4
  if(tmp & INC0_PHASE_A)
5
    inc0 ^= 0x01;
6
    
7
  if(tmp & INC1_PHASE_B)
8
    inc1 = 0x03;
9
    
10
  if(tmp & INC1_PHASE_A)
11
    inc1 ^= 0x01;

Optimal ist der erzeugte Code immer noch nicht, aber wenigstens
entfällt die wilde Bitschieberei.

von Falk B. (falk)


Lesenswert?

Alos für mich sieht das mal wieder nach einer Verschlimmbesserung des 
AVR GCC aus. Mit meinem Alteisen von 2006 kommt ganz normaler Assembler 
raus.
Optimierung -Os
1
#include "avr/io.h"
2
3
#define ENCODER_PIN PINA
4
#define INC0_PHASE_A  (1<<PB4)
5
#define INC0_PHASE_B  (1<<PB5)
6
#define INC1_PHASE_A  (1<<PB6)
7
#define INC1_PHASE_B  (1<<PB7)
8
9
uint8_t inc0_value, inc1_value;
10
11
void decodeGrayCode(void)
12
{
13
  uint8_t inc0 = 0, inc1 = 0;
14
  static int8_t inc0_old = 0x01, inc1_old = 0x01;
15
  uint8_t tmp = ENCODER_PIN;
16
  
17
  if(tmp & 0x10)
18
    inc0 = 0x01;
19
    
20
  if(tmp & INC0_PHASE_B)
21
    inc0 ^= 0x03;
22
    
23
  if(tmp & INC1_PHASE_A)
24
    inc1 = 0x01;
25
  
26
  if(tmp & INC1_PHASE_B)
27
    inc1 ^= 0x03;
28
    
29
  inc0 -= inc0_old;
30
  inc1 -= inc1_old;
31
  
32
  if(inc0 & 0x01)
33
  {
34
    inc0_old += inc0;
35
    inc0_value += (inc0 & 0x02) - 1;
36
  }
37
  
38
  if(inc1 & 0x01)
39
  {
40
    inc1_old += inc1;
41
    inc1_value += (inc1 & 0x02) - 1;
42
  }
43
}
44
45
int main(void) {}
1
void decodeGrayCode(void)
2
{
3
  uint8_t inc0 = 0, inc1 = 0;
4
  8e:  50 e0         ldi  r21, 0x00  ; 0
5
  90:  45 2f         mov  r20, r21
6
  static int8_t inc0_old = 0x01, inc1_old = 0x01;
7
  uint8_t tmp = ENCODER_PIN;
8
  92:  99 b3         in  r25, 0x19  ; 25
9
  
10
  if(tmp & 0x10)
11
  94:  29 2f         mov  r18, r25
12
  96:  33 27         eor  r19, r19
13
  98:  24 fd         sbrc  r18, 4
14
    inc0 = 0x01;
15
  9a:  41 e0         ldi  r20, 0x01  ; 1
16
    
17
  if(tmp & INC0_PHASE_B)
18
  9c:  25 ff         sbrs  r18, 5
19
  9e:  02 c0         rjmp  .+4        ; 0xa4 <decodeGrayCode+0x16>
20
    inc0 ^= 0x03;6 
21
  a0:  83 e0         ldi  r24, 0x03  ; 3
22
  a2:  48 27         eor  r20, r24
23
    
24
  if(tmp & INC1_PHASE_A)
25
  a4:  26 fd         sbrc  r18, 6
26
    inc1 = 0x01;
27
  a6:  51 e0         ldi  r21, 0x01  ; 1
28
  
29
  if(tmp & INC1_PHASE_B)
30
  a8:  97 ff         sbrs  r25, 7
31
  aa:  02 c0         rjmp  .+4        ; 0xb0 <decodeGrayCode+0x22>
32
    inc1 ^= 0x03;
33
  ac:  83 e0         ldi  r24, 0x03  ; 3
34
  ae:  58 27         eor  r21, r24
35
    
36
  inc0 -= inc0_old;
37
  b0:  80 91 60 00   lds  r24, 0x0060
38
  b4:  48 1b         sub  r20, r24
39
  inc1 -= inc1_old;
40
  b6:  90 91 61 00   lds  r25, 0x0061
41
  ba:  59 1b         sub  r21, r25
42
  
43
  if(inc0 & 0x01)
44
  bc:  40 ff         sbrs  r20, 0
45
  be:  0a c0         rjmp  .+20       ; 0xd4 <decodeGrayCode+0x46>
46
  {
47
    inc0_old += inc0;
48
  c0:  84 0f         add  r24, r20
49
  c2:  80 93 60 00   sts  0x0060, r24
50
    inc0_value += (inc0 & 0x02) - 1;
51
  c6:  42 70         andi  r20, 0x02  ; 2
52
  c8:  80 91 62 00   lds  r24, 0x0062
53
  cc:  84 0f         add  r24, r20
54
  ce:  81 50         subi  r24, 0x01  ; 1
55
  d0:  80 93 62 00   sts  0x0062, r24
56
  }
57
  
58
  if(inc1 & 0x01)
59
  d4:  50 ff         sbrs  r21, 0
60
  d6:  0a c0         rjmp  .+20       ; 0xec <decodeGrayCode+0x5e>
61
  {
62
    inc1_old += inc1;
63
  d8:  95 0f         add  r25, r21
64
  da:  90 93 61 00   sts  0x0061, r25
65
    inc1_value += (inc1 & 0x02) - 1;
66
  de:  52 70         andi  r21, 0x02  ; 2
67
  e0:  80 91 63 00   lds  r24, 0x0063
68
  e4:  85 0f         add  r24, r21
69
  e6:  81 50         subi  r24, 0x01  ; 1
70
  e8:  80 93 63 00   sts  0x0063, r24
71
  ec:  08 95         ret

MfG
Falk

von yalu (Gast)


Lesenswert?

> Optimierung -Os

Ups, da fällt mir ein, ich habe das kleine Beispiel in meinem letzten
Post mit -O2 kompiliert. Mit -Os wird der Code zwar 4 Worte kürzer,
dafür braucht er etwa doppelt so viele Zyklen, weil jetzt statt ein
24-Bit-Wort zweimal nach links zu schieben ein 16 Bit-Wort in einer
Schleife um 6 Bits nach rechts geschoben wird. Das Assemblerlisting
von Robert sieht übrigens auch nach -Os aus.

von Robert S. (Gast)


Lesenswert?

Ja ich hab Optimierung Os. Ich hab bereits mit der WinAVR Version 
20071221 getestet. Die liefert jedoch den gleichen Code...

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.