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