Hier mal ein Puzzle für die AVR-Assembler-Hacker: Wie viele Taktzyklen
benötigt man pro Bit für eine Software-SPI Implementierung als Master?
Randbedingungen:
-Es werden nur Daten gesendet. Dazu sind zwei Leitungen notwendig: SCK
und MOSI.
-Beide Leitungen befinden sich auf dem gleichen Port und können demnach
mit einem Befehl geschrieben werden.
-Die Daten werden auf der steigenden Flanke von SCK gesampled. Demnach
können Daten auf der fallenden Flanke geändert werden.
-SCK muss nicht symmetrisch sein.
Ich komme auf ein Minimum von 5 Taktzyklen:
Wie man sieht, hat der Compiler schon einige ziemlich clevere
Optimierungen eingebaut. Auf den ersten Blick kann man mit Assembler nur
einen Taktzyklus herausoptimieren, mit Änderung der Funktion 2.
Die aktuelle C-Version ist schon schneller als der übliche verbreitete
Code:
Theoretisch (nicht getestet) sollte ein MASTER-SPI-TX wie folgt auch in
4 Zyklen gehen:
1
;; DATAR ... Register mit den zu transferierenden Daten
2
;; PORTX ... Das PORT Register für den Transfer
3
;; PINX ... Das PIN Register für den Transfer
4
;; OUTR ... Register mit dem "Standard" Wert für PORTX
5
;; PINMOSI ... Das bit im PORT/PIN Register für MOSI
6
;; PINSCK ... Das bit im PORT/PIN Register für SCK
7
;; SCKPINR ... Register mit dem PINSCK bit gesetzt (1<<PINSCK)
8
;; BITNUM ... das wievielte bit soll verschickt werden?
9
bst DATAR ,BITNUM
10
bld OUTR ,PINMOSI
11
out PORTX ,OUTR
12
out PINX ,SCKPINR
wenn man das bei den weiteren Bits etwas umsortiert, so kann SCK auch
symmetrisch sein - geht halt nur beim ersten Bit nicht.
Sieht dann halt so aus:
1
;; DATAR ... Register mit den zu transferierenden Daten
2
;; PORTX ... Das PORT Register für den Transfer
3
;; PINX ... Das PIN Register für den Transfer
4
;; OUTR ... Register mit dem "Standard" Wert für PORTX
5
;; PINMOSI ... Das bit im PORT/PIN Register für MOSI
6
;; PINSCK ... Das bit im PORT/PIN Register für SCK
7
;; SCKPINR ... Register mit dem PINSCK bit gesetzt (1<<PINSCK)
8
bst DATAR ,7
9
bld OUTR ,PINMOSI
10
out PORTX ,OUTR
11
bst DATAR ,6
12
out PINX ,SCKPINR
13
bld OUTR ,PINMOSI
14
out PORTX ,OUTR
15
bst DATAR ,5
16
out PINX ,SCKPINR
17
bld OUTR ,PINMOSI
18
out PORTX ,OUTR
19
bst DATAR ,4
20
out PINX ,SCKPINR
21
bld OUTR ,PINMOSI
22
out PORTX ,OUTR
23
bst DATAR ,3
24
out PINX ,SCKPINR
25
bld OUTR ,PINMOSI
26
out PORTX ,OUTR
27
bst DATAR ,2
28
out PINX ,SCKPINR
29
bld OUTR ,PINMOSI
30
out PORTX ,OUTR
31
bst DATAR ,1
32
out PINX ,SCKPINR
33
bld OUTR ,PINMOSI
34
out PORTX ,OUTR
35
bst DATAR ,0
36
out PINX ,SCKPINR
37
bld OUTR ,PINMOSI
38
out PORTX ,OUTR
SPI-MASTER-RX ist auch nicht viel aufwändiger:
1
;; DATAR ... Register mit den empfangenen Daten
2
;; PORTX ... Das PORT Register für den Transfer
3
;; PINX ... Das PIN Register für den Transfer
4
;; OUTR ... Register mit dem "Standard" Wert für PORTX
5
;; PINSCK ... Das bit im PORT/PIN Register für SCK
6
;; SCKPINR ... Register mit dem PINSCK bit gesetzt (1<<PINSCK)
7
;; PINMISO ... Das bit im PORT/PIN Register für MISO
8
;; BITNUM ... das wievielte bit soll verschickt werden?
9
out PORTX ,OUTR
10
in TMP ,PINX
11
bst TMP ,PINMISO
12
out PINX ,SCKPINR
13
bld DATAR ,BITNUM
und ist mit 5 Zyklen fast symmetrisch - fuer volle SCK-Symmetrie ein NOP
am Schluss anhaengen.
und zuletzt beides zusammengesetzt SPI-MASTER-TX/RX in 7 Zyklen:
1
;; DATAR ... Register mit den zu transferierenden und empfangenden Daten
2
;; OUTR ... Register mit dem "Standard" Wert für PORTX
3
;; PORTX ... Das PORT Register für den Transfer
4
;; PINX ... Das PIN Register für den Transfer
5
;; PINMOSI ... Das bit im PORT/PIN Register für MOSI
6
;; PINSCK ... Das bit im PORT/PIN Register für SCK
7
;; SCKPINR ... Register mit dem PINSCK bit gesetzt (1<<PINSCK)
8
;; PINMISO ... Das bit im PORT/PIN Register für MISO
9
;; BITNUM ... das wievielte bit soll verschickt werden?
10
bst DATAR ,BITNUM
11
bld OUTR ,PINMOSI
12
out PORTX ,OUTR
13
in TMP ,PINX
14
bst TMP ,PINMISO
15
bld DATAR ,BITNUM
16
out PINXX ,SCKPINR
und ist mit 7 Zyklen fast symmetrisch - fuer volle SCK-Symmetrie wieder
ein NOP am Schluss anhaengen.
Martin
P.s: Eines der Male wo das T-Flag wirklich nuetzlich ist...
P.p.s: ich bin nicht sicher, aber vielleicht liesse sich im TX/RX Fall
bei geschickten HW-Randbedingungen (z.b. "pin-wahl" MISO/MOSI auf
PORT0/7) auch die BST/BLD bloecke durch LSR/LSL und ROL/ROR ersetzen und
so eine Zyklus sparen.
Dürfte aber erfordern dass die PORT Werte der "restlichen" Pins egal
sind und sich ändern dürfen - sprich:
Alle anderen Pins sind auf INPUT mit externem Pullup - damit spielt
interner Pullup Wechsel keine Rolle.
Allerdings ist der Fall TX+RX gleichzeitig doch eher selten, sodass ich
mir das Austuefteln diese Variante spare...
Hallo Martin,
Super Trick! Das T-Flag existierte bei mir gedanklich gar nicht, so
wenig Nutzen hatte es bisher. Für diese Anwendung lässt es sich aber
ideal einsetzen.
Die Einsparung des zusätzlichen Taktzyklus kommt daher, dass Du die
Toggle-Funktion nutzt, statt SCK mit SBI zu setzen. Den gleichen Trick
könnte man auch mit der Bit-Afrage mit SBRS kombinieren, um auf 4
Taktzyklen pro Bit zu kommen. Allerdings ist die Variante mit dem T-Flag
wegen des symmetrischen Clocksignals natürlich eleganter.
Noch einen Taktzyklus einzusparen stelle ich mir schwierig vor. Selbst
mit den Shift-Befehlen muss man irgendwie gleichzeitig das Clock-Signal
setzten.
>Wie man sieht, hat der Compiler schon einige ziemlich clevere ...
Was er nicht eingebaut hat,
mit ein bisschen zus. Logic (mit Enab- u AVR-Clk -Anschluss) kann man
den SCLK autom schalten.
Dann kann man mit
MCUA schrieb:> Was er nicht eingebaut hat,> mit ein bisschen zus. Logic (mit Enab- u AVR-Clk -Anschluss) kann man> den SCLK autom schalten.
Naja, aber die Idee war doch eine reine Softwareimplementierung :)
Ich nutze den Code übrigens für Echtzeitdebugging, indem ich über zwei
unbenutzte Pins Debugginginformationen ausgebe und mit einem LA
analysiere. Das ist z.B. für V-USB Projekte auf dem AVR sehr nützlich.
MCUA schrieb:> mit ein bisschen zus. Logic (mit Enab- u AVR-Clk -Anschluss) kann man> den SCLK autom schalten.
Meintest du damit das Schalten eines Timer-Ausgangs (z.B. OC0A), der
dann als CLK verwendet wird? Könnte auch gehen.