Sending float variables over Serial without loss of precision with Arduino and Processing or Python

For a project I’m working on, I had the need to send some float variables computed on the Arduino board to a Processing program running on a computer over a Serial communication Link.

Arduino doesn’t have any “out of the box” way to send float variables over the Serial interface but one could simply send approximations: send the float as an integer and truncate the decimals or send it only with two digit for the decimals.

Unfortunately, I needed the best precision for my project so the above solutions weren’t actually useful.

So, I came out with the following way: given that on both the Arduino and Processing have 32 bit floats and that both the ATMEL 328 (the microcontroller used by Arduino) and my i386 compatible PC are both big endian processors, I can split the float on Arduino into an array of 4 bytes.

Then, I found 2 possibilities:

  1. using Serial.write() I can send them raw to the Arduino. One could then reassemble on the PC using just the opposite procedure.
  2. send them over the Serial in HEX representation and then unhex on the target machine.

DISCLAIMER: I’m not an experienced low level C programmer, take the following as potentially wrong. It looks that it works in my tests.

Raw bytes

The idea is to prepend to the 4 bytes a delimiter, so that we can understand the formatting of the incoming data. I choose “f:” but one could use anything like that. Then simply use Serial.write() to send the raw bytes over the wire.

Once on the target PC we will do some bitwise logic to reassemble the float exactly how it was on the Arduino.

Arduino Program

/**
* Communicates float data over serial without loss of precision
*/

int state = LOW;

void setup() {
  Serial.begin(9600); // setup serial connection speed
}

void loop() {
  float a = 3.4028235E+38;
  serialFloatPrint(a);

  
  a = -3.4028235E+38;
  serialFloatPrint(a);
  
  a = 0.174387;
  serialFloatPrint(a);
  
  delay(1000);
}


void serialFloatPrint(float f) {
  byte * b = (byte *) &f;
  Serial.print("f:");
  Serial.write(b[0]);
  Serial.write(b[1]);
  Serial.write(b[2]);
  Serial.write(b[3]);
  /* DEBUG */
  Serial.println();
  Serial.print(b[0],BIN);
  Serial.print(b[1], BIN);
  Serial.print(b[2], BIN);
  Serial.println(b[3], BIN);
  //*/
}

Processing Program

import processing.serial.*;

Serial myPort;  // Create object from Serial class


void setup() 
{
  //size(600, 600);
  myPort = new Serial(this, "/dev/ttyUSB0", 9600);
}

void draw() {
  if(myPort.available() > 0) {
    char inByte = myPort.readChar();
    if(inByte == 'f') {
      // we expect data with this format f:XXXX
      myPort.readChar(); // discard ':'
      byte [] inData = new byte[4];
      myPort.readBytes(inData);
      
      int intbit = 0;
      
      intbit = (inData[3] << 24) | ((inData[2] & 0xff) << 16) | ((inData[1] & 0xff) << 8) | (inData[0] & 0xff);
      
      float f = Float.intBitsToFloat(intbit);
      println(f);
    }
  }
}

With HEX representation

Unfortunately, I found out that the raw method above could lead to problems. For example there could be float that contains somehow the same bits of the “f:” delimiter making pretty difficult to understand how to correctly decode the incoming data.

Moreover, any kind of serial debugging is quite impossible as the Arduino serial monitor really gets confused by the random bits flowing through the wire.

So, the HEX representation method uses the same idea of the above but instead of sending raw bytes I send them HEX encoded. This fixes the above problems but at the cost of more heavy bandwidth communication.

Arduino Program

/**
* Communicates float data over serial without loss of precision
*/

int state = LOW;

void setup() {
  Serial.begin(9600); // setup serial connection speed
}

void loop() {
  
  float a = 3.4028235E+38;
  serialFloatPrint(a);
  Serial.println();
  
  a = -3.4028235E+38;
  serialFloatPrint(a);
  Serial.println();
  
  a = 0.174387;
  serialFloatPrint(a);
  Serial.println();
  
  a = 0.0;
  serialFloatPrint(a);
  Serial.println();
  
  delay(1000);
}


void serialFloatPrint(float f) {
  byte * b = (byte *) &f;
  Serial.print("f:");
  for(int i=0; i<4; i++) {
    
    byte b1 = (b[i] >> 4) & 0x0f;
    byte b2 = (b[i] & 0x0f);
    
    char c1 = (b1 < 10) ? ('0' + b1) : 'A' + b1 - 10;
    char c2 = (b2 < 10) ? ('0' + b2) : 'A' + b2 - 10;
    
    Serial.print(c1);
    Serial.print(c2);
  }
}

Processing Program

import processing.serial.*;
import java.nio.ByteBuffer;

Serial myPort;  // Create object from Serial class


void setup() 
{
  //size(600, 600);
  myPort = new Serial(this, "/dev/ttyUSB0", 9600);
}

void draw() {
  if(myPort.available() > 0) {
    String inString = myPort.readStringUntil((int) '\n');
    if(inString != null && inString.length() > 0) {
      float f = decodeFloat(inString.substring(0, 10)); // discard the '\r''\n'
      println(f);
    }
  }
}

float decodeFloat(String inString) {
  byte [] inData = new byte[4];
  
  inString = inString.substring(2, 10); // discard the leading "f:"
  inData[0] = (byte) unhex(inString.substring(0, 2));
  inData[1] = (byte) unhex(inString.substring(2, 4));
  inData[2] = (byte) unhex(inString.substring(4, 6));
  inData[3] = (byte) unhex(inString.substring(6, 8));
      
  int intbits = (inData[3] << 24) | ((inData[2] & 0xff) << 16) | ((inData[1] & 0xff) << 8) | (inData[0] & 0xff);
  //unhex(inString.substring(0, 8));
  return Float.intBitsToFloat(intbits);
}

EDIT: I also implemented a Python version of the decoding. You find it below.

Python Program

import time, string
import serial

from struct import unpack
from binascii import unhexlify

# Decode an hex representation of a float to a float
#
# See:
# http://stackoverflow.com/questions/1592158/python-convert-hex-to-float/1...
# http://stackoverflow.com/questions/4315190/single-precision-big-endian-f...
def decode_float(s):
  """Other possible implementation. Don't know what's better
  #from ctypes import *
  s = s[6:8] + s[4:6] + s[2:4] + s[0:2] # reverse the byte order
  i = int(s, 16)                   # convert from hex to a Python int
  cp = pointer(c_int(i))           # make this into a c integer
  fp = cast(cp, POINTER(c_float))  # cast the int pointer to a float pointer
  return fp.contents.value         # dereference the pointer, get the float
  """
  return unpack('<f', unhexlify(s))[0]
  


# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
	port='/dev/arduino',
	baudrate=9600,
	parity=serial.PARITY_NONE,
	stopbits=serial.STOPBITS_ONE,
	bytesize=serial.EIGHTBITS
)

#print(ser.open())
#print (ser.isOpen())


time.sleep(1)
ser.flushInput(); # clean input buffer

while 1 :
  value = ser.readline()
  if string.index(value, "f:") == 0:
    #print value[2:10]
    print decode_float(value[2:10])
    print ""
  

Suggestions?

Do you have a better solution or did you find any bug? Please leave a comment below. Thanks!

Scroll to Top