Tranfering float via SPI beween two arduinos

Hi, I am trying to send a float from an arduino slave to an arduino master via an SPI interface. I am using a union on the slave to split the float into 4 bytes, and sending each byte over the SPI bus. I am using four bytes in another union on the master, so I can print the resulting float to the serial monitor. The output that I am getting is not correct. I have tried reading the data order as MSB and LSB and neither option yields the same float that is being sent from the slave. Please help? I am at a loss I can not seem to figure out what I am doing wrong.

Here is a copy of the code I am using:

Master Code:

#include <SPI.h>

void setup (void)
{
pinMode(MOSI, OUTPUT);
pinMode(MISO, INPUT);
pinMode(SCK, OUTPUT);
pinMode(SS, OUTPUT);

Serial.begin (115200);
Serial.println ();

digitalWrite(SS, HIGH);

SPI.begin ();

SPI.setBitOrder(MSBFIRST); // MSB first bit order

SPI.setClockDivider(SPI_CLOCK_DIV8);
}

byte transferAndWait (const byte what)
{
byte a = SPI.transfer (what);
delayMicroseconds (20);
return a;
}

union first_union{
float f;
byte b[4];}
data;

float float_number;

void loop (void)
{

digitalWrite(SS, LOW);

transferAndWait ('a');

data.b[0] = transferAndWait ('b');
data.b[1] = transferAndWait ('c');
data.b[2] = transferAndWait ('d');
data.b[3] = transferAndWait (0);

digitalWrite(SS, HIGH);

float_number = data.f;

Serial.print("data.f = ");Serial.println(data.f);

delay(1000);

Serial.print("data.b[0] = ");Serial.println(data.b[0]);
Serial.print("data.b[1] = ");Serial.println(data.b[1]);
Serial.print("data.b[2] = ");Serial.println(data.b[2]);
Serial.print("data.b[3] = ");Serial.println(data.b[3]);

delay(1000);


Slave Code:

byte command = 0;

void setup (void)
{
pinMode(MOSI, INPUT);
pinMode(SCK, INPUT);
pinMode(SS, INPUT);

pinMode(MISO, OUTPUT);

// turn on SPI in slave mode
SPCR |= _BV(SPE);

// turn on interrupts
SPCR |= _BV(SPIE);

} // end of setup

// SPI interrupt routine
ISR (SPI_STC_vect)
{
union first_union{
float f;
byte b[4];}
data;

byte c = SPDR;

data.f = 3.14;

switch (command)
{
// no command? then this is the command
case 0:
command = c;
SPDR = 0;
break;

// incoming byte, return byte result
case 'a':
SPDR = data.b[0];
break;

// incoming byte, return byte result
case 'b':
SPDR = data.b[1];
break;

// incoming byte, return byte result
case 'c':
SPDR = data.b[2];
break;

// incoming byte, return byte result
case 'd':
SPDR = data.b[3];
break;

} // end of switch

} // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{

// if SPI not active, clear current command
if (digitalRead (SS) == HIGH)
command = 0;
}

Here is a sample of the output I am getting:

data.b[0] = 0
data.b[1] = 195
data.b[2] = 195
data.b[3] = 195
data.f = -391.52

The code I as showing here was adapted from code that I got from the following link:

I found this code on another post and modified it slightly, so I could verify what the bytes should be after my SPI transfer.

Test Code:

void setup()
{
Serial.begin(9600);

// prints title with ending line break
Serial.println("Floats coming out as bytes!!");

// wait for the long string to be sent
delay(3000);
}

float number = 3.14;

union u_tag {
byte b[4];
float fval;
} u;

void loop()
{
u.fval = number;

Serial.print("Byte 1: ");
Serial.println(u.b[0], DEC );

Serial.print("Byte 2: ");
Serial.println(u.b[1], DEC );

Serial.print("Byte 3: ");
Serial.println(u.b[2], DEC );

Serial.print("Byte 4: ");
Serial.println(u.b[3], DEC );

Serial.print("velocity: ");
Serial.println(u.fval);

Serial.println();

delay(3000); // allow some time for the Serial data to be sent
}


Here are the results that I get from running the above code.

Floats coming out as bytes!!
Byte 1: 195
Byte 2: 245
Byte 3: 72
Byte 4: 64
velocity: 3.14


I think my approach should work, but I must be transferring the bytes from the slave incorrectly. I mentioned earlier that I tried changing the bit order, and it didn't seem to correct my issue. The way I tested this wasto change the bit order form this SPI.setBitOrder(MSBFIRST); // MSB first bit order to this SPI.setBitOrder(LSBFIRST); // LSB first bit order. Changing the bit order did not achieve the desired result either.

Please help me understand what I am doing wrong with my SPI code.

Thanks

You are only waiting 20uS, not long for the slave to do all that work, and that's MINUS the time it takes to transmit a byte because you are waiting from the start of transmission but the slave doesn't get the interrupt until the end of transmission. It's also minus the 3-4uS interrupt latency.

I would try increasing that delay, even to 1mS just to remove that as a factor.


Rob

I don't see how 'command' gets set to the different incoming values. It starts at 0 so it goes through case 0 which sets command to whatever byte arrived ('a') and responds with 0. For the next arriving character ('b') command is 'a' so the first data byte is sent (195). Same for the other two data bytes.

0, 195, 195, 195

