This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

SPI communication between two microcontrollers

Hello everyone,

I am trying to make communication between an STM32F3 microcontroller and a custom board baser on PIC microcontroller using SPI protocol.

The STM32F3 acts as a Slave and the custom board as a Master. The clock is set to 10 MHz. I’m using NSS hardware, and getting EXTI once CS gets low in NSS pin. The CPOL and CPHA are compatible between the two boards.
I am using Interrupt base communication in the Slave side.
The application consists of:
1) The Master sends a command to the Slave, then a data of 4 bytes : This part works fine
2) The Master sends continuously different commands, The Slave process the command, then sends its proper data (4 bytes) depending on the received command. The Updating frequency sends by the Master is about 20 KHz.

I have a problem with the second case, in fact, when the Master sends a command, let’s say 0x01, the Slave process this address and then sends back a frame let’s say 0x410547AF.

In the MISO line the frame is shifted, c.a.d (0x47, 0xAF, 0x41, 0x05, 0x05) instead of (0xXX, 0x41,0x05, 0x47, 0xAF), because the first byte corresponds to the byte when the address is received.
I also used the debug mode to view the SR register and I can see that the FIFO transmission level is always at 2, that explains why the frame is shifted by 2 bytes. But I can’t find a solution to my problem, do you have an idea what would be the problem please?
If you want some code sample, I can post it.
I hope that my problem is clear.
Thanks in advance
Best regards

