Mark
Mark

Reputation: 5102

How to calculate modbus register offset from packed structures

Here some structures are defined:

#pragma pack(push, 1)
typedef struct
{
    uint8_t discrete_input0:1;
    uint8_t discrete_input1:1;
    uint8_t discrete_input2:1;
    uint8_t discrete_input3:1;
    uint8_t discrete_input4:1;
    uint8_t discrete_input5:1;
    uint8_t discrete_input6:1;
    uint8_t discrete_input7:1;
    uint8_t discrete_input_port1;
    uint8_t discrete_input_port2;
} discrete_reg_params_t;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct
{
    uint8_t coils_port0;
    uint8_t coils_port1;
    uint8_t coils_port2;
} coil_reg_params_t;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct
{
    float input_data0; // 0
    float input_data1; // 2
    float input_data2; // 4
    float input_data3; // 6
    uint16_t data[150]; // 8 + 150 = 158
    float input_data4; // 158
    float input_data5;
    float input_data6;
    float input_data7;
    uint16_t data_block1[150];
} input_reg_params_t;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct
{
    float holding_data0;
    float holding_data1;
    float holding_data2;
    float holding_data3;
    uint16_t test_regs[150];
    float holding_data4;
    float holding_data5;
    float holding_data6;
    float holding_data7;
} holding_reg_params_t;
#pragma pack(pop)

and here are defined the offsets:

#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
#define MB_REG_DISCRETE_INPUT_START         (0x0000)
#define MB_REG_COILS_START                  (0x0000)
#define MB_REG_INPUT_START_AREA0            (INPUT_OFFSET(input_data0)) // register offset input area 0
#define MB_REG_INPUT_START_AREA1            (INPUT_OFFSET(input_data4)) // register offset input area 1
#define MB_REG_HOLDING_START_AREA0          (HOLD_OFFSET(holding_data0))
#define MB_REG_HOLDING_START_AREA1          (HOLD_OFFSET(holding_data4))

to be used later in the same file:

reg_area.type = MB_PARAM_HOLDING; // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
reg_area.size = (MB_REG_HOLDING_START_AREA1 - MB_REG_HOLDING_START_AREA0) << 1; // Set the size of register storage instance
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mbc_slave_set_descriptor fail, returns(0x%x).",
                                (int)err);

reg_area.type = MB_PARAM_HOLDING; // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mbc_slave_set_descriptor fail, returns(0x%x).",
                                (int)err);

// Initialization of Input Registers area
reg_area.type = MB_PARAM_INPUT;
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
reg_area.address = (void*)&input_reg_params.input_data0;
reg_area.size = sizeof(float) << 2;
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);
reg_area.type = MB_PARAM_INPUT;
reg_area.start_offset = MB_REG_INPUT_START_AREA1;
reg_area.address = (void*)&input_reg_params.input_data4;
reg_area.size = sizeof(float) << 2;
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);

// Initialization of Coils register area
reg_area.type = MB_PARAM_COIL;
reg_area.start_offset = MB_REG_COILS_START;
reg_area.address = (void*)&coil_reg_params;
reg_area.size = sizeof(coil_reg_params);
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mbc_slave_set_descriptor fail, returns(0x%x).",
                                (int)err);

// Initialization of Discrete Inputs register area
reg_area.type = MB_PARAM_DISCRETE;
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
reg_area.address = (void*)&discrete_reg_params;
reg_area.size = sizeof(discrete_reg_params);
err = mbc_slave_set_descriptor(reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mbc_slave_set_descriptor fail, returns(0x%x).",
                                (int)err);

Why the offset for "discrete inputs" and "coils" is 0 while for the other structures requires the use of offsetof()? To me the structures seem very similar.

Here I read it is the modbus register's offset: but still I don't understand why it is 0 for discrete inputs and coils (they should lie in 1x and 0x areas).

Bonus: I don't understand the uint16_t data[150]; and data_block1[150]; inside input_reg_params_t. I see they divide the structure into two areas, but looking at the given reg_area.size it seems they don't include at all those variables.

Upvotes: 1

Views: 112

Answers (1)

user694733
user694733

Reputation: 16047

Why the offset for "discrete inputs" and "coils" is 0 while for the other structures requires the use of offsetof()? To me the structures seem very similar.

In Modbus input and holding registers are (usually) 16-bit units. HOLD_OFFSET and INPUT_OFFSET calculate these modbus addresses from using relative position of members in struct with offsetof macro. You can even see the division by 2 with the right shift >> 1 at the end of macro, which essentially converts 8-bit unit offset to 16-bit unit offset.

Discrete inputs and and coils are 1-bit registers in Modbus specification. offsetof macro won't work, because in C you can not have address with 1-bit precision. Smallest memory unit is byte, which is usually 8-bits.

It seems that since Modbus start address for discrete inputs and coils is always 0 anyway, might as well use that directly, and not bother with fancy macro code.

Bonus: I don't understand the uint16_t data[150]; and data_block1[150]; inside input_reg_params_t. I see they divide the structure into two areas, but looking at the given reg_area.size it seems they don't include at all those variables.

I am guessing that it is simply padding for registers they are not interested in. It makes offsetof produce presumably correct address value.


I don't like that code at all.

It abuses packed structs for serialization purposes, relies on poorly defined struct bit-field positioning, uses poor member naming (holding_data4), uses hacky offsetof address calculation and bitshift for division for no reason.

I know it may simply be example code, but please keep above in mind.

Upvotes: 2

Related Questions