STM32 SPI: seltsames Verhalten bei leerem TXFIFO (vorheriger Byte-Verlauf?)

Der folgende Code konfiguriert und aktiviert SPI2 als Slave auf meiner STM32F303RE-Karte , schreibt 0xAA-, 0xBB-, 0xCC-, 0xDD- Bytes in das DR- Register und macht eine Weile eine Schleife (1) :

/* Enable clocks for GPIOB (SPI2 pins) and SPI2 peripheral. */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

/* SPI pin mappings. */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_5); /* SPI2_NSS */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_5); /* SPI2_SCK */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_5); /* SPI2_MISO */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_5); /* SPI2_MOSI */

GPIO_InitTypeDef gpio_init_struct =
{
    .GPIO_Mode = GPIO_Mode_AF,
    .GPIO_OType = GPIO_OType_PP,
    .GPIO_PuPd = GPIO_PuPd_DOWN,
    .GPIO_Speed = GPIO_Speed_50MHz
};

/* SPI NSS pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI SCK pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI MISO pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB, &gpio_init_struct);
/* SPI  MOSI pin configuration. */
gpio_init_struct.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOB, &gpio_init_struct);

SPI_InitTypeDef spi_init_struct =
{
    .SPI_Direction          = SPI_Direction_2Lines_FullDuplex,
    .SPI_Mode               = SPI_Mode_Slave,
    .SPI_DataSize           = SPI_DataSize_8b,
    .SPI_CPOL               = SPI_CPOL_Low,
    .SPI_CPHA               = SPI_CPHA_1Edge,
    .SPI_NSS                = SPI_NSS_Hard,
    .SPI_BaudRatePrescaler  = SPI_BaudRatePrescaler_2,
    .SPI_FirstBit           = SPI_FirstBit_MSB,
    .SPI_CRCPolynomial      = 7
};

SPI_I2S_DeInit(SPI2);
SPI_Init(SPI2, &spi_init_struct);
SPI_CalculateCRC(SPI2, DISABLE);
SPI_TIModeCmd(SPI2, DISABLE);
SPI_NSSPulseModeCmd(SPI2, DISABLE);

SPI_Cmd(SPI2, ENABLE);
SPI_SendData8(SPI2, (uint8_t) 0xAA);
SPI_SendData8(SPI2, (uint8_t) 0xBB);
SPI_SendData8(SPI2, (uint8_t) 0xCC);
SPI_SendData8(SPI2, (uint8_t) 0xDD);

while(1) { }

Bei einem Master, der 2 Bytes pro Chip-Select anfordert , erhält der Master:

0xAA 0xBB
0xCC 0xDD
0xAA 0xAA -----> TXFIFO should be empty here, why not "0x00 0x00"?
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
0xAA 0xAA
......... (0xAA 0xAA infinite times)

Ich hätte erwartet, dass der Master "0x00 0x00" erhält, nachdem TXFIFO leer wird . Warum erhalte ich stattdessen kontinuierlich " 0xAA 0xAA "? Im Handbuch konnte ich nichts finden, was auf ein solches Verhalten hindeuten würde.

AKTUALISIERUNG 1

Warten Sie, bis die Transaktionen kurz vor dem while(1) abgeschlossen sind , und schreiben Sie danach Nullen auf den SPI , wie folgt:

while(SPI_GetTransmissionFIFOStatus(SPI2) != SPI_TransmissionFIFOStatus_Empty) { }
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) != RESET) { }

#define ZEROS_CNT   (1)
for(int i = 0; i < ZEROS_CNT; i++)
    SPI_SendData8(SPI2, 0);

while(1) { }

rendert das folgende Master-Verhalten für verschiedene Werte von ZEROS_CNT:

ZEROS_CNT = 0 => master receives after TXFIFO is empty: 0xAA infinitely
ZEROS_CNT = 1 => master receives after TXFIFO is empty: 0x00 1 times, followed by 0xBB infinitely
ZEROS_CNT = 2 => master receives after TXFIFO is empty: 0x00 2 times, followed by 0xCC infinitely
ZEROS_CNT = 3 => master receives after TXFIFO is empty: 0x00 3 times, followed by 0xDD infinitely
ZEROS_CNT >= 4 => master receives after TXFIFO is empty: 0x00 infinitely

Es sieht so aus, als ob das SPI-Peripheriegerät eine Art Verlauf dessen hat, was in das TXFIFO geschrieben wurde, und wenn es leer wird, sendet es Bytes aus diesem Verlauf.

AKTUALISIERUNG 2

Es verhält sich gleich, unabhängig davon, wie viele Bytes der Master in einem einzelnen Chip-Select anfordert. Ich habe versucht, 1, 2, 4 und 5 @ gleichzeitig anzufordern.

