cancel
Showing results for 
Search instead for 
Did you mean: 

HAL_SPI_Transmit_IT blocks

Marc1
Associate III

Hello,

I've a problem with HAL_SPI_Transmit_IT() function as it blocks similar to HAL_SPI_Transmit.

Microcontroller: STM32F205VCT6

Compiler: arm-none-eabi-gcc, gcc version 7.3.1 20180622 (release)

[ARM/embedded-7-branch revision 261907] (15:7-2018-q2-4)

Code is generated by STM32CubeMX, Version 4.27.0

Activated peripherals:

SPI3 (Transmit Only Master), SPI3 global interrupt enabled

GPIOA.9 as output

My own code:

uint8_t buffer[3] = { 1, 2, 3 };
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET);
HAL_SPI_Transmit_IT(&hspi3, buffer, 1);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET);

If I hand over one single byte to HAL_SPI_Transmit_IT() it does not work non-blocking as you can see at the following scope.

0690X000006CGEWQA4.png

If I hand over more than one byte (e.g. 3 bytes), HAL_SPI_Transmit_IT() works correctly (non-blocking).

0690X000006CGEbQAO.png

What could be the reason? Why can't I send one single byte in non-blocking mode?

17 REPLIES 17
Bob S
Principal

Look at the HAL SPI source code, starting with SPI_Transmit_IT(). It enables the Tx interrupt, which fires immediately because the TX data reg is empty. SPI_TxISR_8BIT() writes 1 byte to the TX data reg. So far so good. But, since your are only sending 1 byte, the SPI_TxISR_8BIT() sees that it is done sending data and calls SPI_CloseTx_ISR() to start shutting down the SPI Tx function. SPI_CloseTx_ISR() calls SPI_EndRxTxTransaction(), which waits for the SPI to be idle before returning. So when sending 1 byte, your call to SPI_Transmit_IT() ends up not returning until the byte has been sent (shifted out), as you see in your scope traces.

Marc1
Associate III

Hi Bob,

Thank you for the explanation.

Now let's send 3 bytes.

HAL_SPI_Transmit_IT(&hspi3, buffer, 3);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET);
  /* Check Busy flag */
  if(SPI_CheckFlag_BSY(hspi, SPI_DEFAULT_TIMEOUT, tickstart) != HAL_OK)
  {
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
  }
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET);

Please have a look at the scope.

0690X000006CGeWQAW.png

It seems that every last byte is sent in blocking mode.

I can't believe it.

Marc1
Associate III

Another test:

HAL_SPI_Transmit_IT(&hspi3, buffer, 3);
while(true) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_9); }

0690X000006CGebQAG.png

This is called "non-blocking transmission". Horrible!

The reason is simple: the "library" wants to ensure that by the time the "finished" callback is called, data are all out. Otherwise, relying only upon the Tx interrupt only it would signal "finished" one frame earlier, when the last frame leaves the holding register and is transferred to the shift register.

There's of course a workaround, using the Rx interrupt, but even there with one of the CPHA settings you can get the interrupt a tad bit earlier than the last clock edge rolls out, so you may need to take some measures for that case anyway.

By using a "library" you decided to live with whatever design decisions the "library" authors chose. The chips have an order of magnitude more usage modes, but "libraries" inevitably implement only a limited subset, presumably of the most often used ones.

In other words: Are you dissatisfied with other's code? Feel free to write your own.

JW

Bob S
Principal

At the risk of paraphrasing an ex president, it depends on what your definition of "blocking" is. HAL_SPI_Transmit_IT() function is indeed written so that it (tries to) return as soon as it has configured everything to transmit (EDIT: "but the interrupt routines get in the way"). It turns out that the return is interrupted by the transmit ISR, which writes the first byte to the SPI port. If there are more bytes to send, the ISR exits. BUT, the SPI data register has a 4-byte (32-bit) FIFO. So before HAL_SPI_Transmit_IT() can return, the ISR is called a second time** to write the 2nd data byte. The ISR will be called up to 4 times, one right after the other, until the FIFO is full and the non-iterrupt code in HAL_SPI_Transmit_IT() gets a chance to finish and return. And when the ISR writes the byte it ends up waiting for the data to finish shifting out as I mentioned above.

If you really need to be doing something else while ALL of the SPI data is sent, use DMA. Though you will need to experiment and/or look at the HAL code to see what DMA does after it writes the last byte to the SPI data register (i.e. how it handles "Tx complete").

** The first ISR may actually never return, since the STM32 has logic that chains interrupt handling if an interrupt is pending when a "return from interrupt" instruction is encountered.

Hello Jan,

thank you for the detailed explanation and the hint with the workaround.

> There's of course a workaround, using the Rx interrupt, but even there with one of the CPHA settings you can get the interrupt a tad bit earlier than the last clock edge rolls out, so you may need to take some measures for that case anyway.

My first workaround was using a timer to predict the end of transmission. But using the RX interrupt is a smarter option. Even with CPHA = 1 timing is good.

> By using a "library" you decided to live with whatever design decisions the "library" authors chose.

It was not my intention to criticize the HAL libraries. My first thought was that "blocking" must be a bug or missconfiguration. But even though I use my own code now.

Marc

Marc1
Associate III

Hello Bob,

thank you for your reply. Meanwhile I fixed the problem by creating my own code and following the idea of @Bob S​ (using RX interrupt).

> If you really need to be doing something else while ALL of the SPI data is sent, use DMA. Though you will need to experiment and/or look at the HAL code to see what DMA does after it writes the last byte to the SPI data register (i.e. how it handles "Tx complete").

I think there is no benefit of using DMA if you need an interrupt for each byte to detect the end of transmission correctly. I might be wrong.

Marc

Bob S
Principal

If you don't want an RX interrupt on every byte, use a combination of DMA and RX IRQ. Configure the SPI port for master tx and rx mode (the rx data is "don't care", you never actually use it and don't even need to enable the pin asociated with it). With DMA and SPI idle, start transmitting using HAL_SPI_Transmit_DMA(). In the "DMA complete" callback, enable the SPI RXNE interrupt. When the SPI RXNE interrupt function is called, the SPI port is done transmitting your data (if you care when it is done). At least according to the diagram RXNE goes high the same time BUSY goes low.

So, no RX interrupt on every byte, and only 2 interrupts: DMA Tx complete and SPI RXNE.

Marc1
Associate III

Thank you for the further suggestion. I may test it later. Currently I'm happy whith my interrupts. Btw. I don't use the HAL libraries any more.