Forum: Mikrocontroller und Digitale Elektronik STM32 HAL_SPI_Transmit_IT blockiert


von Marc L. (dl5sez)


Angehängte Dateien:

Lesenswert?

Ich frage mich ob es eine gute Idee war mit den HAL-Bibliotheken 
anzufangen ...

Mikrocontroller: STM32F205VCT6

Compiler: arm-none-eabi-gcc, gcc version 7.3.1 20180622 (release) 
[ARM/embedded-7-branch revision 261907] (15:7-2018-q2-4)

Die HAL-Bibliotheken wurden mittels STM32CubeMX, Version 4.27.0 
erstellt.

Aktivierte Peripherie:
SPI3 (Transmit Only Master), SPI3 global interrupt enabled
GPIOA.9 als output

Es soll (nicht blockierend) ein Byte gesendet werden:
1
uint8_t buffer[3] = { 1, 2, 3 };
2
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET);
3
HAL_SPI_Transmit_IT(&hspi3, buffer, 1);
4
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET);

Weiterer Code wurde von mir zu dem von STM32CubeMX erzeugten Code nicht 
hinzugefügt.

Folgendes Problem:
Wird mittels HAL_SPI_Transmit_IT() nur ein Byte übergeben, so blockiert 
die Funktion bis das Byte gesendet wurde (siehe SCR203.PNG). Werden 
hingegen mehrere Bytes übergeben, so funktioniert die Funktion korrekt 
(siehe SCR204.PNG). Ich konnte aus dem HAL-Quellcode die Ursache dafür 
nicht finden.

Ein Bug? Oder eine fehlerhafte Konfiguration?

Gruß Marc

von Benjamin S. (recycler)


Lesenswert?

Er macht genau was er soll.
Du ziehst den CS auf low startest den Transfer und dann geht der CS auf 
high.
Was du aber willst ist folgendes:

Möglichkeit A: Aktives Warten - müsste mit folgendem gehen:
1
HAL_SPI_Transmit(&hspi3, buffer, 1);

Möglichkeit B: Du setzt den Befehl ab und nimmst deine Interruptfunktion 
"HAL_SPI_Transmit_IT" und im Interrupt Callback den CS wieder hoch. Da 
müsste ein Funktionsrumpf da sein.

Das bei einem Byte ein Blockierender Aufruf folgt kann sein, da müsste 
man aber genauer schauen. Da kann viel reinspielen.

Blockierender Aufruf würde ich mit der Funktion ohne IT machen.

von Marc L. (dl5sez)


Lesenswert?

Benjamin S. schrieb:
> Er macht genau was er soll.

Nein, macht er nicht.
1
HAL_SPI_Transmit_IT(&hspi3, buffer, 1);
2
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET);
Nachdem die beiden Befehlszeilen ausgeführt wurden, sollte das Signal 
GPIO_PIN_9 unmittelbar auf HIGH gehen, nicht erst nachdem der 
SPI-Transfer beendet ist. Sonst würde der Interrupt-Betrieb keinen Sinn 
machen. Er sollte nicht-blockierend sein.

> Du ziehst den CS auf low startest den Transfer und dann geht der CS auf
> high.

In der aktuellen Konfiguration gibt es kein CS. CS wird zum Senden nicht 
benötigt.

> Das bei einem Byte ein Blockierender Aufruf folgt kann sein, da müsste
> man aber genauer schauen. Da kann viel reinspielen.

Ja genau. Und genau deswegen schrieb ich diesen Post.

Gruß Marc

von Harry L. (mysth)


Lesenswert?

Wenn du die *_IT-Funktionen nutzt, musst du auch den/die zugehörigen 
Callback(s) implementieren, sonst macht das alles wenig Sinn.
In deinem Fall ist das mindestens:
1
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);

Besorg dir mal dieses User-Manual. Da wird auch HAL_SPI genau erklärt:

https://www.st.com/content/ccc/resource/technical/document/user_manual/56/32/53/cb/69/86/49/0e/DM00223149.pdf/files/DM00223149.pdf/jcr:content/translations/en.DM00223149.pdf

