cancel
Showing results for 
Search instead for 
Did you mean: 

What causes SPI to hang on the BUSY flag?

murrellr
Associate II

First, some details.

MCU: STM32F765II

Keil uVision 5.25.2.0

HAL version: 1.6.0 (installed with Keil uVision)

Keil MDK-ARM RTOS2 operating system.

The STM32 is connected to an iMX6(running Linux) via SPI. The iMX6 is the master. Communications uses small 16 word packets. Either side can initiate communications using GPIO lines to interrupt the other side.

When the STM32 wants to send a packet, It calls HAL_SPI_TransmitReceive_DMA() to queue the packet, drops its signal line to the other side, and then pends on a semaphore to wait for completion of the transfer.

The iMX6 will drop its signal line to signify it is ready to transfer, and makes a blocking call to transfer the packet (ioctl()). It then waits for the STM32 signal line to be raised.

On the STM32, when HAL_SPI_TxRxCpltCallback or HAL_SPI_ErrorCallback is called from the HAL library, signifying the completion of the transfer. These callbacks raise the signal line to the iMX6. The iMX6 will then raise its signal line to the STM32. The raising of this line releases the semaphore to complete the transfer.

This works well except when there is a lot of bi-directional traffic. Often, the HAL driver will timeout in SPI_WaitFlagStateUntilTimeout() waiting on the BSY flag. This is a 100mS code loop (in an interrupt!) When this happens, it seems to recover, but the error causes the connection to terminate.

The STM32 must send a minimum of one packet every 1mS. This is a critical timing packet that can't be delayed too long. I have the DMA interrupts configured to the highest group in the system.

So the questions are: What causes the BSY flag to get reset? What can prevent it from being reset?

I see that the HAL function SPI_EndRxTxTransaction waits for the transmit FIFO to empty, waits for BSY to reset, and flushes the receive FIFO. Is this the correct sequence?

Here is my configuration code. Most of it was created by STMCubeMX:

#define IRQ_PRI_GRP_HS_TIMER 1

#define IRQ_PRI_GRP_CRIT_COMM 2

#define IRQ_PRI_GRP_SERVO 3

#define IRQ_PRI_GRP_FAST_COMM 4

#define IRQ_PRI_GRP_SERIAL 5

#define IRQ_PRI_GRP_NETWORK 6

#define IMX6_IRQ GPIO_PIN_0

#define IMX6_SIGNAL GPIO_PIN_1

SPI_HandleTypeDef hspi2;

DMA_HandleTypeDef hdma_spi2_rx;

DMA_HandleTypeDef hdma_spi2_tx;

void SPI_init(xfer_callback awaken, int chan, int master) //TODO currently always slave

{

  GPIO_InitTypeDef GPIO_InitStruct;

  __HAL_RCC_SPI2_CLK_ENABLE();

__HAL_RCC_DMA1_CLK_ENABLE();

  /* SPI2 DMA Init */

  /* SPI2_RX Init */

  hdma_spi2_rx.Instance = DMA1_Stream1;

  hdma_spi2_rx.Init.Channel = DMA_CHANNEL_9;

  hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;

  hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;

  hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;

  hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

  hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

  hdma_spi2_rx.Init.Mode = DMA_NORMAL;

  hdma_spi2_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;

  hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

  if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)

  {

   Error_Handler();

  }

  __HAL_LINKDMA(&hspi2,hdmarx,hdma_spi2_rx);

  /* SPI2_TX Init */

  hdma_spi2_tx.Instance = DMA1_Stream4;

  hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0;

  hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;

  hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;

  hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;

  hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

  hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

  hdma_spi2_tx.Init.Mode = DMA_NORMAL;

  hdma_spi2_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;

  hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

  if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK)

  {

   Error_Handler();

  }

  __HAL_LINKDMA(&hspi2,hdmatx,hdma_spi2_tx);

/* DMA1_Stream1_IRQn interrupt configuration */

HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, IRQ_PRI_GRP_CRIT_COMM, 0);

HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);

/* DMA1_Stream4_IRQn interrupt configuration */

HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, IRQ_PRI_GRP_CRIT_COMM, 0);

HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);

  /*Configure GPIO pins : PD0 IMX6_IRQ */

  GPIO_InitStruct.Pin = IMX6_IRQ;

  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  /*Configure GPIO pin IMX6_IRQ Output Level */

  HAL_GPIO_WritePin(GPIOD, IMX6_IRQ, GPIO_PIN_SET);

/*Configure GPIO pin : PD1 IMX6_SIGNAL*/

GPIO_InitStruct.Pin = IMX6_SIGNAL;

GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;

GPIO_InitStruct.Pull = GPIO_NOPULL;

HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  

  /**SPI2 GPIO Configuration   

  PB10   ------> SPI2_SCK

  PB14   ------> SPI2_MISO

  PB15   ------> SPI2_MOSI

  PI0   ------> SPI2_NSS 

  */

  GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_14|GPIO_PIN_15;

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

  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_0;

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

  HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);

//^^^ SPI 2 initialization

//derived from STMCubeMX main.c

hspi2.Instance = SPI2;

hspi2.Init.Mode = SPI_MODE_SLAVE;

hspi2.Init.Direction = SPI_DIRECTION_2LINES;

hspi2.Init.DataSize = SPI_DATASIZE_16BIT;

hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;

hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;

hspi2.Init.NSS = SPI_NSS_HARD_INPUT;

hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;

hspi2.Init.TIMode = SPI_TIMODE_DISABLE;

hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;

hspi2.Init.CRCPolynomial = 7;

hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;

hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;

if (HAL_SPI_Init(&hspi2) != HAL_OK)

{

Error_Handler();

}

callback = awaken;

channel = chan;

if (!initialized)

{

done = SEM_create(0, NULL);//count of one

    __HAL_SPI_ENABLE(&hspi2);

initialized = true;

}

HAL_NVIC_SetPriority(EXTI1_IRQn, IRQ_PRI_GRP_FAST_COMM, 0);//IMX6_SIGNAL

HAL_NVIC_EnableIRQ(EXTI1_IRQn);

}

4 REPLIES 4
turboscrew
Senior III

I really don't know, but one thing that came to mind: if slave loses a clock tick, it thinks the transmission is unfinished, but the master thinks that all is done.

S.Ma
Principal

What you didn;t tell is how long it takes (worst case) for your EXTI (NSS falling or rising ede) takes to reconfigure the SPI.

Using the HAL, this may take some time which the other MPU doesn't wait?

Allow a pause after changing NSS level.

Personally, I would NEVER try to implement a multi-master slave SPI.

Look at USB, one master, and time regular polling to slave devices.

If you need to poll every 1 msec, then keep the STM32 always as slave mode and it waits to be polled.

If both toggle NSS at the same time, ambiguity will create trouble and weaken the solution.

Although beware, if using SPI Slave, you'll have to reset the TX and RX SPI FIFO which may not be a control bit, you'll have to totally SW Reset the SPI by SYSCFG if you use DMA circular (so nothing will break regardless of the incoming message size)

murrellr
Associate II

The implementation is not multi-master. The STM32 is always a slave. The handshaking is handled by GPIO. The NSS line is under hardware control of the iMX6. Since the the connection is single slave, someone suggested disabling the hardware NSS on the STM32 and just keep it software enabled. I will be testing this soon.

Is the loss of a tick the only way the BSY flag would remain set for a slave? I've scoped the signal and clock lines and they look clean. Both processors are on the same board and the interconnecting traces are short. I'll try dropping the clock rate from 20Mhz to something slower.

I think a missed clock is just one way to stuck busy-flag. And I'm talking about STM32-end. I haven't used TI-stuff.

The HW NSS is (to my knowledge) STM-specific problem.