Modbus TCP floating point conversion
-
wrote on 12 Oct 2018, 14:42 last edited by
It seems there are not ready-to-use converter functions for
QModbus
classes. I need to convert a 32-bit floating point to 2 16-bit registers. I tried the following:QVector<quint16> reg; QByteArray bytes(reinterpret_cast<const char*>(&value), sizeof(value)); quint16 high = static_cast<quint16>(bytes[1] + (bytes[0] << 8)); quint16 low = static_cast<quint16>(bytes[3] + (bytes[2] << 8)); reg.append(high); reg.append(low);
and there write the
reg
values, but the device does not acknowledge (it works for sure because using a third party Modbus client it return OK).As far as I know the required modbus endiannes is:
MSB and LSB for MSW, then MSB and LSB for LSW
is there a more reliable way to achieve this?
-
It seems there are not ready-to-use converter functions for
QModbus
classes. I need to convert a 32-bit floating point to 2 16-bit registers. I tried the following:QVector<quint16> reg; QByteArray bytes(reinterpret_cast<const char*>(&value), sizeof(value)); quint16 high = static_cast<quint16>(bytes[1] + (bytes[0] << 8)); quint16 low = static_cast<quint16>(bytes[3] + (bytes[2] << 8)); reg.append(high); reg.append(low);
and there write the
reg
values, but the device does not acknowledge (it works for sure because using a third party Modbus client it return OK).As far as I know the required modbus endiannes is:
MSB and LSB for MSW, then MSB and LSB for LSW
is there a more reliable way to achieve this?
Hi @Mark81,
one word in advance: you can use Wireshark to capture and debug TCP connections, and so you can probably compare your code with the third party one.
I don't know which Endianess Modbus uses (and I'm too lazy to look that up), but what you describe is Big Endian and that is typically used in networks (e.g. Ethernet) - so that could really be correct. Please note that your Intel computer in contrast uses Little Endian - which means, the data in memory is ordered in reverse.
But as you give words (aka quint16) to the transport layer, I think you only need to make sure high- and low word are correctly ordered.
If it's like this, then the following could work:
float value = 1234.56f; // interpret the memory consumed by the float value as unsigned int // we need to use pointers so the compiler does not type conversion const quint32 *raw = reinterpret_cast<const quint32 *>(&value); QVector<quint16> reg; // append the high word first, then the low word // the endianess of the words should be handled by the modbus layer reg.append(*raw >> 16); reg.append(*raw & 0xFFFF);
Disclaimer: only brain compiled, not tested.
-
A code example for one of my earlier Modbus projects.
enum byteOrder{ ABCD, BADC, CDAB, DCBA }; quint16 fromCharArray(const unsigned *data) { uint16_t value(0); for(int i(0); i < 2; ++i){ value += data[i] << 8 *i; } return value; } QVector<quint16> fromFloat(float abcd, byteOrder order) { short A(0),B(0),C(0),D(0); switch (order) { case ABCD:A = 0; B = 1; C = 2; D = 3;break; case BADC:A = 1; B = 0; C = 3; D = 2;break; case CDAB:A = 2; B = 3; C = 0; D = 1;break; case DCBA:A = 3; B = 2; C = 1; D = 0;break; } unsigned char *cArray = reinterpret_cast<unsigned char *>(&abcd); unsigned value1[] = {cArray[A],cArray[B]}; unsigned value2[] = {cArray[C],cArray[D]}; QVector<quint16> values; values.append(fromCharArray(value1)); values.append(fromCharArray(value2)); return values; }
maybe not the most efficient of codes one, but it gets the job done :-)
-
A code example for one of my earlier Modbus projects.
enum byteOrder{ ABCD, BADC, CDAB, DCBA }; quint16 fromCharArray(const unsigned *data) { uint16_t value(0); for(int i(0); i < 2; ++i){ value += data[i] << 8 *i; } return value; } QVector<quint16> fromFloat(float abcd, byteOrder order) { short A(0),B(0),C(0),D(0); switch (order) { case ABCD:A = 0; B = 1; C = 2; D = 3;break; case BADC:A = 1; B = 0; C = 3; D = 2;break; case CDAB:A = 2; B = 3; C = 0; D = 1;break; case DCBA:A = 3; B = 2; C = 1; D = 0;break; } unsigned char *cArray = reinterpret_cast<unsigned char *>(&abcd); unsigned value1[] = {cArray[A],cArray[B]}; unsigned value2[] = {cArray[C],cArray[D]}; QVector<quint16> values; values.append(fromCharArray(value1)); values.append(fromCharArray(value2)); return values; }
maybe not the most efficient of codes one, but it gets the job done :-)
But does your code take the byte order of the system you are running in account?
I.e. do you need to give a different byteOrder parameter depending on the CPU architecture?
(I know, in practice I does not matter that much, but I'm curious).
-
But does your code take the byte order of the system you are running in account?
I.e. do you need to give a different byteOrder parameter depending on the CPU architecture?
(I know, in practice I does not matter that much, but I'm curious).
@aha_1980
It should be able to cover the whole scope. Might however need a different byteorder parameter, if your cpu architecture changes.IIRC I used it to bruteforce my way to connection, because I has no idea what target used. But it worked and is, I think, still running to day,
-
wrote on 15 Oct 2018, 17:16 last edited by
I'm not sure about this, honestly. My application will run only under Windows and desktop PC. Do I need to take care of endianness?
-
I'm not sure about this, honestly. My application will run only under Windows and desktop PC. Do I need to take care of endianness?
@Mark81 if my version works, then it is compatible with both endianess.
-
I'm not sure about this, honestly. My application will run only under Windows and desktop PC. Do I need to take care of endianness?
Alternatively C99 compliant compilers allow you to do (if ensured a packed structure):
union { struct { quint16 high, low; } reg; float value; } data; data.value = 1234.56f; quint16 high = data.reg.high; quint16 low = data.reg.low;
or the oddly looking conversion should also do:
float value = 1234.56f quint16 (*reg)[2] = reinterpret_cast<quint16(*)[2]>(&value); quint16 high = (*reg)[0], low = (*reg)[1];
1/8