Encoder auswerten, Assembler Code unverständlich

Persönliche Seite #959014
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
Gast #959176
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.
#959278
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
Gast #959374
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.

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