Forum: Mikrocontroller und Digitale Elektronik STM32 Receive via SPI immer um ein paar Bytes versetzt


von Volker K. (vokuit00)


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

von holger (Gast)


Lesenswert?

>als Slave ohne NSS-Nutzung.

OMG

von Volker K. (vokuit00)


Lesenswert?

Hilft mit deine Antwort jetzt?

Bin Anfänger und hab es auch mit NSS funktioniert, da hat es auch nicht 
richtig funktioniert.

von Marc V. (Firma: Vescomp) (logarithmus)


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.

: Bearbeitet durch User
von Volker K. (vokuit00)


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.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


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.

von Volker K. (vokuit00)


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?

von grundschüler (Gast)


Lesenswert?

das hört sich nach Problem mit dem fifo an.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Volker K. schrieb:
> 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.

Sollte das nicht auch mit einem Interrupttreiber zu erledigen sein?

von Volker K. (vokuit00)


Lesenswert?

Kann ich auch mal versuchen.

Zum "Zurücksetzen" der verkorksten Kommunikation hab ich schon:

HAL_SPI_Abort_IT() und
HAL_SPI_DeInit()

versucht, leider ohne Erfolg.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Volker K. schrieb:
> 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.

 Mach es mit 74Byt * 8Takte = 592 Takte, also zur Sicherheit beim
 Reset erstmal 75 * 0xFF raustakten.

: Bearbeitet durch User
von H-G S. (haenschen)


Lesenswert?

Lad dir irgendwas Fertiges runter wie es gern gemacht wird  :-)

von Volker K. (vokuit00)


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?

von Marc V. (Firma: Vescomp) (logarithmus)


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).

: Bearbeitet durch User
von Pfurz (Gast)


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

von Volker K. (vokuit00)


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.

von Pfurz (Gast)


Lesenswert?

Warum machst du es nun nicht per Interrupt (Testweise) und schwenkst 
dann auf DMA um. So weißt du, an welcher Stelle es hackt?

Nutzt du das CRC vom SPI? oder was eigenes?

von Volker K. (vokuit00)


Lesenswert?

Das mit dem IRQ werde ich heute Abend mal testen.

Wir nutzen ne eigene CRC weil der Master keine HW-Unterstützung für ne 
32 Bit CRC hat und schon ziemlich ausgelastet ist. Deshalb ne 16 Bit 
CRC.

von Volker K. (vokuit00)


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.

: Bearbeitet durch User
von Lutz H. (luhe)


Lesenswert?

Es fehlt die Erkennung des Anfangs eines Telegramms im Slave, um Master 
und Slave zu synchronisieren.

von Peter D. (peda)


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.

von Volker K. (vokuit00)


Lesenswert?

Mit dem NSS hat es nicht richtig funktioniert, deshalb hab ich den CS 
mit IRQ-Flankenerkennung probiert. Der Master macht nach der fallenden 
Flanke von CS eine Pause von aktuell 50µs bevor das Senden los geht. Das 
kann ich dem Programmierer des Master vorgeben.

von Peter D. (peda)


Lesenswert?

Volker K. schrieb:
> Mit dem NSS hat es nicht richtig funktioniert

Soso.

von Volker K. (vokuit00)


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.

von Volker K. (vokuit00)


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.

von grundschüler (Gast)


Lesenswert?

probier das Ganze mal mit Bitbang-spi. Wenn es damit nicht läuft, muss 
es an der Hardware liegen.

von Volker K. (vokuit00)


Lesenswert?

Was ist denn eine Bitbang-SPI?

von Keks (Gast)


Lesenswert?

Bitbang SPI: Ask your favourite search engine.
Dort gibt's auch gleich entsprechenden sourcecode.

von Volker K. (vokuit00)


Lesenswert?

Sorry, aber warum sollte ich bei einer vorhandenen HW-SPI auf eine 
simulierte umsteigen?

Das man die SPI zum funktionieren bekommt, davon gehe ich aus. Es fehlen 
sicherlich nur Detailkenntnisse an ein paar Stellen um meine Problemchen 
lösen zu können.

von Jim M. (turboj)


Lesenswert?

grundschüler schrieb:
> probier das Ganze mal mit Bitbang-spi. Wenn es damit nicht läuft, muss
> es an der Hardware liegen.

Bitbang SPI geht nur im Master Mode, hier geht es AFAIK um den SPI 
Client.

von Volker K. (vokuit00)


Lesenswert?

Ja, es geht um den Slave.

von grundschüler (Gast)


Lesenswert?

Jim M. schrieb:
> Bitbang SPI geht nur im Master Mode

Halte ich für eine Mindermeinung.


Volker K. schrieb:
> Sorry, aber warum sollte ich bei einer vorhandenen HW-SPI auf eine
> simulierte umsteigen?

Weil du möglicherweise mit den fifos nicht zurechtkommst und bitbang-spi 
ohne fifo leichter beherrschbar bzw. zur Eingrenzung des Problems 
geeignet ist?

von Volker K. (vokuit00)


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.

von Jim M. (turboj)


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.

von Volker K. (vokuit00)


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".

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.