Parents Reply Children
  • Thank you Jack,

    With the oscilloscope,i can see that the frame is shifted.

    The thing is i don't have the information about the frame at t0, because the master sending request continuously. So, the frame visualized using oscilloscope shows (0x47, 0xAF, 0x41, 0x05, 0x05)instead of (0x00, 0x41,0x05, 0x47, 0xAF),the frame send by the Slave is 0x410547AF.

    The explanation that i find is at t0, the Slave is synchronized to the Master only with the two last bytes 0x41, 0x05 shifted by Two, the two first bytes, i imagine that are 0 . Then the 0x47, 0xAF still in the Tx FIFO(this explains the FIFO level is usually at 2 bytes). At t1, the Slave sends 0x47, 0xAF, then 0x41, 0x05 placed between time in the FIFO. This why in MISO line, i can see (0x47, 0xAF, 0x41, 0x05, 0x05)but i can't understand why 0x05 is redundant.

    My problem could the latency when writing to Tx FIFO and to the DR register ?

    Note: I measured the period of the Chip Select and is about 12µs

    May be it's not sufficient to the Slave to respond quickly ?

    If hope that u can have an idea or explanation to this issue,

    Best Regards

  • Note that if the master performs one more byte transfer than the slave expects, then it's likely that the last byte the slave sent will be repeated because the slave hasn't any newer data available in the FIFO.

    One thing here - SPI can run unsynchronized forever if you don't design a specific synchronization state. Activation of the slave select is intended to synchronize master and slave. If you keep that slave select infinitely active, then it's enough with a single spurious bit on the clock signal to make master and slave one bit off. And if the slave processor hasn't completely booted when the master starts to communicate, then the slave can constantly be one or more bytes - or individual bits - off.

    So in the end - are you regularly toggling the slave select signal, and making sure that the slave ends up with zero incomming/outgoing bytes the next time the master activates the slave select and starts a new transfer?

  • You said

    the frame send by the Slave is 0x410547AF
    

    But as I know, the data register SPI_DR is 16-bit.

    Could you explain how you fill 0x410547AF into SPI_DR? 8-bit or 16-bit? Using interface function? Big endian or small endian?

  • @Per : I make a mistake in the last post about the the select signal.

    The slave select signal is regularly toggled, the low level period is 12µs and the high level is 88µs. It's handled by interruption. The code is below:

    /**
    * @brief This interrupt handler detects rising and falling edges of the slave select line
    */
    void EXTI15_10_IRQHandler(void)
    {
       /* Handle the external interrupt for the SPI chip select  */
       if(EXTI_GetITStatus(EXTI_Line15)==SET)
       {
    
         /*Clear pending bit */
        EXTI_ClearITPendingBit(EXTI_Line15);
    
        if(GPIOA->IDR & GPIO_Pin_15)
        {
    
            /* Waiting until RX FIFO is empty */
             while (SPI_GetReceptionFIFOStatus(SPI1) != SPI_ReceptionFIFOStatus_Empty)
             {}
    
             /* Wait busy flag */
             while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET)
             {}
    
             /* Waiting until TX FIFO is empty */
             //while (SPI_GetTransmissionFIFOStatus(SPI1) != SPI_TransmissionFIFOStatus_Empty)
             {}
             /* Disable the SPI interrupt */
               SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, DISABLE);
               SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_ERR, DISABLE);
    
    
        }else
        {
    
           /* Enable the Rx buffer not empty interrupt */
            SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
    
            /* Enable the SPI Error interrupt */
            SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_ERR, ENABLE);
    
        }
      }// end of if EXTILine
    }
    


    The while (SPI_GetTransmissionFIFOStatus(SPI1) != SPI_TransmissionFIFOStatus_Empty) : Doesn't work because the Tx FIFO level stay at 2.

    @Jack
    Could you explain how you fill 0x410547AF into SPI_DR? 8-bit or 16-bit? : 8-bit
    #define SPI1_RXDR8 *(__IO uint8_t *) (((uint32_t)SPI1) + 0x0C)

  • stm32f10x.h

    typedef struct
    {
      __IO uint16_t CR1;
      uint16_t  RESERVED0;
      __IO uint16_t CR2;
      uint16_t  RESERVED1;
      __IO uint16_t SR;
      uint16_t  RESERVED2;
      __IO uint16_t DR;
      uint16_t  RESERVED3;
      __IO uint16_t CRCPR;
      uint16_t  RESERVED4;
      __IO uint16_t RXCRCR;
      uint16_t  RESERVED5;
      __IO uint16_t TXCRCR;
      uint16_t  RESERVED6;
      __IO uint16_t I2SCFGR;
      uint16_t  RESERVED7;
      __IO uint16_t I2SPR;
      uint16_t  RESERVED8;
    } SPI_TypeDef;
    
    #define SPI1                ((SPI_TypeDef *) SPI1_BASE)
    

    stm32f10x_spi.c

    void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
    {
      /* Check the parameters */
      assert_param(IS_SPI_ALL_PERIPH(SPIx));
    
      /* Write in the DR register the data to be sent */
      SPIx->DR = Data;
    }
    
    uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
    {
      /* Check the parameters */
      assert_param(IS_SPI_ALL_PERIPH(SPIx));
    
      /* Return the data in the DR register */
      return SPIx->DR;
    }
    
    23.5.4  SPI data register (SPI_DR)
    Address offset: 0x0C
    Reset value: 0x0000
    

  • I'm using stm32f30x but it's almost similar to stm32f10x.

    In fact i don't know what you refer by your answer.

    Best Regards

  • Doesn't your processor support a real slave select pin - a pin that you don't need to use any external interrupt to handle, unless you want to reset some internal state in your own code?

    Most SPI slaves have fully hardware-accelerated slave-select.

    So you always have your receive no-empty interrupt enabled.
    And normally also always your transmit empty interrupt.

    You get a "dummy" interrupt that the transmitter is empty. So fill it with a "null" value.

    That value stays there until the master has ticked the first word of transfer in which case you get a receive interupt and picks up the command. Then you know what words to send back. The master sends as many dummy words as needed to clock out the full answer from the slave.

    The master then ends the SPI transfer and disengages the slave select. When the master next time enables the slave select again (either using a normal GPIO pin or controlled by the SPI master device) then the slave SPI device will leave reset state with internal bit clock waiting for the first bit of the first word of the new transfer.

    So with a slave that has a hardware-accelerated slave-select pin, the only real meaningful reason for catching the slave-select toggle is to clear any state information in your own software, or maybe deactivate some other tasks to give more CPU focus to a high-speed SPI transfer.

    Another thing. It doesn't matter how much the slave decides to wait. The transmit FIFO will not change state unless the master sends two dummy words to the slave so the slave gets the required number of SPI clock ticks to send out the two pending words. SPI communication is fully driven by the master. So whenever you get stuck with bytes in the transmit FIFO of the slave, then the slave either has a bug that makes it insert too many words into the FIFO, or the master has a bug that it performs too few "dummy" transfers to let the slave shift out the last of the data. SPI is not like the asynchronous transfer of an UART where you can just wait and the data will get consumed (assuming no handshake blocking further transfer) but totally depends on the master ticking the same number of words - or resetting the slave SPI by deactivating/reactivating the slave select signal.

  • > In fact i don't know what you refer by your answer.

    Test.

  • > In fact i don't know what you refer by your answer.

    Sorry for it is not an answer.

    It is just some references; I think somehow you did not use the register definitions in the header-files provided by your tool-chain or chip manufacturer.

  • Doesn't your processor support a real slave select pin - a pin that you don't need to use any external interrupt to handle, unless you want to reset some internal state in your own code?

    It supports a hardware slave select but the Master(a custom board) toggles the chip select line manually. This is why, i use Trigger_Rising_Falling to detect the Low and High level to synchronize the communication.

    With an oscilloscope, when the slave select line is Low, i can see that the Master sends 40 clock ticks associated to the 5 bytes (One byte address and 4 dummy data), in the same time i can see that the Slave sends back the 5 bytes(normally should send only 4 bytes,after the address reception) but not in correct order as i explained in the post before.
    The same sequence is repeated when the slave select line is Low again in the next transfer.

    I don't know how to add figures of what i get from the oscilloscope, it's possible ?

    @Per :
    Another thing. It doesn't matter how much the slave decides to wait. The transmit FIFO will not change state unless the master sends two dummy words to the slave so the slave gets the required number of SPI clock ticks to send out the two pending words. SPI communication is fully driven by the master As i explained, the Master provides only 4 dummy bytes,but it strange to me that the Tx FIFO, the 2 bytes remains, my be it's a latency problem ?

    Best Regards

  • It supports a hardware slave select but the Master(a custom board) toggles the chip select line manually. This is why, i use Trigger_Rising_Falling to detect the Low and High level to synchronize the communication.

    That reasoning is seriously flawed. How the master organizes outputting the slave select signal has no relevance whatsoever as to how the slaves handles inputting it.

    There's not generally any time to spare on unnecessary interrupt handling when you're implementing a slave device for any serial protocol, and high-bitrate SPI is about the most critical of them all unless you have specialized hardware support for it (DMA and/or a FIFO). And if you do have such support, you still have to configure that exactly right, or it'll make things worse instead of better. At this point it appears you're failing on that latter count.

  • It is quite common that the master uses a standard GPIO pin to drive the slave select. The reasons are two:
    1) The master can then have individual slave-select signals for multiple slaves.
    2) The master then can decide if it should deactivate the slave-select as soon as the transmitter runs empty, or if the master wants to be able to think a bit and then add more data to send, to form a rather large packet, before it releases the slave select.

    So it's irrelevant if the master chains the slave-select function to the SPI hardware or drives it "manually" using a GPIO bit.

    But it is imperative that the slave reacts instantly, because there may be a very short delay between assertion of slave select and the first critical change of the clock and/or data signal for the slave. An interrupt handler is often not quick enough. The slave really is expected to handle the slave select in the SPI hardware.

    If your slave isn't enabled before the master sends the first word, and stays enabled through every single bit of the transfer from the master, then the slave will get out-of-sync and fail with at least one word still not correctly sent. That is why it is so imperative that the slave is ready instantly when the slave-select line goes active.

    And another thing - since there may be a very short interval between slave-select and first transfer, the SPI slave already needs to have a word ready to send - you aren't likely to have time to handle any transmit-empty interrupt quick enough. So the slave is normally given a dummy word to be able to directly send concurrently with the first word from the master. And if the master doesn't give a bit of a pause after having sent the last command word, before starting to send dummy words while waiting for the answer, then you might need to define your protocol to do an extra dummy transfer in both directions just to give the slave time to pick up the command, process it and hand over the answer to the SPI hardware.

    In the end, it's trivial to manually bit-bang an SPI master, since the master is the one controlling all timing. If it's slow, then the slave will not mind. It's only when the master is too fast that the slave will fail. Which is why it is normally never a good idea to have the slave do any SPI operations in software - it should just focus on keeping the transmit buffer non-empty, and picking up incoming data.

  • Thank you sirs Hans-Bernhard and Per for your precious explanations.

    I tried to use the hardware slave select pin in the Slave side but i didn't succeed. I have read many posts about the NSS deficiencies in the STM32.

    Please would you have some details how to manage this signal?

    Thanks in advance,

    Best regards