STM32-SPI-Halbduplex-Problem (1-Draht bidirektional).

Update: Siehe meine Antwort zur Behebung.

Ich versuche, 4 Byte von einem SPI-kompatiblen Slave (MAX31855) in bidirektionalem 1-Draht-SPI-Halbduplex zu lesen.

Hier ist mein Code [SW Controlled SS] [SO->MOSI]

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/dma.h>
#include <libopencm3/stm32/spi.h>
#include <libopencm3/stm32/f0/nvic.h>
#include <libopencm3/stm32/gpio.h>

/* USE: read 4 byte from a spi compatible slave (MAX31855) in 1 wire bidirectonal spi half-duplex */

#define ARRAY_SIZE 50

uint8_t arr_tx[ARRAY_SIZE];
uint8_t arr_rx[ARRAY_SIZE];

/* temp fix for libopencm3 */
#define SPI2_I2S_BASE SPI2_BASE

void main(void)
{
    rcc_periph_clock_enable(RCC_DMA);
    rcc_periph_clock_enable(RCC_SPI2);
    rcc_periph_clock_enable(RCC_GPIOB);

    /* INIT SPI GPIO */
    gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO13|GPIO14|GPIO15);
    gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, GPIO13|GPIO14|GPIO15);
    gpio_set_af(GPIOB, GPIO_AF0, GPIO13|GPIO14|GPIO15);

    /* INIT SPI SS GPIO */
    gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO12);
    gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, GPIO12);
    gpio_set(GPIOB, GPIO12);

    /* DMA NVIC */
    nvic_set_priority(NVIC_DMA1_CHANNEL4_5_IRQ, 3);
    nvic_enable_irq(NVIC_DMA1_CHANNEL4_5_IRQ);

    /* SPI NVIC */
    nvic_set_priority(NVIC_SPI2_IRQ, 3);
    nvic_enable_irq(NVIC_SPI2_IRQ);

    /* INIT DMA SPI RX (DMA CHAN4) */
    DMA1_IFCR = DMA_IFCR_CGIF4;
    DMA1_CCR4 = DMA_CCR_MINC | DMA_CCR_TEIE | DMA_CCR_TCIE;
    DMA1_CNDTR4 = 4;
    DMA1_CPAR4 = (uint32_t)&SPI2_DR;
    DMA1_CMAR4 = (uint32_t)arr_rx;

    /* INIT DMA SPI TX (DMA CHAN5) */
    DMA1_IFCR = DMA_IFCR_CGIF5;
    DMA1_CCR5 = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TEIE | DMA_CCR_TCIE;
    DMA1_CNDTR5 = 4;
    DMA1_CPAR5 = (uint32_t)&SPI2_DR;
    DMA1_CMAR5 = (uint32_t)arr_tx;

    /* INIT SPI */
    SPI2_I2SCFGR = 0;
    SPI2_CR1 = SPI_CR1_BAUDRATE_FPCLK_DIV_256 | SPI_CR1_MSTR | SPI_CR1_BIDIMODE | SPI_CR1_SSM | SPI_CR1_SSI;
    SPI2_CR2 = SPI_CR2_DS_8BIT | SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN | SPI_CR2_ERRIE | SPI_CR2_FRXTH;

    gpio_clear(GPIOB, GPIO12);

    DMA1_CCR4 |= DMA_CCR_EN; /* RX CHAN */
    SPI2_CR1 |= SPI_CR1_SPE;
    DMA1_CCR5 |= DMA_CCR_EN; /* TX CHAN */

    /* LOOP */
    for(;;) {
        __asm__("wfi");
    }
}

void spi2_isr(void)
{
    __asm__("bkpt");
}


void dma1_channel4_5_isr(void)
{
    /* error occured? */
    if(DMA1_ISR & (DMA_ISR_TEIF4 | DMA_ISR_TEIF5)) {
        /* clear the flags */
        DMA1_IFCR = DMA_IFCR_CGIF4 | DMA_IFCR_CGIF5;

        __asm__("bkpt");
    }

    /* execute next if transfer is complete */
    if(DMA1_ISR & (DMA_ISR_TCIF4 | DMA_ISR_TCIF5)) {

        /* Wait to receive last data */
        while (SPI2_SR & SPI_SR_RXNE);

        /* Wait to transmit last data */
        while (!(SPI2_SR & SPI_SR_TXE));

        /* Wait until not busy */
        while (SPI2_SR & SPI_SR_BSY); // infinite loop here: SPI2_SR = 0x06c3

        /* clear the flags */
        DMA1_IFCR = DMA_IFCR_CGIF4 | DMA_IFCR_CGIF5;


        gpio_set(GPIOB, GPIO12);
        /* disable SPI */
        SPI2_CR1 &= ~SPI_CR1_SPE;

        /* disable DMA trigger */
        SPI2_CR2 &= ~(SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN);

        __asm__("bkpt");
    } else {
        __asm__("bkpt");
    }
}

