Sending & Receiving "float" data - SPI
Objective:
Both Master & Slave Arduino will send & receive float type data (ex: 27.88, 12.75)
Theory:
Before we start coding we need to know the basics of how SPI communication works. I won't go with details here, you will better understand from joony786's article on SPI serial communication. It will be very helpful to understand the codes.
Hardware Setup:
Full Code:
Master Arduino
#include<SPI.h> //main library
void intToBytes(int x); //splits integer into bytes
void bytesToInt(byte b0, byte b1); //splits bytes into integer
int a; //stores converted integer
byte sending[2]; //stores 2 sending bytes
byte coming[2]; //stores 2 incoming bytes
void setup()
{
Serial.begin(9600); //MCU & Serial Monitor communication starts
SPI.begin(); //SPI communication starts
SPI.setClockDivider(SPI_CLOCK_DIV16); //data transfering speed
digitalWrite(SS, LOW); //the clock pulse started from Master
}
void loop()
{
float f_sending= 27.88;
int x= f_sending*100;
intToBytes(x); //spliting integer to bytes
coming[0]= SPI.transfer(sending[0]);
delay(100); //for smooth communication
coming[1]= SPI.transfer(sending[1]);
bytesToInt(coming[0], coming[1]); //converting bytes to integer
float f_coming= (float) a/100; //integer to float
Serial.println(f_coming);
delay(10);
}
void bytesToInt(byte b0, byte b1)
{
a = (b0 << 8) | (b1); //bitwise operation
}
void intToBytes(int x)
{
sendData[0]= (x >>8); //bitwise operation
sendData[1]= x & 0xFF;
}
Slave Arduino
#include<SPI.h> //main library
void intToBytes(int x); //splits integer into bytes
void bytesToInt(byte b0, byte b1); //splits bytes into integer
int a; //stores converted integer
byte sending[2]; //stores 2 sending bytes
byte coming[2]; //stores 2 incoming bytes
int i=0;
volatile bool flag=false; //used for confirmation
void setup()
{
Serial.begin(9600);
SPI.setClockDivider(SPI_CLOCK_DIV16);
pinMode(SS, INPUT_PULLUP); //make this device slave
pinMode(MISO, OUTPUT); //transfer data to Master
SPCR |= _BV(SPE);
SPCR |= !(_BV(MSTR));
SPI.attachInterrupt(); //data handler
}
void loop()
{
float f_sending= 12.75;
int x= f_sending*100;
intToBytes(x); //spliting integer to bytes
if(flag == true) //data received confirmation
{
bytesToInt(coming[0], coming[1]);
float f= (float) a/100; //converting integer to float
Serial.println(f);
}
flag = false; //reset for next loop
delay(10);
}
ISR(SPI_STC_vect)
{
coming[i]= SPDR; //data receives one by one
SPDR= sending[i]; //data sends one by one
i++;
if(i==2) //we only expecting 2 data bytes
{
i=0; //reset for next loop
flag=true; //data received confirmation
}
}
void bytesToInt(byte b0, byte b1)
{
a = (b0 << 8) | (b1); //bitwise operation
}
void intToBytes(int x)
{
sendData[0]= (x >>8); //bitwise operation
sendData[1]= x & 0xFF;
}
Code Explanation:
At Master
void intToBytes(int x);
We can not directly send/receive the "integer/float" type data from Slave. All Serial Communication is only possible with "byte" type data(ex: 0x12). So we first convert the float to integer then integer to bytes. Then only the bytes data is sent to Slave Arduino. We created this user-defined function which will take an integer input then will split it into 2 data bytes. We used the Bitwise (>>, &) operators for this.
void bytesToInt(byte b0, byte b1);
We created this user-defined function for receiving purpose which will take the incoming 2-byte type of data as input and convert those to one integer. So at the Slave's monitor, we will see the same float data that was sent from Master Arduino.
SPI.begin();
SPI.begin() only used for Master, in slave we need to write multiple lines to establish the communication.
SPI.setClockDivider(SPI_CLOCK_DIV16);
Setting the data transferring speed at 1Mbit/s. Others option are "SPI.setClockDivider(SPI_CLOCK_DIVa)" where a=2,4,6,8,16,........
digitalWrite(SS, LOW);
The clock pulse started from Mastera, Slave cant start clock pulses. Without clock pulses, data can not be transferred.
coming[0]= SPI.transfer(sending[0]);
This is a special function only for Master Arduino. By this command, we can simultaneously receive & send data from the serial communication line. While "coming[0]" received the 1st byte, the "sending[0]" will send the 1st byte at the same time.
At Slave:
volatile bool flag=false;
By using "volatile" we specified/fixed a memory location for "flag" which is a "bool" type variable, so next time when we need to access this variable compiler will know the exact location, the compiler won't need to reload and recheck variable's current/new memory location, unlike regular "int, float, char, bool" type variable. Learn more about it.
pinMode(MISO, OUTPUT);
This line helps to transfer data from Slave to Master. Though we dont need to transfer data from Slave to Master here but I think it's a good practice to use a common pattern everywhere.
SPCR |= _BV(SPE);
SPCR |= !(_BV(MSTR));
These 2 lines help to establish SPI communication from the Slave side, where at the master we only used "SPI.begin()"
SPI.attachInterrupt();
When there will be any interference/data transfer, a default "ISR(SPI_STC_vect)" function will run automatically to send & receive data. Here "SPI_STC_vect" is known as SPI status vector. This function only used in Slave Arduino, Master Arduino doesn't have this function.
coming[i]= SPDR;
SPDR= sending[i];
"SPDR" is the register where data is saved byte by byte, but old data is replaced immediately after new data comes, that's why this function has to be as smallest as possible. No delay/print function can be used here. Because these functions may increase the run time and we can lose data. Here the first byte will be received and stored in "coming[0]" and 1st byte will be sent using "sending[0]" then "i" increments, it will keep receiving & sending bytes until our expected data is shared completely.
Result:
Master's Serial Monitor Slave's Serial Monitor
See More Examples:
Comments