Skip to main content
Visitor II
January 22, 2019
Question

SPI Slave transmit has extra byte at times

  • January 22, 2019
  • 11 replies
  • 4575 views

0690X000006DCWkQAO.jpg0690X000006DCWfQAO.jpg0690X000006DCWaQAO.jpgI have two STM32F7 processors set up in SPI Master/Slave configuration. When the slave wants to transmit data to the master, it issues an interrupt and the master reads 4 bytes. The first two are the number of data bytes to read, the second two is a command code. The master waits for a go-ahead interrupt, then reads the rest of the bytes.

At times, I'm seeing the first byte of the 4-byte transfer duplicated in the data transfer - thus shifting the bytes by one, and adding one byte to the transfer. This may make more sense by looking at the captures from the logic analyzer. I am using HAL_SPI_Transmit_IT to the master, and HAL_SPI_Receive_IT on the master side to receive the bytes.

The transfer rate is 14 MHz. Slowing the clock down makes no difference.

Using SPI_NSS_PULSE_DISABLE or SPI_NSS_PULSE_ENABLE does not help

On the Master side, using SPI_NSS_HARD_OUTPUT or generating the NSS signal manually makes no difference.

The only thing that seems to help is enforcing a minimum transfer rate of 2ms. In other words, the slave needs to wait 2ms minimum between transfers.

Slave initialization:

/* SPI Port for communications with Master processor
 **/
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
 GPIO_InitTypeDef GPIO_InitStruct;
 if(hspi->Instance==SPI1)
 {
 /* Peripheral clock enable */
 __HAL_RCC_SPI1_CLK_ENABLE();
 
 /**SPI1 GPIO Configuration 
 PA4 ------> SPI1_NSS
 PA5 ------> SPI1_SCK
 PA6 ------> SPI1_MISO
 PA7 ------> SPI1_MOSI 
 */
 GPIO_InitStruct.Pin = SPI_CS_IN_Pin|SPI_CLK_IN_Pin|SPI_MISO_Pin|SPI_MOSI_Pin;
 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
 GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
 /* SPI1 interrupt Init */
 HAL_NVIC_SetPriority(SPI1_IRQn, 1, 0); 
 HAL_NVIC_EnableIRQ(SPI1_IRQn);
 }
}
 
 
 
