espressif / esp-modbus

ESP-Modbus - the officially suppported library for Modbus protocol (serial RS485 + TCP over WiFi or Ethernet).
Apache License 2.0
117 stars 55 forks source link

Abnormality occurs when reading meter data through modbus R485 (IDFGH-11994) #51

Closed yel-best closed 9 months ago

yel-best commented 10 months ago

esp-idf version: 5.1.2 esp-modbus version: 1.0.12

In my master example, make the following adjustments:

mb_parameter_descriptor_t device_parameters[] = {
    // CID, Name, Units, Modbus addr, register type, Modbus Reg Start Addr, Modbus Reg read length,
    // Instance offset (NA), Instance type, Instance length (bytes), Options (NA), Permissions
    { CID_DEV_REG0, STR("Volts"), STR("V"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0x2006, 2,
                    INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 0, 1 ), PAR_PERMS_READ },
};
 *(float*)temp_data_ptr = value;
                        if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
                            (param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
                            **ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%4x) read successful.",
                                            param_descriptor->cid,
                                            (char*)param_descriptor->param_key,
                                            (char*)param_descriptor->param_units,
                                            value,
                                            *(uint32_t*)temp_data_ptr);**

                             // Add the informative triplets to the array, ready to report/send
                            **mb_readings[cid].key = param_descriptor->param_key;
                            mb_readings[cid].value = value;
                            mb_readings[cid].unit = param_descriptor->param_units;**

                            ESP_LOGI(TAG, "MB readings stored: for %s with value: %f\n", mb_readings[cid].key, mb_readings[cid].value);

                            if (((value > param_descriptor->param_opts.max) ||
                                (value < param_descriptor->param_opts.min))) {
                                    alarm_state = true;
                                    break;
                            }
                        }

Read the voltage value of the electric meter. The result of the value should be float. After I made the above definition and ran it, the output result through the console is as follows:

I (503) MASTER_TEST: Characteristic #0 Volts (V) value = -158790234117799422060857917440.000000 (0xf000450d) read successful. I (513) MASTER_TEST: MB readings stored: for Volts with value: -158790234117799422060857917440.000000

The result is an exception and the hexadecimal result read is (0xf000450d)

image

The hex code is incorrect, the correct one should be (0x450df000)

image

image

Please help me find out what is the reason. I have read the idf-modbus document many times, but I still can’t figure out why the order is reversed. I cannot get the data in the correct order from the read results, and it is done with 2 bytes. Displacement conversion, if it is bytes order, it should be swapped in the order of 1 byte.

This problem bothered me for a weekend, 😢

thx

alisitsyn commented 10 months ago

This is the float format conversion issue because your slave uses the custom float format representation. The esp-modbus currently does not support conversion of other float or U32 formats. This will be addressed as planned to support all available types of float and U32.

There are 4 combinations of 32-bit float representation in modbus devices: Little-Endian Big-Endian Big-Endian byte swap Little-Endian byte swap - supported by esp-modbus

The fix should be performed here.


// the macro to fix endianess of the float value with format BADC (change the offsets as per your device format)
#define _XFER_4_FLOAT(dst, src) { \
    *(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 0); \
    *(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 1); \
    *(uint8_t *)((uint8_t*)dst + 0) = *(uint8_t *)(src + 2); \
    *(uint8_t *)((uint8_t*)dst + 1) = *(uint8_t *)(src + 3) ; \
}

