Float over serial between two Arduino [CLOSED]

First off, sorry!

I know this has been asked a good few times before but I haven’t been able to get this working yet.
Basically I’m trying to send a float from one Arduino to another over serial for the second Arduino to process. My current approach is to try and serialise it (to the best of my current knowledge).

The master currently sends this following test code:

float CountMin = 60.0;
float CountMax = 72.0;
float Count = CountMax;
int Data = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  Data = (int) round(Count*10);
  Serial.write(Data);
  if (Count == CountMin){Count = CountMax;}
  else {Count = Count - 0.1;}
}

Basically its going to count from 72.0 down to 60.0 in steps of 0.1 then repeat. Each time, it multiplies the value by 10 to preserve one decimal place and turns it into an int. The second Arduino is meant to then multiply the received value by 0.1 to turn it into a float again:

bool DEBUG = false./;
float DebugVoltage = 70.2;                                                            // 70.2 is a good default
bool DebugSerialWorking = true;

/***********************************************************************************/
/*                                      SETUP                                      */
/***********************************************************************************/
void setup() {
  // Data
  Serial.begin(9600);                                                                // For getting voltage value from Master Arduino
  int BatRange = MAXVOLTAGE - MINVOLTAGE;                                            // For calculating battery percentage

  // Ring
  ring.begin();                                                                      // Shows when loadinng has completed
  for (int LED = 0; LED < ring.numPixels(); LED++){
      ring.setPixelColor(LED, ring.Color(25,50,50));
      ring.show();
  }

  // Screen
  u8g2.begin();
  u8g2.enableUTF8Print();                                                            // enable UTF8 support for the Arduino print() function
  u8g2.setFont(u8g2_font_inb19_mr);
  u8g2.setCursor(3,23);
  u8g2.print("00.0V");
}

/***********************************************************************************/
/*                                      MAIN                                       */
/***********************************************************************************/
void loop() {   
    // Determine voltage & percentage
    Voltage = ReadData();
    Percentage = BatPercentage(Voltage);
    
    // Write to ring
    RingCtrl();

    //Write to screen
    ScreenWrite();

    //ERROR HANDLING
    Serial.print(SerialStatus);
    Serial.print(Voltage);

}

/***********************************************************************************/
/*                                   FUNCTIONS                                     */
/***********************************************************************************/
float ReadData(){
  if (DEBUG == true){                                                                // DEBUG
    if (DebugSerialWorking == true){SerialStatus = 1;}
    else {SerialStatus = 0;}
    return DebugVoltage;
    }
    
  else {
    if (Serial.available() > 0) {
      // This serial reading uses "serialisation" as float can't be sent over serial.
      // It assumes the data sent is sent as:   data = (int) round(Voltage*10)
      // This calculation multiplies the float Voltage by 10 to preserve 1 decimal.
      // It then converts to integer and sends over serial. By multiplying by 0.1
      // this end, it should be transformed back into a float with 1 decimal.
      SerialStatus = 1;                                                              // ERROR HANDLING
      float Data = (Serial.read())*0.1;
      return Data;
    }
    else {
      SerialStatus = 0;                                                              // ERROR HANDLING
      return 00.0;
      }
  }
}

I’ve captured the relevant code above, and some extra. My Debug shows that SerialStatus = 0 (no serial) constantly.

Any advice would be appreciated! I’m pretty sure its the serial itself but there could be some small nuance in the surrounding code that I haven’t gotten right. I end up coding after work when I’m pretty out of it so everything starts to blend together!
Thank you very much :slight_smile:

Arduino float is 4 bytes. You can send the 4 bytes through serial though you probably want to send a CRC byte to check the result with, serial is not perfect.

float x;
float *fPtr = &x;

byte *bPtr = (byte *) fPtr; // bPtr points to the 1st byte of x

byte fByte = *bPtr; // fByte = 1st byte of float x -- just showing you can
Serial.write( *bPtr ); // and you don't -need- to make or set fByte

byte fByte = *bPtr + 1; // fByte = 2nd byte of float x
byte fByte = *bPtr + 2; // fByte = 3rd byte of float x
byte fByte = *bPtr + 3; // fByte = 4th byte of float x

