C Software SPI

die Schleife scheint über R24:R25 zu laufen.
Stimmt, habe ich ganz übersehen, GCC benutzt die beiden Register als Schleifenzähler.
Besser optimiert braucht man die beiden Register nicht, sondern nur die Abfrage ob i null ist, wie in meinem vorigen Beitrag beschrieben.

Mein Vorschlag war:
Da fehlt noch das or.
Ansonsten wird das aber noch ganz anders, weil:
Letztendlich willst Du aber nicht im Kreis springen sondern stattdessen auf die Clock-Flanken reagieren...
 
Stimmt, habe ich ganz übersehen, GCC benutzt die beiden Register als Schleifenzähler.
Besser optimiert braucht man die beiden Register nicht, sondern nur die Abfrage ob i null ist, wie in meinem vorigen Beitrag beschrieben.


Da fehlt noch das or.
Ansonsten wird das aber noch ganz anders, weil:
Du meinst die "If" Abfrage die du gepostet hattest oder was meinst du mit != 0 ?

Würde es jetzt so verstehen..


CodeBox C
if( SPI_PORT & SPI_PIN != 0)
 
Was'n für'n OR??

  1. Das Result ist mit 0x01 vorinitialisiert
  2. Result wird (final bei jeder Clockflanke) einmal nach links geschoben
  3. anschließend wird der Pin mit SBIC geprüft, nur bei high wird der nächste Schritt ausgeführt (Skip if Pin Clear)
  4. Result wird inkrementiert (also das LSB gesetzt, Pin war ja wie gesagt high)
  5. Wenn das Carry (durch Schritt 2) gesetzt wurde, kann hier das empfangene Byte aus Result freigegeben, und anschließend auf 0x01 zurückgesetzt werden.
Bei jedem Durchlauf von 2, 3, 4 wird salopp gesagt der Zustand des Pins in das Result Reingerollt, nach acht Durchläufen steht das ganze Byte drin, und die Initialisierungs-Eins ist im Carry gelandet, was in Schritt 5 ausgewertet wird.
Finde ich recht effizient...

Wie das in C zu verpacken ist, weiß ich(!) nicht...
 
Verstehe nicht was du damit meinst?

Ich nehme an, dieses meinst du ...

for (uint8_t i = 1; i; i <<= 1)

Ich vermute, der Compiler weiß hier von vornherein, wieviele Schleifendurchgänge notwendig sind und "zählt" diese (Initialisiert Register R24 R25, dekrementiert in der Schleife und prüft auf 0) anstelle i auf 0 (unwahr) zu prüfen. Möglicherweise ist das abhängig von der Optimierungseinstellung, wie es letztendlich gemacht wird?!
 
Verstehe nicht was du damit meinst?
Du fragtest, wann "i" unwahr wird. Das habe ich versucht in Beitrag 19 zu beantworten.
Ich vermute, der Compiler weiß hier von vornherein, wieviele Schleifendurchgänge notwendig sind und "zählt" diese (Initialisiert Register R24 R25, dekrementiert in der Schleife und prüft auf 0) anstelle i auf 0 (unwahr) zu prüfen.
Habe erst im Disassemblerlisting gesehen, daß GCC da eine überflüssige Schleife eingebaut hat, anstatt einfach nur auf null zu prüfen.
Vielleicht probiere ich heute Abend mal 'ne andere Optimierungseinstellung aus.
 
Solch trickreiche Varianten wird man in C in Inlineassembler schreiben, da man sonst keinen direkten Zugriff auf die Flags hat.
Ja, mit inline-ASM ist klar. Aber Du hattest ja schon ein paar Beispiele gezeigt, wo der Compiler recht gut optimiert - wenn man den Code entsprechend verfaßt.
In #9 wurde zB die If Then Geschichte rausoptimiert.

Daß ich grundsätzlich zwei Wege sehe, hatte ich ja schon angedeutet:
1.: das Input-Bein wird in irgendeiner Variante abgefragt (Laden, maskieren, Vergleichen und dann bedingt springen, oder direkt im I/O abfragen mit SBIC etc...)
2.: der Pin Zustand wird irgendwo zwischengespeichert (idealerweise im T ) und dann (irgendwie) in das geschobene Result übertragen.

Beide Varianten gabs hier ja schon ansatzweise - den Code aus #9 könnte man wie folgt verfeinern...
R28=Result wieder mit 0x01 vorinitialisiert, wie gehabt...
bei jeder gewählten Clock-Flanke:


CodeBox Assembler
LSL R28
IN R24, 0x19
BST R24, 3
BLD R28, 0
anschließend kann dann wie gehabt das eventuelle Carry ausgewertet werden, um das ganze Byte zu erkennen.
Wie gefordert kein konditionaler Sprung (wie SBIC), dafür ein Register mehr im Einsatz. Wahrscheinlich auch etwas(!) langsamer.
 
Das ist ein bisschen wie, wenn du neben der Fahrertür deines Autos stehst, läufst um das Auto rum, steigst auf der Beifahrerseite ein und krabbelst zur Fahrerseite, weil du die if-Fahrertür nicht nutzen oder es einfach mal anders probieren möchtest ;):D Ok, sowas habe ich auch schon mal gemacht. Es gibt auch noch viel mehr Wege ;)
Das ist, wie der Fachmann sagt, von hinten durch die Brust in's Auge ;)
 
Aber Du hattest ja schon ein paar Beispiele gezeigt, wo der Compiler recht gut optimiert - wenn man den Code entsprechend verfaßt.
In #9 wurde zB die If Then Geschichte rausoptimiert.
Die Version in Beitrag 11 wurde sogar noch besser optimiert.

Die Schleife kriegt GCC anscheinend nicht besser hin.
Mit -O3 macht er Loop unrolling (schneller, aber viel größer).
Mit -Og (optimiert für debugging):

CodeBox Assembler
       for (uint8_t i = 1; i; i <<= 1)
00000058  LDI R24,0x01       Load immediate
00000059  RJMP PC+0x0004     Relative jump
           if (PINA & (1<<PA3))
0000005A  SBIC 0x19,3        Skip if bit in I/O register cleared
               result |= i;
0000005B  OR R28,R24         Logical OR
0000005C  LSL R24            Logical Shift Left
0000005D  CPSE R24,R1        Compare, skip if equal
0000005E  RJMP PC-0x0004     Relative jump
Mag sein, daß es nicht besser wird, weil ich die Zeilen zum Testen nur schnell in ein anderes Projekt eingefügt habe. Vielleicht sieht es anders aus, wenn man ein eigenes Projekt dafür anlegt. Vielleicht spielt es auch eine Rolle, welchen Controller man wählt...

Bin jetzt auch noch nicht dazu gekommen, darüber nachzudenken, wie man es noch anders formulieren könnte. Dazu wäre es auch hilfreich zu wissen, ob Master (Clock generieren) oder Slave (auf Clock reagieren), nur Empfangen oder Senden oder beides...
 
Moin Moin,

habe jetzt mal meine "Software SPI" Bibliothek fertig gestellt. Kann mal jemand drüber fliegen wenn er bisschen Luft hat?
Es geht mir speziell um die "SPI Modes" ob Ich das so richtig implementiert hab.

Vielen dank!
 

Anhänge

  • spi_soft.c
    1,6 KB · Aufrufe: 3
  • spi_soft.h
    1,7 KB · Aufrufe: 3
Hast Du es ausprobiert? Funktioniert es?

Auf den ersten Blick scheint es, als hättest Du noch nicht bemerkt, daß bei SPI immer ein Byte gesendet und GLEICHZEITIG ein Byte empfangen wird. Warum also eine Funktion für Senden und eine für Empfangen?
Es reicht doch eine, die beides gleichzeitig macht.

