Transmission of a float through a serial link

edited October 2015 in Arduino

Hi everybody,

I am trying to send a float through a USB link between an Arduino and a Processing (ver 3) application.

On the Arduino, I am breaking the float with a union before sending the 4 chars:

`union _UVAL { float f; char c[4]; } uval;

`

That's the easy part!

Now I am trying to reconstruct the float under Processing knowing that Processing doesn't handle the union, the pointer, the bitwise shift (<<, >>), etc.

As an example, 1.0 is encoded as '63 128 0 0' (or in hexa: 3F 80 00 00). You can check on the IEEE754 web site:

http://www.h-schmidt.net/FloatConverter/IEEE754.html

With the 4 bytes received and coded as chars, can you please provide a way to retrieve the float.

Thank you very much in advance.

JM

Answers

  • AFAIK, you should send it as a String w/ print() or println().

  • Hi GoToLoop, thank you for your prompt answer.

    My requirement is to do it binary and not by string as this is an application for control-command of an RC aircraft where I need to transmit fast and with a constant message length. I really need to encode the 4 bytes into a float.

  • Maybe I don't understand the question, but bitshifting works 100% in Processing. We have it documented: https://processing.org/reference/rightshift.html

  • edited October 2015

    1st of all, in order to post properly formatted code in this forum, read this article: :-\"
    http://forum.Processing.org/two/discussion/8045/how-to-format-code-and-text

    So you want the best transmission performance as possible, right? \m/
    And you're sending out 4 bytes representing an IEEE 754 float primitive type from C to Java.

    Then you need a byte[] array w/ its length = Float.SIZE/8 = Float.SIZE>>3 = Float.BYTES = 4:
    http://docs.Oracle.com/javase/8/docs/api/java/lang/Float.html#SIZE
    http://docs.Oracle.com/javase/8/docs/api/java/lang/Float.html#BYTES

    I'm also assuming those transmitted bytes are in ByteOrder.BIG_ENDIAN too:
    http://docs.Oracle.com/javase/8/docs/api/java/nio/ByteOrder.html#BIG_ENDIAN

    By default, serialEvent() is triggered for each byte received:
    https://Processing.org/reference/libraries/serial/serialEvent_.html

    In order to force it to buffer more than 1 byte, we can either issue bufferUntil():
    https://Processing.org/reference/libraries/serial/Serial_bufferUntil_.html
    In which we specify the byte value which should denote "end of transmission".

    Or simply buffer(), in which we specify exactly how many bytes to buffer before serialEvent() is invoked:
    https://Processing.org/reference/libraries/serial/Serial_buffer_.html

    bufferUntil() is more appropriate for String transmissions though.
    In which we generally specify \n or ENTER as the terminal byte character: bufferUntil(ENTER)
    Therefore for fixed number of bytes transmitted we should go w/ buffer() instead. :-B

    Finally inside serialEvent(), we call readBytes() in order to fill up our byte[] array:
    https://Processing.org/reference/libraries/serial/Serial_readBytes_.html

    However, we still need to convert the received byte[] array into 1 single primitive float value. :-SS

    We could for example use bitshifting in order to fuse the 4 bytes from the byte[] array as 1 int.
    Then invoke Float.intBitsToFloat() in order to interpret the int as an IEEE 754 float:
    http://docs.Oracle.com/javase/8/docs/api/java/lang/Float.html#intBitsToFloat-int-

    But an easier approach would be to create a ByteBuffer by calling its wrap() method over our byte[]:
    http://docs.Oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#wrap-byte:A-

    Then either call its getFloat() method: http://docs.Oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#getFloat-int-

    Or make a FloatBuffer view outta the ByteBuffer by invoking asFloatBuffer():
    http://docs.Oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#asFloatBuffer--

    Then we can choose to use FloatBuffer's get() method instead of ByteBuffer's getFloat(): *-:)
    http://docs.Oracle.com/javase/8/docs/api/java/nio/FloatBuffer.html#get-int-

    As a last touch, we can deactivate draw()'s auto-callback w/ noLoop():
    https://Processing.org/reference/noLoop_.html

    Then issue redraw() inside serialEvent() after we got our float: <:-P
    https://Processing.org/reference/redraw_.html

  • edited May 2017
    // forum.processing.org/two/discussion/12757/
    // transmission-of-a-float-through-a-serial-link#Item_5
    
    // GoToLoop (2015-Oct-02)
    
    import processing.serial.Serial;
    
    import java.nio.ByteBuffer;
    import java.nio.FloatBuffer;
    
    static final int PORT = 0, BAUDS = 9600, FPS = 30;
    static final int BYTES = Float.SIZE/8; // alt. Float.BYTES
    
    final byte[] bytes = new byte[BYTES];
    
    final FloatBuffer buf = ByteBuffer.wrap(bytes).asFloatBuffer();
    float f;
    
    void setup() {
      noLoop();
      frameRate(FPS);
    
      println(buf, buf.order(), java.nio.ByteOrder.nativeOrder(), BAUDS, BYTES);
    
      new Serial(this, Serial.list()[PORT], BAUDS).buffer(BYTES);
    }
    
    void draw() {
      print(f, '\t');
    }
    
    void serialEvent(Serial s) {
      s.readBytes(bytes);
      f = buf.get(0);
      redraw = true; // alt. redraw();
    }
    
  • Hi GoToLoop and Reas,

    thank you very much for all these detailed answers! I will now take some time to explore all possibilities you describe.

    In the mean time I found a 'quick and not so sexy' (not to say 'dirty') solution that consists in multiplying at the source the float by 100, transmit it as a (rounded) long int which is straightforward to reconstruct, and divide by 100 at destination. Doing so I am loosing less than 1/100th of the measure which is still acceptable.

    However I would like to explore deeper the Processing world so your tips will definitively help me in there.

    Thank you again. JM

  • edited May 2017

    Hi @jmeuf. Just letting you know that any doubts about the code just ask!
    And btW, has my example worked for ya? I don't have the hardware to test it though.

    For now I'm leaving a modified example which takes into consideration that 1 float isn't enough.

    Let's say the RC aircraft needs to send out not 1 but 5 reading values.
    Let's say they're "PosX", "PosY", "Altitude", "Spd", "Accel" for example.

    So it's gonna send out not only 4 bytes, but 5 * 4 bytes = 20 bytes = 5 floats.
    Then we need a new constant: FLOATS. And replace float f w/ float[] floats.

    Instead of f = buf.get(0);, we're gonna use buf.get(floats).rewind();.
    That is, the FloatBuffer view is gonna fill up floats[] w/ the received bytes[]:
    http://docs.Oracle.com/javase/8/docs/api/java/nio/FloatBuffer.html#get-float:A-

    The rewind() is to pre-reset get()'s position to its beginning at the next serialEvent() trigger:
    http://docs.Oracle.com/javase/8/docs/api/java/nio/Buffer.html#rewind--

    // forum.processing.org/two/discussion/12757/
    // transmission-of-a-float-through-a-serial-link#Item_7
    
    // GoToLoop (2015-Oct-03)
    
    import processing.serial.Serial;
    
    import java.nio.ByteBuffer;
    import java.nio.FloatBuffer;
    
    static final int PORT = 0, BAUDS = 9600, FPS = 30;
    static final int FLOATS = 5, BYTES = FLOATS * Float.SIZE/8; // alt. Float.BYTES
    
    static final String[] READINGS = {
      "PosX", "PosY", "Altitude", "Spd", "Accel"
    };
    
    final float[] floats = new float[FLOATS];
    final byte[] bytes = new byte[BYTES];
    
    final FloatBuffer buf = ByteBuffer.wrap(bytes).asFloatBuffer();
    
    void setup() {
      noLoop();
      frameRate(FPS);
    
      println(buf, PORT, BAUDS, FLOATS, BYTES);
      new Serial(this, Serial.list()[PORT], BAUDS).buffer(BYTES);
    }
    
    void draw() {
      println("\nReadings #" + frameCount + ':');
      for (int i = 0; i != FLOATS; println(READINGS[i] + ':', floats[i++]));
    }
    
    void serialEvent(Serial s) {
      s.readBytes(bytes);
      buf.get(floats).rewind();
      redraw = true; // alt. redraw();
    }
    
  • Hi GoToLoop,

    this is noted. Effectively I transmit currently 16 values coded in float. I have all the hardware ready for testing. I will need a couple of days to implement your suggestions.

    Just one question: what does the keyword 'final' means?

    Thank you again for all this support, and I will post the results.

    JM

  • edited October 2015

    Since the RC aircraft transmits 16 float values, you need to change constant FLOATS = 16.
    Thus, BYTES becomes 16 * 4 = 64.
    That is, the aircraft sends out 64 bytes to be buffer() before serialEvent() is triggered.

    And also have 16 String[] "names" for each of those data values.
    Of course you can simply get rid of that String[]. It's just for data description after all. ;;)

  • edited October 2015

    Also change constants PORTS & BAUDS so it matches your PC's COM port & transmission speed. ~O)

  • In the mean time I found a 'quick and not so sexy' (not to say 'dirty') solution that consists in multiplying at the source the float by 100, transmit it as a (rounded) long int which is straightforward to reconstruct, and divide by 100 at destination. Doing so I am loosing less than 1/100th of the measure which is still acceptable.

    congrats, you have just reinvented fixed point numbers 8) and it's a perfectly valid solution.

    i'd've multiplied by 256, or just shifted 8 bits to the left, rather than the decimal-centric x100 but it works the same way.

Sign In or Register to comment.