STM32 Receive via SPI immer um ein paar Bytes versetzt

OP #4864813
Lesenswert?

Hallo,

ich nutze die HAL für einen kleinen Versuchsaufbau.

Ein Nucleo64 mit STM32L476 sendet alle 5 ms 74 Bytes Daten auf der SPI3 
als Master mit 625 kBit/s. Chipselect wird als GPIO-Output angesteuert. 
Clock High im Idle, CS low active, Trigger auf die fallende Flanke.

Ein zweites Nucelo64 soll diese 74 Byte empfangen, konfiguriert als 
Slave ohne NSS-Nutzung. ChipSelect als IRQ-Eingang mit fallender Flanke.

Ich wollte das jetzt so lösen, dass ich im IRQ von der fallenden Flanke 
von ChipSelect ein HAL_SPI_TransmitReceive_DMA() aufrufe. CS geht 
einstellbar auf dem ersten Nucleo früher auf LOW als angefangen wird zu 
senden (hab es bis 50µs versucht).

Auf dem Slave werden die Daten empfangen, aber sie sind immer um ein 
paar Byte versetzt. D.h. meistens sind die ersten 3-4 Bytes noch das 
Ende des letzten Telegrammes und dann beginnt erst das neue Telegramm. 
Ich hab schon alles mögliche versucht, verstehen kann ich das Verhalten 
aber nicht.

Mit dem Logikanalyzer mitgeloggt sind die Daten ok.

Hab ich vielleicht einen Denkfehler?

Grüße
Volker
(Firma: Vescomp) #4864841
Lesenswert?

Volker K. schrieb:
> Ein Nucleo64 mit STM32L476 sendet alle 5 ms 74 Bytes Daten auf der SPI3
> als Master mit 625 kBit/s. Chipselect wird als GPIO-Output angesteuert.
> Clock High im Idle, CS low active, Trigger auf die fallende Flanke.

 Hilft dir nicht weiter, aber deine Einstellungen sind etwas unüblich.
 Normal macht man das mit:
 CPOL = 0 [Clock Low when Idle]
 CPHA = 0 [Trigger auf die steigende Flanke]

 90% der Geräte (Module) funktionieren auf diese Weise.
OP #4864878
Lesenswert?

Danke für die Antwort,

ich soll später Daten von einer HW auslesen, die ich fertig bekomme und 
die arbeitet mit diesen Einstellungen.

Deshalb möchte ich das gerne damit zum laufen bekommen. Würde eine 
andere Einstellung denn etwas an der Problematik ändern?

Hab es gerade mit den empfohlenen Einstellungen versucht, gleiches 
Verhalten.
(Firma: Vescomp) #4864892
Lesenswert?

Volker K. schrieb:
> Auf dem Slave werden die Daten empfangen, aber sie sind immer um ein
> paar Byte versetzt. D.h. meistens sind die ersten 3-4 Bytes noch das
> Ende des letzten Telegrammes und dann beginnt erst das neue Telegramm.

 Slave empfängt nur das, was ihm reingetaktet wird, d.h. dein Master
 hat die vorherigen Daten noch nicht vollständig rausgetaktet.

 Versuche es mal ohne DMA.
OP #4864903
Lesenswert?

Ohne DMA geht es. DMA wäre besser, da die zukünftige HW nur mit 120 kBit 
senden kann und ich für die 74 Byte dann lange blockiert wäre wenn ich 
kein DMA nutze.

Aber bei beiden Modi hab ich ein Problem. Wenn ich während dieser 
laufenden Kommunikation den Master resette, dann läuft wieder alles 
verschoben. Ausser ich resette danach den Slave.

Wie kann ich denn beim SLAVE, wenn ich z.B. über eine Checksumme 
bemerke, dass das Telegramm nicht passt, quasi die SPI zurücksetzen und 
sofort wieder aktivieren?
OP #4864962
Lesenswert?

@Marc:

Du meinst auf dem Master? Der sendet ja das Richtige, sonst würde doch 
nicht der Slave, wenn ich ihn nach dem Problem alleine resette, wieder 
das Richtige empfangen.

Irgendwie kommt der Slave in diesem Fall durcheinander, das könnte ja 
auch mal passieren, wenn durch ein EMV-Problem etwas gestört wird. Es 
gibt ne Checksumme, die ich am Slave auswerten kann, nur was wäre dann 
die richtige Reaktion auf eine detektierte Störung?
(Firma: Vescomp) #4865003
Lesenswert?

