Home > Back-end >  Separate a float into 4 uint8_ts and then merge back together
Separate a float into 4 uint8_ts and then merge back together

Time:12-02

I'm working on developing both the client(C) and server(C ) side of an RF connection. I need to send a float value, but the way the architecture is set up I have to arrange my message in a struct that limits me to 3 uint8t parameters: p0, p1, p2. My solution was to break the float into an array of 4 uint8_ts and send in 2 separate messages and use p0 as an identifier whether the message contains the first or second half.

So far I have something like this:

Server (C ):

sendFloat(float f)
{
   messageStruct msg1, msg2;
   uint8_t* array = (uint8_t*)(&f);

   msg1.p0 = 1; //1 means it's the first half
   msg1.p1 = array[0];
   msg1.p2 = array[1];

   msg2.p0 = 0; //0 means it's the second half
   msg2.p1 = array[2];
   msg2.p2 = array[3];

   sendOverRf(msg1);
   sendOverRf(msg2);
}

Client(C):

processReceivedMessage (uint32_t id, uint32_t byteA, uint32_t byteB) //(p0,p1,p2) are routed here
{
   static uint32_t firsHalfOfFloat;
   uint32_t ondHalfOfFloat;
   float combinedFloat;
   
   if(id == 1) //first half
   {
       firstHalfOfFloat = (byteA << 8) | byteB;
   }
   else        //second half
   {
       secondHalfOfFloat = (byteA << 8) | byteB;
       combinedFloat = (float)((firstHalfOfFloat << 16) | secondHalfOfFloat);
   }
  
    writeFloatToFile(combinedFloat);
}  

then on request the client must then send that float back

Client(C):

sendFloatBack(uint8_t firstHalfIdentifier) // is commanded twice by server with both 0 and 1 ids
{
    messageStruct msg;
    float f = getFloatFromFile();
    uint8_t* array = (uint8_t*)(&f);
    
    msg.p0 = firstHalfIdentifier;

    if(firstHalfIdentifier == 1) //First half
    {
        msg.p1 = array[0];
        msg.p2 = array[1];       
    }
    else                         //Second half
    {
        msg.p1 = array[2];
        msg.p2 = array[3];  
    }
    
    sendOverRf(msg);
}

and finally the Server (C ) gets the value back:

retrieveFunc()
{
    float f;
    uint32_t firstHalf;
    uint32_t secondHalf;
        
    messageStruct msg = recieveOverRf();
    firstHalf = (msg.p1 << 8) | msg.p2;
    msg = receiveOverRf();
    firstHalf = (msg.p1 << 8) | msg.p2;
    f = (firstHalf << 16) | secondHalf;  
}

but I'm getting really wrong values back. Any help would be great.

CodePudding user response:

Unions are a very convenient way to disassemble a float into individual bytes and later put the bytes back together again. Here's some example code showing how you can do it:

#include <stdio.h>
#include <stdint.h>

typedef union {
   uint8_t _asBytes[4];
   float   _asFloat;
} FloatBytesConverter;

int main(int argc, char** argv)
{
    FloatBytesConverter fbc;
    fbc._asFloat = 3.14159;

    printf("Original float value is:  %f\n", fbc._asFloat);

    printf("The bytes of the float are:  %u, %u, %u, %u\n"
       , fbc._asBytes[0]
       , fbc._asBytes[1]
       , fbc._asBytes[2]
       , fbc._asBytes[3]);

    // Now let's put the float back together from the individual bytes
    FloatBytesConverter ac;
    ac._asBytes[0] = fbc._asBytes[0];
    ac._asBytes[1] = fbc._asBytes[1];
    ac._asBytes[2] = fbc._asBytes[2];
    ac._asBytes[3] = fbc._asBytes[3];

    printf("Restored float is %f\n", ac._asFloat);

    return 0;
}

CodePudding user response:

memcpy is your friend.

float toFloat(const uint8_t *arr)
{
    float result;

    memcpy(&result, arr, sizeof(result));

    return result;
}

