Forum: Mikrocontroller und Digitale Elektronik STM32 SPI zu viele Clocks


von Traubensaft .. (increasingcurrant)


Angehängte Dateien:

Lesenswert?

Hallo,

ich nutze die SPI-Schnittstelle eines STM32F405 mit HAL und STM32CubeMX 
und möchte mit 20 kHz einen Sensor auslesen.
Um das Auslesen zu starten nutze ich einen Timer, in dessen 
PeriodElapsedCallback ich die Funktion HAL_SPI_Receive_IT(...) aufrufe. 
Das NSS-Signal wird durch einen GPIO-Pin realisiert, der im 
Timer-Callback auf LOW gesetzt wird und im SPI-Callback auf HIGH gesetzt 
wird.

Das funktioniert soweit ganz gut. Gelegentlich läuft die SPI-Clock 
jedoch nach setzen des NSS-Pins auf High einfach weiter. Im darauf 
folgenden Zyklus werden dann nur 8 der gewünschten 16 bit vom Sensor 
geesen und die Daten sind unbenutzbar.

Weiß jemand wie das kommen kann? Wie kann man das eleganter lösen?
1
// HAUPTTIMER 20 kHz
2
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *ht)
3
{
4
  if(ht == &htim10)
5
  {
6
    // DEBUG-Pin HIGH
7
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
8
    
9
    
10
    
11
    // NSS LOW
12
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
13
    // SPI1 auslesen
14
    HAL_SPI_Receive_IT(&hspi1, spidummy, 2);
15
    
16
  
17
    
18
    // DEBUG-Pin LOW
19
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
20
  }
21
}
22
23
24
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
25
{
26
  if(hspi == &hspi1)
27
  {
28
    // NSS HIGH
29
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
30
    /* Daten aus spidummy nutzen... */
31
  }
32
33
}

von STMApprentice (Gast)


Lesenswert?

Ich kenne das.

Traubensaft .. schrieb:
> Gelegentlich läuft die SPI-Clock
> jedoch nach setzen des NSS-Pins auf High einfach weiter.

Nein. Ich kann dir grob die Ursache nennen, aber nicht für
deinen (CubeMX-)Fall die böse Stelle im Code:

Der NSS Pin wird zu früh auf HIGH gezogen da irgendwo im
Ablauf nicht darauf gewartet wird dass die SPI Maschine
noch im Busy-Zustand ist.

Die SPI Maschine kann noch busy sein obwohl das TX empty Flag
bereits gesetzt ist. Das liegt an der gepufferten Verarbeitung
von Schreibvorgängen auf die Peripherie.

Zur Erläuterung siehe:

Beitrag "[STM32F4xx] SPI Optimierung"

Vielleicht solltest du doch mal dazu übergehen den HAL-Sch....
wegzulassen und selbst Hand anzulegen.

von STMApprentice (Gast)


Lesenswert?

Traubensaft .. schrieb:
> der im
> Timer-Callback auf LOW gesetzt wird und im SPI-Callback auf HIGH gesetzt
> wird.

Das tut man nicht. Das ist Spaghetti Code.

von Traubensaft .. (increasingcurrant)


Lesenswert?

STMApprentice schrieb:
> Die SPI Maschine kann noch busy sein obwohl das TX empty Flag
> bereits gesetzt ist.

Ist es in meinem Fall das RX flag?

In dem verlinkten Beitrag fügst du eine Zeile hinzu um abzuwarten, bis 
das BSY bit im SR wirklich 0 ist. Ich habe mir daher die "HAL-Version"
1
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
2
{
3
  if(hspi == &hspi3)
4
  {
5
    // NSS HIGH
6
    while (hspi3.Instance->SR & (1<<7) != 0);
7
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
8
    
9
  }
10
11
}
überlegt. Leider funktioniert das so nicht. (Portierbarkeit und "Magic 
numbers" stören mich erstmal nicht. Ich versuche nur die Funktion 
herszustellen.)



