SPI "dummy" bytes?

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
I just encountered this concept while reading about SPI. I am confused, I can't tell if this idea is a fundamental part of SPI or if its a peculiarity of certain slave device or if it's something we must always consider when doing certain kinds of communication.

Since SPI is not a formal standard I can't really find a "reference" so I'm quite confused by this, can anybody clarify?
 

nsaspook

Joined Aug 27, 2009
13,079
I just encountered this concept while reading about SPI. I am confused, I can't tell if this idea is a fundamental part of SPI or if its a peculiarity of certain slave device or if it's something we must always consider when doing certain kinds of communication.

Since SPI is not a formal standard I can't really find a "reference" so I'm quite confused by this, can anybody clarify?
To quote a line from the above link: "It's a simple/yet not simple process.."
As you say there is no standard interface for exactly what data is valid for each bi-directional SPI exchange. Each slave device must be handled according to its data sheet.
Example:
https://microchipsupport.force.com/s/article/WINC-SPI-driver-dummy-byte-for-read-operation
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Yes, thanks. This is a bit confusing but that's the nature of the beast with this digital stuff when one starts working with some new gizmo.

I'm using a Nucleo F446RE and SPI to "manage" an nRF24L01 device. The docs say (although I missed this until a few mins ago) that when we send a command to the device, then during that SPI time interval, the device simultaneously sends the value of its status register. Then the next byte(s) we receive is the command response itself.

So there is example code that simply processes commands like this (HAL)

Code:
uint8_t RX_ADDR_P2 = 12;

HAL_GPIO_WritePin(GPIOA, SPI_CS, GPIO_PIN_RESET);
spi_status = HAL_SPI_TransmitReceive(&spi, &RX_ADDR_P2, &status, 1, HAL_MAX_DELAY);
spi_status = HAL_SPI_Receive(&spi, &read_val, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, SPI_CS, GPIO_PIN_SET);
That is, it does a send/recv of 1 byte (which consumes the "status register" value that was sent) followed by the actual read of response byte generated by the command in this case a 0x0C.

If I send these commands I get the responses listed in the chart:

1666727366298.png

So 0C ends up giving me a C3, 0D a C4 etc. So this seems to be working.

But something I'm now trying to understand is that the very first time I send a command the returned value is always 00.

So in this loop:

1666727942950.png

A break on line 87 - when hit for the very first time, has a read_val = 0, but thereafter, read_val is always as expected, so the second time we hit line 87 read_val is C3 and is always C3 thereafter, all of the other reads into read_val are always as expected be it the first time or not.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
In addition my scope, ST's HAL and the various docs about SPI all use slightly different terms for the two mode values, very very confusing!
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
The NRF's status seems to - initially - be set to 1C, but after the first command is sent, it becomes 0E, and remains as 0E from thereafter (I'm not, knowingly, altering the status either).

1666734160851.png

So going from 1C to 0E means that MAX_RT was initially ON and RX_P_NO was initially 110. After issuing the first write, first command, MAX_RT becomes OFF and RX_P_NO becomes 111.

I don't think my code does anything to explicitly change the status, all it does is send a few R_REGISTER commands, most odd...

This code is what I used to get some insights.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
I'm done for the day, but here's another puzzler. Look:

1666737206936.png

The code on the Nucleo sends (MOSI, D3) the 0x0C - simultaneously we receive (MISO, D4) the 0x0E (device status reg, this is documented, the device takes advantage of the time interval to simply send the status reg).

Then a short time later we receive the response to the 0x0C which is the 0xC3 (the second data byte seen on MISO) - that value is exactly what we expect when we send a reg read of 0x0C - all good.

But the code on the Nucleo never sends the 0xC4 but that appears on the MOSI line, likewise the code never sends the 0xC3 (the fourth byte on that MOSI line) these values are previously received responses, received on the MISO line but for some reason also appear on the scope as if they are explicitly written to the MOSI line !!

So - as documented -

1666738275403.png

whenever we send a reg read command the device concurrently, always, sends its status register and then sends the response to that reg read command.