At the read end you reverse the process and you have identical float.

Other thing to do is to Serial.print(yourFloat ) and interpret at the read end by buffering the input text and using atof() to convert ascii to float. You might need to #include a library already in your IDE for that.

IEE floating point is a pain. Every time you operate on the values you can lose precision, 1 can become .9999...

Thanks for the reply, though I must say it’s all above my level of understanding!
While I try and figure it out, would you mind explaining why my attempt to convert to int, send and convert back didn’t work?
Also, I’m using Serial.available(), should I instead be using Serial.timeout() and a truth check? I just realised that in its current form I’m probably more likely to get false errors.

I find it much easier to debug problems if I send the data as text using Serial.print() although if printing a floating point number you need to be careful to specify the number of digits after the decimal point.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

The technique in the 3rd example will be the most reliable. It is what I use for Arduino to Arduino and Arduino to PC communication.

You can send data in a compatible format with code like this (or the equivalent in any other programming language)

Serial.print('<'); // start marker
Serial.print(value1);
Serial.print(','); // comma separator
Serial.print(value2);
Serial.println('>'); // end marker

…R

Thanks, I'll take a look!
I'm somewhat familiar with basic serial communication, including columnised debug readouts, I haven't dealt with float over serial though and what I did know has somewhat faded with my memory.
If you could, would you mind explaining why my serialisation attempt failed?

@OP

Please, see the changes in your Sender and Receiver programs to meet your objectives. Hope you will understand the codes; else, ask for clarification. Connect Sender-UNO and Receiver-UNO/NANO this way (called Software UART Port): DPin-2 (Sender) -----> DPin-3 (Receiver); DPin-3(Sender) ----> DPin-2 (Receiver); GND <--------> GND.

Sender Codes (a slight change in your codes)

#include<SoftwareSerial.h>
SoftwareSerial SUART(2, 3);

float CountMin = 60.0;
float CountMax = 72.0;
float Count = CountMax;
int Data = 0;

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  SUART.begin(9600);
}

void loop()
{
  // put your main code here, to run repeatedly:
  Data = (int) round(Count * 10); 
  Serial.println(Data);//write(Data);
  SUART.print(Data);//write(Data);
  SUART.println();   //Newline code (0x0A) as end mark
  if (Count == CountMin)
  {
    Count = CountMax;
  }
  else
  {
    Count = Count - 0.1;
  }
  delay(1000);
}

Receiver Codes (complete new sketch)

#include<SoftwareSerial.h>
SoftwareSerial SUART(2, 3);
char myData[10] = "";
int i = 0;

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

void loop()
{
  byte n = SUART.available();
  if (n != 0)
  {
    char x = SUART.read();
    if (x != 0x0A)  //end mark no found
    {
      myData[i] = x;  //save ASCII coded data in array
      i++;
    }
    else
    {
      int z = atoi(myData);   // getting the data in integer form
      //Serial.println(z);
      float y = (float)z * 0.10;
      Serial.println(y, 1);  //shows 70.2 .....
      i = 0;
    }
  }
}

sm84.png

sm85.png

sm84.png

sm85.png

@Golam.
Thank you, I've come across SoftwareSerial before but don't know much about it. Does it basically spoof serial on any pins? I figured it was possible but didn't know how!
So the pins can be set to anything but you just need to be careful to ensure that Tx and Rx are the right way round as per usual?

Thanks again, that's brilliant! I'll modify the example you gave me into a function and stick it in my code. :slight_smile:

Floating point numbers are like piles of sand; every time you move them around, you lose a little sand and pick up a little dirt. — Brian Kernighan and P.J. Plauger

It’s because of how the floats work. If you want to be exact then use integers or BCD math or similar.

I showed you C pointer use. For some reason this very basic aspect of C has been called advanced. It’s a fundamental part of the language! They’re actually simple once you know about variables and memory but… oh horrors there are symbols used to save typing and that scares the sense out of people who think they know enough already.

All a C pointer is is a variable type and a memory address. That’s IT.

If you make an array, the name of that array is a pointer to the variable type the array is made of.

Knowing system is all well but you should know what the system runs on. For example, UNIX was written in C.

Another angle on sending floats or other types across serial is to us a union. The members of a union occupy the same memory so the “translation” from type float to byte array is automatic. For example, the union

union Onion
{
    uint8_t     fBytes[sizeof( float )];
    float       fValue;
};

has an array of bytes (uint8_t) of size float and a float called fValue. If you assign a value to the fValue member the fBytes array will automatically reflect the bytes that make up the float. You can send those across the serial interface starting from a known index (e.g. 0); when they arrive at the receiver, simply put them into the fBytes array in the receiver’s union in the same order. When the bytes are finished, you can read out the float value by reading fValue again.

Think of it like Star Trek’s transporter: The union disassembles the float into its constituent parts, transmits/“beams” it to another location where a similar apparatus puts them back together again, and out pops a float.

Both these code example compile though I didn’t have a chance to test them. Note that in the serial message, I preface the message with a ‘>’ character and finish off with a ‘<’. These characters tell the receiver the start and end of the message. Note that with pure binary transmissions like sending a float it’s possible that the receiver will see a legit binary value for ‘<’ before the actualy end of the message. As well, the receiver blocks waiting for the message to end and has no timeout or other mechanisms to escape. Seeing that this is a simple model, I’m not going to try to fix these things here; this is just to illustrate the basics of the technique.

Transmitter code:

union Onion
{
    uint8_t     fBytes[sizeof( float )];
    float       fValue;
};

Onion flt;

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

    flt.fValue = 1234.5678;

    
}//setup

void loop( void )
{    
    Serial.write( '>' );
    Serial.write( flt.fBytes, sizeof( float ) );
    Serial.write( '<' );

    delay(1000);
    
}//loop

Receiver code:

union Onion
{
    uint8_t     fBytes[sizeof( float )];
    float       fValue;
};

Onion flt;

void setup( void )
{
    Serial.begin(9600);
    while( !Serial );
    
}//setup

void loop( void )
{
    byte
        ch,
        idx;
    bool
        done;
        
    if( Serial.available() > 0 )
    {
        if( Serial.read() == '>' )
        {
            done = false;
            idx = 0;
            while( !done )
            {
                if( Serial.available() > 0 )
                {
                    ch = Serial.read();
                    if( ch == '<' )
                        done = true;
                    else
                    {
                        if( idx < sizeof( float ) )
                            flt.fBytes[idx++] = ch;
                        
                    }//else
                
                }//if
                   
            }//while

            Serial.print( "Float value received: " ); Serial.println( flt.fValue, 4 );
            
        }//if
        
    }//if
    
}//loop

SJMaybury:
I’ve come across SoftwareSerial before but don’t know much about it. Does it basically spoof serial on any pins?

I leave it to you to judge based on the following points if spoof is an appropriate word to describe the features of a Software UART Port.

1. ATmega328P MCU of the UNO board has only one hardware UART Port (UART Port) which is permanently engaged with Serial Monitor/IDE via Dpin-0/1.

2. To meet the need of another UART Port (s), the SoftwareSerial.h Library simulates ‘almost the same UART’ Port (s) using any valid DPins. These simulated ports are known as Software UART Ports (SUART Port).

3. A SUART Port has the following limitations:
(1) SUART Port is not recommended to use at Bd > 9600.
(2) The serialEvent() handler is not available with SUART Port.
(3) All other command/functions/methods of UART Port are equally applicable for SUART Port.

4. A SUART Port is automatically created when we include the following lines in a sketch.

#include<SoftwareSerial.h>
SoftwareSerial SUART(2, 3); //SRX = DPin-2, STX = DPin-3
SUART.begin(9600);

Robin2:
I find it much easier to debug problems if I send the data as text using Serial.print() although if printing a floating point number you need to be careful to specify the number of digits after the decimal point.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

The technique in the 3rd example will be the most reliable. It is what I use for Arduino to Arduino and Arduino to PC communication.

You can send data in a compatible format with code like this (or the equivalent in any other programming language)

Serial.print('<'); // start marker

Serial.print(value1);
Serial.print(’,’); // comma separator
Serial.print(value2);
Serial.println(’>’); // end marker




...R