Code für HW-gesteuerte SS

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/dma.h>
#include <libopencm3/stm32/spi.h>
#include <libopencm3/stm32/f0/nvic.h>
#include <libopencm3/stm32/gpio.h>

/* USE: read 4 byte from a spi compatible slave (MAX31855) in 1 wire bidirectonal spi half-duplex */

#define ARRAY_SIZE 50

uint8_t arr_tx[ARRAY_SIZE];
uint8_t arr_rx[ARRAY_SIZE];

/* temp fix for libopencm3 */
#define SPI2_I2S_BASE SPI2_BASE

void main(void)
{
    rcc_periph_clock_enable(RCC_DMA);
    rcc_periph_clock_enable(RCC_SPI2);
    rcc_periph_clock_enable(RCC_GPIOB);

    /* INIT SPI GPIO */
    gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO12|GPIO13|GPIO14|GPIO15);
    gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, GPIO12|GPIO13|GPIO14|GPIO15);
    gpio_set_af(GPIOB, GPIO_AF0, GPIO12|GPIO13|GPIO14|GPIO15);

    /* DMA NVIC */
    nvic_set_priority(NVIC_DMA1_CHANNEL4_5_IRQ, 3);
    nvic_enable_irq(NVIC_DMA1_CHANNEL4_5_IRQ);

    /* SPI NVIC */
    nvic_set_priority(NVIC_SPI2_IRQ, 3);
    nvic_enable_irq(NVIC_SPI2_IRQ);

    /* INIT DMA SPI RX (DMA CHAN4) */
    DMA1_IFCR = DMA_IFCR_CGIF4;
    DMA1_CCR4 = DMA_CCR_MINC | DMA_CCR_TEIE | DMA_CCR_TCIE;
    DMA1_CNDTR4 = 4;
    DMA1_CPAR4 = (uint32_t)&SPI2_DR;
    DMA1_CMAR4 = (uint32_t)arr_rx;

    /* INIT DMA SPI TX (DMA CHAN5) */
    DMA1_IFCR = DMA_IFCR_CGIF5;
    DMA1_CCR5 = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TEIE | DMA_CCR_TCIE;
    DMA1_CNDTR5 = 4;
    DMA1_CPAR5 = (uint32_t)&SPI2_DR;
    DMA1_CMAR5 = (uint32_t)arr_tx;

    /* INIT SPI */
    SPI2_I2SCFGR = 0;
    SPI2_CR1 = SPI_CR1_BAUDRATE_FPCLK_DIV_256 | SPI_CR1_MSTR | SPI_CR1_BIDIMODE;
    SPI2_CR2 = SPI_CR2_DS_8BIT | SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN | SPI_CR2_ERRIE | SPI_CR2_FRXTH | SPI_CR2_SSOE;

    DMA1_CCR4 |= DMA_CCR_EN; /* RX CHAN */
    SPI2_CR1 |= SPI_CR1_SPE;
    DMA1_CCR5 |= DMA_CCR_EN; /* TX CHAN */

    /* LOOP */
    for(;;) {
        __asm__("wfi");
    }
}

void spi2_isr(void)
{
    __asm__("bkpt");
}


void dma1_channel4_5_isr(void)
{
    /* error occured? */
    if(DMA1_ISR & (DMA_ISR_TEIF4 | DMA_ISR_TEIF5)) {
        /* clear the flags */
        DMA1_IFCR = DMA_IFCR_CGIF4 | DMA_IFCR_CGIF5;

        __asm__("bkpt");
    }

    /* execute next if transfer is complete */
    if(DMA1_ISR & (DMA_ISR_TCIF4 | DMA_ISR_TCIF5)) {

        /* Wait to receive last data */
        while (SPI2_SR & SPI_SR_RXNE);

        /* Wait to transmit last data */
        while (!(SPI2_SR & SPI_SR_TXE));

        /* Wait until not busy */
        while (SPI2_SR & SPI_SR_BSY); // infinite loop here: SPI2_SR = 0x06c3

        /* clear the flags */
        DMA1_IFCR = DMA_IFCR_CGIF4 | DMA_IFCR_CGIF5;


        /* disable SPI */
        SPI2_CR1 &= ~SPI_CR1_SPE;

        /* disable DMA trigger */
        SPI2_CR2 &= ~(SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN);

        __asm__("bkpt");
    } else {
        __asm__("bkpt");
    }
}

