Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Modbus TCP floating point conversion



  • 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?


  • Lifetime Qt Champion

    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.


  • Moderators

    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 :-)


  • Lifetime Qt Champion

    @J.Hilk

    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).


  • Moderators

    @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,



  • 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?


  • Lifetime Qt Champion

    @Mark81 if my version works, then it is compatible with both endianess.


  • Qt Champions 2017

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

Log in to reply