Guten Tag,
vorweg, ich bin relativ neu im Bereich Microcontrollertechnik und
befasse mich das erste Mal mit dem SPI-Bus.
Ich habe mir nun einige Beispiele im Internet angesehen und den groben
Ablauf einer Kommunikation verstehe ich nun auch.
Mein Problem liegt darin, dass ich immer nur fertige Funktionen z.B.
void transmitData(...), void receiveData(...) finde.
In diesem Forum (Beitrag "STM32 SPI Slave ohne HAL") habe ich
dann gelesen, dass die Programmierung einer solchen Funktion nicht
unmöglich sein soll, und nun habe ich mich selbst daran gewagt.
while(SPI1->SR&SPI_SR_BSY)//warten bis Kommunikation abgschlossen
25
{
26
;
27
}
28
GPIOA->BSRR|=GPIO_BSRR_BS4;//CS-->high
29
}
Ich wollte mir einfach einmal Feedback abholen, ob diese Ideen brauchbar
sind, oder totaler Schwachsinn. Vorallem beim Einsatz von Pointern bin
ich noch sehr unsicher. Über Verbesserungsvorschläge wäre ich sehr
dankbar
ffs_jz schrieb:> Ich wollte mir einfach einmal Feedback abholen,> ob diese Ideen brauchbar sind, oder totaler Schwachsinn.
Sieht brauchbar aus, abgesehen davon dass da noch die ganze
Initialisierung fehlt, die wesentlich spannender wäre.
Was mir auffällt ist, dass du blockierend kommunizierst. Bei jedem
einzelnen byte wartest du darauf, dass es gesendet wurde. Kann man
machen, aber dann braucht man eigentlich keine SPI "teure" Schnittstelle
zu verwenden sondern kann das gleich per Bit-Banging "zu Fuß" machen.
Vorteil: Es geht dann mit jedem beliebigen I/O Pin und man hat die volle
Kontrolle über eventuell gemeine Feinheiten des Übertragungsprotokolls.
> Vor allem beim Einsatz von Pointern bin ich noch sehr unsicher.> Über Verbesserungsvorschläge wäre ich sehr dankbar
Du hast keine Pointer verwendet, außer bei den Zugriffen auf die
Register.
Das geht nicht:
1
voidreceiveByteSPI1(uint8_tbyte)
2
{
3
...
4
byte=SPI1->DR;
5
}
Weil Parameter immer als Kopie an die Funktion übergeben werden. Es gibt
aber keinen Weg zurück, das ist eine Einbahnstraße. Die Funktion sollte
stattdessen einen Rückgabewert haben:
1
voiduint8_treceiveByteSPI1()
2
{
3
...
4
uint8_tbyte=SPI1->DR;
5
...
6
returnbyte;
7
}
Oder einen Zeiger auf ein Byte:
1
voidreceiveByteSPI1(uint8_t*byte)
2
{
3
...
4
*byte=SPI1->DR;
5
}
Hier bekommt die Funktion eine Kopie des Zeigers auf das Byte. Durch den
indirekten Schreibzugriff wird die originale Variable beschrieben, auf
die sowohl der originale als auch der kopierte Zeiger zeigt.
Hmm also wenn ich mich nicht irre wird dein receive zumindest so nicht
funktionieren. Du scheinst ja code für den SPI Master zu schreiben da du
die Kommunikation ja mit dem CS startest. Also müsstest du für jedes Bit
was du vom Slave über MISO rein bekommen möchtest vorher ein Bit über
MOSI raussenden. Wenn nicht anders vom Slave vorgegeben und du nur
stumpf Daten von ihm abrufen willst dann können das einfach dummy Bytes
sein die du schickst.
>Sieht brauchbar aus, abgesehen davon dass da noch die ganze
Initialisierung fehlt, die wesentlich spannender wäre.
Zur Initialisierung:
Ich will mit einem STM32F466 über SPI Bus einen Schrittmotortreibe
TMC5160 betreiben.
Der Schrittmotor wird intern getaktet --> maximaler SCK = 4 MHz und es
muss im SPI-Mode 3 kommuniziert werden --> CPOL = 1 CPHA = 1.
>Oder einen Zeiger auf ein Byte:
So habe ich es jetzt realisiert (nur bei receiveByteSPI1(...)?
>Also müsstest du für jedes Bit
was du vom Slave über MISO rein bekommen möchtest vorher ein Bit über
MOSI raussenden.
Ich habe jetzt die Funktion transmit_receiveData() gebaut:
Der TMC5160 kommuniziert mit 40bit. 1 Adressbyte und 4 Datenbyte.
Ich habe mir jetzt gedacht ich "zerhacke" das uint64_t tx_data in 5
uint_8_t tx_byte[0..4] und sende sie nacheinander an den TMC5160.
Anschließend erhalte ich 5 uint8_t *rx_byte[0...4]. Diese muss ich dann
wieder zu dem uint64_t *rx_data zusammenbauen. Hierbei komm ich momentan
nicht weiter.
Bin ich mit dem Lösungsansatz auf dem richtigen Weg, oder ist das eine
Sackgasse.
ffs_jz schrieb:>>Oder einen Zeiger auf ein Byte:> So habe ich es jetzt realisiert (nur bei receiveByteSPI1(...)?
Ja, denn nur da willst du ein Ergebnis zurück liefern.
//Hier sollten die rx_byte[0...4] in rx_data zusammengefasst werden
13
14
GPIOA->BSRR|=GPIO_BSRR_BS4;
du verlässt dich voll darauf, dass die SPI mindestens 5 byte HW FIFO
hat.
Das mag so sein bei dem STM32, den du benutzt. Aber ich würde das
trotzdem nicht so machen. Du wartest in der sendByteSPI1() darauf, dass
alle bits rausgesendet wurden. Und erst dann willst Du empfangen. Du
hast ein vollduplex Interface. Wenn 1 byte gesendet wurde, wurde auch 1
byte empfangen. Also würde ich (wenn es schon byte weise sein muss) eine
Funktion machen, die ein Byte sendet und das empfangene Byte zurück
gibt. Eigentlich würde ich eine Funktion machen, die 2 Pointer (RX/TX
buffer) und Anzahl zu sendender Bytes bekommt und ein OK oder NOT_OK
zurück gibt.
Ich vermute dein Ansatz kommt daher, das dieser TMC5160 so seltsam
arbeitet.
Um etwas zu lesen brauchst du tatsächlich 2 Transfers zu je 40bit.
Der 1. Transfer enthält das Lesekommando und 4 byte dummy daten.
Dabei empfängst du 8bit SPI Status und entweder 4 byte dummy daten oder
die zu lesenden daten aus einem vorangegangenen Lesekommando. Der 2.
Transfer kann irgendwas enthalten zB Schreibkommando + Daten und liefert
SPI Status und die Daten zum Lesekommando des 1. Transfers.
Noch was, das BSRR Register verodert man eigentlich nicht. Pins die
manipuliert werden sollen, schreibt man mit 1 und die anderen mit 0.
Also GPIOA->BSRR = GPIO_BSRR_BS4; setzt nur PA4 nach high. Alle anderen
werden nicht angefasst.
Hallo,
wie bereits Darth Moan beschrieb, sollte der Zugriff auf die SPI anders
ausgeführt werden.
Also wenn schreiben UND lesen gleichzeitig erfolgt, dann sollte das in
einer Funktion erfolgen.
Die Read Funktion sollte dafür sorgen dass auch Daten geshifted werden.
Diese muss also Dummy Bytes in das DR Register schreiben.
Du solltest eine Read(), Write() und WriteRead() haben. Oder eine
universelle, die alles abdeckt.
Diese Funktionen sollten nur beenden, wenn der Transfer wirklich
abgeschlossen ist. Also bei Write() das BSY beachten.
Noch 2 Ergänzungen:
1) die ARM Archiketur ist stark parallel. Nach dem Aktivieren des
CS-Signals musst du unbedingt darauf warten, dass es am Ausgang
aktiviert wurde.
Das erfolgt mit einem Barrier-Befehl:
GPIOA->BSRR = GPIO_BSRR_BR4;
DSB();
auch wenn jetzt hier noch viel Code dazwischen steht, kann ein Compiler
vieles davof weglassen oder umstellen!!!
PS: bei NXP gibt es eine Integrierte CS-Logik, so dass man das CS nicht
"per Hand" setzen muss.
2) ich würde nicht bei der Kommunikation darauf vertrauen, dass das
Read-DR Register leer ist.
Also VOR dem Aktivieren der CS-Leitung mal den Read-Buffer leeren.
while(SPI1->SR & SPI_SR_RXNE) {
SPI1->DR;
}
HTH, Adib.
--
Ich hatte mir mal Cube32 Von ST heruntergeladen.
Für 'meinen' Prozessor STM32H7 sind die HAL Funktionen vorhanden und
funktionieren prima. Ich benutze nur die HAL Funktionen.
Aber das ist Geschmackssache.
Das Gute ist aber, dass die Sourcen der HAL-Funktionen dabei liegen. Man
kann also selber schauen, was die machen und dann eigene Modifikationen
erstellen.
Bevor man rätselt, was alles notwendig ist, einfach mal in den HAL
Sourcecode schauen. Da sind sogar Kommentare drin.
PittyJ schrieb:> Bevor man rätselt, was alles notwendig ist, einfach mal in den HAL> Sourcecode schauen. Da sind sogar Kommentare drin.
Ich mag die HAL nicht, aber für diesen Zweck nutze ich sie auch oft
gerne. Manche Zusammenhänge stehen nämlich im Reference Manual irgendwo
zwischen den Zeilen vieler Kapitel verteilt, so dass man sie als
Anfänger leicht übersieht.
Darth Moan schrieb:> Du wartest in der sendByteSPI1() darauf, dass> alle bits rausgesendet wurden. Und erst dann willst Du empfangen. Du> hast ein vollduplex Interface. Wenn 1 byte gesendet wurde, wurde auch 1> byte empfangen. Also würde ich (wenn es schon byte weise sein muss) eine> Funktion machen, die ein Byte sendet und das empfangene Byte zurück> gibt.
Was meinst du mit: (wenn es schon byte weise sein muss)? Das
DataRegister meines STM ist doch 16bit lang aber ich muss aber 40bit
versenden. Ich hätte dann einfach 5 mal 8bit versendet. Wenn das zu
umständlich ist, würde es mir helfen wenn du mir erklärst wie ich dann
dieses "Problem" lösen kann.
Darth Moan schrieb:> Noch was, das BSRR Register verodert man eigentlich nicht. Pins die> manipuliert werden sollen, schreibt man mit 1 und die anderen mit 0.> Also GPIOA->BSRR = GPIO_BSRR_BS4; setzt nur PA4 nach high. Alle anderen> werden nicht angefasst.
Danke für den Tipp, ich werde es ab sofort beherzigen.
Adib schrieb:> Also wenn schreiben UND lesen gleichzeitig erfolgt, dann sollte das in> einer Funktion erfolgen.
Ok, dann war das ein grundsätzliches Verständnisproblem im Ablauf des
SPI Bus, nach eurer Erklärung klingt es aber logisch.
PittyJ schrieb:> Für 'meinen' Prozessor STM32H7 sind die HAL Funktionen vorhanden und> funktionieren prima.
Danke für den Tipp, ich habe mithilfe der HAL-Funktion auch schon eine
Kommunikation mit dem TMC5160 zustande gebracht wollte aber versuchen,
es ohne vorgefertigte Funktion zu lösen, um ein besseres Verständnis für
die Thematik zu bekommen.
Vielen Dank für die tollen Hilfestellungen :-D.
Leider kann ich an der Funktion kaum weiterbasteln, bis ich verstehe,
wie ich die 40bit versende und empfange, wenn mir nur maximal 16bit
DataRegister zur Verfügung stehen.
Ich bin noch sehr frisch im Gebiet Mikrocontrolling, und habe
wahrscheinlich einfach nicht die "Denke" wie ich solche Probleme löse.
Ein Denkanstoß würde mir evtl. helfen ;-)
Moin,
Johannes Z. schrieb:> Was meinst du mit: (wenn es schon byte weise sein muss)? Das> DataRegister meines STM ist doch 16bit lang aber ich muss aber 40bit> versenden. Ich hätte dann einfach 5 mal 8bit versendet. Wenn das zu> umständlich ist, würde es mir helfen wenn du mir erklärst wie ich dann> dieses "Problem" lösen kann.
Mit byte weise meine ich deine Funktionen die genau ein Byte senden oder
empfangen wollen. Wenn du mehr als ein Byte senden willst, rufst du die
Funktion entsprechend oft auf.
Wenn du das so haben willst, würde ich eben nur eine Funktion empfehlen,
die ein Byte sendet und gleichzeitig ein Byte empfängt.
...// warte auf byte in RxFifo ggf. mit timeout abbrechen ->NOK
6
RxBuf[0]=SPI1->DR;
7
...
8
returnOK/NOK;//je nach ergebnis der checks/timeouts
9
}
Aber eigentlich würde ich eine Funktion empfehlen die n Bytes sendet und
empfängt.
Dann kannst du immer noch 1 Byte senden und empfangen indem du die
Anzahl der zu sendenden Bytes mit 1 übergibst.
In deinem Fall würde diese Funktion genau einmal aufgerufen werden, mit
einem Zeiger auf die zu sendenden Daten, Zeiger auf den Empfangsbuffer,
und Anzahl Bytes, die gesendet und empfangen werden sollen.
Also ungefähr so:
...// warte auf byte in RxFifo ggf. mit timeout abbrechen ->NOK
8
*RxBuf++=SPI1->DR;
9
}
10
...
11
returnOK/NOK;//je nach ergebnis der checks/timeouts
12
}
Rückgabewert wäre ein Error Code. OK wenn alles glatt ging sonst NOK.
Ggf. kannst du da auch detailiertere Error Codes basteln. Aber für
Daheim reicht mir meistens OK/NOK.
Da kann man dann auch so Sachen einbauen, dass wenn zum Bleistift der
RxBuf pointer NULL ist, die Empfangsdaten in den Orcus gehen. Nützlich
wenn man grosse Messages hat die man aber nur senden will und keinen
Dummy RxBuffer zur Verfügung stellen möchte, den man dann eh nicht
auswertet.
Aber bei deinem Baustein wird ja eigentlich immer gesendet und
empfangen.
Ich habe hier auch noch ein Projekt mit nem TMC5160 herumliegen, ist
jetzt grade auf der Warteliste wegen STM32-Mangel, habe mit dem
Prototyp-Board aber schonmal die Funktionen der Hardware getestet.
So habe ich die blockierende SPI Kommunikation zum Testen des Treibers
implementiert: