Forum: Compiler & IDEs SPI Master Code Optimierung


von nga (Gast)


Lesenswert?

Hallo Forum,

ich habe das Problem dass mein AVR-GCC nicht das macht was ich will:
Hier der Code:
1
//slave.h:
2
typedef struct {
3
  const uint8_t slaveSelectPin;
4
  volatile uint8_t * slaveSelectPort;
5
  const uint8_t outputSignalPin;
6
  volatile uint8_t * outputSignalPort;
7
} slave_t;
8
uint8_t transmitData(const slave_t *slave, uint8_t data);
9
10
//slave.c:
11
const slave_t slave0= {
12
  .slaveSelectPin = PB0,
13
  .slaveSelectPort = &PORTB,
14
  .outputSignalPin = PD2,
15
  .outputSignalPort = &PORTD
16
};
17
const slave_t slave1= {
18
  .slaveSelectPin = PB1,
19
  .slaveSelectPort = &PORTB,
20
  .outputSignalPin = PD3,
21
  .outputSignalPort = &PORTD
22
};
23
const slave_t slave2= {
24
  .slaveSelectPin = PB3,
25
  .slaveSelectPort = &PORTB,
26
  .outputSignalPin = PB2,
27
  .outputSignalPort = &PORTB
28
};
29
30
uint8_t transmitData(const slave_t *slave, uint8_t data) {
31
  uint8_t tmp;
32
  *(slave->slaveSelectPort) &= ~(1<<slave->slaveSelectPin);
33
  tmp = spi_transmit(data);
34
  *(slave->slaveSelectPort) |= (1<<slave->slaveSelectPin);
35
  return tmp;
36
}

Dieser Code erzeugt eine wahnsinnig lange ASM-Funktion:
1
000000e2 <transmitData>:
2
  e2:  cf 93         push  r28
3
  e4:  df 93         push  r29
4
  e6:  ec 01         movw  r28, r24
5
  e8:  86 2f         mov  r24, r22
6
  ea:  0e 94 60 00   call  0xc0  ; 0xc0 <spi_transmit>
7
  ee:  e9 81         ldd  r30, Y+1  ; 0x01
8
  f0:  fa 81         ldd  r31, Y+2  ; 0x02
9
  f2:  90 81         ld  r25, Z
10
  f4:  21 e0         ldi  r18, 0x01  ; 1
11
  f6:  30 e0         ldi  r19, 0x00  ; 0
12
  f8:  08 80         ld  r0, Y
13
  fa:  02 c0         rjmp  .+4        ; 0x100 <transmitData+0x1e>
14
  fc:  22 0f         add  r18, r18
15
  fe:  33 1f         adc  r19, r19
16
 100:  0a 94         dec  r0
17
 102:  e2 f7         brpl  .-8        ; 0xfc <transmitData+0x1a>
18
 104:  92 2b         or  r25, r18
19
 106:  90 83         st  Z, r25
20
 108:  df 91         pop  r29
21
 10a:  cf 91         pop  r28
22
 10c:  08 95         ret
Man beachte dass der Teil zum löschen des Pins auskommentiert ist, sonst 
wäre die gesamte Sequenz hinter dem call spi_transmit noch einma davor.

Nun meine Frage:
Kann ich den Compiler irgendwie dazu bewegen meinen Code mit dem Umweg 
über die "Konstante" genauso zu übersetzen wie wenn ich eine direkte 
Portzuweisung machen würde?

mfg
nga

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wenn du die Funktion "static" machst, dann kann er sie inline
ersetzen.  Ansonsten kann der Compiler natürlich innerhalb von
transmitData() nicht ahnen, mit welchen Parametern sie irgendwann
konkret gerufen wird.  Wäre höchstens, dass die linker relaxations
das noch rausreißen können, aber das kann ich mir angesichts der
Komplexität der notwendigen Optimierung schwer vorstellen.

von nga (Gast)


Lesenswert?

Tatsache, das mit dem static hilft schon einmal.
Danke schon mal.
Würde evtl ein C++ Compiler besseren Code erzeugen (wegen echten 
Konstanten)? Evtl auch mit templates?
eigentlich sollte es ein C-Programm werden...

Oder gibt es andere Vorschläge?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

nga schrieb:
> Würde evtl ein C++ Compiler besseren Code erzeugen (wegen echten
> Konstanten)?

Auch nur unter vergleichbaren Umständen: wenn die Adressen der
jeweiligen Ports und Portbits zur Compilezeit nicht bekannt sind, dann
kann der Compiler natürlich keine IN/OUT-Befehle generieren (für die
die Port- und Bitnummer ja im Opcode stehen), sondern nur LDS/STS.

von Eric B. (beric)


Lesenswert?

Wie wird es wenn du alles in ein Array packst und über den Index 
auswählst? Das hat noch den entscheidenden Vorteil, dass der Aufrufer 
nix mit den slave_t zu tun haben muss.