Desweiteren wäre es hilfreich, (zumindest beim ersten mal,) ein compilierbares Projekt hochzuladen...
Ohne hard_def.h und Controllertyp zu kennen, kann man sich selbst keins bauen...
 
Auf den ersten Blick scheint es, als hättest Du noch nicht bemerkt, daß bei SPI immer ein Byte gesendet und GLEICHZEITIG ein Byte empfangen wird. Warum also eine Funktion für Senden und eine für Empfangen?
Es reicht doch eine, die beides gleichzeitig macht.
Genau solches Feedback brauche Ich. Danke! Habe es jetzt erstmal nur mit „Mode 1“ testen können, dass funktionierte. Das man gleichzeitig auch ein Byte einlesen kann war mir bewusst. Nennt man glaub auch „Full Duplex“. Das ist denke auch sinnvoll das direkt beim schreiben mit ein zu bauen.

Das mit dem Kontroller.: Das soll eine Universelle Bibliothek sein die für etliche Chips gilt.

Alles andere werde Ich noch verbessern.
 
@Mikro23
Habe das mit der "senden / lesen" Routine mal ergänzt.


CodeBox C
uint8_t spiSoftTxRx( uint8_t byte )
{
 uint8_t n = 0;
 uint8_t ret = 0;
 for (n = 0 ; n < 8 ; n++)
 {
  #if  ( CPOL == 1 )
   PORT(SPI_SOFT_SCK_DDR) |=  (1<<SPI_SOFT_SCK_BP);
  #elif ( CPOL == 0 )
   PORT(SPI_SOFT_SCK_DDR) &= ~(1<<SPI_SOFT_SCK_BP);
  #endif
 
  /*
  * Daten ausgeben
  */
  if (byte & 0x80)
  {
   PORT(SPI_SOFT_MOSI_DDR) |=  (1<<SPI_SOFT_MOSI_BP);
  }
  else
  {
   PORT(SPI_SOFT_MOSI_DDR) &= ~(1<<SPI_SOFT_MOSI_BP);
  }
  byte <<= 1;
 
  #if  ( CPOL == 1 )
   PORT(SPI_SOFT_SCK_DDR) &=  ~(1<<SPI_SOFT_SCK_BP);
  #elif ( CPOL == 0 )
   PORT(SPI_SOFT_SCK_DDR) |=   (1<<SPI_SOFT_SCK_BP);
  #endif
 
  /*
  * Daten einsammeln
  */
  if( ( SPI_SOFT_MISO_PIN ) & ( 1<<SPI_SOFT_MISO_BP ) )
  {
   ret |= n;
  }
 
 }
 return ret;
}


Ehrlich gesagt bin Ich mir jetzt überhaupt nicht mehr sicher ob das so richtig ist.
* CPHA (Clock Phase)
* 0: Daten werden bei steigender Taktflanke (=abh. von CPOL) eingelesen, bei fallender ausgegeben
* 1: Daten werden bei fallender Taktflanke eingelesen, bei steigender ausgegeben

Wobei Ich mir nicht sicher bin ob das wirklich richtig Implementiert ist.
Bei dem einlesen ist es doch nur wichtig, wie der Takt sich verhält oder?
 
Zuletzt bearbeitet:
Was hältst Du davon, ein Oszilloskop anzuschließen und die einzelnen Modi nacheinander auszuprobieren?
Dann wirst Du sehen, ob die Ausgabe mit der Theorie übereinstimmt.

Ohne jetzt länger drüber nachzudenken, würde ich sagen, daß das Einlesen zwischen den Takten erfolgen sollte, bei der Ausgabe ist es egal, aber es kann sein, daß das nicht für alle Modi gleichermaßen gilt.
 
Ohne jetzt länger drüber nachzudenken, würde ich sagen, daß das Einlesen zwischen den Takten erfolgen sollte, bei der Ausgabe ist es egal, aber es kann sein, daß das nicht für alle Modi gleichermaßen gilt.
Habe es zwischendurch auch schon gemerkt. Ist jetzt direkt vor im "Zwischenschritt" mit drin.
 