Die gleichen Codeausschnitte können verwendet werden, um eine Übertragung in Vollduplex durchzuführen, indem das BIDMODE-Bit entfernt und SO->MISO verbunden wird.

Im Vollduplexmodus tritt kein SR_OVR-Fehler auf, aber im Halbduplexmodus verursacht das SR_OVR-Bit eine Endlosschleife.

Getestet: STM32F072RBT6

Frage:

  • Warum ist das SR_OVR-Bit gesetzt und verursacht eine Endlosschleife?

  • Was ist falsch an meinem Code ODER an einer Problemumgehung für dieses Problem?

Konnten Sie DMAmit verwenden 3 wire SPI.
@abhiarora ja. DMA wurde verwendet
Wäre toll, wenn du deinen ganzen Code posten könntest?

Antworten (3)

endlich das Problem gelöst. \Ö/

Das Problem liegt daran, dass Spi-Halbduplex weiterhin Daten vom Slave liest. um es anzuhalten (stellen Sie es in den Halbduplex-Übertragungsmodus)

hier einige zum Aktualisieren in der Interrupt-Routine.

   if((SPI2_CR1 & (SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE)) == SPI_CR1_BIDIMODE) {
        /* force to transmit mode. dont forget to 
         * set SPI_CR2_TXDMAEN and disable TX-DMA-CHANNEL,
         * to prevent sending garbage
         */
        SPI2_CR1 |= SPI_CR1_BIDIOE;
    } else {
        /* Wait to receive last data */
        while(SPI2_SR & SPI_SR_RXNE);

        /* Wait to transmit last data */
        while(!(SPI2_SR & SPI_SR_TXE));

        /* Wait until not busy */
        while(SPI2_SR & SPI_SR_BSY);
    }

Afaik, sie müssen sich keine Sorgen machen, etwas falsch zu machen, da der Interrupt aufgerufen wird, nachdem alle Daten empfangen wurden (dma hat alle Daten von spi in den RAM geschrieben) [im Gegensatz zu Vollduplex, der warten muss, damit das letzte Byte nicht beschädigt wird]

auch danke an den Typen von ST Noida.

Ich hatte ein ähnliches Problem bei der Interaktion mit demselben MAX31855 nur mit SPI Receive. Ich verwende tatsächlich die STM32f103 HAL-Bibliothek und hier die Teile des Codes, auf die es ankommt:

SPI-Init (erzeugt von CubeMX)

/* SPI2 init function */
void MX_SPI2_Init(void)
{

  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLED;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
  hspi2.Init.CRCPolynomial = 10;
  HAL_SPI_Init(&hspi2);

}

Lesen von Daten

void MAX31855_chipUnselect()
{
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}

void MAX31855_chipSelect()
{
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
}

uint32_t MAX31855_readData()
{
  uint8_t pDataRx[4] = { };

  MAX31855_chipSelect();
  HAL_SPI_Receive(&hspi2, (uint8_t*) pDataRx, 4, 1000);
  MAX31855_chipUnselect();

  return (pDataRx[i] << 24) | (pDataRx[i] << 16) | (pDataRx[i] << 8) | pDataRx[i];

}

Durch die Verwendung des SPI-Init oben war nur der erste Sensor-Lesevorgang gut und danach wurden alle anderen die ersten Bits/Bytes für die SPI-Übertragung bis zum nächsten MCU-Reset verloren/übersprungen.

Nachdem ich dieses Forum gelesen hatte, entschied ich mich, die hspi2.Init.Direction in SPI_DIRECTION_2LINES zu ändern , was der Full Duple Master ist. Auf magische Weise beginnen alle zu arbeiten. Lesen Sie die Referenz RM0008 (DocID13902 Rev 16), um zu verstehen, dass ich Folgendes auf Seite 716 gefunden habe:

