Forum: Mikrocontroller und Digitale Elektronik Highspeed Software SPI AVR


von Kai F. (kai-) Benutzerseite


Lesenswert?

Hallo Forum,

ich habe gerade heute eine Software SPI für 3 LED Treiber geschrieben, 
die jeweils die Datenpins (SIN1 bis SIN3) an einem Pin von einem mega88 
verbunden hat.
Es funktioniert auch alles soweit ganz hervorragend, allerdings bin ich 
mir nicht sicher ob man die Routine nicht vielleicht ein wenig schneller 
machen kann, da sie in einer for Schleife hängt und sich somit schon 
wenige Clocks bemerkbar machen würden.

Hinter SIN1 bis SIN3 verbirgt sich nichts weiteres als PORTD.3 bis 
PORTD.5, was mein Compiler (CodevisionAVR) bestimmt recht efficient 
umsetzt.
Daten[] ist ein Array aus dem Struct:
1
struct data
2
3
{
4
    unsigned char gs[24];
5
    unsigned char dc[12];
6
};
7
8
// Beginn der SPI MSB first
9
SIN1=Daten[0].dc[i]>>7;
10
SIN2=Daten[1].dc[i]>>7;
11
SIN3=Daten[2].dc[i]>>7;
12
SCLK=HIGH;
13
SCLK=LOW;
14
15
SIN1=(Daten[0].dc[i]&0b01000000)>>6;
16
SIN2=(Daten[1].dc[i]&0b01000000)>>6;
17
SIN3=(Daten[2].dc[i]&0b01000000)>>6;
18
SCLK=HIGH;
19
SCLK=LOW;
20
21
SIN1=(Daten[0].dc[i]&0b00100000)>>5;
22
SIN2=(Daten[1].dc[i]&0b00100000)>>5;
23
SIN3=(Daten[2].dc[i]&0b00100000)>>5;
24
SCLK=HIGH;
25
SCLK=LOW;
26
27
SIN1=(Daten[0].dc[i]&0b00010000)>>4;
28
SIN2=(Daten[1].dc[i]&0b00010000)>>4;
29
SIN3=(Daten[2].dc[i]&0b00010000)>>4;
30
SCLK=HIGH;
31
SCLK=LOW;
32
33
SIN1=(Daten[0].dc[i]&0b00001000)>>3;
34
SIN2=(Daten[1].dc[i]&0b00001000)>>3;
35
SIN3=(Daten[2].dc[i]&0b00001000)>>3;
36
SCLK=HIGH;
37
SCLK=LOW;
38
39
SIN1=(Daten[0].dc[i]&0b00000100)>>2;
40
SIN2=(Daten[1].dc[i]&0b00000100)>>2;
41
SIN3=(Daten[2].dc[i]&0b00000100)>>2;
42
SCLK=HIGH;
43
SCLK=LOW;
44
45
SIN1=(Daten[0].dc[i]&0b00000010)>>1;
46
SIN2=(Daten[1].dc[i]&0b00000010)>>1;
47
SIN3=(Daten[2].dc[i]&0b00000010)>>1;
48
SCLK=HIGH;
49
SCLK=LOW;
50
51
SIN1=Daten[0].dc[i]&0b00000001;
52
SIN2=Daten[1].dc[i]&0b00000001;
53
SIN3=Daten[2].dc[i]&0b00000001;
54
SCLK=HIGH;
55
SCLK=LOW;

wäre super wenn da jemand eine Idee hätte

Gruß
Kai

von hans (Gast)


Lesenswert?

Hallo Kai,

schau doch mal, ob dein compiler immer neu auf das Array zugreift.
"Daten[0].dc[i]" wird 8 mal benötigt. ([1][2] auch)
Evtl. vorne eine Zuweisung Dummy_0 (1 2) die im Register
gehalten wird.
Kurz im Assemblerlisting prüfen.
Gruß Hans

von Kai F. (kai-) Benutzerseite


Lesenswert?