STMApprentice schrieb:
> Das tut man nicht. Das ist Spaghetti Code.

Das hat schon beim Programmieren weh getan. Bei dieser 
Interrup-basierten Variante fällt mir leider keine schönere Möglichkeit 
ein. Gibt's da was?

von dasrotemopped (Gast)


Angehängte Dateien:

Lesenswert?

>Die SPI Maschine kann noch busy sein obwohl das TX empty Flag
>bereits gesetzt ist. Das liegt an der gepufferten Verarbeitung
>von Schreibvorgängen auf die Peripherie.
korrekt

>Vielleicht solltest du doch mal dazu übergehen den HAL-Sch....
>wegzulassen und selbst Hand anzulegen.
falsch, den HAL richtig benutzen. Das CS per GPIO setzen ist
überhaupt nicht nötig und wird auf Wunsch vom SPI Controller
übernommen. Dann passt das Timing auch.
Funktioniert übrigens nicht bei mehreren SPI Devices an einem SPI Kanal 
parallel. Als DaisyChain aber dann doch wieder.
Jede Lösung hat halt das passende Problem.

Gruß,
dasrotemopped.

von grundschüler (Gast)


Lesenswert?

Der normale spi-Schreibcode auf rxne siehst so aus:
1
uint8_t spi_txrx (uint8_t dat){
2
  SPI2->DR = dat;
3
  while (!(SPI2->SR & SPI_SR_RXNE));//0_rxne 1_txe 7_bsy// ok
4
  return (SPI2->DR);
5
}

in Anlehnung an deinen code-schnipsel müsste es bei dir so 
funktionieren:
1
uint16_t spi_txrx (uint16_t dat){
2
  hspi3.Instance->DR = dat;
3
  while (!(hspi3.Instance->SR & (1<<0)));//0_rxne 1_txe 7_bsy// ok
4
  return (hspi3.Instance->DR);
5
}

von Johannes S. (Gast)


Lesenswert?

Das CS ist dem Fall doch nur ein beliebiger GPIO, das SPI im Controller 
weiss nix davon und damit kann das Problem auch nicht am CS liegen. Das 
Clock Signal wird beim Master doch anhand der Anzahl Bytes * Wortbreite 
generiert, unabhängig vom CS.

von STMApprentice (Gast)


Lesenswert?

Johannes S. schrieb:
> Das CS ist dem Fall doch nur ein beliebiger GPIO

Käse. In diesem Fall ist CS (=NSS) ein hart von der SPI-
Maschine gesteuerter Pin. Das ist auch konfigurierbar.

von Johannes S. (Gast)


Lesenswert?

ich sehe in dem Code nur einen Softwaregesteuerten CS. Und ob PA4 den 
Output vom Slave hochohmig oder irgendeine LED anmacht ist doch egal. 
Wenn die gelbe Kurve im Oszillogramm das CS Signal ist kommt es ja auch 
richtig. Nur der Controller meint er müsse noch ein paar Takte 
raushauen.

von Traubensaft .. (increasingcurrant)


Lesenswert?

dasrotemopped schrieb:
> HAL richtig benutzen. Das CS per GPIO setzen ist
> überhaupt nicht nötig und wird auf Wunsch vom SPI Controller
> übernommen.

Ich möchte gerne einen "Receive Only Master" betreiben. Das Thema 
Hardware-NSS habe ich gestern ausgiebig gehabt. Es hat für Baudraten >= 
1 MBaud auch prima funktioniert. Wenn ich jedoch nur den Prescaler für 
SPI im CubeMX (ohne Änderungen am User-Code!) so gewählt habe dass die 
Baudrate kleiner als 1 MBaud war, wurde NSS überhaupt nicht mehr 
angesprochen.


grundschüler schrieb:
> while (!(hspi3.Instance->SR & (1<<0)));

Ich will doch prüfen ob bit 7 noch gesetzt ist oder? Was ist mit meiner 
Variante
1
while (hspi3.Instance->SR & (1<<7) != 0);
denn falsch?


Johannes S. schrieb:
> ich sehe in dem Code nur einen Softwaregesteuerten CS.

Eben. Weil ich für das Verhalten von Hardware-NSS bisher noch keine 
Erklärung gefunden habe.


Johannes S. schrieb:
> ommt es ja auch
> richtig. Nur der Controller meint er müsse noch ein paar Takte
> raushauen.

Genau das ist der Kern meines aktuellen Problems :)

von Johannes S. (Gast)


Lesenswert?

Es sieht irgendwie asynchron aus, als ob schon wieder oder noch ein 
Receive ansteht wenn der Complete Callback kommt und das CS rücksetzt. 
HAL_SPI_Receive_IT() liefert doch einen Status zurück, den vielleicht 
mal überwachen ob da nicht ein Busy oder Error kommt?

von grundschüler (Gast)


Lesenswert?

Was ist mit meiner
Variante

while (hspi3.Instance->SR & (1<<7) != 0);

denn falsch?

probiers aus.
Beitrag "Re: Stm32f103 Problem mit Spi2"

von Traubensaft .. (increasingcurrant)


Lesenswert?

Johannes S. schrieb:
> als ob schon wieder oder noch ein
> Receive ansteht
Wie ist das zu verstehen? HAL_SPI_Receive_IT() wird nur aufgerufen wenn 
NSS runter gezogen wurde. Und auf dem Scope sehe ich, dass die Frequenz 
von NSS konstant ist.


Johannes S. schrieb:
> liefert doch einen Status zurück
Gute Idee. Der ist leider permanent HAL_OK.


grundschüler schrieb:
> probiers aus
Habe ich. Die Schleife läuft keine einzige Iteration. Heißt also BSY ist 
immer 0?

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

sollte
1
  if (HAL_TIM_OC_Init(&htim10) != HAL_OK)

nicht besser mit einer TIM_OC_InitTypeDef Struktur gefüttert werden?

von grundschüler (Gast)


Lesenswert?

Traubensaft .. schrieb:
> ?
1
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
2
{
3
  if(hspi == &hspi3)
4
  {
5
    // NSS HIGH
6
    while (hspi3.Instance->SR & (1<<0) == 0);
7
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
8
    
9
  }
10
11
}

mein letzter Versuch

Die Warteroutine stimmt nicht. statt 16bit rutschen 24bit durch und 
anschließend dann nur 8bit? Das wäre genau das gleiche Phänomen wie bei 
meinem Spi2-Problem.

von Traubensaft .. (increasingcurrant)


Lesenswert?

Was hat das mit SPI zu tun? Timer 10 läuft genau wie er soll.

Der Funktionskopf dazu lautet
1
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
Scheint also alles zu passen. Ist im übrigen Teil des autogenerierten 
Codes.

von Johannes S. (Gast)


Lesenswert?

und was möchte HAL_TIM_OC_Init() als Argument bekommen? Und was bekommt 
es?

ok, ist korrekt. Ist schon spät...

von Traubensaft .. (increasingcurrant)


Lesenswert?

Ja, falschen Funktoinskopf kopiert.
1
HAL_StatusTypeDef HAL_TIM_OC_Init(TIM_HandleTypeDef* htim)
 aus der stm32f4xx_hal_tim.c Den Zusammenhang sehe ich aber noch nicht.

grundschüler schrieb:
> statt 16bit rutschen 24bit durch und anschließend dann nur 8bit?
Genau. Aber auch diese Schleife läuft bei mir nicht. Und das Phänomen 
bleibt erhalten.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Traubensaft .. schrieb:
> Den Zusammenhang sehe ich aber noch nicht.