Das sieht ziemlich seltsam aus. Haben Sie versucht, den SPI nach dem Senden von 0xdd zu deaktivieren?
Hallo Vladimir, ja, eine weitere seltsame Sache ist, dass es nach dem Deaktivieren "0x00 0x00" sendet - aber wenn es wieder aktiviert wird, beginnt es erneut, "0xAA 0xAA" zu senden ... zwei Problemumgehungen dafür wären entweder das Deaktivieren bis ich etwas Neues zu senden habe - ODER - das Peripheriegerät vollständig zurücksetzen (SPI_I2S_DeInit (SPI2)). Ich würde jedoch gerne wissen, warum es sich so verhält (und nach welcher Logik) und ob ich ein vollständiges Zurücksetzen vermeiden könnte (was auch eine Neukonfiguration des Peripheriegeräts erfordert - irgendwie überflüssig).
Möchten Sie den Link zur entsprechenden Appnote/Datenblatt/was auch immer hinzufügen? Ich kann es mir gerade nicht ansehen, aber vielleicht fällt es mir heute Abend wieder ein.
Du meinst das STM32-Referenzhandbuch? Für mein STM32F303RE-Board ist es hier: st.com/content/ccc/resource/technical/document/reference_manual/…
Haben Sie versucht, auf das Eintreffen der empfangenen Daten zu warten?
@domen Siehe UPDATE
Was? Nein. Auf SPI-Daten RX warten. Das Herumspielen mit GPIOs beim Debuggen von SPI-Problemen verlangt nach Ärger (und hackigen Lösungen, die funktionieren könnten ).
Ich habe einen Logikanalysator, der obige funktioniert korrekt. Ändern Sie es mit Warten auf SPI RX (übrigens, warum auf RX warten und nicht, bis TXFIFO leer ist und BSY stattdessen nicht gesetzt ist?) Und Sie werden sehen, dass es sich genauso verhält. :)
Hallo @domen, Entschuldigung, ich hatte gerade meinen Raspberry Pi 2 (Master) vorhin ausgeschaltet und hatte keine Zeit, den Code zu ändern. Frage aktualisiert. Wie ich erwartet hatte, ergibt das Warten darauf, dass TXFIFO leer wird und BSY nicht gesetzt wird, das gleiche Verhalten.
OK. Ich glaube, Sie sollten vor jedem SPI_SendData8 auf ein leeres TXFIFO warten. Um fair zu sein, bin ich mir nicht sicher, wie dies zusammenhängen würde. 0xaa (10101010) sieht so aus, als ob die Uhr auf der Datenleitung induziert werden könnte. Wie lang sind deine Drähte? Was steht in Statusregistern? Ich denke, es ist auch möglich, dass ein zufälliges Byte verwendet wird, wenn im TX-Register nichts Neues steht.
Die Uhr ist doppelt so schnell wie die Daten. Wenn also eine (sehr schlechte) Kopplung auftreten würde, würde ich je nach Polarität des Kanals entweder 0x00 oder 0xff erwarten.
@domen Sie sind überhaupt nicht zufällig. Das soll der beschriebene Versuch verdeutlichen. Das Warten darauf, dass TXFIFO nach jedem Byte leer ist, ist keine Option (macht den Zweck des FIFO zunichte) und verhält sich trotzdem gleich.

Antworten (2)

Ich habe das herausgefunden und beschlossen, diese Antwort umfassender zu machen, indem ich einige Animationen erstellt habe. Zunächst einmal gibt es 2 Tatsachen zu beachten, die die Logik hinter dem Verhalten von TXFIFO bestimmen:

  • Wie im Handbuch angegeben, hat TXFIFO eine Größe von 32 Bit = 4 Bytes - lassen Sie diese Bytes B1|B2|B3|B4 in dieser Reihenfolge sein.
  • FAKT #1 : Wenn der Master ein Byte vom Slave anfordert, ist das entnommene und zurückgegebene Byte B1 , aber es wird nicht wirklich aus dem TXFIFO entfernt ---> der Inhalt des TXFIFO wird nach links rotiert und nicht nach links verschoben . Dies führt dazu, dass TXFIFO nach diesem Pop wie B2|B3|B4|B1 aussieht , anstatt wie B2|B3|B4|00 (was @ogrenci meiner Meinung nach mit seiner Antwort gemeint hat).
  • FAKT #2 : Wenn TXFIFO leer wird, erhält eine Master-Anfrage aus irgendeinem Grund das 1. Byte, das sich in diesem Moment in TXFIFO befindet, anstelle von 0x00 , wie ich es erwartet hätte.

RXFIFO verhält sich höchstwahrscheinlich genauso.

Der in C# geschriebene Algorithmus für push/pop lautet wie folgt:

public class TXFIFO
{
    public byte[] data;
    byte push_position = 1;
    byte occupied = 0;

    public TXFIFO()
    {
        data = new byte[4];
    }

    public byte Push(byte v)
    {
        // write
        data[push_position - 1] = v;
        // push_position
        if (push_position < 4) push_position++;
        else push_position = 1;
        // occupied
        if (occupied < 4) occupied++;
        return v;
    }

    public byte Pop()
    {
        // read
        if (occupied == 0) return data[0];
        byte v = data[0];
        // rotate left once
        for (int i = 1; i < 4; i++)
            data[i - 1] = data[i];
        data[3] = v;
        //push_position
        if (push_position > 1) push_position--;
        else push_position = 4;
        //occupied
        if (occupied > 0) occupied--;
        return v;
    }

