Forum: Mikrocontroller und Digitale Elektronik SPI Slave - "Registerkonzept" mit STM32


von Stefan H. (cheeco)


Lesenswert?

Hallo Forum,

viele SPI Slaves (beispielsweise ADCs) haben ein Registerkonzept 
implementiert. Mit dem ersten Byte, welches der Master an den Slave 
sendet, wird bestimmt ob der Zugriff lesend oder schreibend ist und auf 
welche interne Register des ADCs man zugreifen möchte. Im nächsten Byte 
bekommt man dann entweder den Inhalt des Registers oder kann selbst 
etwas reinschreiben. Auf diese Weise kann der Zustand eines relativ 
komplexen Geräts recht elegant konfiguriert werden.

Zu meinem Problem: Ich möchte einen SPI-Slave entwerfen, der denselben 
internen Registeraufbau besitzt, damit der Master meinen Slave elegant 
konfigurieren kann. Das Problem ist nun, dass die Antwort des Slaves vom 
ersten empfangenen Byte abhängen würde. Kann man mit einem STM32 einen 
Interrupt nach dem ersten empfangenen Byte aufrufen und die 
entsprechenden Daten noch während des SPI-Zugriffs bereitstellen? Eine 
geringe Latenz des Interrupts könnte durch ein paar Nullbytes 
ausgeglichen werden.

Kann man das so lösen oder kommt dann die Mikrocontrollerpolizei? 
Sonderlich elegant scheint es nicht zu sein. Gibt es andere, bewährte 
Alternativen für diesen Fall?

Grüße,

Stefan

von Ralf (Gast)


Lesenswert?

Stefan H. schrieb:
> Kann man das so lösen oder kommt dann die Mikrocontrollerpolizei?
> Sonderlich elegant scheint es nicht zu sein. Gibt es andere, bewährte
> Alternativen für diesen Fall?
Warum sollte das nicht elegant sein? Welche andere Lösung würde dir 
einfallen? Genauso und nicht anders geht's aus meiner Sicht. "Unelegant" 
wird's, wenn die Anforderungen zu hoch sind.

> Zu meinem Problem: Ich möchte einen SPI-Slave entwerfen, der denselben
> internen Registeraufbau besitzt, damit der Master meinen Slave elegant
> konfigurieren kann. Das Problem ist nun, dass die Antwort des Slaves vom
> ersten empfangenen Byte abhängen würde. Kann man mit einem STM32 einen
> Interrupt nach dem ersten empfangenen Byte aufrufen und die
> entsprechenden Daten noch während des SPI-Zugriffs bereitstellen? Eine
> geringe Latenz des Interrupts könnte durch ein paar Nullbytes
> ausgeglichen werden.
Wichtig ist erstmal, wie schnell dein SPI-Master taktet. 50MHz wäre n 
bisschen arg schnell, es kommt drauf an, was du vorhast - prinzipiell so 
langsam wie möglich, so schnell wie nötig.
Aus der Hüfte geschossen fallen mir zwei Ansätze ein:
1) komplett Interruptgesteuert: nach jedem Byte wertest du aus, ob's 
Kommando (bzw. Schreib-/Leseadresse) oder Daten sind. Für's Schreiben 
ist es relativ einfach, die Schreibadresse geht als Offset auf einen 
Pointer, nachfolgende Bytes werden an diesen Pointer geschrieben und 
dieser dann inkrementiert. Lesen funktioniert im Prinzip genauso, nur 
dass du hier bereits beim Setzen des Pointers auch gleich ausliest, 
damit den Tx-Buffer füllst und auch hier den Pointer inkrementierst. 
Beim nächsten Interrupt jeweils dann schreiben/lesen. Das ganze jeweils 
solange wie das /CS-Signal aktiv ist, d.h. das deaktivieren des 
/CS-Signals bedeutet, dass ein komplettes Paket gelesen/geschrieben 
wurde.
2) Zugriff per DMA: das ist vielleicht etwas aufwendiger zu 
implementieren, aber für höhere SPI-Taktraten ggf. noch etwas 
wirkungsvoller. Hier verwendest du verkettete Transaktionen. Das erste 
Byte kannst du noch im Interrupt abarbeiten, um zu erkennen, ob du 
lesen/schreiben willst. Für den DMA-Zugriff hast du dann zwei 
Transaktionen, SPI->RAM und RAM->SPI. Die empfangene Adresse setzt du 
dann als Quell- bzw. Zieladresse ein. Wenn deine Implementierung 
vorsieht, dass auch die Anzahl zu lesender/schreibender Bytes 
übermittelt wird, dann hast du auch die Anzahl der Bytes, die in der 
jeweiligen Transaktion übertragen werden sollen, ansonsten musst du es 
wieder am /CS-Signal festmachen. Soll gelesen werden, startest du die 
Transaktion per Software für's erste Byte, nachfolgende Bytes sollen 
dann per DMA über das SPI-Interruptsignal in Hardware getriggert werden. 
Für's Schreiben setzt du die Transaktion direkt auf den Hardwaretrigger.
Ich kenne leider die Eigenschaften der STM32-DMA-Controller nicht im 
Detail, und du schreibst nicht, welchen STM32 genau du verwendest. Bei 
einem PSoC kann man den DMA-Controller so einstellen, dass in einer 
Transaktion mit mehreren Bytes jedes weitere Byte erst einen Trigger 
bekommen muss, damit es transferiert wird (die andere Variante ist 
Trigger für's erste Byte und danach alle Bytes so schnell wie möglich 
direkt nacheinander, aber das scheidet ja als Slave aus). Ich vermute, 
den STM32 kann man auch genauso einstellen. Die kritische Phase ist das 
Aufsetzen der Transaktion durch den DMA-Controller, also das Einrichten 
der Adresse, etc. Das braucht die meiste Zeit, der eigentliche Transfer 
der Bytes brauchen weniger Zeit (Achtung, PSoC-Wissen, auch hier die 
Vermutung, dass es beim STM32 nicht anders ist).

In beiden Fällen würde ich den von dir erwähnten Registeraufbau auch im 
RAM so abbilden. Also nicht einzelne Variablen, die du dann anhand der 
Auswertung der übermittelten Adresse selektieren musst, sondern ein 
Array bzw. struct, welches den Registeraufbau abbildet. Das hat den 
Vorteil, dass du die übermittelte Adresse direkt als Offset verwenden 
kannst (vorausgesetzt, dass alle Elemente die gleiche Breite haben -> 
wenn du gemischte Breiten brauchst, dann teile sie in 
Low/High-Bytes/Words auf).

Als Vorschlag würde ich dir empfehlen, das erstmal interruptgesteuert zu 
machen, aber schön kurz und knackig: der Interrupt kümmert sich nur um 
den Transfer der Daten von/zu den "Registern". Wenn sich da was geändert 
hat, dann signalisiert du das dem Hauptprogramm über ein Flag, und das 
Hauptprogramm übernimmt dann die Änderungen. Und wenn gelesen wird, dann 
darf währenddessen das Hauptprogramm die Register nicht verändern, 
sondern muss warten bis das Lesen abgeschlossen wurde. Kann man 
ebenfalls über ein Flag realisieren.

Ich hoffe es war einigermaßen verständlich, wenn nicht, nochmal 
nachfragen.

Ralf

von Stefan H. (cheeco)


Lesenswert?

Hallo Ralf,

danke für die Rückmeldung. Ich denke dass ich es verstanden habe und 
werde es ausprobieren. Ich melde mich wieder.

Stefan

von Lxox (Gast)


Lesenswert?

>in Hardware getriggert werden.
>Für's Schreiben setzt du die Transaktion direkt auf den Hardwaretrigger.
>Ich kenne leider die Eigenschaften der STM32-DMA-Controller nicht im
>Detail, und du schreibst nicht, welchen STM32 genau du verwendest. Bei
>einem PSoC kann man den DMA-Controller so einstellen, dass in einer
>Transaktion mit mehreren Bytes jedes weitere Byte erst einen Trigger
>bekommen muss, damit es transferiert wird (die andere Variante ist
>Trigger für's erste Byte und danach alle Bytes so schnell wie möglich
>direkt nacheinander, aber das scheidet ja als Slave aus). Ich vermute,
>den STM32 kann man auch genauso einstellen.

HW Trigger bei STM32 für externe Peripherie scheint zumindest nicht bei 
allen Varianten zu gehen.

Siehe auch
Beitrag "STM32 SPI DMA - Overrun error"

von Michael S. (michatroniker)


Lesenswert?

Hi,

ich stehe vor dem gleichen Problem. Ich würde das wie oben beschrieben 
komplett interrupt-gesteuert machen.

Hast du dazu schon was fertig und könntest den Code teilen? Dann müsste 
ich das Rad nicht neu erfinden =)

wäre super!
Viele Grüße,
Michael

von Teddy (Gast)


Lesenswert?

Hat dein STM SPI einen FIFO? Würde damit erheblich leichter gehen.

von Peter D. (peda)


Lesenswert?

Stefan H. schrieb:
> Ich möchte einen SPI-Slave entwerfen, der denselben
> internen Registeraufbau besitzt

Viele ADCs haben ein krudes Datenformat, das möchte man sich nicht ohne 
Not antun.
Am einfachsten ist folgendes Protokoll:
Befehlsbyte, Dummybyte, Antwortbyte[s].
Dann hat der Slave eine ganze Bytezeit Zeit, die Antwort zu basteln.

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.