von Marc L. (dl5sez)


Lesenswert?

Harry L. schrieb:
> Wenn du die *_IT-Funktionen nutzt, musst du auch den/die zugehörigen
> Callback(s) implementieren, sonst macht das alles wenig Sinn.
> In deinem Fall ist das mindestens:
>
1
> void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
2
>
>
> Besorg dir mal dieses User-Manual. Da wird auch HAL_SPI genau erklärt:
>
> 
https://www.st.com/content/ccc/resource/technical/document/user_manual/56/32/53/cb/69/86/49/0e/DM00223149.pdf/files/DM00223149.pdf/jcr:content/translations/en.DM00223149.pdf

Die Callback-Funktion ist für die Demonstration des Problems nicht 
notwendig, da sie erst NACH der Übertragung aufgerufen wird. Die 
Blockade besteht aber WÄHREND der Übertragung.

von Harry L. (mysth)


Lesenswert?

Marc L. schrieb:
> Harry L. schrieb:
>> Wenn du die *_IT-Funktionen nutzt, musst du auch den/die zugehörigen
>> Callback(s) implementieren, sonst macht das alles wenig Sinn.
>> In deinem Fall ist das mindestens:
>>
1
>> void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
2
>>
>>
>> Besorg dir mal dieses User-Manual. Da wird auch HAL_SPI genau erklärt:
>>
>>
> 
https://www.st.com/content/ccc/resource/technical/document/user_manual/56/32/53/cb/69/86/49/0e/DM00223149.pdf/files/DM00223149.pdf/jcr:content/translations/en.DM00223149.pdf
>
> Die Callback-Funktion ist für die Demonstration des Problems nicht
> notwendig, da sie erst NACH der Übertragung aufgerufen wird. Die
> Blockade besteht aber WÄHREND der Übertragung.

Ja, weil CS viel zu früh inaktiv wird.
Genau das muß im Callback passieren.
W.g.: ohne die Callbacks wird das nix mit den *_IT-Varianten.

von Marc L. (dl5sez)


Lesenswert?

Harry L. schrieb:

> Ja, weil CS viel zu früh inaktiv wird.

Es gibt in meiner Konfiguration kein CS!

> W.g.: ohne die Callbacks wird das nix mit den *_IT-Varianten.

Ich geb's auf.

von Thomas E. (picalic)


Lesenswert?

Meine Vermutung ist, daß es nur so aussieht, als ob es nicht 
funktioniert.
Wenn Du nur ein Byte sendest, wird ja sofort die DMA Transfer Complete 
ISR aufgerufen, d.h. bereits vor dem Befehl zum Setzen des Ports auf 
High, und möglicherweise wird dort blockierend auf den Abschluss der 
Übertragung des letzten Bytes gewartet. Normalerweise dürfte das 
notwendig sein, damit die (bei Dir nicht vorhandene) Callback-Funktion 
tatsächlich erst NACH dem Abschluss des kompletten Sendevorgangs 
aufgerufen wird und nicht schon, wenn das letzte Byte per DMA gerade 
erst aus dem Speicher gelesen wurde.

Ist aber bloß eine Theorie - von dem HAL-Zeugs halte ich mich lieber 
fern. Es reicht mir schon, die Bugs in meinem eigenen Code zu finden, da 
muss ich nicht auch noch den Code von ST debuggen... ;)

von Lutz (Gast)


Lesenswert?

Marc L. schrieb:
> Harry L. schrieb:
>
>> Ja, weil CS viel zu früh inaktiv wird.
>
> Es gibt in meiner Konfiguration kein CS!

Welche Funktion hat denn GPIO A9 in deinem Programm?

von Marc L. (dl5sez)


Lesenswert?

Lutz schrieb:
> Welche Funktion hat denn GPIO A9 in deinem Programm?

GPIOA.9 dient dazu den Startzeitpunkt und den Endzeitpunkt der Funktion 
HAL_SPI_Transmit_IT() am Oszilloskop darzustellen.

von Doctor Snuggles (Gast)


Lesenswert?

Marc L. schrieb:
> Ich frage mich ob es eine gute Idee war mit den HAL-Bibliotheken
> anzufangen ...


Natürlich nicht. Wüsstest Du aber, wenn Du öfters hier im Forum mal 
mitgelesen hättest... Habe gerade mal in den Code geschaut. Der ist ja 
sowas von aufgeblasen. Dabei sind das nur ein paar Bits und ein paar 
Zeilen Code - selbst wenn man DMA nutzt - wenn man das in richtig macht.


Thomas E. schrieb:
> Meine Vermutung ist, daß es nur so aussieht, als ob es nicht
> funktioniert.
> Wenn Du nur ein Byte sendest, wird ja sofort die DMA Transfer Complete
> ISR aufgerufen, d.h. bereits vor dem Befehl zum Setzen des Ports auf
> High, und möglicherweise wird dort blockierend auf den Abschluss der
> Übertragung des letzten Bytes gewartet. Normalerweise dürfte das
> notwendig sein, damit die (bei Dir nicht vorhandene) Callback-Funktion
> tatsächlich erst NACH dem Abschluss des kompletten Sendevorgangs
> aufgerufen wird und nicht schon, wenn das letzte Byte per DMA gerade
> erst aus dem Speicher gelesen wurde.


Klar, dazu reicht ein Blick in dieses üble Framework:

Erst:
1
  /* Wait until TXE flag is set to send data */
2
  if(SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_TXE, RESET, SPI_TIMEOUT_VALUE) != HAL_OK)
3
  {
4
    hspi->ErrorCode |= HAL_SPI_ERROR_FLAG;
5
  }

Dann:
1
    if(SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_BSY, SET, SPI_TIMEOUT_VALUE) != HAL_OK)
2
    {
3
      hspi->ErrorCode |= HAL_SPI_ERROR_FLAG;
4
    }


Mein Vorschlag: Mach das selber. Ist wirklich trivial.

von Thomas E. (picalic)


Lesenswert?

Marc L. schrieb:
> GPIOA.9 dient dazu den Startzeitpunkt und den Endzeitpunkt der Funktion
> HAL_SPI_Transmit_IT() am Oszilloskop darzustellen.

Du könntest den Pin nach Aufruf der Tx-Funktion in einer Endlosschleife 
togglen, dann könnte man sehen, ob da am Ende der Übertragung irgendwas 
blockiert und wie lange.

von Marc L. (dl5sez)


Angehängte Dateien:

Lesenswert?

Thomas E. schrieb:
> Du könntest den Pin nach Aufruf der Tx-Funktion in einer Endlosschleife
> togglen, dann könnte man sehen, ob da am Ende der Übertragung irgendwas
> blockiert und wie lange.

Gute Idee!

Dann senden wir mal 3 Bytes:
1
HAL_SPI_Transmit_IT(&hspi3, buffer, 3);
2
while(true) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_9); }

Siehe Scope -> grauenhaft!

Doctor Snuggles schrieb:
> Mein Vorschlag: Mach das selber. Ist wirklich trivial.

Ich denke Du hast recht. Die Fehlersuche, Code-Analyse der 
HAL-Bibliotheken und daraus folgenden Workarounds haben mich schon 
soviel Zeit gekostet ...

: Bearbeitet durch User
von Marc L. (dl5sez)


Lesenswert?

Inzwischen konnte ich die Ursache des Problems finden.

Das Problem ist, dass das Ende einer SPI-Übertragung nicht durch einen 
TX-Interrupt signalisiert wird. Die HAL-Bibliotheken behelfen sich damit 
über eine while-Schleife das Busy-Flag abzufragen. Dadurch entsteht die 
Blockade. Leider löst das Busy-Flag keinen Interrupt aus.

ST hat mir vorgeschlagen das Ende einer Übertragung über den 
RX-Interrupt signalisieren zu lassen. Das funktioniert sehr gut. 
Allerdings verwende ich inzwischen meinen eigenen Code.

Falls es jemanden interessiert, hier der Link zum 
Kommunikationsaustausch mit ST:
https://community.st.com/s/question/0D50X00009sW5LdSAK/halspitransmitit-blocks

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.