Problema STM32 SPI semidúplex (bidireccional de 1 cable)

Actualización: vea mi respuesta para solucionarlo.

Estoy tratando de leer 4 bytes de un esclavo compatible con SPI (MAX31855) en medio dúplex SPI bidireccional de 1 cable.

Aquí está mi código [SS controlado por SW] [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");
    }
}

Código para SS controlado por HW

#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");
    }
}

Los mismos fragmentos de código se pueden usar para realizar la transferencia en Full Duplex quitando el bit BIDIMODE y conectando SO->MISO.

En el modo dúplex completo no se produce ningún error SR_OVR, pero en el modo dúplex medio, el bit SR_OVR provoca un bucle infinito.

Probado: STM32F072RBT6

Pregunta:

  • ¿Por qué el bit SR_OVR está establecido y provoca un bucle infinito?

  • ¿Qué está mal con mi código O alguna solución para este problema?

¿Pudiste usarlo DMAcon 3 wire SPI.
@abhiarora sí. Se usó DMA
Sería genial si pudieras publicar todo tu código.

Respuestas (3)

finalmente resuelto el problema. \o/

el problema se debe a que la recepción dúplex medio de spi continúa leyendo datos del esclavo. así que para detenerlo (colocarlo en modo de transmisión semidúplex)

aquí algunos para actualizar en la rutina de interrupción.

   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, no hay que preocuparse por hacer nada malo, ya que se llama a la interrupción después de que se reciben todos los datos (dma ha escrito todos los datos en ram desde spi) [a diferencia del dúplex completo, que requiere esperar para no dañar el último byte]

también, gracias al chico de ST Noida.

Tuve un problema similar al interactuar con el mismo MAX31855 usando solo SPI Receive. De hecho, estoy usando la biblioteca HAL STM32f103 y aquí las partes del código que importan:

SPI Init (Generado por 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);

}

Lectura de datos

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];

}

Al usar SPI Init arriba, solo la primera lectura del sensor fue buena y después de eso, todos los demás estaban perdiendo/omitiendo los primeros bits/bytes para la transferencia SPI hasta el próximo reinicio de MCU.

Después de leer este foro, decidí cambiar hspi2.Init.Direction a SPI_DIRECTION_2LINES , que es Full Duple Master. Mágicamente todos empiezan a funcionar. Leyendo la referencia RM0008 (DocID13902 Rev 16) para entender que encontré lo siguiente en la página 716:

Procedimiento unidireccional de solo recepción (BIDIMODE=0 y RXONLY=1)

En este modo, el procedimiento se puede reducir como se describe a continuación (ver Figura 244):

  1. Establezca el bit RXONLY en el registro SPI_CR2.
  2. Habilite el SPI configurando el bit SPE en 1:

a) En modo maestro, esto activa inmediatamente la generación del reloj SCK, y los datos se reciben en serie hasta que se deshabilita el SPI (SPE=0).

b) En el modo esclavo, los datos se reciben cuando el dispositivo maestro SPI reduce el NSS y genera el reloj SCK.

  1. Espere hasta que RXNE=1 y lea el registro SPI_DR para obtener los datos recibidos (esto borra el bit RXNE). Repita esta operación para cada elemento de datos a recibir.

Todavía no he conectado un alcance, pero creo que el problema es que el CLK nunca se detiene y una vez que Chip-Selecciono el sensor, ya está transmitiendo antes de que la MCU SPI esté lista para recibir datos.

Además, hay una buena referencia sobre STM32 SPI Half duplex que sugiere un enfoque diferente para detener el CLK: http://www.ba0sh1.com/howto-use-stm32-spi-half-duplex-mode/

¡eres genial! Su fragmento de código y su sugerencia me ayudaron a resolver el problema. Estaba luchando con MAX31855K ​​para trabajar con STM32F030F3. ¡ También estaba usando SPI_DIRECTION_2LINES_RXONLYasumiendo que MAX31855 es de solo lectura! DataSizeutilizado se SPI_DATASIZE_4BITcambió a SPI_DATASIZE_8BIT. También tuve otro error. Estaba leyendo datos SPI directamente a una uint32_tvariable. Ahora lo cambié a similar a su código para leer en uint8_tuna matriz de 4 bytes. ¡Ahora mi MAX31855K ​​comenzó a dar valores correctos! ¡Estuve luchando durante los últimos dos días! ¡¡Una vez mas, Gracias!!

El MAX31855 no es bidireccional, es una interfaz patentada similar a SPI que es de solo lectura, ¿no es así? Proporciona reloj y CS, y escupe datos.

Bien podría ser que en el modo dúplex completo, la línea de transmisión se ignore pero aún emita un reloj válido y una línea CS para el dispositivo. El dispositivo responde registrando datos en la línea MISO (SO en el dispositivo).

Al cambiar la configuración a medio dúplex, desde el STM32 estará manejando la línea de datos única (SO) y proporcionando un reloj y CS. El dispositivo MAX recibirá el reloj y CS, e intentará registrar datos en la línea de datos única, pero no puede conducirlo contra el STM32.

Este puede ser el problema, o es algo completamente diferente. Pero vale la pena verificar dos veces cómo está hablando con el dispositivo MAX y cómo quiere hablar. Como mencioné, al mirar la hoja de datos tengo la impresión de que el SO en el dispositivo MAX es solo una salida. Conducir contra la salida de datos del MAX solo terminará en lágrimas.

Gracias por ayudar. estaba tratando de comunicarme con MAX usando STM32 en modo bidireccional de 1 cable spi cuando noté el problema. Incluso si MAX no está conectado en absoluto. STM32 SPI debe leer cuatro bytes de valor 0x00 ((aunque gdb p arr_txmuestra cuatro bytes 0xFF por razones desconocidas)). entonces mi pregunta es más como ¿cómo puedo solucionar el problema STM32 SPI?
eso deberia ser gdbp arr_rx