Electronic – STM32 SPI half duplex (1-wire Bidirectional) problem

dmaspistm32

Update: see my answer for fix.

I'm trying to read 4 byte from a SPI-compatible slave (MAX31855) in 1-wire bidirectonal SPI half-duplex.

Here is my 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 for HW Controlled 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");
    }
}

The same code snippets can be used perform transfer in Full Duplex by removing the BIDIMODE bit and connecting SO->MISO.

In full duplex mode no SR_OVR error occur but in half duplex mode, SR_OVR bit cause infinite loop.

Tested: STM32F072RBT6

Question:

  • Why is SR_OVR bit set and causing infinite loop?

  • What is wrong with my code OR any workaround for this problem?

Best Answer

finally solved the problem. \o/

the problem is due to spi half duplex receive continue to read data from slave. so to halt it (place it in half duplex transmit mode)

here some to update in 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, their is no worry of doing anything wrong since, the interrupt is called after all data is received (dma has written all data to ram from spi) [unlike full duplex, that require to wait so that dont corrupt the last byte]

also, thanks to the guy at ST Noida.