So, we send 0x0C it concurrently sends 0x0E (the status register's current value), then it sends 0xC3. The values on the bottom (MOSI) line do not make sense, I see no reason why that bottom line has 0xC4 and 0xC3 as being sent - these are RECEIVED but never sent (unless the code has a nasty bug, which I dont see).

All odd, very odd...like something we receive gets "echoed" so to speak...
 
Last edited:

MrChips

Joined Oct 2, 2009
30,707
SPI is two serial shift registers in a loop.

1666739243452.png

Are you aware of this?

When the Master sends a byte, a byte from the Slave is received immediately.
Obviously, what is sent from the Slave would be what was already sitting in the shift register. It cannot be a response to the message sent from the Master because it has not yet received the message. The only reasonable information to send from the Slave would be the current status just before the message was received.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
SPI is two serial shift registers in a loop.

View attachment 279320

Are you aware of this?

When the Master sends a byte, a byte from the Slave is received immediately.
Obviously, what is sent from the Slave would be what was already sitting in the shift register. It cannot be a response to the message sent from the Master because it has not yet received the message. The only reasonable information to send from the Slave would be the current status just before the message was received.
Man, no, I was not aware of that - what the diagram depicts. I assumed that stuff received by the slave is "lost" by the master, consumed by it so to speak. This likely explains what I'm seeing, the "echoing" effect. How do people design with this though? how can the master distinguish between something generated and sent by a slave from something that is just a recycled earlier transmission made by the master?

This is very interesting, I need to read up more now...
 

nsaspook

Joined Aug 27, 2009
13,079
Man, no, I was not aware of that - what the diagram depicts. I assumed that stuff received by the slave is "lost" by the master, consumed by it so to speak. This likely explains what I'm seeing, the "echoing" effect. How do people design with this though? how can the master distinguish between something generated and sent by a slave from something that is just a recycled earlier transmission made by the master?

This is very interesting, I need to read up more now...
SPI was designed for daisy-chained as a connection possibility.
1666800949389.png
https://www.maximintegrated.com/en/design/technical-documents/app-notes/3/3947.html
How Daisy-Chaining Is Accomplished
In a daisy-chained system (Figure 2), SLAVE 1 receives data directly from the microcontroller. This data is clocked into SLAVE 1's internal shift register. As long as active-low CS (or active-low SS) remains low, this data propagates through to SLAVE 1's DOUT output. DOUT of SLAVE 1 goes into DIN of SLAVE 2, so the data is clocked into SLAVE 2's internal shift register as the data appears on SLAVE 1's DOUT output. Just as SLAVE 2 receives its data from SLAVE 1, the microcontroller can simultaneously send another command to SLAVE 1. This new command overwrites the previous data in SLAVE 1's shift register. As long as active-low CS remains low, the data propagates through the entire daisy-chain until each of the slave devices has received its appropriate command. The command loaded into each slave's shift register executes on the rising edge of active-low CS. The following examples use the MAX5233 and MAX5290 to demonstrate daisy-chaining.
CS/SS is the clocked data I/O latch trigger. "It's a simple/yet not simple process.."

Welcome to our world of embedded programming. :cool:
https://www.ti.com/lit/an/slvae25a/slvae25a.pdf
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Most interesting. But I have a question, I see different byte exchanges in each of these cases:

Code:
        spi_cs_lo();
        spi_status = HAL_SPI_TransmitReceive(&spi, &RX_ADDR_P2, &status, 1, HAL_MAX_DELAY);
        spi_status = HAL_SPI_Receive(&spi, &read_val, 1, HAL_MAX_DELAY);
        spi_cs_hi();
        
        spi_cs_lo();
        spi_status = HAL_SPI_TransmitReceive(&spi, &RX_ADDR_P3, &status, 1, HAL_MAX_DELAY);
        spi_status = HAL_SPI_Receive(&spi, &read_val, 1, HAL_MAX_DELAY);
        spi_cs_hi();
vs

Code:
        spi_cs_lo();
        spi_status = HAL_SPI_TransmitReceive(&spi, &RX_ADDR_P2, &status, 1, HAL_MAX_DELAY);
        spi_status = HAL_SPI_Receive(&spi, &read_val, 1, HAL_MAX_DELAY);

        spi_status = HAL_SPI_TransmitReceive(&spi, &RX_ADDR_P3, &status, 1, HAL_MAX_DELAY);
        spi_status = HAL_SPI_Receive(&spi, &read_val, 1, HAL_MAX_DELAY);
        spi_cs_hi();
Also the clock signal seems to not be something I can enable/disable explicitly, so that also is not crystal clear to me.
 

MrChips

Joined Oct 2, 2009
30,707
The Master triggers the transfer. All the Slave can do is read its shift register (SR) and write a new value to the shift register.

The Master writes to its SR and the data is shifted out serially automatically. SCK and SS would be sent automatically. You do not need to control SS. When shifting is over, reading the SR will get what came from the Slave.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
The Master triggers the transfer. All the Slave can do is read its shift register (SR) and write a new value to the shift register.

The Master writes to its SR and the data is shifted out serially automatically. SCK and SS would be sent automatically. You do not need to control SS. When shifting is over, reading the SR will get what came from the Slave.
Oh OK so I can configure the STM32 SPI to use hardware CS - then wire the slave to that, to the SPI1_CS pin:

1666808287498.png

This signal has too many names NSS, CS, SS...
 

MrChips

Joined Oct 2, 2009
30,707
This signal has too many names NSS, CS, SS...
True. They all mean the same thing.

SS - This is the proper name for SPI Slave Select
/SS - This is sometimes used to indicate active low logic
NSS - This is another way of saying active low SS
CS - Chip Select is traditionally used when selecting a HW component
CE - same as CS and is not commonly used for SPI HW

nRF24L01 adds to the confusion by adding CSN and then CE which means something differently.
1666820115218.png
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
The nRF24L01 manual, explicitly states that operations like reading a register be "wrapped" in a hi-to-low CS signal. So my code does that (as does various examples I've looked at). But if we were to use the dedicated SPI1_CS pin on the Nucleo board, then how would one explicitly pulse it low like that?

In my case I'm uisng the same physical pin but configured as a GPIO (PA4 on the Nucleo).
 
Top