How to Read M103 Modbus Doc?



  • Hi all,

    ok, I manged to use the relays through modbus with libmodbus with my C-code. Now I am trying to use the DI/DO/AO/AI interfaces.

    I see the Neuron Registers Map but I am only able to understand it partially...

    I am aware the function modbus_read_bits() can be usedd to read the DIs. So I open a modbus_tcp connection with the address of DI2.1 for example:

    modbus_read_bits(modbus_handle, 108, nb, *dest);
    

    *dest is supposed to be uint16_t.

    I was able to read the above- finally I have some data in *dest. But how do I have to read the data there? I expected something like "bit 1 tells if the DI is currently ON or OFF and bit2 is the counter while writing bit3 will reset the counter" or stuff like this.

    So I am a little bit lost here to interpret the values I got.

    Nearly the same is for writing. What values should I put into uint16_t to enable the DI?

    And how can I configure the AO?

    Thanks a lot for hints!

    /KNEBB



  • @knebb said in How to Read M103 Modbus Doc?:

    *dest is supposed to be uint16_t.

    No, it is an uint8_t array. Every element of the array contains the value of a single digital channel, set to TRUE (that is, IIRC, 1) or FALSE (that is surely 0).

    Here is my untested snippet:

    // Reading one channel
    uint8_t my_digital;
    modbus_read_bits(bus, 108, 1, &my_digital);
    if (my_digital) {
        ...
    }
    // Reading multiple channels
    uint8_t my_digitals[2];
    modbus_read_bits(bus, 108, 2, my_digitals);
    if (my_digitals[0]) {
        ...
    }
    


  • @ntd :Thanks a lot four your input, I really appreciate it.

    I am just wondering as there seems to be no difference between uint8_t, uint16_t and uint32_t.

    See my question here.
    When using uint8_t the result is exactly the same. All seem to be 32bit long...

    So how does modbus deal with this? It writes only the least significant 8 bits? Others are set to zero?

    /KNEBB



  • @knebb TL/DR: just use uint8_t. This is the right thing to do.

    There is no differences (because of integer promotion) only when you use that variable in an expression. Incidentally, and for different reasons [1], reading a single bit will work with whatever 0-initialized integer you throw at modbus_read_bits, although conceptually wrong. But I can assure you that, when reading multiple channels, using anything but uint8_t will give you wrong results.

    [1] Raspberry Pi is little-endian. Setting the first byte of any 0-initialized uint* variable to a specific value (e.g., TRUE), makes that very same variable initialized to that value, regardless of its type.

    Here is an example that hopefully will shed some light:

    #include <assert.h>
    #include <stdint.h>
    #include <string.h>
    
    #define TRUE 12
    
    int main()
    {
        // Using an union to be able to set the first byte of everything
        // with only one instruction (m.byte = ...)
        union {
            uint8_t byte;
            uint16_t word;
            uint32_t dword;
            uint64_t qword;
        } m;
    
        // Let's set the first byte of 0 initialized memory
        memset(&m, 0, sizeof m);
        m.byte = TRUE;
    
        assert(m.byte == TRUE);  // Ok
        assert(m.word == TRUE);  // Ok on little-endian machines
        assert(m.dword == TRUE); // Ok on little-endian machines
        assert(m.qword == TRUE); // Ok on little-endian machines
    
        // Now let's try with random initialized memory
        memset(&m, 3, sizeof m);
        m.byte = TRUE;
    
        assert(m.byte == TRUE);  // Ok
        assert(m.word == TRUE);  // Error!
        assert(m.dword == TRUE); // Error!
        assert(m.qword == TRUE); // Error!
    
        return 0;
    }
    


  • Thanks for the explanation; I figured it already out Raspbian is littleEndian.

    Just to make sure:
    To read i.e. the counter for digital input 2.1 (address 103-104 with DWORD, 32bit in total) I would use:

    uint8_t counter[4];
    modbus_read_bits(bus, 103, 4, counter);
    

    Or this one?

    uint8_t counter1[2],counter2[2];
    modbus_read_bits(bus, 103, 2, counter1);
    modbus_read_bits(bus, 104, 2, counter2);
    

    Would both work?

    Sorry for my dumb question, I am really new to modbus...

    /KNEBB



  • @knebb said:

    Would both work?

    None of them will. You should really read a ModBUS introduction... I think the wikipedia page should suffice.

    The counter is an input register, not a bit, so:

    union {
        uint16_t word[2];
        uint32_t dword;
    } counter;
    
    if (modbus_read_registers(bus, 103, 2, counter.word) != 2) {
        printf("Error\n");
    } else {
        printf("DI2.1 counter is %u\n", counter.dword);
    }
    

    Not sure which is the less and the most significant word, so if the above gives wrong results just swap counter.word[0] and counter.word[1].



  • @ntd
    Perfect!

    Got it finally. I mixed up modbus_read_bits and modbus_read_register. Now working like a charm!

    Thanks a lot!

    /KNEBB



  • @all

    As I was struggling again with this I figured it out now finally (at least I hope so).

    My issue was the interpretation of whatever value I got.

    So for alle future readings:

    In the linked Calc-Sheet above you have notes about the registers (read only registers, not bits!).

    So for the M103 you have the "Registers - group 1" and "Registers - group 2". Use these. The coils tables are somehow misleading (at least for me).

    Additionally be aware modbus_read_registers() need to write the data into an uint16_t variable. With the help of @ntd and his suggestes "union" I was easy to "convert" them into the uint32_t. Unions access the same memory by different variables. See more here.

    In the code sniplet above modbus_read_registers() writes the content into the counter.word memory location and the prinft reads them as uint32_t. Easy going now to re-calculate the counter.dword variable to get the values you need.

    So to read the current settings and values of all relays and digital inputs I use the following code:

    uint16_t state;
    union {
            uint16_t word[2];
            uint32_t dword;
    } counter;
    
    
    /* Relais */
    modbus_read_registers(modbusglob,101,1,&state);
    printf("Lesen Relay Register: %u\n",state);
    printf("\n");
    
    /* DigitalIN 1 */
    modbus_read_registers(modbusglob,0,1,&state);
    printf("Lesen DigIn1: %u\n",state);
    
    printf("\n");
    /* Digital-In 1 Counter */
    for (i=8;i<16;i=i+2) {
            modbus_read_registers(modbusglob,i,2,counter.word);
            printf("Counter %d DigIn1: %u\n",i,counter.dword);
    }
    printf("\n");
    
    /* DigitalIN 2 */
    modbus_read_registers(modbusglob,100,1,&state);
    printf("Lesen DigIn1: %d\n",state);
    printf("\n");
    
    
    /* DigitalIN 2 Counter */
    for (i=103;i<118;i=i+2) {
            modbus_read_registers(modbusglob,i,2,counter.word);
            printf("Counter %d DigIn2: %u\n",i,counter.dword);
    }
    
    
    

    This works now perfectly.

    /KNEBB



  • Just as an update.

    I was struggling with multiple registers which are used i.e. for the AO1.1 port of my Neuron M103.

    So for the AO registers 3000 and 3001 are given as "real".

    I extended the above union as follows:

    union {
            uint16_t word[2];
            uint32_t dword;
            float fl;
    } counter;
    

    And I am reading the register like this:

    modbus_read_registers(modbusglob, 3000, 2, &counter.word[0]);
    

    It reads 2 registers starting at 3000 and writes the result in the word array where it is automatically converted to "float" by the union structure.

    Works fine!

    /KNEBB


Log in to reply