2

I need to receive hex encoded single precision big endian float values coming from an Arduino over a serial line (RS-232). How do convert them to Python's float which are big endians with double precision?

The Arduino send something like "8192323E" and in Python I would like to have 0.174387. I found "Convert hex to float" but it seems that all of them don't work for single precision floats.

From the linked page, this looks promising:

from ctypes import *

def convert(s):
    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

But it still doesn't work with my single precision floats.

In Java (Processing) I've been able to do that:

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);
}

For your reference, this is the C code running on the Arduino implementing the hex encoding.

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);
  }
}
Community
  • 1
  • 1
Fabio Varesano
  • 459
  • 6
  • 13
  • Can you give some examples of how floats are decoded by the ctypes code? My first suspicion would be byte order. You may need to swap bytes to get the decoding right. – Gintautas Miliauskas Nov 30 '10 at 15:13
  • On the wire it flows: f:FFFF7F7F f:FFFF7FFF f:8192323E f:00000000 While the ctypes code reads: 'FFFF7F7F' nan 'FFFF7FFF' nan '8192323E' -5.37040237597e-38 '00000000' 0.0 – Fabio Varesano Nov 30 '10 at 15:20
  • 1
    Since the Arduino processor is little-endian (AFAICT) the C code running on it doing the hex encoding is processing low bytes first -- so when all four bytes are concatenated together in the order produced they will be backwards to the normal way multi-byte hex (and multi-digit decimal) numbers are written and interpreted. This is consistent with and why the Java decoder code "works" (see my answer). – martineau Dec 01 '10 at 11:53

4 Answers4

5

Building on Ignacio Vazquez-Abrams's answer,

import binascii
import struct

text='8192323E'
print(struct.unpack('<f',binascii.unhexlify(text))[0])
# 0.17438699305057526
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
4
>>> struct.unpack('<f', '\x81\x92\x32\x3e')
(0.17438699305057526,)
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

Indeed, you need to invert the byte order. Look:

>>> convert('8192323E')
-5.370402375965945e-38
>>> convert('3E329281')
0.17438699305057526
Gintautas Miliauskas
  • 7,744
  • 4
  • 32
  • 34
0

It looks like the data sequence in the hex string values has the lower-order bytes first (little-endian?). At least that's the way your Java code -- which you say works -- treats it:

  inData[0] = (byte) unhex(inString.substring(0, 2));  // gets _first_ two bytes
  inData[1] = (byte) unhex(inString.substring(2, 4));  // and the next two
  inData[2] = (byte) unhex(inString.substring(4, 6));  // etc...
  inData[3] = (byte) unhex(inString.substring(6, 8));

  // this shifts the bytes at the end of the hex string more the the leading ones
  int intbits = (inData[3] << 24) | ((inData[2] & 0xff) << 16) | 
                ((inData[1] & 0xff) << 8) | (inData[0] & 0xff);

Python's int() function expects the higher-order bytes to appear first when passed a (hex or decimal) string. So you should be able fix your convert() function by adding something like:

def convert(s):
    s = ''.join(s[i:i+2] for i in range(8,-2,-2)) # put low byte pairs first
    i = int(s, 16)                   # convert from hex to a Python int
      ...

near the beginning to fix it.

Update

Or you could fix the serialFloatPrint() function running on the Arduino to do the hex encoding properly for a little-endian architecture.

martineau
  • 119,623
  • 25
  • 170
  • 301