Da ich mich fast nicht mit Assembler auskenne hab ich mal mittendrin den 
entsprechenden Teil des Codes herauskopiert:
1
CBI  0xB,6
2
3
;     165 
4
5
;     166                 SIN1 = (Daten[0].dc[i] & 0b01000000) >> 6;
6
7
  __POINTW1MN _Daten,24
8
9
  ADD  R30,R16
10
11
  ADC  R31,R17
12
13
  LD   R30,Z
14
15
  ANDI R30,LOW(0x40)
16
17
  SWAP R30
18
19
  ANDI R30,0xF
20
21
  LSR  R30
22
23
  LSR  R30
24
25
  CPI  R30,0
26
27
  BRNE _0x1F
28
29
  CBI  0xB,3
30
31
  RJMP _0x20
32
33
_0x1F:
34
35
  SBI  0xB,3
36
37
_0x20:
38
39
;     167                 SIN2 = (Daten[1].dc[i] & 0b01000000) >> 6;
40
41
  __POINTW1MN _Daten,60
42
43
  ADD  R30,R16
44
45
  ADC  R31,R17
46
47
  LD   R30,Z
48
49
  ANDI R30,LOW(0x40)
50
51
  SWAP R30

ich hatte überlegt ob es nicht vielleicht noch eine geschicktere 
Möglichkeit gibt die Bits zu maskieren, weil ich sie ja alle der Reihe 
nach brauche, bin aber zu keinem Ergebnis gekommen

von Kai F. (kai-) Benutzerseite


Lesenswert?

mit den dummies komme ich auf folgenden ASM Code, der für mich etwas 
schlanker aussieht:
1
;     174                 SIN2 = (dummy2 & 0b01000000) >> 6;
2
3
  MOV  R30,R18
4
5
  ANDI R30,LOW(0x40)
6
7
  SWAP R30
8
9
  ANDI R30,0xF
10
11
  LSR  R30
12
13
  LSR  R30
14
15
  CPI  R30,0
16
17
  BRNE _0x21
18
19
  CBI  0xB,4
20
21
  RJMP _0x22
22
23
_0x21:
24
25
  SBI  0xB,4
26
27
_0x22:

von hans (Gast)


Lesenswert?

Vermutung stimmt. Er adressiert über register immer
neu.
-> also Dummyvariablen für Daten[0].dc[i] etc.

Maskierung ist scheinbar ok.
hans

von Kai F. (kai-) Benutzerseite


Lesenswert?

hab es jetzt mal für die gesamte Senderoutine geändert und statt 4,6 
Sekunden braucht er jetzt nur noch 4,0
Ist zwar alles von Hand gemessen, aber es hat sich schonmal gelohnt, 
vielen Dank :)
Das Programm ist gleichzeitig auch noch kleiner geworden

Hat zur Maskierung vielleicht jemand noch eine Idee?
Ich hatte überlegt ob es vielleicht möglich ist das Byte immer um eins 
nach links zu verschieben und abzufragen ob es einen overflow gibt oder 
irgendetwas in der Art. Die Größe des Programmcodes ist erstmal egal.

Wäre für weiteres Input sehr dankbar :)

Gruß
Kai

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wie wär das?

Beitrag "geht es noch schneller in ASM?"

Oder sowas in C:
1
unsigned char sin1 = Daten[0].dc[i];
2
unsigned char sin2 = Daten[1].dc[i];
3
unsigned char sin3 = Daten[2].dc[i];
4
5
unsigned char k;
6
7
for (k=0; k < 8; k++)
8
{
9
   SIN1 = LOW; if (sin1 & 0x80) SIN1 = HIGH; sin1 >>= 1;
10
   SIN2 = LOW; if (sin2 & 0x80) SIN2 = HIGH; sin2 >>= 1;
11
   SIN3 = LOW; if (sin3 & 0x80) SIN3 = HIGH; sin3 >>= 1;
12
   SCLK=HIGH;
13
   SCLK=LOW;
14
}

von Kai F. (kai-) Benutzerseite


Lesenswert?

