Why am I unable to make SPI.transfer(buffer, sizeof buffer) code working?

My intention is to collect by SPI-Master (the UNO) binray32 formatted temperature signal from SPI-Slave (the NANO) by the hardware setup of Fig-1 and using SPI.transfer(buffer, sizeof buffer); type instruction; where, the buffer contents are replaced by the incoming data bytes from the Slave. Unfortunately, I could not make the said instruction working; rather, I have managed to make the system working using byte recByte = SPI.transfer(byte); type instruction.

I have posted working sketches (based on byte recByte = SPI.transfer(byte); tinstruction) of both Master and Slave so that someone may point out where to bring modifications/adjustments in order to make the SPI.transfer(buffer, sizeof buffer); instruction working.


Figure-1:

SPI-Master Sketch:

#include<SPI.h>
byte myData[] = {0x00, 0x00, 0x00, 0x00};
float myTemp;
unsigned long tempData;  //edit

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  delay(100);
  digitalWrite(SS, LOW);  //Slave is selected
}

void loop()
{
  for (int i = 0; i < 4; i++)
  {
    myData[i] = SPI.transfer(myData[i]);
    Serial.println(myData[i], HEX); //1M/0S; 2M/1S; 3M/2S; 0M/3S(MSB)
  }
  tempData = (unsigned long)myData[0] << 24 | (unsigned long)myData[3] << 16 |
             (unsigned long)myData[2] << 8 | (unsigned long)myData[1];  //edit
  toFloat();  //converting received binary32 bit to float
  Serial.print("Temperature received from Slave = ");
  Serial.print(myTemp, 1); Serial.println(" degC");//shows: Room Temp
  Serial.println("======================================");
  delay(3000);  //test interval
}

/*void toFloat()
{
  long *ptr;
  ptr = (long*)&myTemp;
  *ptr = tempData;
}*/                                       //edit

//As discussed many times before, this is not a valid cast. You should use memcpy:
void toFloat() 
{
  static_assert(sizeof(tempData) == sizeof(myTemp), "");
  memcpy(&myTemp, &tempData, sizeof tempData);
}

SPI-Slave Sketch:

#include<SPI.h>
byte myData[4];
int i = 0;
float myTemp;

void setup()
{
  Serial.begin(9600);
  analogReference(INTERNAL);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  bitSet(SPCR, SPE);
  bitSet(SPCR,MSTR); //Arduino is Slave
  SPI.attachInterrupt();   //interrupt logic is enabled
}

void loop()
{
  myTemp = 100 * (1.1 / 1023.0) * analogRead(A3);
  Serial.print("Room Temperature = ");
  Serial.print(myTemp, 1); Serial.println(" degC");
  toBytes();    //converting binary32 data to bytes
  delay(2000);
}

ISR(SPI_STC_vect)
{
  SPDR = myData[i];
  i++;
  if (i == 4)     //4-byte data are sent
  {
    i = 0;          //array pointer is reset
  }
}

/*void toBytes()
{
  byte *ptr;
  ptr = (byte*)&myTemp;
  for (int i = 0; i < sizeof myTemp; i++)
  {
    myData[i] = *ptr;
    ptr++;
  }
}*/      //edit

//Although this is fine, it's probably best to use:
void toBytes() 
{
  static_assert(sizeof(myData) == sizeof(myTemp), "");
  memcpy(myData, &myTemp, sizeof myTemp);
}

Master's Serial Monitor Output

41    //byte-3 (MSByte) of binray32 coming from Slave
DA   //byte-0 (LSbyte) of binray32 coming from Slave
65    // byte-1 of binary32 coming from Slave
E7    //byte-2 of binary32 coming from Slave
Temperature received from Slave = 28.9 degC
============================================

what's your code with the buffer?

Two issues: the order of the indices seems wrong, and you should use uint32_t instead of long. Bit shifting of signed integers can easily lead to undefined behavior.
You should also make sure that the endianness always matches between sender and receiver.

As discussed many times before, this is not a valid cast. You should use memcpy:

void toFloat() {
  static_assert(sizeof(tempData) == sizeof(myTemp), "");
  memcpy(&myTemp, &tempData, sizeof tempData);
}

Although this is fine, it's probably best to use:

void toBytes() {
  static_assert(sizeof(myData) == sizeof(myTemp), "");
  memcpy(myData, &myTemp, sizeof myTemp);
}

This variable is accessed in the main program and in an ISR, so should be marked volatile, or you need fences around the accesses. If you make it volatile, you cannot use memcpy, though, so you'll need a manual loop. Avoid using C-style casts so you don't accidentally cast away const or volatile qualifiers, use reinterpret_cast instead.

If endianness matches then a pointer of any type can be used for a buffer transfer.

For the SPI slave a
void transmit(void* buf, size_t count) function can remember the byte count and byte pointer for the ISR. Whenever SS is asserted the slave should start with these values and put the first byte into SPDR. Endianness can be handled by iterating through the buffer from begin or end.

1. NOT Working Test sketches for known 4-byte (0x123456AB) exchange using SPI.transfer(buffer, sizeof buffer) code

SPI-Master Sketch:

//to receive known 0x123456AB from Slave using
//SPI.transfer(buffer, sizeof buffer); code
#include<SPI.h>
byte recData[] = {0x00, 0x00, 0x00, 0x00};

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  delay(100);
  digitalWrite(SS, LOW);  //Slave is selected
  //--------------------
}

void loop()
{
  SPI.transfer(recData, sizeof recData);  //recData[] arry/buffer will be filled by Slave's data
  Serial.println(recData[0], HEX); //expecting AB
  Serial.println(recData[1], HEX); //expecting 12 but getting AB
  Serial.println(recData[2], HEX); //expecting 34  but getting AB
  Serial.println(recData[3], HEX); //expecting 56  but getting AB
  Serial.println("=========================");
  delay(1000);   //test interval
}