Hi @Robin2, I hope you have the time to reply. I implemented your solution last weekend, then my boards refused to be flashed though they still sent serial and operated fine else wise… but anyway:
I’ve modified both the send and receive code like so:
SEND

float CountMin = 60.0;
float CountMax = 72.0;
float Count = CountMax;
int Data = 0;
bool CountFWD = true;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  if (CountFWD == true){
    if (Count <= CountMax){
      Serial.print('<'); // start marker
      Serial.print(Count);
      Serial.println('>'); // end marker
      Count = Count + 0.1;}
    else {CountFWD = false;}  
  }
  else if (CountFWD == false)
    if (Count >= CountMin){
      Serial.print('<'); // start marker
      Serial.print(Count);
      Serial.println('>'); // end marker
      Count = Count - 0.1;}
    else {CountFWD = true;}
}

RECIEVE FUNCTION

float ReadData(){
  if (DEBUG == true){                                                                // DEBUG
    if (DebugSerialWorking == true){SerialStatus = 1;}
    else {SerialStatus = 0;}
    return DebugVoltage;
    }
    
  else {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;
    float Data = 00.0;
    while (Serial.available() > 0 && newData == false) {
      rc = Serial.read();
      if (recvInProgress == true) {
        if (rc != endMarker) {
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= numChars) {ndx = numChars - 1;}
        }
        else {
          receivedChars[ndx] = '\0'; // terminate the string
          recvInProgress = false;
          ndx = 0;
          newData = true;
        }
      }
      else if (rc == startMarker) {recvInProgress = true;}
    }
    
    if (MINVOLTAGE >= Data || MAXVOLTAGE <= Data){SerialStatus = 0;}
    else {
      SerialStatus = 1;
      Data = atof(receivedChars);
    }
    return Data;
  }
}

int BatPercentage (float V){ // FIND CURRENT BATTERY PERCENTAGE
  int P = (int) round(((V - MINVOLTAGE)/(MAXVOLTAGE - MINVOLTAGE)*100));
  if (P >= 100){P = 100;}
  return P;
}

I plugged the Master into my computer to see what the serial print was and it was what we’d expect (<72.0><71.9><71.8>…) but when I hook up the Tx and Rx of the Master and Slave, nothing happens and the neither’s TxRx LEDs flash.
Any ideas?
Thanks!

SJMaybury:
Hi @Robin2, I hope you have the time to reply. I implemented your solution last weekend, then my boards refused to be flashed though they still sent serial and operated fine else wise.. but anyway:
I've modified both the send and receive code like so:

Why are you not using the function recvWithStartEndMarkers() with no changes whatever? Save yourself a lot of trouble and it might even work.

...R

Robin2:
Why are you not using the function recvWithStartEndMarkers() with no changes whatever? Save yourself a lot of trouble and it might even work.

...R

Because if I did that how could I expect to incorporate it into the rest of the code? If I can't adapt it to integrate with the rest of the program then its of no use whatsoever ¯_(ツ)_/¯

EDIT: I'm quite sure I kept the essence of the code pretty much identical. I simply incorporated in the other aspects I need (i.e. returning the received data instead of printing it and implementing my debug, error and "no data" code.

In pseudo:

float ReadData(){
  if (DEBUG enabled){do debug override stuff}
    
  else {do copy-paste of recvWithStartEndMarkers()}
    if (data not in expected range){report error}
    else {report no error}
    return received data
  }

I have a sketch where I am receiving a string from the Serial Monitor.
My Serial Monitor is set up to send a carriage return when I hit the Enter key.
Once all the ASCII characters are sent, it writes to the Serial Monitor what was received to 5 decimal places.

char inputString[100];
int i = 0;


void setup() 
{
    // initialize serial:
    Serial.begin(115200);

}

void loop() 
{
    char inChar;

    if (Serial.available())
    {
        //read the byte
        inChar = Serial.read();

        if (inChar != '\r')
        {
            inputString[i] = inChar;
            Serial.print(i);
            Serial.println(inChar);
            i++;        
        }
        else
        {
            // all the characters have been received

            inputString[i] = '\0';          //terminate with the NULL

            Serial.print("float value :");
            Serial.println(atof(inputString), 5); 

            // prepare to receive the next set of bytes
            inputString[0]= '\0';
            i = 0;
            
        }
    }
}

@Robin2

Looking into it more closely I'm noticing you're storing the imported data as separate chars in an array. I'm not sure if atof works on an array of chars? I'll look into this further.
In any case, I'm still expecting Rx and Tx LEDs to flash, no?

EDIT: atof does work on the array, silly me for doubting. Or maybe not, its good to double check.

so you read serial 1 then 2 then 3 then 4 then space…

If you start with

int total;

total = 0;

int addDigit( char num ) // check for >= ‘0’ && <= ‘9’ before calling
{
total *= 10;
total += ‘num’ - ‘0’;
}

1 → total = 10 * 0 + 1 = 1
2 → " = 10 * 1 + 2 = 12
3 → " = 10 * 12 + 3 = 123
4 → " = 10 * 123 + 4 = 1234

You init total and call addDigit for reads ‘0’ to ‘9’ until non-digits arrive then total holds your value.

You can watch for a ‘.’ and count decimal places, float = integer / 10^places if you need a float.

When you interpret serial values this way you have your answer as soon as you get a delimiter, with no buffering needed.
If you buffer up to the delimiter and then run atof() you have your answer that much later with that much less RAM.

SJMaybury:
Because if I did that how could I expect to incorporate it into the rest of the code? If I can't adapt it to integrate with the rest of the program then its of no use whatsoever ¯_(ツ)_/¯

I have written it to make that incorporation easy.

When I have a project that needs to receive data I just copy the function recvWithStartEndMarkers() into my program, put the necessary global variables at the top of my program and then my code in loop() is like this

void loop() {
   recvWithStartEndMarkers() ;
   if (newData == true) {
      // call the function to process the data
   }
   // other stuff
}

An even simpler way is to use the complete example from my tutorial as the starting point for your program and add your other stuff to it. In fact, most times that is what I do.

...R

Robin2:
I have written it to make that incorporation easy.

An even simpler way is to use the complete example from my tutorial as the starting point for your program and add your other stuff to it. In fact, most times that is what I do.

...R

Then why don't you like the fact I'm trying to incorporate it? I'll take a look at doing it your approach, but I'm a fan of keeping my functions nice and tidy and not calling one from another unnecessarily. If Function A is the only function to call Function B, why not incorporate A into B?

Unfortunately by the time I started this thread I had about 264 lines of nicely formatted, commented and compact code. I'm somewhat unwilling to scrap that and start again revolving everything around your function, though I'm sure that's a good approach!

In any case, I'm not sure breaking out your function and doing "if NewData then process" will solve the issue (I'll give it a go though).
Could you please tell me how exactly my function setup is not implementing your code correctly? I just keep getting nan out of my atof

Robin2:
When I have a project that needs to receive data I just copy the function recvWithStartEndMarkers() into my program, put the necessary global variables at the top of my program and then my code in loop() is like this

void loop() {

recvWithStartEndMarkers() ;
  if (newData == true) {
      // call the function to process the data
  }
  // other stuff
}

Hey! So I’ve tried this approach:

Above setup

// SERIAL COMMUNICATION
// Take care to make sure Arduinos use common ground.
const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;

In setup

  Serial.begin(9600);

In main

void loop() {   
    // Determine voltage & percentage
    recvWithStartEndMarkers();                // <--------- These are the important ones

    Voltage = ProcessData();                    // <--------- These are the important ones
    Percentage = BatPercentage(Voltage);
    
    // Write to ring
    RingCtrl();

    //Write to screen
    ScreenWrite();

    Serial.print("Serial: ");
    Serial.print(SerialStatus);
    Serial.print(" , Voltage: ");
    Serial.print(Voltage);
    Serial.print("\n");

}

As Functions

void recvWithStartEndMarkers() { // Receive data in format <DATA> and extract from end markers
    // http://forum.arduino.cc/index.php?topic=396450.0 example 3                                                    
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;
 
    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

float ProcessData(){
  float Data = 00.0;
  if (newData == true) {
    newData = false;
    Data = atof(receivedChars);
    }
  SerialStatus = 1;
  return Data;
}

Still no dice.

Still no dice

Were you expecting dice?