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