Wenn das offensichtliche richtig ist muss man weitere Kreise ziehen, das 
können schon irgendwelche Seiteneffekte sein die noch versteckt sind. So 
schlecht sieht der Code ja nicht aus.
Ist das Ganze mit Debug übersetzt und sind die Asserts aktiv?

grundschüler schrieb:
> while (hspi3.Instance->SR & (1<<0) == 0);
ist das Problem nicht auf SPI1 ? Und hast du auch schon die nicht direkt 
am Problem beteiligten Komponenten abgeschaltet?

von Traubensaft .. (increasingcurrant)


Lesenswert?

Johannes S. schrieb:
> Ist das Ganze mit Debug übersetzt und sind die Asserts aktiv?
Jap, läuft im Debugging von IAR.

Johannes S. schrieb:
> grundschüler schrieb:
>> while (hspi3.Instance->SR & (1<<0) == 0);
> ist das Problem nicht auf SPI1 ? Und hast du auch schon die nicht direkt
> am Problem beteiligten Komponenten abgeschaltet?
Da hat er schon recht. Ich nutze beide SPIs mit gleichartigen Sensoren. 
Darum war vorhin ein wechsel drin. Um Überschneidungseffekte 
auszuschließen habe ich eine neue Projektmappe angelegt.

von grundschüler (Gast)


Lesenswert?

Traubensaft .. schrieb:
doch noch ein Versuch:

Setz mal hinter das bsy-wait noch ein delay von ca. 100us. Dann hat der 
Sensor genug Zeit, die gesendeten 16bit zu verarbeiten. Wenn das klappt, 
das delay reduzieren.

von Traubensaft .. (increasingcurrant)


Lesenswert?

grundschüler schrieb:
> Dann hat der Sensor genug Zeit, die gesendeten 16bit zu verarbeiten.
Mhm, ich hätte die Signale mal beschriften sollen. Oben im 
Scope-Screenshot sehen wir MISO. Meine SPIs sind als "Receive Only 
Master" konfiguriert. Das Problem tritt beim Empfangen der Sensordaten 
auf.

von Nico W. (nico_w)


Lesenswert?

Also beim stm32f411re teste ich nach TXE, RXNE und BSY.
1
uint8_t spi_rw(uint8_t byte) {
2
  SPI2->DR = byte;
3
  while(!(SPI2->SR & SPI_SR_TXE));
4
  while(!(SPI2->SR & SPI_SR_RXNE));
5
  while(SPI2->SR & SPI_SR_BSY);
6
  return SPI2->DR;
7
}

von Darth Moan (Gast)


Lesenswert?

Moin,

also ich wuerde eher erwarten, dass die SPI zu spaet disabled wird, und 
daher weiter clocks generiert.
Im RefManual steht zu RX Only Mode:
1. Set the RXONLY bit in the SPI_CR2 register.
2. Enable the SPI by setting the SPE bit to 1:
a) In master mode, this immediately activates the generation of the SCK 
clock, and data are serially received until the SPI is disabled 
(SPE=0)....

Nun muss ich sagen, dass ich weder HAL noch RX Only mode nutze.
Die Clock generierung laesst sich da ganz einfach ueber die TX Buffer 
writes kontrollieren.
Ich frage mich nur, ob die Suche hier ueberhaupt sinnvoll ist.
Wennn CubeMX fuer >1Mbit korrekt alles macht, incl. HW NSSI dann sollte 
man doch versuchen da von Hand nur den Prescaler zu "Patchen" um die 
gewuenschte Bitrate zu erhalten, oder?

von grundschüler (Gast)


Lesenswert?

Traubensaft .. schrieb:
> Das Problem tritt beim Empfangen der Sensordaten auf.

auch das master-rx setzt ein master-tx voraus. Der Master liefert den 
clk- Takt und sendet damit Daten auf Mosi, die aber nicht interessieren. 
der slave sendet mit dem gleichen Takt auf miso Daten zurück. Dann wird 
gewartet bis die Verarbeitung rxne/txe/bsy im master beendet ist.