Ausserdem kann das Bit-shiften in der Configuration stattfinden.
1
//slave.h:
2
#define SLAVE_0 0
3
#define SLAVE_1 1
4
#define SLAVE_2 2
5
6
uint8_t transmitData(uint8_t id, uint8_t data);
7
8
//slave.c:
9
typedef struct
10
{
11
  const    uint8_t   slaveSelectPin;
12
  volatile uint8_t * slaveSelectPort;
13
  const    uint8_t   outputSignalPin;
14
  volatile uint8_t * outputSignalPort;
15
} slave_t;
16
17
const slave_t slave[]=
18
{
19
  { 1 << PB0, &PORTB, 1 << PD2, &PORTD },
20
  { 1 << PB1, &PORTB, 1 << PD3, &PORTD },
21
  { 1 << PB3, &PORTB, 1 << PB2, &PORTB }
22
};
23
24
 
25
uint8_t transmitData(uint8_t id, uint8_t data)
26
{
27
  uint8_t tmp;
28
29
  slave[id].slaveSelectPort &= ~(slave[id].slaveSelectPin);
30
  tmp = spi_transmit(data);
31
  slave[id].slaveSelectPort |=   slave[id].slaveSelectPin;
32
33
  return tmp;
34
}

EDIT:
Zur Run-Time Optimierung könnte man dann noch switch/casen über den 
index:
1
uint8_t transmitData(uint8_t id, uint8_t data)
2
{
3
  uint8_t tmp;
4
5
  switch(id)
6
  {
7
    case 0:
8
      slave[0].slaveSelectPort &= ~(slave[0].slaveSelectPin);
9
      tmp = spi_transmit(data);
10
      slave[0].slaveSelectPort |=   slave[0].slaveSelectPin;
11
    break;
12
13
    case 1:
14
      slave[1].slaveSelectPort &= ~(slave[1].slaveSelectPin);
15
      tmp = spi_transmit(data);
16
      slave[1].slaveSelectPort |=   slave[1].slaveSelectPin;
17
    break;
18
19
    case 2:
20
      slave[2].slaveSelectPort &= ~(slave[2].slaveSelectPin);
21
      tmp = spi_transmit(data);
22
      slave[2].slaveSelectPort |=   slave[2].slaveSelectPin;
23
    break;
24
25
    default:
26
      tmp = ~tmp;
27
    break;
28
  }
29
30
  return tmp;
31
}

: Bearbeitet durch User
von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

nga schrieb:
> Dieser Code erzeugt eine wahnsinnig lange ASM-Funktion:

Nicht nur das.
Du erzeugst 3 Structs a 6 Byte, die müssen befüllt, übergeben und wieder 
auseinander gefummelt werden.
Das kostet natürlich.

Du mußt schon dem Compiler etwas entgegenkommen und möglichts oft 
Konstanten benutzen.

Es reicht außerdem völlig, wenn man unterschiedliche Select-Pins hat.
Und da bietet es sich an, alle auf den selben Port zu legen und nur die 
Maske zu übergeben:
1
#include "sbit.h"
2
3
#define SSCK            PORT_B0
4
#define SMOSI           PORT_B1
5
#define SMISO_in        PIN_B2
6
7
#define SSEL_PORT       PORTB
8
#define SEL_MASK0       0x08
9
#define SEL_MASK1       0x10
10
#define SEL_MASK2       0x20
11
#define SEL_ALL         (SEL_MASK0 | SEL_MASK1 | SEL_MASK2)
12
13
static uint8_t sw_spi( uint8_t val )
14
{
15
  for( uint8_t i = 8; i; i-- ){
16
    SMOSI = 0;
17
    if( val & 0x80 )
18
      SMOSI = 1;
19
    SSCK = 1;
20
    SSCK = 0;
21
    val <<= 1;
22
    if( SMISO_in )
23
      val++;
24
  }
25
  return val;
26
}
27
28
void sw_spi_block( uint8_t *pt, uint8_t length, uint8_t ssel_mask )
29
{
30
  SSEL_PORT ^= ssel_mask;
31
  while( length-- ){
32
    *pt = sw_spi( *pt );
33
    pt++;
34
  }
35
  SSEL_PORT |= SEL_ALL;
36
}

Anbei das Listing.

von nga (Gast)


Lesenswert?

kurzes Update:

Peter Dannegger schrieb:
> Du mußt schon dem Compiler etwas entgegenkommen und möglichts oft
> Konstanten benutzen.

Das ist natürlich klar. Ich habe es ja versucht mittels const den 
Compiler die Variable als Konstante zu sehen, leider ist C da nicht so 
klar...

Peter Dannegger schrieb:
> Es reicht außerdem völlig, wenn man unterschiedliche Select-Pins hat.
> Und da bietet es sich an, alle auf den selben Port zu legen und nur die
> Maske zu übergeben

Ich stimme dir voll zu. Aber in dem struct werden auch noch die Daten 
gespeichert, deswegen wollte ich die 'Konstanten' dort speichern.

Bleibt abschließend zu sagen, dass

Eric B. schrieb:
> Zur Run-Time Optimierung könnte man dann noch switch/casen über den
> index:

das hier genau wie gewollt funktioniert hat:
es werden sbi und cbi Instruktionen erzeugt!

Danke an alle

von Eric B. (beric)


Lesenswert?

nga schrieb:
> Ich stimme dir voll zu. Aber in dem struct werden auch noch die Daten
> gespeichert, deswegen wollte ich die 'Konstanten' dort speichern.

Da liegt dann der grundsätzliche Fehler: Konfiguration (konstant -> kann 
ins ROM/Flash) und Nutzdaten (variabel -> muss ins RAM) soll man nicht 
mischen.

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.