uint8_t *toArray(const float x, uint8_t * const arr)
{
    memcpy(arr, &x, sizeof(x));
    return arr;
}
void sendFloat(float f)
{
   messageStruct msg1, msg2;
   uint8_t array[4];

   toArray(f, array);

   msg1.p0 = 1; //1 means it's the first half
   msg1.p1 = array[0];
   msg1.p2 = array[1];

   msg2.p0 = 0; //0 means it's the second half
   msg2.p1 = array[2];
   msg2.p2 = array[3];

   sendOverRf(msg1);
   sendOverRf(msg2);
}
float retrieveFunc(void)
{
    float f;
    unit8_t array[4]
        
    messageStruct msg = recieveOverRf();
    array[0] = msg.p1;
    array[1] = msg.p2;
    msg = receiveOverRf();
    array[2] = msg.p1;
    array[3] = msg.p2;
    return toFloat(array);
}

CodePudding user response:

Well, bytes are bytes as far as architectures are concerned.

We assume that we're using IEEE 754 (or whatever) on both sides.

We can put one float (4 bytes) into/outof one uint32_t with memcpy

But, we have to deal with processor endianness.

Edit:

problem with this is that p0, p1, p2 are all uint8_ts. –  TheBigJabronie

Oops, my bad. I've updated the code to use only bytes [I've left my original/incorrect answer below for reference].


Here is the updated code. The functions will work on either host, regardless of the endianness of each architecture.

Note that your main issue is the encoding of the float. So, the code below assumes that the packets arrive intact (i.e. retry/resend is done in the lower RF layer)

void
sendFloat(float f)
{
    messageStruct msg;
    uint32_t i32;

    assert(sizeof(float) == sizeof(uint32_t));

    // get bytes of the float in native endian order
    memcpy(&i32,&f,sizeof(i32));

    // handle endianness
    i32 = htonl(i32);

    // send MSW half
    msg.p0 = 1;
    msg.p1 = i32 >> 24;
    msg.p2 = i32 >> 16;
    sendOverRf(msg);

    // send LSW half
    msg.p0 = 2;
    msg.p1 = i32 >> 8;
    msg.p2 = i32 >> 0;
    sendOverRf(msg);
}

float
recvFloat(void)
{
    uint32_t i32 = 0;
    float f;

    messageStruct msg;

    // NOTE: the two packets _should_ come in the same order as the sender, but
    // we'll handle out of order packets to be complete
    for (int rcount = 0;  rcount < 2;    rcount) {
        msg = recieveOverRf();

        uint32_t tmp = msg.p1;
        tmp <<= 8;
        tmp |= msg.p2;

        switch (msg.p0) {
        case 1:
            i32 |= tmp << 16;
            break;
        case 2:
            i32 |= tmp << 0;
            break;
        }
    }

    // handle endianness
    i32 = ntohl(i32);

    // get bytes into float
    memcpy(&f,&i32,sizeof(float));

    return f;
}

My original code and further assumptions.

We can do this in a single message with room to spare.

Here is the code I would use.

void
sendFloat(float f)
{
    messageStruct msg;
    uint32_t i32;

    assert(sizeof(float) == sizeof(uint32_t));

    // get bytes of the float in native endian order
    memcpy(&i32,&f,sizeof(i32));

    // handle endianness
    i32 = htonl(i32);

    // means we're sending a float
    msg.p0 = CMD_FLOAT;
    msg.p1 = i32;
    msg.p2 = 0;

    sendOverRf(msg);
}

float
recvFloat(void)
{
    uint32_t i32;
    float f;

    messageStruct msg = recieveOverRf();

    // ensure we got correct message
    if (msg.p0 != CMD_FLOAT)
        exit(1);

    // get int in network order
    i32 = msg.p1;

    // handle endianness
    i32 = ntohl(i32);

    // get bytes into float
    memcpy(&f,&i32,sizeof(float));

    return f;
}
  • Related