www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Highspeed Software SPI AVR


Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
struct data

{
    unsigned char gs[24];
    unsigned char dc[12];
};

// Beginn der SPI MSB first
SIN1=Daten[0].dc[i]>>7;
SIN2=Daten[1].dc[i]>>7;
SIN3=Daten[2].dc[i]>>7;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b01000000)>>6;
SIN2=(Daten[1].dc[i]&0b01000000)>>6;
SIN3=(Daten[2].dc[i]&0b01000000)>>6;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b00100000)>>5;
SIN2=(Daten[1].dc[i]&0b00100000)>>5;
SIN3=(Daten[2].dc[i]&0b00100000)>>5;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b00010000)>>4;
SIN2=(Daten[1].dc[i]&0b00010000)>>4;
SIN3=(Daten[2].dc[i]&0b00010000)>>4;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b00001000)>>3;
SIN2=(Daten[1].dc[i]&0b00001000)>>3;
SIN3=(Daten[2].dc[i]&0b00001000)>>3;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b00000100)>>2;
SIN2=(Daten[1].dc[i]&0b00000100)>>2;
SIN3=(Daten[2].dc[i]&0b00000100)>>2;
SCLK=HIGH;
SCLK=LOW;

SIN1=(Daten[0].dc[i]&0b00000010)>>1;
SIN2=(Daten[1].dc[i]&0b00000010)>>1;
SIN3=(Daten[2].dc[i]&0b00000010)>>1;
SCLK=HIGH;
SCLK=LOW;

SIN1=Daten[0].dc[i]&0b00000001;
SIN2=Daten[1].dc[i]&0b00000001;
SIN3=Daten[2].dc[i]&0b00000001;
SCLK=HIGH;
SCLK=LOW;

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

Gruß
Kai

Autor: hans (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Da ich mich fast nicht mit Assembler auskenne hab ich mal mittendrin den 
entsprechenden Teil des Codes herauskopiert:
CBI  0xB,6

;        165 

;        166                 SIN1 = (Daten[0].dc[i] & 0b01000000) >> 6;

  __POINTW1MN _Daten,24

  ADD  R30,R16

  ADC  R31,R17

  LD   R30,Z

  ANDI R30,LOW(0x40)

  SWAP R30

  ANDI R30,0xF

  LSR  R30

  LSR  R30

  CPI  R30,0

  BRNE _0x1F

  CBI  0xB,3

  RJMP _0x20

_0x1F:

  SBI  0xB,3

_0x20:

;        167                 SIN2 = (Daten[1].dc[i] & 0b01000000) >> 6;

  __POINTW1MN _Daten,60

  ADD  R30,R16

  ADC  R31,R17

  LD   R30,Z

  ANDI R30,LOW(0x40)

  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

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mit den dummies komme ich auf folgenden ASM Code, der für mich etwas 
schlanker aussieht:
;        174                 SIN2 = (dummy2 & 0b01000000) >> 6;

  MOV  R30,R18

  ANDI R30,LOW(0x40)

  SWAP R30

  ANDI R30,0xF

  LSR  R30

  LSR  R30

  CPI  R30,0

  BRNE _0x21

  CBI  0xB,4

  RJMP _0x22

_0x21:

  SBI  0xB,4

_0x22:

Autor: hans (Gast)
Datum:

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

Maskierung ist scheinbar ok.
hans

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie wär das?

Beitrag "geht es noch schneller in ASM?"

Oder sowas in C:
unsigned char sin1 = Daten[0].dc[i];
unsigned char sin2 = Daten[1].dc[i];
unsigned char sin3 = Daten[2].dc[i];

unsigned char k;

for (k=0; k < 8; k++)
{
   SIN1 = LOW; if (sin1 & 0x80) SIN1 = HIGH; sin1 >>= 1;
   SIN2 = LOW; if (sin2 & 0x80) SIN2 = HIGH; sin2 >>= 1;
   SIN3 = LOW; if (sin3 & 0x80) SIN3 = HIGH; sin3 >>= 1;
   SCLK=HIGH;
   SCLK=LOW;
}

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sind 6 Ticks pro SINX (wenn Dein Compiler gut ist).

avr-gcc macht das:
  ldi r19,lo8(7)
.L107:
  cbi 56-0x20,0
  sbrc r18,7
  sbi 56-0x20,0
  lsr r18
  cbi 56-0x20,1
  sbrc r25,7
  sbi 56-0x20,1
  lsr r25
  cbi 56-0x20,2
  sbrc r24,7
  sbi 56-0x20,2
  lsr r24
  sbi 56-0x20,3
  cbi 56-0x20,3
  subi r19,lo8(-(-1))
  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.

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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
Daten[i].gs[0] = single_color[i].gs[15] >> 4;       

Daten[i].gs[1] = single_color[i].gs[15] << 4;

Daten[i].gs[1] |= single_color[i].gs[14]>> 8;

Daten[i].gs[2] = single_color[i].gs[14];     

        

Daten[i].gs[3] = single_color[i].gs[13] >> 4;       

Daten[i].gs[4] = single_color[i].gs[13] << 4;

Daten[i].gs[4] |= single_color[i].gs[12]>> 8;

Daten[i].gs[5] = single_color[i].gs[12]; 

        

Daten[i].gs[6] = single_color[i].gs[11] >> 4;       

Daten[i].gs[7] = single_color[i].gs[11] << 4;

Daten[i].gs[7] |= single_color[i].gs[10]>> 8;

Daten[i].gs[8] = single_color[i].gs[10]; 

        

Daten[i].gs[9]  = single_color[i].gs[9] >> 4;       

Daten[i].gs[10] = single_color[i].gs[9] << 4;

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 :)

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.
> Daten[i].gs[0] = single_color[i].gs[15] >> 4;
> Daten[i].gs[1] = single_color[i].gs[15] << 4;
> Daten[i].gs[1] |= single_color[i].gs[14]>> 8;
> 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.

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
Daten[i].dc[0] = single_color[i].dc[15] << 2;
Daten[i].dc[0]|= single_color[i].dc[14] >> 4; 
Daten[i].dc[1] = single_color[i].dc[14] << 4;
Daten[i].dc[1]|= single_color[i].dc[13] >> 2;
Daten[i].dc[2] = single_color[i].dc[13] << 6;
Daten[i].dc[2]|= single_color[i].dc[12];
... usw ...

so allerdings nicht mehr und ich sehe einfach nicht wieso
dummy2 = single_color[i].dc[15] << 2;
dummy3 = single_color[i].dc[14];
dummy2|= dummy3 >> 4;
Daten[i].dc[0] = dummy2;

dummy2 = dummy3 << 4;
dummy3 = single_color[i].dc[13];
dummy2|= dummy3 >> 2;
Daten[i].dc[1] = dummy2;

dummy2 = dummy3 << 6;
dummy2 |= single_color[i].dc[12];
Daten[i].dc[2] = dummy2;

es handelt sich bei allen Variablen um unsigned chars

Autor: Kai Franke (kai-) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.