An dieser Stelle liegt wahrscheinlich der Fehler, weil - warum auch 
immer - nicht ausreichend gewartet wird.

Hier müsste also das delay hin.

Da es ein rx-Vorgang ist scheint mir das Warten auf tx unlogisch. Da rx 
und tx eigene Flags haben, kommt es hier vermutlich zu 
Zeitunterschieden. Dann scheint mir das Abwarten des zeitlich späteren 
rxne eigentlich zwingend. Wieso rxne bei dir nicht funktioniert 
erschließt sich mir noch nicht.

von Darth Moan (Gast)


Lesenswert?

Moin,

> auch das master-rx setzt ein master-tx voraus. Der Master liefert den
> clk- Takt und sendet damit Daten auf Mosi, die aber nicht interessieren.

Nein, er konfiguriert RXONLY mode, da gibt es laut RefManual kein TX.
Ich benutze immer bidirektional, und habe dann auf MOSI gleich einen
Byte/Word counter fuers Oszi. Weil mir das so besser gefaellt, und ich 
den
Pin uebrig habe. Aber er halt nicht. Und der HAL scheint das ja zu 
supporten.
Es gibt halt aber manchmal ein Problem mit dem Transfer-Ende.

Wenn man das untersuchen will, sollte man die Zugriffe aufs DR mit ein 
Paar Debug Pins visualisieren und ggf. zeitlich verschieben, um die 
Effekte in
der SPI zu beobachten.

von Johannes S. (Gast)


Lesenswert?

Ist in der Hardware ein Mosi Pin frei? Dann könntest du ja mal 
Init.Direction = SPI_DIRECTION_2LINES anstatt RXONLY testen.

von Darth Moan (Gast)


Lesenswert?

Moin,

hoppala:
> ... Zugriffe aufs DR mit ein Paar Debug Pins visualisieren...

Meinte natuerlich CR bzw. CR1, weil da ja das enable bit drin ist.
Das DR sollte beim RXONLY mode nicht so sonderlich viel Einfluss haben.
Ausser, dass man da die Daten einliesst oder es bleiben laesst und sie
verloren gehen.

von Traubensaft .. (increasingcurrant)


Lesenswert?

Hi,

ich lese gerade Seite 898 im Referece Manual RM0090. Dort ist das 
korrekte Disabling des SPI beschrieben.

Mein Fall sollte dieser sein:
> In master unidirectional receive-only mode (MSTR=1, BIDIMODE=0, RXONLY=1)

Dort steht geschrieben:
> This case must be managed in a particular way to ensure that the SPI
> does not initiate a new transfer.
> 1. Wait for the second to last occurrence of RXNE=1 (n–1)
> 2. Then wait for one SPI clock cycle (using a software loop) before
>    disabling the SPI (SPE=0)
> 3. Then wait for the last RXNE=1 before entering the Halt mode
>    (or disabling the peripheral clock)


"ensure that the SPI does not initiate a new transfer" klingt ziemlich 
vielversprechend.

Bei Schritt 1: das vorletzte RXNE=1 bekomme ich beim ersten meiner zwei 
Bytes (richtig?)
Dann warte ich ein bisschen bis er anfängt das zwei Byte rauszuclocken 
und disable das SPI (SPE=0) während er noch dabei ist das zweite Byte zu 
holen.


Meine Frage ist nun: wie ziehe ich das mit Interrupts auf? Wenn ich die 
ganze Zeit auf Flags warte, verballere ich Rechenzeit ohne dass etwas 
passiert...

Eine Idee wäre nach HAL_SPI_Receive_IT(...) ein paar µs zu warten bis er 
sicher beim zweiten Byte ist und dann SPE=0. Das hat aber nicht 
funktioniert.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Hört sich kompliziert an und verlangt wohl Eingriffe in den HAL. Poste 
das Problem doch mal im ST Forum.
Und zum Test erstmal nicht den RxOnly Mode nutzen?