static esp_err_t mbc_serial_master_get_parameter(uint16_t cid, char* name,
                                                    uint8_t* value_ptr, uint8_t *type)
{
    MB_MASTER_CHECK((name != NULL),
                        ESP_ERR_INVALID_ARG, "mb incorrect descriptor.");
    MB_MASTER_CHECK((type != NULL),
                        ESP_ERR_INVALID_ARG, "type pointer is incorrect.");
    esp_err_t error = ESP_ERR_INVALID_RESPONSE;
    mb_param_request_t request ;
    mb_parameter_descriptor_t reg_info = { 0 };

    error = mbc_serial_master_set_request(name, MB_PARAM_READ, &request, &reg_info);
    if ((error == ESP_OK) && (cid == reg_info.cid)) {
        // Send request to read characteristic data
        error = mbc_serial_master_send_request(&request, value_ptr);
        if (error == ESP_OK) {
            ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
                                    __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        } else {
            ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
                                            __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        }
        // Set the type of parameter found in the table
        *type = reg_info.param_type;
        if (((  reg_info.param_type == PARAM_TYPE_FLOAT)
            || (reg_info.param_type == PARAM_TYPE_U32))
            && (reg_info.param_size == 4)) {
            // Fix endianess for FLOAT and U32 according to your slave manual
            float dest_val = 0;
            _XFER_4_FLOAT(&dest_val, value_ptr);
            ESP_LOGW(TAG, "Convert %"PRIx32" = %"PRIx32, *(uint32_t*)value_ptr, *(uint32_t*)&dest_val);
            *(uint32_t*)value_ptr = (uint32_t)dest_val;
        }
    } else {
yel-best commented 10 months ago

This is the float format conversion issue because your slave uses the custom float format representation. The esp-modbus currently does not support conversion of other float or U32 formats. This will be addressed as planned to support all available types of float and U32.

There are 4 combinations of 32-bit float representation in modbus devices: Little-Endian Big-Endian Big-Endian byte swap Little-Endian byte swap - supported by esp-modbus

The fix should be performed here.

// the macro to fix endianess of the float value with format BADC (change the offsets as per your device format)
#define _XFER_4_FLOAT(dst, src) { \
    *(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 0); \
    *(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 1); \
    *(uint8_t *)((uint8_t*)dst + 0) = *(uint8_t *)(src + 2); \
    *(uint8_t *)((uint8_t*)dst + 1) = *(uint8_t *)(src + 3) ; \
}

static esp_err_t mbc_serial_master_get_parameter(uint16_t cid, char* name,
                                                    uint8_t* value_ptr, uint8_t *type)
{
    MB_MASTER_CHECK((name != NULL),
                        ESP_ERR_INVALID_ARG, "mb incorrect descriptor.");
    MB_MASTER_CHECK((type != NULL),
                        ESP_ERR_INVALID_ARG, "type pointer is incorrect.");
    esp_err_t error = ESP_ERR_INVALID_RESPONSE;
    mb_param_request_t request ;
    mb_parameter_descriptor_t reg_info = { 0 };

    error = mbc_serial_master_set_request(name, MB_PARAM_READ, &request, &reg_info);
    if ((error == ESP_OK) && (cid == reg_info.cid)) {
        // Send request to read characteristic data
        error = mbc_serial_master_send_request(&request, value_ptr);
        if (error == ESP_OK) {
            ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
                                    __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        } else {
            ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
                                            __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        }
        // Set the type of parameter found in the table
        *type = reg_info.param_type;
        if (((    reg_info.param_type == PARAM_TYPE_FLOAT)
            || (reg_info.param_type == PARAM_TYPE_U32))
            && (reg_info.param_size == 4)) {
            // Fix endianess for FLOAT and U32 according to your slave manual
            float dest_val = 0;
            _XFER_4_FLOAT(&dest_val, value_ptr);
            ESP_LOGW(TAG, "Convert %"PRIx32" = %"PRIx32, *(uint32_t*)value_ptr, *(uint32_t*)&dest_val);
            *(uint32_t*)value_ptr = (uint32_t)dest_val;
        }
    } else {

thank you very much for your help

When I merge your code into the project, the running results are as follows

W (3153) MB_CONTROLLER_MASTER: Convert b000450e = e45b000

The value I got through modbus is B0 00 45 0E

Float of slave the actual result should be 45 0E B0 00

I think the _XFER_4_FLOAT function needs to be adjusted, but I don’t understand this function very well. Can you help me with the answer?

thk

yel-best commented 10 months ago

This is the float format conversion issue because your slave uses the custom float format representation. The esp-modbus currently does not support conversion of other float or U32 formats. This will be addressed as planned to support all available types of float and U32. There are 4 combinations of 32-bit float representation in modbus devices: Little-Endian Big-Endian Big-Endian byte swap Little-Endian byte swap - supported by esp-modbus The fix should be performed here.

// the macro to fix endianess of the float value with format BADC (change the offsets as per your device format)
#define _XFER_4_FLOAT(dst, src) { \
    *(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 0); \
    *(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 1); \
    *(uint8_t *)((uint8_t*)dst + 0) = *(uint8_t *)(src + 2); \
    *(uint8_t *)((uint8_t*)dst + 1) = *(uint8_t *)(src + 3) ; \
}

static esp_err_t mbc_serial_master_get_parameter(uint16_t cid, char* name,
                                                    uint8_t* value_ptr, uint8_t *type)
{
    MB_MASTER_CHECK((name != NULL),
                        ESP_ERR_INVALID_ARG, "mb incorrect descriptor.");
    MB_MASTER_CHECK((type != NULL),
                        ESP_ERR_INVALID_ARG, "type pointer is incorrect.");
    esp_err_t error = ESP_ERR_INVALID_RESPONSE;
    mb_param_request_t request ;
    mb_parameter_descriptor_t reg_info = { 0 };

    error = mbc_serial_master_set_request(name, MB_PARAM_READ, &request, &reg_info);
    if ((error == ESP_OK) && (cid == reg_info.cid)) {
        // Send request to read characteristic data
        error = mbc_serial_master_send_request(&request, value_ptr);
        if (error == ESP_OK) {
            ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
                                    __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        } else {
            ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
                                            __FUNCTION__, (unsigned)reg_info.cid, (char*)esp_err_to_name(error));
        }
        // Set the type of parameter found in the table
        *type = reg_info.param_type;
        if (((  reg_info.param_type == PARAM_TYPE_FLOAT)
            || (reg_info.param_type == PARAM_TYPE_U32))
            && (reg_info.param_size == 4)) {
            // Fix endianess for FLOAT and U32 according to your slave manual
            float dest_val = 0;
            _XFER_4_FLOAT(&dest_val, value_ptr);
            ESP_LOGW(TAG, "Convert %"PRIx32" = %"PRIx32, *(uint32_t*)value_ptr, *(uint32_t*)&dest_val);
            *(uint32_t*)value_ptr = (uint32_t)dest_val;
        }
    } else {

thank you very much for your help

When I merge your code into the project, the running results are as follows

W (3153) MB_CONTROLLER_MASTER: Convert b000450e = e45b000

The value I got through modbus is B0 00 45 0E

Float of slave the actual result should be 45 0E B0 00

I think the _XFER_4_FLOAT function needs to be adjusted, but I don’t understand this function very well. Can you help me with the answer?

thk

I tried to adjust the following function and the result is as follows

#define _XFER_4_FLOAT(dst, src) { \
    *(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 1); \
    *(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 0); \
    *(uint8_t *)((uint8_t*)dst + 0) = *(uint8_t *)(src + 2); \
    *(uint8_t *)((uint8_t*)dst + 1) = *(uint8_t *)(src + 3); \
}

before fixing

 *(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 0); \
 *(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 1); \

After modification

*(uint8_t *)((uint8_t*)dst + 3) = *(uint8_t *)(src + 1); \
*(uint8_t *)((uint8_t*)dst + 2) = *(uint8_t *)(src + 0); \

This will make my conversion results correct, I don't know if this is correct

W (9683) MB_CONTROLLER_MASTER: Convert 4000450e = 450e4000

But I also found that if I add this code, the result obtained by _XFER_4_FLOAT (uint32_t)value_ptr = (uint32_t)dest_val; 450ea000 will be converted to IEEE 754 Converter. How should I do it? 450f0000 = float: 2288

image

It seems that the original value conversion cannot be used after modification. I hope you can give me some opinions. Thank you.

alisitsyn commented 10 months ago

`> After I modify the esp-modbus lib code according to your instructions, the subsequent compilation will prompt that the file needs to be deleted, otherwise the compilation cannot pass. It means that I have modified the package. How should I solve it? Does it mean that I should not modify the package directly? Solved in my business code?

Please just move the changed code of modbus component from managed_components into components folder. After this you will be able to compile it without any issues and make changes as needed.

But I also found that if I add this code, the result obtained by _XFER_4_FLOAT (uint32_t)value_ptr = (uint32_t)dest_val; 450ea000 will be converted to IEEE 754 Converter. How should I do it? 450f0000 = float: 2288

After your modification the result looks good to me. Please carefully read the manual for your device to correctly convert this, I guess you just need to adjust the point position and your result is 228.8V?

You also can add the PARAM_TYPE_FLOAT_CDAB enumeration value and then use it in object dictionary to handle your custom float type. In this case this conversion will be performed only for the custom values and regular functionality will be unchanged. This is how the conversion functionality will be performed in future update of the library.

yel-best commented 10 months ago

After I modify the esp-modbus lib code according to your instructions, the subsequent compilation will prompt that the file needs to be deleted, otherwise the compilation cannot pass. It means that I have modified the package. How should I solve it? Does it mean that I should not modify the package directly? Solved in my business code?

Please just move the changed code of modbus component from managed_components into components folder. After this you will be able to compile it without any issues and make changes as needed.

But I also found that if I add this code, the result obtained by _XFER_4_FLOAT (uint32_t)value_ptr = (uint32_t)dest_val; 450ea000 will be converted to IEEE 754 Converter. How should I do it? 450f0000 = float: 2288

After your modification the result looks good to me. Please carefully read the manual for your device to correctly convert this, I guess you just need to adjust the point position and your result is 228.8V?

Yes, that's right. After adjustment, the result is the 228.8V I want. Now I can get the correct result for me

But I also have another issue, in a separate issue, about compilation errors after mixing other components

Can you take a look at it for me?

https://github.com/espressif/esp-modbus/issues/52

Thx

yel-best commented 9 months ago

yes, this problem has been solved. I hope to use the new v2.0 earlier. I hope these problems can be solved from the package. Thank you for your support. 😄