Struct Values over SPI Transmission (Full Duplex)

Hi All

Hope your well,

Im working on a project that requires Full Duplex communication over SPI, initially i wrote the code for half duplex I2C however the project became more complex the more boards i added and achieving two way communication between 6 boards. bus clashes became an issue.

I have run a test on Full Duplex transmitting and receiving a singe byte simultaneously which has been successful.

The goal is send a Struct packet of data containing 8 bit, 16 bit and 32bit values which i have been successful with using I2C protocol i.e. breaking down the 16bit and 32bit values to 8 bits and reassembling them.

See below the code i started with which sends a single byte Full Duplex.

Master:-

#include<SPI.h>                         
byte x = 10;

void setup (void){
  Serial.begin(115200);                  
  SPI.begin();                           
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  digitalWrite(SS,HIGH);                  
}

void loop(void){
  byte Mastersend,Mastereceive;          
  digitalWrite(SS, LOW);                
  Mastersend = x;                            
  Mastereceive = SPI.transfer(Mastersend); 
  Serial.print(Mastersend);
  Serial.print(" ");  
  Serial.println(Mastereceive);     
  digitalWrite(SS,HIGH);              
  delay(100);
}

Slave:-

#include<SPI.h>
volatile boolean received;
volatile byte Slavereceived,Slavesend;
byte x =20;

void setup(){
  Serial.begin(115200);
  pinMode(MISO,OUTPUT);                   
  SPCR |= _BV(SPE);                      
  received = false;
  SPI.attachInterrupt();                 
  }
  ISR (SPI_STC_vect)                        
  {
  Slavereceived = SPDR;         
  received = true;                     
  }

void loop(){
  if(received){
  Serial.print(Slavesend);
  Serial.print(" ");  
  Serial.println(Slavereceived); 
  Slavesend=x;                             
  SPDR = Slavesend;                           //Sends the x value to master via SPDR 
  delay(100);
}
}

Now he is the test code i have been playing with to try issue a Struct to the slave , its very important that i remain full duplex, i.e. sending and receiving data simultaneously between boards.