von Darth Moan (Gast)


Lesenswert?

Moin,

also da du die HAL_SPI_Receive_IT() ausm CallBack im Interrupt context
aufrufst, ist busy wait wohl keine gute Idee.
Ich wuerde ebenfalls empfehlen, diriektional zu konfigurieren. Dann wird
mit dummy transmit bytes nur genau die Anzahl Clocks generiert, die man
möchte.
Wenn das absolut nicht geht, und du den HAL code nicht patchen magst,
wuerde ich einen weiteren Timer empfehlen, der zeitversetzt einen IRQ
ausloest. Das sollte natuerlich an den Start-Timer synchronisiert sein.
Ich glaube da mal was gelesen zu haben, dass das geht. Kann ich aber
ausm Kopf nicht runterbeten (naja, fuer Bolero eMIOS ginge es, aber beim
M4 hab ichs nicht parat). Die Timer duerfen ja nie auseinander laufen.
Wenn beide von der selben Clock gespeist werden kann das ja nicht
passieren, aber den exakten versatz kennt man nicht, wenn man die
einfach nacheinander startet.
Wenn das am Anfang passiert wenn noch keine Ints akriv sind sollte das
aber keine allzugrossen Probleme bereiten (solange sich im System nix
aendert).

von grundschüler (Gast)


Lesenswert?

Traubensaft .. schrieb:
> Wenn ich die ganze Zeit auf Flags warte, verballere ich Rechenzeit

Bevor es mit standard-spi nicht ordentlich funktioniert, brauchst du dir 
darüber keine Gedanken zu machen. Ich würde es auch erstmal im 2x 8bit 
modus probieren, bevor auf 16bit umgestellt wird. Bis es ordentlich 
läuft, so einfach wie möglich.

von Darth Moan (Gast)


Lesenswert?

grundschüler schrieb:
> Bevor es mit standard-spi nicht ordentlich funktioniert, brauchst du dir
> darüber keine Gedanken zu machen. Ich würde es auch erstmal im 2x 8bit
> modus probieren, bevor auf 16bit umgestellt wird. Bis es ordentlich
> läuft, so einfach wie möglich.

Aber der 16bit mode ist doch standard. Und es reduziert die
Komplexitaet. Dann kann nach dem HAL_SPI_Receive_IT() nach einem
SPI clock cycle das SPI enable auf 0 gesetzt werden. Bei 2x8Bit
muss ein IRQ abgewartet werden und dann 1 SPI Clock spaeter das
SPI enable auf 0 gesetzt werden.
Dazu sind dann noch 7 Clockcycles - der IRQ delay (Systemlast,
andere IRQs, Interruptsperren) uebrig. Beim 16Bit betrieb sind
es 15 Clockcycles. Das Fenster, dass zum SPI disable genutzt
werden muss ist bei 8bit nicht mal halb so gross.

von Darth Moan (Gast)


Lesenswert?

Moin,

ein weitere Timer ist eigentlich voelliger Qautsch. Der "Start" Timer
wird einfach auf halbes Zeitintervall aufgezogen. In der Timer CB
Funktion dann 2 Phasen.
Phase 1: HAL_SPI_Receive_IT() mit 16bit Betrieb, -> Phase 2
Phase 2: SPI Enable auf 0 setzen, -> Phase 1

Aber das SPI enable bit Hardcoremaessig auf 0 zu setzen wenn man
eigentlich streng nur den ungepatchten HAL nutzen will ist auch
irgendwie unschoen.

von Traubensaft .. (increasingcurrant)


Lesenswert?

Hey,

ich danke euch allen vielmals für die Unterstützung! Mit 
HAL_SPI_TransmitReceive_IT(...) und GPIO als NSS funktioniert es.

Vieleicht bastel' ich mir in der vorlesungsfreien Zeit dann mal eine 
HAL-frei Variante.

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.