SPI-Slave Sketch:

#include<SPI.h>
int i = 0;
byte myData[] = {0x12, 0x34, 0x56, 0xAB};

void setup()
{
  Serial.begin(9600);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  bitSet(SPCR,SPE);
  bitSet(SPCR, MSTR); //Arduino is Slave
  SPI.attachInterrupt();   //interrupt logic is enabled
}

void loop()
{

}

ISR(SPI_STC_vect)
{
  SPDR = myData[i]; //places 0x12, then 0x34, then 0x56, then 0xAB
  i++;
  if (i == 4)     //4-byte data are sent
  {
    i = 0;          //array pointer is reset
  }
}

Output on Master Monitor:

AB     //expecting AB
AB     //expecting 12
AB     //expecting 34
AB      //expecting 56
=========================

2. (As refernce) Working Test sketches for known 4-byte (0x123456AB) exchange using byte recByte = SPI.transfer(byte) code

SPI-Master Sketch:

//to receive 0x123456AB from Slave using
//byte recByte = SPI.transfer(byte; code
#include<SPI.h>

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  delay(100);
  digitalWrite(SS, LOW);  //Slave is selected
  //--------------------
}

void loop()
{
  byte x0 = SPI.transfer(0x00);//first brings garbage and then LSByte
  delayMicroseconds(100); //allows Slave to process received byte
  Serial.println(x0, HEX);  //shows: 0xAB
  //---------------------------
  byte x1 = SPI.transfer(0x00); //x1 = 0x12
  delayMicroseconds(100);
  Serial.println(x1, HEX);  //shows: 0x12
  //---------------------------
  byte x2 = SPI.transfer(0x00);  //x2 = 0x34
  delayMicroseconds(100);
  Serial.println(x2, HEX);    //shows: 0x34
  //---------------------------
  byte x3 = SPI.transfer(0x00);
  delayMicroseconds(100);
  Serial.println(x3, HEX); //x3 = 0x56
  //---------------------------
  long x = (long)x1 << 24 | (long)x2 << 16 | (long)x3 << 8 | (long)x0;
  Serial.println(x, HEX); //shows: 0x12345678
  Serial.println("======================");
  delay(1000);   //test interval
}

SPI-Slave Sketch:

#include<SPI.h>
int i = 0;
byte myData[] = {0x12, 0x34, 0x56, 0xAB};

void setup()
{
  Serial.begin(9600);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  bitSet(SPCR,SPE);
  bitSet(SPCR, MSTR); //Arduino is Slave
  SPI.attachInterrupt();   //interrupt logic is enabled
}

void loop()
{

}

ISR(SPI_STC_vect)
{
  SPDR = myData[i]; //places 0x12, then 0x34, then 0x56, then 0xAB
  i++;
  if (i == 4)     //4-byte data are sent
  {
    i = 0;          //array pointer is reset
  }
}

Output on Master Monitor

======================
AB    //expecting AB
12     //expecting 12
34      //expecting 34
56      //expecting 56
123456AB
======================

Try making this volatile but also compare your master/slave SPI pair with the equivalent here: Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino

1.

Because the SPDR Registers of Master-Slave are back-to-back connected (Fig-1; circulating buffer), the indexed-0 data of Slave arrives to Master in the next Port Cycle (analogy to CPU Bus Cycle). The diagram (Fig-2) is based on experiment about the sequence of 3-byte data exchange between SPI-Master/Slave (0xAB, 0xCD, 0xEF from Master to Slave ; 0x12, 0x34, 0x56 from Slave to Master); where, there is one Port Cycle offset.
spi328latest
Figure-1:

spitxrx
Figure-2:

2.

Noted down.

3.

I am aware in my sketches which byte is coming first from Slave in response to which Port Cycle of the Master. (It takes few cycles to stay at a stabilized value/pattern.)

4.

I used to use union which I have left due to type punning aspect; instead, I have used the pointer variable which is, I see now, also "no good". I have noted down your memcpy() recommendation which I will test soon.

5.

I have noted down.

6.

I usually do it; this time, I have forgotten. Thanks for pointing out.

7.

I will test the above comments for my understanding.1

1.

Interesting point to note down. If so, can the union and pointer be used for float/byte conversion?

2.

I am aware in my sketches which byte is coming first from Slave in response to which Port Cycle of the Master. (It takes few cycles to stay at a stabilized value/pattern.)

Tried making both i and myData[] volatile with no luck!

No union required. A float address can be passed directly to the function which expects a void (any) pointer. See void.

The slave must prepare the first byte to transfer before the master starts the transmission. See how SPiF works.

It may be required to deselect and select the slave between buffered transmissions.

In non-buffered transmission, we can offer time as much as we wish to the Slave to manage the received data byte and then to put data byte into its transmit register (the SPDR Register). In buffered transmission, we don't have that opportunity; the transmission of the send bytes of the Master goes one-after-another nonstop; as a result, the Slave (perhaps) does not get enough time to update its transmit buffer with new data items. Could it be that the buffer transmission is intended for sensors and not for another Arduino?

I think you hit the nail on the head there! :wink:

just another of many threads where arduino<->arduino SPI comms did not turn out quite right...
SPI Exchanging data in both directions

1 Like

So, I am giving up the hope of receiving possible solution of my (our) problem as the Arduino has limitations to handle the buffered transmission scheme. Thanks to everybody who participated in this thread and offered their valuable suggestions particularly to @PieterP who added much values.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.