Volker K. schrieb:
> auch mal passieren, wenn durch ein EMV-Problem etwas gestört wird. Es
> gibt ne Checksumme, die ich am Slave auswerten kann, nur was wäre dann
> die richtige Reaktion auf eine detektierte Störung?

 Wenn du am Protokoll schrauben kannst, vieles.

 a) Falls der Slave eine falsche Checksumme detektiert, wird in SPI
 Datenregister sofort eine 0x7F reingeschrieben (Beispiel).
 Master kriegt dann beim ersten rausgesendetem Byte (neues Telegramm)
 ein 0x7F anstatt 0xFF und weiss somit, dass mit dem vorigen Telegramm
 etwas nicht in Ordnung war. Natürlich können andere Werte auch andere
 Fehler bezeichnen.

 b) Ein Telegramm kann niemals mit 0xFF anfangen und kann nicht
 länger als z.B. 74 Bytes sein.
 Deswegen weiss der Slave wenn der Master sich z.B. beim 25-sten Byte
 resettet:
   1) Nach weiteren 50 Bytes sollte das Telegram zu Ende sein aber es
      kommen weitere Bytes an (Master hat sich resettet und versucht
      ein neues Telegramm zu senden).
   2) Slave prüft nach 73 Bytes die Checksumme und stellt fest, dass
      diese falsch ist.
   3) Slave schreibt 0x7F ins Dataregister (einmalig oder dauernd) und
   wartet auf das erste Byte <> 0xFF (oder was auch immer als SOF gilt).

 Das ist dann auch das neue Telegramm.

 Oder so ähnlich (ziemlich lange her, dass ich so etwas geschrieben
 habe, kann natürlich versuchen, es rauszugraben).
Gast #4865027
Lesenswert?

Setzt du bei einer neuen Übertragung im Slave den DMA komplett zurück? 
Wie soll der Slave wissen, dass es eine neue Übertragung ist?

Der NSS ist etwas nervig bei dem STM32F ich meine mich zu erinnern, dass 
du über den Interrupt (EXTI) den Slave initalisieren kannst (Pointer 
zurück setzen DMA...) und dann die Übertragung starten.

Versuche es erst einmal nur über den Interrupt und wenn das klappt bau 
es zu DMA um
OP #4865287
Lesenswert?

Hallo,

da ich nicht sicher bin was die richtigen Schritte zum zurücksetzen 
sind, mache ich das vermutlich aktuell nicht korrekt.

@Marc:

Unser Ablauf soll so sein:

1. Das Telegramm hat am Anfang zwei definierte Bytes als SOF und am Ende 
eine 16 Bit Checksumme.

2. Der Master sendet immer die kompletten 74 Byte, zyklisch alle 5 ms. 
Unabhängig davon was der Slave antwortet. Er prüft ebenfalls das SOF und 
die Checksumme.

3. Kommt ein "falsches" Paket an, dann soll das verworfen werden. 
Geprüft wird das im Callback TxRxCplt...

Zwischen zwei Telegrammen ist ja immer 5 ms Zeit, so dass dafür genug 
Zeit zur Verfügung steht. Da ich aber nicht weiß, was der Grund für das 
falsche Paket ist, würde ich gerne in diesem Moment auch die SPI und die 
DMA etc. komplett zurücksetzen.

DeInit/Init hat nicht geklappt, davor noch ein Abort auch nicht.

Also wenn da jemand noch eine konkrete Vorgehensweise kennt, wäre das 
prima. Wie schon gesagt bin ich als Anfänger im Moment ein HAL-Nutzer 
und nicht so tief in den Register-Varianten drin.
OP #4865309
Lesenswert?

Ich hatte Folgendes versucht, wenn die Kommunikation schief lief:


    halRet = HAL_DMA_Abort_IT(&hdma_spi3_rx);
    halRet = HAL_DMA_DeInit(&hdma_spi3_rx);
    halRet = HAL_DMA_Abort_IT(&hdma_spi3_tx);
    halRet = HAL_DMA_DeInit(&hdma_spi3_tx);
    halRet = HAL_SPI_DeInit(&hspi3);
    MX_DMA_Init();
    MX_SPI3_Init();

Der erste HAL_DMA_Abort_IT() liefert dann immer ein HAL_ERROR zurück.

Der hdma->State ist dabei: HAL_DMA_STATE_READY
Lock = HAL_UNLOCKED.

Der Inhalt von hdma ist in beiden Fällen (gut/schlecht) identisch.
#4865324
Lesenswert?

Volker K. schrieb:
> konfiguriert als
> Slave ohne NSS-Nutzung. ChipSelect als IRQ-Eingang mit fallender Flanke.

Das /SS sagt dem Slave, wann ein Paket beginnt. Warum willst Du das 
nicht nutzen?

Ein Master fängt typisch direkt nach der 1-0 Flanke an, zu senden. Da 
hast Du keine Zeit mehr, erst noch irgendwas zu initialisieren. Laß es 
also die HW machen.
Oder nimm die abschließende 0-1 Flanke zum Reset des SPI.
OP #4865339
Lesenswert?

