SPI.transfer() - how to send 1 byte, but receive 2?

See bottom third of Figure 15 on page 30 of this datasheet:

https://www.ti.com/lit/ds/symlink/cc1101.pdf

Need to send a single header byte, but receive back two bytes (chip status byte + data byte).

The SPI.transfer() function sounds like it will only send a single byte and receive a single byte and the SPI.transfer16() function sounds like it will only send two and receive two:

https://www.arduino.cc/reference/en/language/functions/communication/spi/transfer/

Is there some workaround to send 1, but receive 2?

Side question: is it okay to declare a variable using a function on the same line or should the variable be declared beforehand and then the returned value of the function stored in the variable?

i.e. is this okay?

byte receviedByte = SPI.transfer(byteToSend);

or is this preferrable?

byte receivedByte = 0;
receivedByte = SPI.transfer(byteToSend);

Study this:


You can try things to see if a hypothesis works.
:wink:


Some prefer the last way, others not so much.

See "Protocols" in the Gammon Forum link.

After the header (command) byte send two more dummy bytes and collect the returned bytes. Choose the dummy bytes so that they cannot be confused with a header/command byte in case the transmission is out of sync.

I prefer this short form because it leaves less room for name typos in multiple lines.

You always receive exactly as many bytes as you send. To receive a second byte when you have no more information to send you would send a byte that the receiver ignores. Typically 0.

The send and receive happen one bit at a time so if you are sending a command and receiving a response you usually have to ignore the data you received while sending the command:

   SPI.transfer(CommandByte);
   firstResponseByte = SPI.transfer(0);
   secondResponseByte = SPI.transfer(0);

The following diagram (Fig-1) explains why the SPI Port of ATmega328P MCU of the UNO Board is limited to send 8-bit (1-byte) data to Slave in one "transfer cycle" and receives back 8-bit (1-byte) data from Slave over the same "transfer cycle".


Figure-1:

(1) Data exchange takes place using 8-bit SPDR Register.
(2) SPDR Registers of Master and Slave are back-to-back connected.
(3) To push-out 8-bit data from Master to Slave and to push-in 8-bit from Slave to Master over the same transfer cycle, 8 SCK pulses are required which are automatically generated by the Master when the following code is executed:

byte y = SPI.transfer(0x23);  //23 goes to Slave; y holds data coming from Slave

(4) As SPDR Register of Slave is 8-bit, the Slave has no ability to transfer 2-byte data to Master over one transfer cycle.

Thanks for the link to the Gammon forum post - some good information there.

I think I will keep using my single-line variable declaration to keep things simpler.

That stinks that the SPI transfer cycle is limited by the MCU.

I don't think I can send a dummy byte of 0000 0000 to the CC1101 - I think it would interpret that as header byte for write, single access, and address 0x00 (which is the configuration register for the GDO2 output pint). With 6-bit addresses, there are 64 possible and the only one that looks like it may be unused is 0x2F (see page 70 of datasheet). I may have to ping someone at TI or ask about this over on their forum.

I think I will hook up my Bus Pirate so that I can see what is really being sent and received.

To read one byte from a register:

   digitalWrite(CSPin, LOW);
   statusByte = SPI.transfer(RegisterAddress | 0x80);
   dataByte = SPI.transfer(0); // Data is ignored
   digitalWrite(CSPin, HIGH);

To read more than one byte:

   digitalWrite(CSPin, LOW);
   statusByte = SPI.transfer(RegisterAddress | 0x80 | 0x40);
   dataByte[0] = SPI.transfer(0); // Sent data is ignored
   dataByte[1] = SPI.transfer(0); 
   dataByte[2] = SPI.transfer(0);
  .
  .
  .
   dataByte[n] = SPI.transfer(0);
   digitalWrite(CSPin, HIGH);

For writing, don't set the 0x80 (Read) bit.

John - I like the idea of using the byte array to store the bytes read from the device. I will have to try that.

I got my Bus Pirate set up with Open Logic Sniffer and did some testing for this project after some learning time with that hardware and software. It's no fancy high-speed logic analyzer, but it's pretty cool for what I paid for the Bus Pirate years ago and open-source software.

The Bus Pirate can sample at 1 MHz, so I throttled the SPI in sketch down to 125 kHz.

First capture is sending a header byte of 1 0 0 0 0 0 0 0 (128 in decimal or 0x80 in hex). Bit breakdown is as follows:

b7: 1 = read
b6: 0 = single access
b5:0: address of 0x00 (IOCFG2 register for configuration of GDO2 pin)

In the capture below, you can see MOSI is high during the rising edge of the first clock cycle and then low for the remaining 7 rising edges of the clock, so it's sending that byte correctly.

I purposely held CS low for another byte's worth of time (using the delayMicroseconds() function in my sketch) to see if it would return a second byte, but it appears that the MISO line staying low after the first byte is merely it reverting to its low-state behavior described in the last paragraph of page 29 of the datasheet. Also, there's no clock signal after the first byte, so that would indicate no data being sent.

The MISO line appears to have sent the following chip status byte back (looking at the states of MISO at the rising edge of the clock cycles): 0 1 1 0 1 1 1 1. Per table 23 on page 31 of the datasheet, that would mean:

b7: 0 = chip ready
b6:4: 110 = Rx FIFO overflow (believable, as I've run some generic receiver code on this CC1101 module previously)
b3:0: 1111 = number of bytes available for reading in the RX FIFO

FIFOs are 64-byte, so I guess 1111 means it could be anywhere between 15 and 64 bytes.

I also tried sending two bytes of 0x80 back to back to see what would happen (thinking maybe the second byte would keep the SPI clock going so it could send back the data read byte for the register address queried).

Good news: got two clock cycles. Possibly inconclusive news: I either got two bytes back or MISO just stayed low as chip ready during the first byte.

Let's assume the first case. Chip status byte would be 0 0 0 0 0 0 0 0, which would mean chip ready, idle state, and 0 bytes in Rx FIFO. Data byte (with first rising edge of clock just after -1930 µs) would be 0 0 1 0 1 0 0 1, which would mean GDO2 output not invertered and GDO2 configured to 1 0 1 0 0 1 (0x29). See datasheet pages 71 and 62. 0x29 is the default config for this register, as a chip ready signal.

I'll have to see what other addresses I can read that may yield some more conclusive results in order to see if my method of sending the same byte twice is a reasonable way to keep the SPI comm going for receiving a second byte back from the device.