Unidirektionales Nur-Empfang-Verfahren (BIDMODE=0 und RXONLY=1)

In diesem Modus kann das Verfahren wie folgt reduziert werden (siehe Abbildung 244):

  1. Setzen Sie das RXONLY-Bit im SPI_CR2-Register.
  2. Aktivieren Sie den SPI, indem Sie das SPE-Bit auf 1 setzen:

a) Im Master-Modus aktiviert dies sofort die Generierung des SCK-Takts, und Daten werden seriell empfangen, bis der SPI deaktiviert wird (SPE=0).

b) Im Slave-Modus werden Daten empfangen, wenn das SPI-Master-Gerät NSS auf Low treibt und den SCK-Takt generiert.

  1. Warten Sie, bis RXNE = 1, und lesen Sie das SPI_DR-Register, um die empfangenen Daten zu erhalten (dies löscht das RXNE-Bit). Wiederholen Sie diesen Vorgang für jedes zu empfangende Datenelement.

Ich habe noch kein Oszilloskop angeschlossen, aber ich glaube, das Problem ist, dass der CLK nie stoppt und sobald ich den Sensor mit Chip auswähle, sendet er bereits, bevor der MCU-SPI bereit ist, Daten zu empfangen.

Außerdem gibt es eine gute Referenz zu STM32 SPI Half Duplex, die einen anderen Ansatz zum Stoppen des CLK vorschlägt: http://www.ba0sh1.com/howto-use-stm32-spi-half-duplex-mode/

du bist großartig! Ihr Code-Snippet und Ihr Vorschlag haben mir geholfen, das Problem zu lösen. Ich hatte Probleme mit MAX31855K, um mit STM32F030F3 zu arbeiten. Ich habe auch angenommen SPI_DIRECTION_2LINES_RXONLY, dass MAX31855 schreibgeschützt ist! DataSizeverwendet wurde SPI_DATASIZE_4BITes in geändert SPI_DATASIZE_8BIT. Ich hatte auch einen anderen Fehler. Ich habe SPI-Daten direkt in eine uint32_tVariable gelesen. Jetzt habe ich es ähnlich wie in Ihrem Code geändert, um ein uint8_tArray von 4 Bytes zu lesen. Jetzt fing mein MAX31855K ​​an, korrekte Werte zu liefern! Ich habe die letzten zwei Tage dagegen gekämpft! Nochmals vielen Dank!!

Der MAX31855 ist nicht bidirektional, sondern eine proprietäre SPI-ähnliche Schnittstelle, die schreibgeschützt ist, nicht wahr? Du gibst Takt und CS an, und es spuckt Daten aus.

Es kann gut sein, dass im Vollduplexmodus die Sendeleitung ignoriert wird, aber dennoch einen gültigen Takt und eine CS-Leitung für das Gerät ausgibt. Das Gerät antwortet durch Austakten von Daten auf der MISO-Leitung (SO auf dem Gerät).

Indem Sie das Setup auf Halbduplex ändern, treiben Sie vom STM32 aus die einzelne Datenleitung (SO) an und stellen einen Takt und CS bereit. Das MAX-Gerät empfängt den Takt und CS und versucht, Daten auf der einzelnen Datenleitung auszutakten, aber es kann sie nicht gegen den STM32 treiben.

Das kann das Problem sein, oder es ist etwas ganz anderes. Aber es lohnt sich, noch einmal zu überprüfen, wie Sie mit dem MAX-Gerät sprechen und wie es sprechen möchte. Wie ich bereits erwähnt habe, habe ich beim Betrachten des Datenblatts den Eindruck, dass das SO auf dem MAX-Gerät nur ein Ausgang ist. Das Fahren gegen die Datenausgabe des MAX wird nur in Tränen enden.

Danke fürs Helfen. Ich habe versucht, mit MAX über STM32 im bidirektionalen Spi-1-Draht-Modus zu kommunizieren, als ich das Problem bemerkte. Auch wenn MAX überhaupt nicht verbunden ist. STM32 SPI sollte vier Bytes mit dem Wert 0x00 lesen ((obwohl gdb p arr_txaus unbekannten Gründen vier 0xFF-Bytes anzeigt)). Meine Frage lautet also eher, wie ich das STM32-SPI-Problem beheben kann.
das sollte gdb seinp arr_rx