Ich glaub ich weiss, was mein Problem mit dem NSS war. Ich konnte nicht 
den NSS in der SPI nutzen und gleichzeitig einen IRQ von dem NSS-Pin 
bekommen. Den IRQ wollte ich ja nutzen um dort den TransmitReceive_DMA() 
scharf zu schalten.

Aber ich glaube ich hab einen Denkfehler. Ich schaue in der TxRxCplt() 
nach dem DMA Status. Dort ist der natürlich OK, weil die 74 Byte ja 
korrekt empfangen wurden.

Ich müsste im IRQ vom CS schauen, ob da der DMA noch nicht fertig ist.
OP #4879915
Lesenswert?

Also kurz zum aktuellen Stand:

Ich habe auf IRQ umgestellt und es wurde etwas besser. Allerdings ist 
nicht ganz zu verstehen, wie der NSS in Hardware funktioniert. Lege ich 
ihn komplett auf High statt Low, dann werden immer noch Daten empfangen. 
Da der Versatz der Daten immer noch möglich ist, wollte ich folgendes 
umsetzen:

Parallel zum NSS werte ich die steigende Flanke von CSS aus (Dann ist ja 
die SPI-Kommunikation für das eine Telegramm beendet).

In der IRQ-Routine hab ich dann Folgendes implementiert:

  if (GPIO_Pin == GPIO_PIN_3)
  {
     if (hspi3.ErrorCode != HAL_SPI_ERROR_NONE)
    {
      if (hspi3.ErrorCode == HAL_SPI_ERROR_OVR)
      {
        __HAL_SPI_CLEAR_OVRFLAG(&hspi3);
      }
      else if (hspi3.ErrorCode == HAL_SPI_ERROR_MODF)
      {
        __HAL_SPI_CLEAR_MODFFLAG(&hspi3);
      }
      else if (hspi3.ErrorCode == HAL_SPI_ERROR_FRE)
      {
        __HAL_SPI_CLEAR_FREFLAG(&hspi3);
      }


      HAL_SPI_DeInit(&hspi3);
      MX_SPI3_Init();
      HAL_SPI_Abort_IT(&hspi3);
      HAL_SPIEx_FlushRxFifo(&hspi3);

      // Alles neu intialisiert, jetzt wieder den Empfang triggern
      halRet = HAL_SPI_TransmitReceive_IT(&hspi3, abSend, abReceive, 
74);
    }
    else
    {
      HAL_SPI_Abort_IT(&hspi3);
      HAL_SPI_DeInit(&hspi3);
      MX_SPI3_Init();
      HAL_SPIEx_FlushRxFifo(&hspi3);

      // Alles neu intialisiert, jetzt wieder den Empfang triggern
      halRet = HAL_SPI_TransmitReceive_IT(&hspi3, abSend, abReceive, 
74);
    }
  }

Damit hätte meiner Meinung nach am Ende jedes SPI-Telegramms die SPI 
wieer in den Anfangszustand versetzt werden sollen, der ja immer 
funktioniert. Nach einem Reset war der SPI-Empfang immer korrekt.

Leider scheint da etwas zu kollidieren, weil er immer wieder im 
SPI_Abort an dieser Stelle hängt:

  /* Change Rx and Tx Irq Handler to Disable TXEIE, RXNEIE and ERRIE 
interrupts */
  if (HAL_IS_BIT_SET(hspi->Instance->CR2, SPI_CR2_TXEIE))
  {
    hspi->TxISR = SPI_AbortTx_ISR;
    while (hspi->State != HAL_SPI_STATE_ABORT);
  }

Er kam dann aus dem while nicht mehr raus. Ich hatte mit IRQ-Prioritäten 
rum gespielt, aber das brachte keine Besserung.

Mir wäre es am liebsten, wenn ich mit der steigenden Flanke von CS die 
komplette SPI zurücksetzen kann, so dass diese dann immer wieder "neu" 
startet. Ohne alte Daten im RX-Buffer.
OP #4880596
Lesenswert?

Ich glaube ich muss noch ein bisschen ausholen, warum ich das hier 
mache.

Ich soll für einen Bekannten ein Gateway SPI->Ethernet implementieren. 
Die SPI (als Master mit einem PIC realisiert) funktioniert korrekt.

Da das Ganze später aber auch unter EMV-Bedingungen funktionieren soll, 
will ich das Ganze weitestgehend wasserdicht implementieren.

Ich hab hier also beide Boards (ein Nucleo 64 und das PIC-Board) 
nebeneinander stehen und hab für die ersten Tests beide mit 10 cm 
Leitungen verbunden. SPI läuft mit 250 kBit.

Der PIC als Master sendet fröhlich alle 5 ms ein neues Telegramm mit 
einer Startsequenz und einer 16 Bit CRC am Ende.

Ich empfange mit dem STM fröhlich und versuche jetzt durch Aus- und 
Wiedereinstecken der SPI-Kabel oder RESETs am PIC die SPI-Kommunikation 
zu stören. Und ich erwarte, dass Sie sich immer wieder fängt und korrekt 
die gesendeten Telegramme empfängt.

Manchmal geht der STM sogar in HardFault, bis jetzt meist, wenn man die 
GND-Leitung manipuliert.

Das Ganze läuft in der jetzigen Implementierung schon sehr stabil, aber 
in ganz seltenen Fällen bekommt man einen Zustand, wo die SPI immer Ihre 
korrekte Byteanzahl empfängt, aber die Bytes verschoben sind. Oft um 
ganze Bytes, ganz selten auch mal um ein paar Bits.

Mein Ziel wäre es, dass sich die SPI nach Fehlern immer wieder fängt und 
ich dann wieder korrekte Telegramme empfangen kann.

So viel zu meiner Intention an dieser Stelle.

Die SPI-Übertragung selbst dauert knapp unter 3 ms, die restlichen 2 ms 
habe ich Zeit die SPI zurück zusetzen und wieder in einen 
empfangsfähigen Zustand zu versetzen.
#4880607
Lesenswert?

Volker K. schrieb:
> Ich empfange mit dem STM fröhlich und versuche jetzt durch Aus- und
> Wiedereinstecken der SPI-Kabel oder RESETs am PIC die SPI-Kommunikation
> zu stören. Und ich erwarte, dass Sie sich immer wieder fängt und korrekt
> die gesendeten Telegramme empfängt.

Huiii, das kann aber ins Auge gehen...

> Manchmal geht der STM sogar in HardFault, bis jetzt meist, wenn man die
> GND-Leitung manipuliert.

Klar: Dann fließen eventuelle Ausgleichsströme über die GPIO Pins. Nicht 
so gut. Man bedenke das ohne GND die Referenz fehlt, das gibt 
"interessante" Effekte.

Volker K. schrieb:
> Das Ganze läuft in der jetzigen Implementierung schon sehr stabil, aber
> in ganz seltenen Fällen bekommt man einen Zustand, wo die SPI immer Ihre
> korrekte Byteanzahl empfängt, aber die Bytes verschoben sind. Oft um
> ganze Bytes, ganz selten auch mal um ein paar Bits.

Das ist der Erwartungswert, wenn kein NSS benutzt wird. Mitten in einer 
Übertragung angesteckt empfängt er ein zufälliges Bit als Erstes.

Volker K. schrieb:
> Die SPI-Übertragung selbst dauert knapp unter 3 ms, die restlichen 2 ms
> habe ich Zeit die SPI zurück zusetzen und wieder in einen
> empfangsfähigen Zustand zu versetzen.

Dann mach nach der Übertragung den NSS hoch. Auf dem Slave triggert das 
die Auswertung + Neuinitialiserung, um wieder bei fallender Flanke für 
die nächste Übertragung bereit zu sein. So ist beim Anstecken nur die 1. 
Übertragung kaputt.

Braucht man übrigens nicht unbedingt mit an-und Abstecken testen - 
einfach die SPI Ports im Master zwischendurch mal auf GPIO umstellen und 
Bits rauskloppen ginge auch.
OP #4880621
Lesenswert?

NSS brachte an dieser Stelle keine Verbesserung. Empfangen wird auch 
wenn der NSS-Pin auf HIGH bleibt.

Das war ja die grundsätzliche Idee, die man bei Nutzung der CS-Leitung 
hat. Ohne NSS auf LOW sollte die SPI nichts empfangen und am Ende, wenn 
NSS wieder auf High geht, dann soll der Empfang aufhören. Pustekuchen.

Ich hab dann parallel zu NSS noch einen Pin als IRQ mit steigender 
Flanke konfiguriert und den NSS da drauf gebrückt. Da wollte ich dann in 
der IRQ-Routine einfach immer der SPI zurücksetzen (mit SPI_ABORT, 
FlushFifo etc.). Hat aber nicht immer funktioniert, ab und zu scheint 
die SPI noch nicht ganz fertig gewesen zu sein, wenn der IRQ aufgerufen 
wurde. Dann hing er in der Funktion SPI_Abort() in einer 
while()-Schleife fest.

Also jetzt wieder ohne NSS und nur mit einem Software-CS mit dem ich 
selbst bestimmen kann was bei welcher Flanke getan wird.

Das ist der aktuelle Stand. Heute noch auf DMA erweitert und es läuft 
gar nicht schlecht.

Erklären kann ich mir nur nicht, dass er beim Abziehen/Einstecken der 
GND-Verbindung der SPI ab und an im HardFault_Handler() landet. Und zwar 
meist mit dem ErrorCode NOCOP "No Coprozessor".

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren