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

2

Actualización: vea mi respuesta para corregirla.

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

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

Código para SS controlados 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 utilizar para realizar la transferencia en Full Duplex eliminando 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 semidúplex, el bit SR_OVR causa un bucle infinito.

Probado: STM32F072RBT6

Pregunta:

  • ¿Por qué se establece el bit SR_OVR y causa un bucle infinito?

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

3 respuestas

0

finalmente resolvió el problema. \ o /

el problema se debe a que la recepción semidúplex spi continúa leyendo los datos del esclavo. para detenerlo (colóquelo 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 mal, ya que se llama a la interrupción después de que se reciben todos los datos (dma ha escrito todos los datos en el ram desde spi) [a diferencia del dúplex completo, que requiere esperar para que no se dañen los últimos byte]

también, gracias al chico de ST Noida.

    
respondido por el Kuldeep Singh Dhaka
1

Tuve un problema similar al interactuar con el mismo MAX31855 usando SPI Receive solamente. En realidad estoy usando la biblioteca HAL STM32f103 y aquí están las partes del código que son importantes:

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 el SPI Init anterior, solo la primera lectura del sensor fue buena y, después de eso, todos los demás perdieron / saltaron los primeros bits / bytes para la transferencia de SPI hasta el próximo restablecimiento de la MCU.

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

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

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

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

a) En el 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 controla NSS bajo 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 selecciono el sensor, ya está transmitiendo antes de que el SPI de la MCU esté listo para recibir datos.

También, hay una buena referencia sobre STM32 SPI Half duplex que sugiere un enfoque diferente para detener el CLK: enlace

    
respondido por el Talk2
0

El MAX31855 no es bidireccional, es una interfaz propietaria tipo SPI que es de solo lectura, ¿no es así? Proporcionas reloj y CS, y escupe datos.

Es posible que en el modo dúplex completo, la línea de transmisión se ignore, pero aún así genera un reloj y una línea CS válidos para el dispositivo. El dispositivo responde registrando los datos en la línea MISO (SO en el dispositivo).

Al cambiar la configuración a semidúplex, desde el STM32 manejará la línea de datos única (SO) y proporcionará un reloj y un CS. El dispositivo MAX recibirá el reloj y el CS, e intentará sincronizar los datos en una sola línea de datos, pero no podrá conducirlo contra el STM32.

Este puede ser el problema, o es algo completamente diferente. Pero vale la pena revisar dos veces cómo está hablando con el dispositivo MAX y cómo quiere hablar. Como mencioné, mirando 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.

    
respondido por el Oliver

Lea otras preguntas en las etiquetas