Exactly what you are receiving. Perhaps you should send the byte corresponding to the command. Remember, the outgoing data you put in SPDR won't go out until the next byte is received:

  switch (c)
  {
  case 0:
    SPDR = 0;
    break;
    
  // incoming byte, return byte result
  case 'a':
    SPDR = data.b[0];  
    break;
    
  // incoming byte, return byte result
  case 'b':
    SPDR = data.b[1];  
    break;

  // incoming byte, return byte result    
  case 'c':
    SPDR = data.b[2];  
    break;

  // incoming byte, return byte result    
  case 'd':
    SPDR = data.b[3];  
    break;

  } // end of switch

Thanks for the replies guys. I appreciate the help.

Hi Rob, I increased the delay to 1millisecond, and I still get the same results as before.

Hi John, I am quite new to coding, but what I thought I was telling the master to do is as follows please help me understand if I am mistaken (all constructive criticism is welcome):

  1. master sets slave select (SS) Low--> this allows communication with the slave

  2. call function transferAndWait ('a') --> sends command 'a' receives 0

  3. call function transferAndWait ('b') --> sends command 'b' receives 'b[0]' and stores it in data.b[0] on master

  4. call function transferAndWait ('c') --> sends command 'c' receives 'b[1]' and stores it in data.b[1] on master

  5. call function transferAndWait ('d') --> sends command 'c' receives 'b[2]' and stores it in data.b[2] on master

  6. call function transferAndWait (0) --> sends command 0 receives 'b[3]' and stores it in data.b[3] on master

  7. master sets slave select High --> this ends communication with the slave

  8. print results stored in data.f along with each byte of data.b[4]

Thanks,
Matt

That's what you intended to do. What you actually did was:

  1. master sets slave select (SS) Low--> this allows communication with the slave

Slave has command set to 0.

  1. call function transferAndWait ('a') --> sends 'a' receives garbage (whatever was in the slave SPDR)

Slave receives 'a'. Because command is 0, sets command to 'a', puts 0 in SPDR.

  1. call function transferAndWait ('b') --> sends 'b' receives 0 from SPDR and stores it in data.b[0] on master

Slave receives 'b'. Because command is 'a', puts data.b[0] in SPDR.

  1. call function transferAndWait ('c') --> sends 'c' receives 'b[0]' from SPDR and stores it in data.b[1] on master

Slave receives 'c'. Because command is 'a', puts data.b[0] in SPDR.

  1. call function transferAndWait ('d') --> sends 'c' receives 'b[0]' from SPDR and stores it in data.b[2] on master

Slave receives 'd'. Because command is 'a', puts data.b[0] in SPDR.

  1. call function transferAndWait (0) --> sends 0 receives 'b[0]' from SPDR and stores it in data.b[3] on master

Slave receives 0. Because command is 'a', puts data.b[0] in SPDR.

  1. master sets slave select High --> this ends communication with the slave

  2. print results stored in data.f along with each byte of data.b[4]

Thanks for the help John!

I figured out what I was doing wrong. I had a problem with my switchcase statement in my slave code.

I am posting my final code below.

Master Code:

#include <SPI.h>

void setup (void)
{
pinMode(MOSI, OUTPUT);
pinMode(MISO, INPUT);
pinMode(SCK, OUTPUT);
pinMode(SS, OUTPUT);

Serial.begin (115200);
Serial.println ();

digitalWrite(SS, HIGH);

SPI.begin ();

SPI.setClockDivider(SPI_CLOCK_DIV8);
}

byte transferAndWait (const byte what)
{
byte a = SPI.transfer (what);
delayMicroseconds (10);
return a;
}

union first_union{
float f;
byte b[4];}
data;

void loop (void)
{

digitalWrite(SS, LOW);

transferAndWait ('a');

data.b[0] = transferAndWait ('b');
data.b[1] = transferAndWait ('c');
data.b[2] = transferAndWait ('d');
data.b[3] = transferAndWait (0);

digitalWrite(SS, HIGH);

Serial.print("data.f = ");Serial.println(data.f);

Serial.print("data.b[0] = ");Serial.println(data.b[0]);
Serial.print("data.b[1] = ");Serial.println(data.b[1]);
Serial.print("data.b[2] = ");Serial.println(data.b[2]);
Serial.print("data.b[3] = ");Serial.println(data.b[3]);

delay(1000);
}

Slave Code:

byte command = 0;

void setup (void)
{
pinMode(MOSI, INPUT);
pinMode(SCK, INPUT);
pinMode(SS, INPUT);

pinMode(MISO, OUTPUT);

// turn on SPI in slave mode
SPCR |= _BV(SPE);

// turn on interrupts
SPCR |= _BV(SPIE);

} // end of setup

// SPI interrupt routine
ISR (SPI_STC_vect)
{
union first_union{
float f;
byte b[4];}
data;

byte c = SPDR;

data.f = 3.14;

command = c;

switch (command)
{
// no command? then this is the command
case 0:

SPDR = 0;
break;

// incoming byte, return byte result
case 'a':

SPDR = data.b[0];
break;

// incoming byte, return byte result
case 'b':

SPDR = data.b[1];
break;

// incoming byte, return byte result
case 'c':

SPDR = data.b[2];
break;

// incoming byte, return byte result
case 'd':

SPDR = data.b[3];
break;

} // end of switch

} // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{

// if SPI not active, clear current command
if (digitalRead (SS) == HIGH)
command = 0;
}