Ehrlich gesagt bin Ich mir jetzt überhaupt nicht mehr sicher ob das so richtig ist.
* CPHA (Clock Phase)
* 0: Daten werden bei steigender Taktflanke (=abh. von CPOL) eingelesen, bei fallender ausgegeben
* 1: Daten werden bei fallender Taktflanke eingelesen, bei steigender ausgegeben
???
Das kommt doch auch auf die Clock-Polarität an...
Grundsätzlich gilt:
  • bei CPOL=0 ist die Clk im idle low (folglich wäre die erste Flanke steigend), bei CPOL=1 high (folglich wäre die erste Flanke fallend).
  • bei CPHA=0 werden die Daten direkt nach der ersten (bzw jeder ungeraden) Flanke übernommen/gelesen (müssen also vorher, nach dem Chip Select bzw bei jeder geraden Flanke bereitgestellt werden -> einige Sensoren/ADCs machen das so) - bei CPHA=1 werden die Daten erst bei der ersten (und jeder ungeraden) Flanke bereitgestellt, und dann erst bei der zweiten (und jeder geraden) übernommen/eingelesen
Auf den ersten Blick scheint es, als hättest Du noch nicht bemerkt, daß bei SPI immer ein Byte gesendet und GLEICHZEITIG ein Byte empfangen wird. Warum also eine Funktion für Senden und eine für Empfangen?
Es reicht doch eine, die beides gleichzeitig macht
Wobei die zweite Richtung bei manchen Anwendungsfällen unnötig ist. Da könnte man dann ein Bein einsparen, wenn die Transfer-Routine eine Möglichkeit bietet, die rauszuschiebenden Daten ins Nirvana wandern zu lassen, oder irgendwelche beliebigen Daten einzutakten.
Also daß für DI und DO nicht zwingend Beine vorgegeben müssen.
(bei den mir bekannten AVR geht das in Hardware nicht - wird das SPI aktiviert, werden immer beide Richtungen (mit beiden Datenpins) auf's SPI geschaltet. Aber bei einer Softwarelösung kann man ja stricken, was man will...)
Wenn man für sowas extra-Subroutinen anlegt, sind diese Spezialfälle schneller - insgesamt kostet es natürlich mehr Programmspeicher.
 
Mit großartigem Speed habe Ich jetzt auch nicht gerechnet. Musste feststellen, dass Ich bei Hardware SPI deutlich zügiger unterwegs bin als mit der Softwarelösung.
Das ist aber erstmal egal. Mir ging es jetzt in erster Linie erstmal darum, zu testen ob die "MODES" auch alle so funktionieren wie sie sollen.
 
Musste feststellen, dass Ich bei Hardware SPI deutlich zügiger unterwegs bin als mit der Softwarelösung.
Das ist logisch. HW-SPI kann mit einer CLK von einem viertel des Systemtaktes arbeiten, also hättest Du vier Takte, um auf die Clock-Flanke zu reagieren, das Bein einzulesen, das Ergebnis zu schieben/rollen. Um eine halbe Phase versetzt hast Du die andere Flanke, wo du äquivalent das Ein Bit des zu sendenden Bytes ausgeben mußt. Unter ASM (möglicherweise auch eingebettetem) ist das möglicherweise(!) sogar machbar... aber sicher nicht einfach.
Außerdem hast Du in HW zwischen zwei Bytes Zeit, das empfangene Byte zu verarbeiten, das nächste zu sendende vorzubereiten. In SW mußt Du Dich stattdessen bereits um den nächsten Transfer kümmern.

Die vier Takte hast Du übrigens, weil sich de Slave auf den Takt "synchronisieren" muß - als Master kann die Hardware sogar mit halbem Systemtakt arbeiten.
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)