Skip to main content
Diez.R.
Associate III
August 21, 2026
Question

SPI master: reliably detecting end of frame

  • August 21, 2026
  • 10 replies
  • 99 views

Hi all:

I have an STM32F407 as main platform, as well as other types like STM32G0B1 and STM32F072, and I am trying to implement a robust SPI master for all of them.

I want to reliably detect the end of frame in order to toggle the Chip Select line, but it is well known that the BSY flag is unusable for that purpose:

STM32 gotchas
68. SPI_SR.BSY is unusable
http://www.efton.sk/STM32/gotcha/g68.html

The hardware NSS output, if available on the particular SPI, would fix the problem, but I have several SPI slaves, so that wouldn't work, would it? Or is there a way to check what the NSS output would be without sacrificing a pin?

The recommendation is to use RXNE instead ob BSY, even if you are only transmitting and do not care about the received data.

The idea is that, as soon as RXNE is set, the SPI frame has ended, as the last clock edge is what allows the master to finish reading the data from the slave, right?

After RXNE is set, I can disable the Chip Select straight away. I could do that even before reading the data from register SPI_DR, correct? Well, that is assuming the SPI slave does not impose some timing requirements, but I just want to understand the idea.

The trouble is, the web page referenced above states:

"however, its timing (which again is not something properly documented by ST) might not be suitable in all CPOL/CPHA combinations"

I am trying to understand which combinations may pose a problem. I thought that reading the last bit from the slave is the last operation that an SPI transfer performs, so how can RXNE possibly fail me?

Say I just sent the first byte after initialising the SPI. Can I assume that, if RXNE is set, TXE should also be set? The reasoning is: SPI has to generally "send" each bit first, and "receive" the corresponding bit afterwards. If I send just one byte, TXE should set before RXNE is set, right? Otherwise, this would be the full sequence for the first byte:

- Write to SPI_DR.

- First loop to wait for RXNE:
  - Read SPI_SR once, as reading can have side effects like clearing error flags.
  - At least in debug builds: check all error flags, to make sure everything is OK.
  - If RXNE is not set, do another loop iteration.

- Read from SPI_DR, which will automatically clear RXNE.

- Second loop to wait for TXE:
  - Read SPI_SR once, as reading can have side effects like clearing error flags.
  - At least in debug builds:
    - Check all error flags, to make sure everything is OK.
    - Check also that RXNE is still reset.
  - If TXE is not set, do another loop iteration.

- Third loop to wait for BSY, in case today this flag lags behind:
  - Read SPI_SR once, as reading can have side effects like clearing error flags.
  - At least in debug builds:
    - Check all error flags, to make sure everything is OK.
    - Check also that RXNE is still reset.
    - Check also that TXE  is still set.
  - If BSY is set, do another loop iteration.

- Turn Chip Select off.

I could optimise the loops in this way: When the first loop detects that RXNE is set, it could also check whether TXE is set and BSY is not set, and then skip reading SPI_SR again.

But the most important question is whether I can optimise the TXE and the BSY loops away:
- If TXE is always set before RXNE, or at the same time, then the TXE loop is not necessary.
- If RXE is reliable enough, then the BSY loop is not necessary.

After these matters are clear, I am planning to discuss optimising sending and receiving more than 1 byte, which probably can go faster depending on whether the SPI has a FIFO. And how to use DMA and still reliably detect the end of the last frame.

I have searched the web and read quite a lot about SPI, BSY etc., but I still could not figured all the details out.

Thanks in advance, rdiez.

10 replies

ST Technical Moderator
September 24, 2026

Hi ​@Diez.R. 

TXE does not mean the last SPI clock edge has finished; it indicates the transmit side is ready for more data,

RXNE corresponds to a fully received frame and can be a completion indicator in receive on STM32F4.

BSY can be unreliable in slave mode as stated in errata sheet 2.12.5 BSY flag may stay high at the end of a data transfer in Slave mode

To give better visibility on the answered topics, please click on "Best answer" on the reply which solved your issue or answered your question.Best regards,FBL
Andrew Neil
Super User
September 24, 2026

BSY can be unreliable in slave mode 

and, as ​@waclawek.jan described in that link, it is also unreliable (unusable?) in master mode.

 

http://www.efton.sk/STM32/gotcha/g68.html

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Associate II
September 24, 2026

You need not have NSS at all, the probability of a bit slip over even a 4KB block in a local setup is very small, one device will be the clock generator and will establish bit sync as long as that clock is stopped often enough for an interval all the SPI can reset because of absence of clock.

Additionally some STM32 can do hardware CRC32 and can even check their own returning CRC32, so as long as CRC32 is always passing then no bit slip, no data corruption and no need for any NSS or any hardware handshake lines.

Andrew Neil
Super User
September 24, 2026

so as long as CRC32 is always passing then no bit slip, no data corruption and no need for any NSS 

Until it doesn’t!

So maybe keep NSS, but have it permanently asserted unless an error is detected via CRC failure.

Then use NSS to re-sync ... 

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Associate II
September 24, 2026

Having NSS does not save you from bit slip or data corruption probability in a given number of bits is same.  If you arrange CRC32 check over a reasonable size block you don’t loose much, often people assert NSS at start of that very same block holding it low until block ends what’s wrong with that?

So a CRC32 over that same block is an identical check - no difference.

Pulling NSS low for every word transferred is fixation - not adding anything.

And the CRC32 is done in hardware at no cost. I’m working with STM32H5E5 CRC can be over any allowed TSIZE block length.

 

 

TDK
September 24, 2026

The reference manual gives explicit instructions on what needs to be done. If you have additional timing requirements such as NSS low for X bits before data starts, or is idle for X bits before de-asserting, you will need to implement those separately.

Don’t overthink it.

“SPI_SR.BSY is unusable” is an overstatement. Most devices out there won’t care if NSS goes high 0.5 bits before end of data.

The hardware NSS output, if available on the particular SPI, would fix the problem

hardware NSS on the STM32F4 is useless. It stays asserted as long as SPI is enabled, even if data is not flowing.

Using RXNE to detect end of frame suffers from the same “issue” as using BSY.

"If you feel a post has answered your question, please click ""Accept as Solution""."