Hallo
das ist eine ganz nette Idee, allerdings kann ich mir nicht wirklich 
vorstellen, dass sie schneller ist, weil ich bei deiner Version zweimal 
auf den Datenpin SIN123 zugreifen muss und da sich dahinter auch eine 
Maskierung eines Bits versteckt, würde ich mal tippen, dass es langsamer 
ist, werde es aber morgen mal versuchen. Heute studiere ich nochmal den 
geposteten Beitrag

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sind 6 Ticks pro SINX (wenn Dein Compiler gut ist).

avr-gcc macht das:
1
  ldi r19,lo8(7)
2
.L107:
3
  cbi 56-0x20,0
4
  sbrc r18,7
5
  sbi 56-0x20,0
6
  lsr r18
7
  cbi 56-0x20,1
8
  sbrc r25,7
9
  sbi 56-0x20,1
10
  lsr r25
11
  cbi 56-0x20,2
12
  sbrc r24,7
13
  sbi 56-0x20,2
14
  lsr r24
15
  sbi 56-0x20,3
16
  cbi 56-0x20,3
17
  subi r19,lo8(-(-1))
18
  brpl .L107

Spart zum einen Platz und ist schneller als Du denkst, weil das genze 
Rumgeschiebe wegfällt. Schneller geht's dann wenn man die Schleife noch 
aufrollt, und noch mehr rausholen geht per Assembler wie im Link oben.

von Kai F. (kai-) Benutzerseite


Lesenswert?

der Assembler sagt mir zwar nicht sehr viel ich werde es morgen aber mal 
ausprobieren, dass ich die Schleife auflöse und mir damit das >>= 1 
erspare
Ich werde berichten ob es einen Unterschied macht.
Sollte ich eigentlich generell die Daten erst aus dem Struct holen und 
zwischenspeichern auch wenn ich sie nur zweimal brauche? Würde sich ja 
fast anbieten
Konkret geht es um eine Array Konvertierung, die jedes Mal vor der 
Senderoutine abläuft um die Daten invertiert hintereinander zu ordnen
1
Daten[i].gs[0] = single_color[i].gs[15] >> 4;       
2
3
Daten[i].gs[1] = single_color[i].gs[15] << 4;
4
5
Daten[i].gs[1] |= single_color[i].gs[14]>> 8;
6
7
Daten[i].gs[2] = single_color[i].gs[14];     
8
9
        
10
11
Daten[i].gs[3] = single_color[i].gs[13] >> 4;       
12
13
Daten[i].gs[4] = single_color[i].gs[13] << 4;
14
15
Daten[i].gs[4] |= single_color[i].gs[12]>> 8;
16
17
Daten[i].gs[5] = single_color[i].gs[12]; 
18
19
        
20
21
Daten[i].gs[6] = single_color[i].gs[11] >> 4;       
22
23
Daten[i].gs[7] = single_color[i].gs[11] << 4;
24
25
Daten[i].gs[7] |= single_color[i].gs[10]>> 8;
26
27
Daten[i].gs[8] = single_color[i].gs[10]; 
28
29
        
30
31
Daten[i].gs[9]  = single_color[i].gs[9] >> 4;       
32
33
Daten[i].gs[10] = single_color[i].gs[9] << 4;
34
35
und noch viel weiter :P

Vielen Dank schonmal für die Hilfe

PS: Deine Homepage ist sehr schön und übersichtlich gestaltet und vor 
allem hast du komplett fertige Projekte vorzuweisen!! Respekt :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kai Franke wrote:

> Sollte ich eigentlich generell die Daten erst aus dem Struct holen und
> zwischenspeichern auch wenn ich sie nur zweimal brauche? Würde sich ja
> fast anbieten

Ob es notwendig ist hängt von Deinen Geschwindigkeitsvorgaben ab und 
davon, wie gut der Compiler optimiert und wieviel Informationen er 
bekommt.
1
> Daten[i].gs[0] = single_color[i].gs[15] >> 4;
2
> Daten[i].gs[1] = single_color[i].gs[15] << 4;
3
> Daten[i].gs[1] |= single_color[i].gs[14]>> 8;
4
> Daten[i].gs[2] = single_color[i].gs[14];

