If the data needs to be in the form of a string, then you can't send the raw data in a float, because that may contain a byte with a value of 0, which will be seen as the end of the string.
You could use memcpy() to copy the float into an array, or use pointers to copy the bytes individually.
Also need to be careful of the endianness of the processors at each end of the transmission. Some processors store the least significant byte of a variable at the lowest memory address (little endian), other store the most significant byte at the lowest address (big endian).
@david_2018 makes a good point
also the number of bytes to store integer types can vary from processor to processor, e.g. int on a UNO is 16bits on an ESP32 is 32bits
if you know the size of your data use int16_t, int32_t, etc - see Fixed width integer types
even more tricky can be transmitting binary data between languages, e.g. C++ to java or Python
since i may want to put my 4 byte float IN the middle of my final string and get my float FROM anywhere in the string , memcpy is absolutely the way to go
(my string is fixed length and will alway have a float at [17] for the exemple)
.
Union is too new to me to understand how it work , and i am not sure if i can do something like that with union
You could write a few floats to a file, close it and re-open it to read bytes. Once you are sure of the format you can read a float from any address in the file, no need to play put-together or take-apart at all. The file isn’t memory space, the compiler doesn’t control it like RAM. Even the buffer is like that, you only read and write to buffers.
just to be sure we are in line there : the float will start at index 17 but will occupy bytes at 17, 18, 19 and 20.
not sure I really understand what you mean. If you have a text string with 34 characters (and a null terminator I assume) that is something a human can read, why would you store a binary representation in the middle of it ? You won't be able to print the text anymore...
Wonder what data types are in the rest of that buffer. Seems like a struct might be more appropriate here than a byte array. Probably best to do so with __attribute__((packed))
yes i know it will occupy 4 bytes a 17 , 18 , 19 ,20
.
i call it a string , but it doesnt have to be human readable , it’s just for sending data to a remote location and use less data on my very limited IOT mobile plan
.
string will be fixed length and something like 3 start char like "ABC”
a bunch of long , int , float , that will always have the same order and length
4 byte for CRC
3 end char like “DEF”
i do that because it’s simpler for me , and with my limited knowledge , i know how to parse it and get my data at the remote side
then to fill in your float you just do aPayload.myFloat = 3.1415927;
(in practice we probably would not store the header and trailer inside each structure, they would just be added in front and at the end of the payload when you send it out)
by keeping to even word boundaries I try to avoid attribute_((packed)) and the overhead of packing the structure at the transmitter and unpacking at the receiver
e.g. used on a LoRa client/server network with multiple clients and multiple structures
// test packet - must match receiver
struct Struct1 {
byte StructureID; // identifies the structure type
byte NodeID; // ID of transmitting node
int16_t seq; // sequence number
int16_t distance;
float voltage;
char text[20];
unit16_t crc;
};
Struct1 struct1 = { structureID, nodeID, 0, 1, 4.5, "hello",0 }; // test data
the sequence number can be used to check for lost/duplicate packets and the crc as an error check
as a check I usually print the
at the transmitter/receiver to make sure there are no problems
however, when dealing with multiple different processors have I have used attribute_((packed))
does not help when transmitting to Java though!
in practice we probably would not store the header and trailer inside each structure, they would just be added in front and at the end of the payload when you send it out
#include <Arduino.h>
struct __attribute__((packed)) Payload {
// ....
float myFloat;
// ....
uint32_t crc; // has to be the last element
};
// CRC32 (polynomial 0x04C11DB7)
uint32_t crc32(const uint8_t* data, size_t length) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < length; i++) {
crc ^= ((uint32_t)data[i] << 24);
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80000000) crc = (crc << 1) ^ 0x04C11DB7;
else crc <<= 1;
}
}
return crc ^ 0xFFFFFFFF;
}
void send(Payload &aPayload) {
// update crc
aPayload.crc = crc32((uint8_t*)&aPayload, sizeof aPayload - sizeof aPayload.crc);
Serial.write("ABC", 3); // send header
Serial.write((uint8_t*)&aPayload, sizeof(aPayload)); // send payload and CRC
Serial.write("DEF", 3); // send trailer
}
On a 32-bit system, aligning data on even addresses (2-byte boundaries) is not enough because the CPU usually requires natural alignment, which for 32-bit types means 4-byte boundaries.
Also if you have different MCU on the sender and receiver, you might end up with a different alignment if you don't pack.
what do you mean by "packing the structure at the transmitter and unpacking at the receiver". if fields are not aligned you pay indeed the cost of accessing across natural boundaries but there is nothing to pack and unpack ?
The user doesn't have to pack and unpack, but as you noted there is a cost. See toward the end of my first post in the rant here to see what the compiler has to do to "fix" it.