i have been trying to follow [Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino (Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino) without success.

Master Struct:-

  #include<SPI.h>        

  byte data1; 
  int data2;
  long data3;

  typedef struct myStruct{                // Struct data to transmit   
  byte val_1;
  int val_2;
  long val_3;
  };

  myStruct DataOut;                                            

void setup (void){
  Serial.begin(115200);                  
  SPI.begin();                           
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  digitalWrite(SS,HIGH);                  
}

void loop(void){
  DataOut.val_1 = 42;
  DataOut.val_2 = 32000;
  DataOut.val_3 = 100000;

  data1 = DataOut.val_1;
  data2 = DataOut.val_2;
  data3 = DataOut.val_3;

  byte DataOut,Mastereceive;

  digitalWrite(SS, LOW);                                           
  Mastereceive = SPI.transfer(DataOut); 

  Serial.print(data1);
  Serial.print(" "); 
  Serial.print(data2);
  Serial.print(" "); 
  Serial.print(data3);
  Serial.print(" ");  
  Serial.println(Mastereceive);   

  digitalWrite(SS,HIGH);              
  delay(1000);

}

Slave Struct:-

  #include<SPI.h>

  volatile boolean received;
  volatile byte Slavesend;

  byte x =20;

  byte val_1; 
  int val_2;
  long val_3;

  typedef struct myStruct{                // Struct data received from master 
  byte val_1;
  int val_2;
  long val_3;
  };

 volatile myStruct DataIn;  

void setup(){
  Serial.begin(115200);
  pinMode(MISO,OUTPUT);                   
  SPCR |= _BV(SPE);                      
  received = false;
  SPI.attachInterrupt();                 
  }
  ISR (SPI_STC_vect)                        
  {
  //Slavereceived = SPDR;         
  received = true;                     
  }

void loop(){
  if(received){
  Serial.print((int) DataIn.val_1);
  Serial.print(" "); 
  Serial.print(DataIn.val_2);
  Serial.print(" "); 
  Serial.print(DataIn.val_3);
  Serial.print(" "); 
  Serial.println(Slavesend);
  Slavesend=x;                             
  SPDR = Slavesend;                     //Sends the x value to master via SPDR 
  delay(1000);
}
}

Look forward to your responses.

Thanks

Can you provide a link to the document that defines full duplex SPI communications?

Hi Paul

Thanks for the response, please see below link.

https://fastbitlab.com/spi-bus-configuration-discussion-full-duplex-half-duplex-simplex/

I am transferring data simultaneously over MISO & MOSI between two boards when the master pulls the SS pin LOW.

Thanks

Below is how i managed to receive and break apart a struct on I2C Bus , however SPI appears to be simple , however i dont have alot of experience with SPI.

Im sharing this code so you can see how i acheived this on I2C

thought it may help.

void SENDataPacketIn(){
  SENData_t SEN;                            // Receives data from EMS
  if( SENgetData( &SEN )){                  // Confirms data has been received 
  SAQ_STATE = SEN.SAQ_STATE;                // Connected I2C devices Variable 
  SAQ_I2C = SEN.SAQ_I2C;                    // SystemState Input Variable 
  SAQ_ID = SEN.SAQ_ID;                      // EMS ID Input Variable  
  PT1 = SEN.PT1;                            // Solenoid Valve 1 Input Variable
  PT2 = SEN.PT2;                            // Solenoid Valve 2 Input Variable
  PT3 = SEN.PT3;                            // Solenoid Valve 3 Input Variable
  PT4 = SEN.PT4;                            // Solenoid Valve 4 Input Variable
  }
  else
  {
  //Serial.println(F("No SEN packet received"));  // Debug 
  }
  //delay(10);                                // Delay 10ms 
  } 

  // I2C Request data from slave
  bool SENgetData(SENData_t* SENDataIn){
  bool SENgotI2CPacket = false;    
  byte i=0;
  byte SENi2CData[PACKET_SIZE_SEN];     
  Wire.requestFrom(SENAddress, PACKET_SIZE_SEN);    
  while(SENAddress<Wire.available()) { 
  SENi2CData[i++] = Wire.read(); 
  SENgotI2CPacket = true; 
  //delay(50);
  }

  // If we got an I2C packet, we can extract the values
  if(SENgotI2CPacket){
  SENgotI2CPacket = false;                     // Reset flag
  SENDataIn->SAQ_I2C   = SENi2CData[0];     // Save Command ID from MPU into array
  SENDataIn->SAQ_STATE = SENi2CData[1];     // Save MPU address into array
  SENDataIn->SAQ_ID    = SENi2CData[2];     // Save val from MPU into array (16bit)
  SENDataIn->PT1    = SENi2CData[3] << 8;   // Save val from MPU into array (16bit)
  SENDataIn->PT1   |= SENi2CData[4];        // Save val from MPU into array (16bit)
  SENDataIn->PT2    = SENi2CData[5] << 8;   // Save val from MPU into array (16bit)
  SENDataIn->PT2   |= SENi2CData[6];        // Save val from MPU into array (16bit)
  SENDataIn->PT3    = SENi2CData[7] << 8;   // Save val from MPU into array (16bit)
  SENDataIn->PT3   |= SENi2CData[8];        // Save val from MPU into array (16bit)
  SENDataIn->PT4    = SENi2CData[9] << 8;   // Save val from MPU into array (16bit)
  SENDataIn->PT4   |= SENi2CData[10];       // Save val from MPU into array (16bit)
  return true;
  }                                         // end got packet
  else
  { return false; }                         // No Packet received
  }                                         // end getData

Then both boards must transmit the identical number of bytes. Is that what you are doing?

Unfortunately not, each board will be transmitting a different number of bytes, for example the master board will be transmitting say 8 bytes and the slave board 2 bytes.

With no control of the clock, how does that work? As long as the clock is ticking away, bytes are being transferred. Perhaps all zero bits? or ALL one bits?

It is a full duplex operation every time. Whenever the master runs the clock the slave both receives bits from the master and simultaneously sends bits to the master.

I need to keep track of how many bytes the master has clocked in and out, say 8 bytes the master is expecting valid data, so I need to make sure that I put data into the SPDR for those bytes.

Of course, but the master receives bits from the slave based on the clock ticking. Those bits are there whether anything is "sent" by the slave. The bit will always be there as a zero bit whether the slave sent it or not. The master has no clue if the bits were actually "sent" or are the normal inactive state of the data line. They are identical.

Thanks Paul, so what do i need to do to get this working lets say i send and receive the same amount of bytes , how do i send a struct or array each way

Just thinking, if i break my data down into 8bit vaules and send 1byte at a time then re-assemble the data on master and slave will that work ? is that possible on SPI

I have tested the following code that allows me to send struct over SPI, i have also tried it both ways but cant get the interrupts right, the data going back is stopped short before the full byte is transmitted back to the master.

Master:-

// master

#include <SPI.h>
#include "SPI_anything.h"

// create a structure to store the different data values:
typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

myStruct foo;

void setup ()
  {
  SPI.begin ();
  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

  foo.a = 42;
  foo.b = 32000;
  foo.c = 100000;
  }  // end of setup

void loop () 
  { 
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI_writeAnything (foo);
  digitalWrite(SS, HIGH);
  delay (100);  // for testing  
  
  foo.c++;
  }  // end of loop

Slave:-

// slave

#include <SPI.h>
#include "SPI_anything.h"

// create a structure to store the different data values:
typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

volatile myStruct foo;
volatile bool haveData = false;

void setup ()
  {
  Serial.begin (115200);   // debugging

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // now turn on interrupts
  SPI.attachInterrupt();
  
  }  // end of setup

void loop () 
  { 
  if (haveData)
     {
     Serial.println ((int) foo.a);
     Serial.println (foo.b);
     Serial.println (foo.c);
     Serial.println ();
     haveData = false;
     }
  }  // end of loop

// SPI interrupt routine
ISR (SPI_STC_vect)
  {
  SPI_readAnything_ISR (foo);
  haveData = true;
  }  // end of interrupt routine SPI_STC_vect

Im happy to keep using IC2 , however its getting very complicated transmitting and receiving data from multiple boards as well as reading I2C sensor values without running into I2C bus clashes.

There must be a protocol where i can transmit and receive data from multiple boards without using Serial communication. its becoming frustrating every time i get close to achieving my goal something else pops up in the protocol.

Does anyone know how i can achieve this? is doesn't need to be full duplex as long as i can transmit and receive data between 6 boards 10x per second.

Thanks

That is exactly how SPI works, but you have NOT addressed your full duplex problem at all.

Hi Paul

You are right i am far from resolving the Full Duplex, maybe i'm just not smart enough to write the code, I am only amateur code writer. its been stressing me out for months trying to get SPI Full Duplex.

Are you aware of any articles that demonstrate full duplex but more than a single value i.e. 0 or 1.

Today i have started looking at UART as a option which route i didn't want to go down.

Thanks

Well, you could use a separate control line so the slave could toggle on when it was sending and off when not sending. Timing it with the clock signal, of course.

Thanks Paul

i was actually doing something similar on my I2C code, using separate line allowing data transmission between master and slaves.

I will take a look and do some research

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