3
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ What is your question? \$\endgroup\$ – Keelan Sep 23 '14 at 9:26
  • \$\begingroup\$ Google (read near total disaster) returns quickiwiki.com/en/STM32 which i can't veryfy coz 'ey don't support my browser (discrimination) \$\endgroup\$ – user49399 Sep 23 '14 at 11:06
2
\$\begingroup\$

I had a similar problem interacting with the same MAX31855 using SPI Receive only. I'm actually using the STM32f103 HAL Library and here the parts of the code which matters:

SPI Init (Generated by 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);

}

Reading Data

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

}

By Using the SPI Init above only the first sensor read was good and after that all others it was losing/skipping the first bits/bytes for the SPI Transfer until the next MCU reset.

After reading this Forum I decided to change the hspi2.Init.Direction to SPI_DIRECTION_2LINES, which is the Full Duple Master. Magically all start to work. Reading the Reference RM0008 (DocID13902 Rev 16) to understad that I found the following on the page 716:

Unidirectional receive-only procedure (BIDIMODE=0 and RXONLY=1)

In this mode, the procedure can be reduced as described below (see Figure 244):

  1. Set the RXONLY bit in the SPI_CR2 register.
  2. Enable the SPI by setting the SPE bit to 1:

a) In master mode, this immediately activates the generation of the SCK clock, and data are serially received until the SPI is disabled (SPE=0).

b) In slave mode, data are received when the SPI master device drives NSS low and generates the SCK clock.

  1. Wait until RXNE=1 and read the SPI_DR register to get the received data (this clears the RXNE bit). Repeat this operation for each data item to be received.

I haven't plugged a scope yet but I believe the problem is that the CLK never stops and once I Chip-Select the sensor it's already transmitting before the MCU SPI is ready to receive data.

Also, there's a good reference about STM32 SPI Half duplex that suggest a different approach to stop the CLK: http://www.ba0sh1.com/howto-use-stm32-spi-half-duplex-mode/

\$\endgroup\$
  • \$\begingroup\$ you are great! Your code snippet and suggestion helped me to solve the issue. I was struggling with MAX31855K to work with STM32F030F3. I was also using SPI_DIRECTION_2LINES_RXONLY assuming MAX31855 is read only! DataSize used was SPI_DATASIZE_4BIT changed it to SPI_DATASIZE_8BIT. I also had another mistake. I was reading SPI data directly to a uint32_t variable. Now I changed it to similar to your code to read to uint8_t array of 4 bytes. Now my MAX31855K started to give correct values! I was fighting it for last two days! Once again, thank you!! \$\endgroup\$ – Junaid Oct 11 '18 at 10:54
1
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

The MAX31855 is not bidirectional, it is a proprietary SPI-like interface which is read-only, is it not? You provide clock and CS, and it spits out data.

It might well be that in full duplex mode, the transmit line is ignored but it still outputs a valid clock and CS line for the device. The device responds by clocking out data on the MISO line (SO on the device).

By changing the setup to half duplex, from the STM32 you will be driving the single data line (SO) and providing a clock and CS. The MAX device will receive the clock and CS, and try to clock out data on the single data line but it cannot drive it against the STM32.

This may be the issue, or it's something completely different. But it's worth double checking how you are talking to the MAX device and how it wants to talk. As I mentioned, looking at the datasheet I get the impression the SO on the MAX device is an output only. Driving against the MAX's data output will only end in tears.

\$\endgroup\$
  • \$\begingroup\$ Thanks for Helping. i was trying to communicate with MAX using STM32 in spi 1-wire bidirectional mode when i noticed the problem. Even if MAX isnt connected at all. STM32 SPI should read four byes of value 0x00 ((though gdb p arr_tx shows four 0xFF bytes for unknown reason)). so my question is more like how can i fix STM32 SPI problem. \$\endgroup\$ – Kuldeep Singh Dhaka Sep 23 '14 at 12:28
  • \$\begingroup\$ that should be gdb p arr_rx \$\endgroup\$ – Kuldeep Singh Dhaka Sep 24 '14 at 6:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.