    public byte GetOccupied()
    {
        return occupied;
    }
}

Und hier sind 5 Animationen, die die ursprünglich beschriebenen ZEROS_CNT- Szenarien veranschaulichen (siehe UPDATE 1 der Frage ). Beachten Sie, dass ich , um den Punkt klarer zu machen, anstatt Nullen einzufügen , hier 0x01-0x02-..to..-ZEROS_CNT -Werte eingefügt habe.

ZEROS_CNT = 0:

Geben Sie hier die Bildbeschreibung ein

ZEROS_CNT = 1:

Geben Sie hier die Bildbeschreibung ein

ZEROS_CNT = 2:

Geben Sie hier die Bildbeschreibung ein

ZEROS_CNT = 3:

Geben Sie hier die Bildbeschreibung ein

ZEROS_CNT = 4:

Geben Sie hier die Bildbeschreibung ein

ZEROS_CNT = 5:

Geben Sie hier die Bildbeschreibung ein

...usw...

Wie bereits in der Frage erwähnt, würde eine Problemumgehung für das Senden von 0x00 bei leerem TXFIFO darin bestehen, das SPI- Peripheriegerät deaktiviert zu lassen , wenn TXFIFO leer ist, bis neue Daten in DR geschrieben werden , was ich schließlich tat, nachdem ich verstanden hatte, was los ist .

Tatsächlich werden die Daten wahrscheinlich nicht wirklich im Puffer herumkopiert, sondern es werden zwei Indizes für den Anfang und das Ende der gepufferten Daten beibehalten. Wenn Start und Ende auf dasselbe Element des Puffers zeigen, kann das zweierlei bedeuten: Entweder ist der Puffer vollständig voll oder er ist vollständig leer. Keine Möglichkeit festzustellen, welches es ist, ohne irgendwo ein anderes Flag zu pflegen. Siehe Ringpuffer .
@m.Alin danke! Ich hoffe, sie machen die Sache klarer. JimmyB ist sich nicht sicher, was Sie sagen wollen - der SPI speichert Informationen, die Ihnen sagen, wie hoch die aktuelle Auslastung der FIFOs ist.
@JimmyB ooh, ich verstehe. Ja, du hast höchstwahrscheinlich recht. Es wäre zu ineffizient, Elemente bei einem Pop weiter nach links zu verschieben. Wie auch immer, ich denke, die Ergebnisse der obigen Pop/Push-Operationen würden dieselben Werte wiedergeben. Ich denke, man könnte versuchen zu verstehen, wie dieser Schaltplan im SPI-Blockdiagramm tatsächlich auf elektronischer Ebene funktioniert, um wirklich herauszufinden, wie diese Operationen intern durchgeführt werden (die verwendete Datenstruktur).
Ich wollte nur eine einfache Erklärung liefern, warum ein bestimmtes Element im Puffer zu bleiben scheint. Wenn das Gerät weiß, wann der Puffer leer ist, könnte/sollte die SPI-Schaltung dieses Informationsbit verwenden, um eine konsistente Ausgabe (0x00) anstelle von "zufälligem" Müll bereitzustellen.
@JimmyB ja, danke für den Input und sorry, ich habe nicht verstanden, was du beim ersten Mal vorschlagen wolltest;)
Dumme Frage: Was hast du für die Animationen verwendet?
Hallo @pgvoorhees. Eigentlich nichts Besonderes, nur ein bisschen C# (meine TXFIFO-Klasse hat eine Draw-Methode, die eine Bitmap-Instanz zurückgibt, auf der sie ihren Zustand gezeichnet hat (siehe Bitmap- und Grafikklassen). Ich würde ein Szenario erstellen (Push/Pops-Sequenz) und aufrufen Zeichnen Sie nach jeder Operation. Speichern Sie die Bitmaps in Bilddateien und verwenden Sie gifcreator.me , um die GIFs zu erstellen. Aber wahrscheinlich gibt es bessere Tools, um dies zu tun, anstatt das Rad neu zu erfinden, ich hatte einfach nicht die Neigung, danach zu suchen, da ich andere Arbeit zu erledigen hatte.

Die Werte nach dem 4. Byte stammen nicht aus dem TX-Puffer des Slaves. Untersuchen Sie das SPI-Blockdiagramm. Sie werden ursprünglich als Speicherauszugsdaten vom Master gesendet, wenn der SCI durchlaufen wird, und sie kehren vom Schieberegister des Slaves zurück und erscheinen als gültige Daten. Wenn der Master Daten abruft, wird erwartet, dass dies überschrieben wird, in diesem Fall jedoch nicht, da der Slave keine Daten in den TX-Puffer drückt und das Schieberegister geladen wird.

Hallo ogrenci, meinten Sie "als Dump-Daten gesendet, wenn der SPI von Raspberry PI durchlaufen wird"?
Hallo @CorneliuZuzu. Ich meine Raspberry PI. Ich habe die Antwort bearbeitet.