/* SPI1 init function */
void MX_SPI1_Init(void)
{
 /* SPI1 parameter configuration*/
 SPI_SLAVE_1.Instance = SPI1;
 SPI_SLAVE_1.Init.Mode = SPI_MODE_SLAVE;
 SPI_SLAVE_1.Init.Direction = SPI_DIRECTION_2LINES;
 SPI_SLAVE_1.Init.DataSize = SPI_DATASIZE_8BIT;
 SPI_SLAVE_1.Init.CLKPolarity = SPI_POLARITY_LOW;
 SPI_SLAVE_1.Init.CLKPhase = SPI_PHASE_1EDGE;
 SPI_SLAVE_1.Init.NSS = SPI_NSS_HARD_INPUT;
 SPI_SLAVE_1.Init.FirstBit = SPI_FIRSTBIT_MSB;
 SPI_SLAVE_1.Init.TIMode = SPI_TIMODE_DISABLE;
 SPI_SLAVE_1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
 SPI_SLAVE_1.Init.CRCPolynomial = 7;
 SPI_SLAVE_1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
 SPI_SLAVE_1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
 if (HAL_SPI_Init(&SPI_SLAVE_1) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
}

Has anyone seen similar issues with the SPI Slave transfer?

Mark

    This topic has been closed for replies.

    11 replies

    Visitor II
    January 22, 2019

    Try to reset the SPI between the header and data block transfer (SYSCFG).

    If this is improving the situation, look at the SPI Slave Transmit FIFO.

    Super User
    January 22, 2019

    You did not show us the signal which is the "go-ahead interrupt". My crystal cube says, that the problem is in that in the second phase of communication the master starts to clock before the interrupt routine stores the first byte into SPI_DR (and the issue is marginal). You probably first send from slave to master the ""go-ahead interrupt"" signal, then call HAL_SPI_Receive_IT(), don't you.

    JW

    MPatt.11Author
    Visitor II
    January 22, 2019

    Thank you both for the excellent ideas.

    0690X000006DCYqQAO.jpg

    This is a failed transfer go-ahead interrupt showing. The idea behind that interrupt was to prevent the master from starting the transfer before the slave is ready. The go-ahead (rising edge) interrupt goes out, then /ACS goes active, then the clocks start.

    This image has one more change - I reset the SPI between the first transfer and the second. Now instead of getting the extra 0x14, I get an extra 0x00. Resetting the SPI would make sure that there is nothing in the TX buffer. I also removed the SPI_NSS_PULSE_DISABLE from the Slave TX Side which is why all of the clock pulses are now together.

    BTW: I forgot to mention in the original post that I am making sure that the TXE is set and the fifo is empty. I check this before starting the first transmit and before the second transmit. Now I check it before the second transmit and before the reset.

    static inline void SPI_check_ready(void)
    {
     /* Wait End Of Transmission: TXE set and Tx Fifo empty */
     while((LL_SPI_IsActiveFlag_TXE(SPI_SLAVE_1.Instance) != 1));	
     while (LL_SPI_GetTxFIFOLevel(SPI_SLAVE_1.Instance) != LL_SPI_TX_FIFO_EMPTY);	
    }

    It is still failing as you can see. I find it hard to believe this could be a HAL issue. I guess I could try using the LL_ transfer functions and see if that makes any difference.

    Super User
    January 22, 2019

    My crystal cube says, that the problem is in that in the second phase of communication the master starts to clock before the interrupt routine stores the first byte into SPI_DR (and the issue is marginal). You probably first send from slave to master the ""go-ahead interrupt"" signal, then call HAL_SPI_Receive_IT(), don't you.

    JW

    Visitor II
    January 23, 2019

    A general point to remember:

    As SPI Master, the end of transmission is ruled by RXNE, which tells all clocks have been sent and the incoming data is now captured in the DR. TXE will rise as soon as you write the first data byte because it will be pushed to the shift register and free DR to be written for the next data to transmit later without "pause" between bytes.

    Visitor II
    January 24, 2019

    I struggled with this problem for some time and after analyzing the implied functions, I discovered that it is a problem in the ISR provided by HAL. It doesn't matter if you use IT/DMA calls, after a while the SPI stream stars to shift whole bytes. I've solved the problem by using the LL library and writing my own ISR handler.

    MPatt.11Author
    Visitor II
    January 24, 2019

    Thank you Harold. That was the path I was just starting to go down. I'm glad to hear that this is likely the right one!

    Visitor II
    October 13, 2020

    I know it's been almost 2 years since this thread began but I ran into this as well and, based on JW's idea, was able to fix it by adding a slight delay between my request for data and pulling it out of the slave. In other words, I gave my STM32 time to execute the HAL_SPI_TransmitReceive_IT function before starting the SPI clock om my MASTER.

    Visitor II
    June 3, 2023

    Hi @HABOT​ , I have the same problem. Can you please explain how you solve the problem? Can you give example code of your ISR please.

    Much appreciated your support.

    Thank you so much.

    Visitor II
    June 3, 2023

    I've spent 6 months playing around various SPI with Master - Slave STM32, what I learnt :

    • There are different generations of SPI, the 8 or 16 bit old one, the 4..16 bit one, the 32 bit hidden TX/RX FIFO and the H7 one (so far)
    • SPI is the potentially highest 1 bit serial bus datarate in an MCU and deserves DMA use
    • NSS is manually operated on the master side, HW used on the slave side to Hi-Z the pins when not selected
    • The protocol should be carefully done to try to avoid interrupts during the SPI exchange
    • Always think bi-directional = receive/transmit
    • Full HW slaves (ADC, DAC, Memories) are usually half duplex, with write, dummy byte (if latency to turnaround from write to read), then read
    • Robustness (buy a cheap headphone, which will break within weeks and add plastic trash) : Don't expect the transmitter data lenght, use DMA in cyclic mode and use EXTI on NSS to get told there is something in your mailbox... = buffers. The master needs to provide enough time (MCU=SW=Delay) for the slaves to pump the data away and get ready for the next. Add extra margin as in real world, application are having many interrupts firering anytime and extending lags of others. In technical term is the hunt for WCET value.
    • You want your protocol to survive glitches, incorrect transmittion, survive ESD shocks and still operating properly overnight.
    • Reset the slave SPI once the NSS goes high, and reconfigure it. The HW FIFO slave TX classical issue is there and was mentionned in this post. Won't reexplain it every time it pops in this forum.
    • In the protocol, it is a good idea to have the packet length (receive or transmit) and even an error / overrun bit

    Good luck !

    History : RS232, then SPI 4 wires, then SPI 3 wires, then I2C (1982)