Wenn Daten[i].gs[0] zur gleichen Adresse auflöst wie 
single_color[i].gs[15], dann ergeben sich zB andere Werte, als wenn das 
nicht der Fall ist.

Nun könnte man einwänden, das sei pathologisch und wer Symbole so 
definiert sei selber Schuld. Dennoch darf ein Compiler Optimierungen 
nicht machen, wenn er die Ungleichheit nicht nachweisen kann. Und 
Schalter wie -optimize-if-code-not-braindead hab ich noch nie gesehen 
;-)

Um guten Code aus nem Compiler zu bekommen, ist es lohnend, sich 
anzuschauen, was er aus dem C-Code so bastelt und bei welchen 
C-Konstrukten sich Hilfestellung lohnt. Auf dem PC ist einem das 
Wurscht, aber auf nem kleinen µC ist die Gemengelage etwas anders. Und 
Asseneber-Code zu überfliegen und kurz zu beurteilen ist wesentlich 
weniger Arbeit, als selber in Assembler zu proggen.

ZB erkennt man mehrfache Speicherzugriffe, und kann dem Compiler dann 
Hilfestellung über lokale Vaiablen geben, in die man die Werte lädt.
Der C-Code wird dadurch nicht schöner, aber was man an asm-Instruktionen 
spart, spart man dann an Speicher und an Laufzeit.

von Kai F. (kai-) Benutzerseite


Lesenswert?

Hallo,
wie versprochen habe ich das heute alles mal ausprobiert und habe 
durchaus wieder etwas Zeit eingespart. Jetzt wollte ich das ganze auch 
noch bei der Arraykonvertierung ausnutzen, jedoch stimmt da irgendetwas 
noch nicht.
So funktioniert der Code:
1
Daten[i].dc[0] = single_color[i].dc[15] << 2;
2
Daten[i].dc[0]|= single_color[i].dc[14] >> 4; 
3
Daten[i].dc[1] = single_color[i].dc[14] << 4;
4
Daten[i].dc[1]|= single_color[i].dc[13] >> 2;
5
Daten[i].dc[2] = single_color[i].dc[13] << 6;
6
Daten[i].dc[2]|= single_color[i].dc[12];
7
... usw ...

so allerdings nicht mehr und ich sehe einfach nicht wieso
1
dummy2 = single_color[i].dc[15] << 2;
2
dummy3 = single_color[i].dc[14];
3
dummy2|= dummy3 >> 4;
4
Daten[i].dc[0] = dummy2;
5
6
dummy2 = dummy3 << 4;
7
dummy3 = single_color[i].dc[13];
8
dummy2|= dummy3 >> 2;
9
Daten[i].dc[1] = dummy2;
10
11
dummy2 = dummy3 << 6;
12
dummy2 |= single_color[i].dc[12];
13
Daten[i].dc[2] = dummy2;

es handelt sich bei allen Variablen um unsigned chars

von Kai F. (kai-) Benutzerseite


Lesenswert?

Entwarnung!
Der Code funktionert so wie ich ihn gepostet habe, ich dachte nur, dass 
an der Übertragung was nicht stimmt, weil die LEDs angefangen haben zu 
flackern. Das war aber nur so, weil ich während der Datenübertragung 
alle LEDs ausgeschaltet hatte (ist laut Datenblatt empfohlen) und die 
Konvertierung jetzt so schnell ging, dass man dieses AN und AUS schalten 
als Flackern gesehen hat. Ich habe jetzt den netten Hinweis des 
Datenblatts ignoriert und es funktioniert wunderbar.

Wenn jemand eine Ansteuerung für den TLC5946 braucht, soll er sich bei 
mir melden, ich stelle es aber auch noch als Open Source in die 
Codesammlung.

Vielen Dank an alle!

Grüße
Kai

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.