Modbus byte ordering

breshead
Posts: 3
Joined: Wed Sep 01, 2021 10:30 pm

Modbus byte ordering

Postby breshead » Wed Sep 01, 2021 10:43 pm

The [Modbus][https://docs.espressif.com/projects/esp ... odbus.html] Serial Master module works when accessing "Little Endian" slaves but fails with a timeout when accessing "Big Endian" slaves.

I verified this with a modbus slave simulator on the desktop, keeping the same comms and register values and just changing from "Little Endian" (successful reads) to "Big Endian" caused the modbus reads to fail.

Is there a way to change the "Endianness" / Byte Ordering of the communications with the slave device? Barring that, is there another library what works with ESP32 that does support this?

I did find a reference to an attempt in this direction in "esp-idf/components/freemodbus/common/include/esp_modbus_common.h" using macros but it appears these macros are not used anywhere.

Code: Select all

// The Macros below handle the endianness while transfer N byte data into buffer
#define _XFER_4_RD(dst, src) { \
    *(uint8_t *)(dst)++ = *(uint8_t*)(src + 1); \
    *(uint8_t *)(dst)++ = *(uint8_t*)(src + 0); \
    *(uint8_t *)(dst)++ = *(uint8_t*)(src + 3); \
    *(uint8_t *)(dst)++ = *(uint8_t*)(src + 2); \
    (src) += 4; \
}

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

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

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



breshead
Posts: 3
Joined: Wed Sep 01, 2021 10:30 pm

Re: Modbus byte ordering

Postby breshead » Fri Sep 03, 2021 6:59 pm

Turns out I had a bad MAX485 transceiver causing the timeouts. I swapped it out and It now gets the data successfully but still in the wrong order (Little Endian on 32bit values).

I would appreciate any thoughts or success stories getting around this.

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus byte ordering

Postby ESP_alisitsyn » Mon Sep 06, 2021 9:31 pm

@breshead,

The Little Endian Byte Swap format is used often for Modbus devices (the same for IEEE754 floating point format). The macros you are referenced are used to transfer data from the input buffer into the variable buffer and wise versa. The transfer is performed in read/write callback functions which are located in the modbus controller code and executed by function handler. For example, see below:
https://github.com/espressif/esp-idf/bl ... ter.c#L487

It is possible to update the code to use Big Endian in your code. The selection of endianess is not supported for now in the library. It will be taken into account during update to new version.

The custom endianess can be fixed here:

https://github.com/espressif/esp-idf/bl ... ter.c#L388

Code: Select all

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(MB_MASTER_TAG, "%s: Good response for get cid(%u) = %s",
                                    __FUNCTION__, (int)reg_info.cid, (char*)esp_err_to_name(error));
        } else {
            ESP_LOGD(MB_MASTER_TAG, "%s: Bad response to get cid(%u) = %s",
                                            __FUNCTION__, 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 parameter here <<<<<<<<<<<<<<<<<<<
        }
    } else {
        ESP_LOGE(MB_MASTER_TAG, "%s: The cid(%u) not found in the data dictionary.",
                                                    __FUNCTION__, reg_info.cid);
        error = ESP_ERR_INVALID_ARG;
    }
    return error;
}

breshead
Posts: 3
Joined: Wed Sep 01, 2021 10:30 pm

Re: Modbus byte ordering

Postby breshead » Tue Sep 07, 2021 10:49 pm

Thanks so much for the replies! I figured out the location last week. Could have saved myself a lot of sleuthing if I waited, but at least I know a bit more about how this library works.
This is what I came up with.. Do I need to specify if is FLOAT or UINT32 ??

Code: Select all

			if (request.reg_size == 2 && reg_info.bigendian == true){
				*(uint32_t*)value_ptr = __beswap_32(*(uint32_t*)value_ptr);
				//ESP_LOGI(MB_MASTER_TAG, "POST-Byte Swap (Address) Hex (0x%x)", (uint32_t)value_ptr);
			}
I also added the macro to `freemodbus/common/include/esp_modbus_common.h`
I copied/modified a 16bit byte swap macro since all of the 32bit ones seemed to deal with 8-bit boundaries instead of complete 16-bit registers..

Code: Select all


// BigEndian 32bit swap 
//   just swap the two 16 bit registers since the two bytes in each
//   register are already swapped (BigEndian) as per modbus standard.
# define __beswap_32(x) \
    (__extension__							      \
     ({ uint32_t __bsx = (x);					      \
        ((((__bsx) >> 16) & 0xffff) | (((__bsx) & 0xffff) << 16)); }))

And changed the `mb_parameter_descriptor_t` definition to have `bool bigendian` at the end so that I could differentiate between Big Endian and Little Endian devices.

Code: Select all

/**
 * @brief Characteristics descriptor type is used to describe characteristic and
 * link it with Modbus parameters that reflect its data.
 */
typedef struct {
    uint16_t            cid;                /*!< Characteristic cid */
    const char*         param_key;          /*!< The key (name) of the parameter */
    const char*         param_units;        /*!< The physical units of the parameter */
    uint8_t             mb_slave_addr;      /*!< Slave address of device in the Modbus segment */
    mb_param_type_t     mb_param_type;      /*!< Type of modbus parameter */
    uint16_t            mb_reg_start;       /*!< This is the Modbus register address. This is the 0 based value. */
    uint16_t            mb_size;            /*!< Size of mb parameter in registers */
    uint16_t            param_offset;       /*!< Parameter name (OFFSET in the parameter structure) */
    mb_descr_type_t     param_type;         /*!< Float, U8, U16, U32, ASCII, etc. */
    mb_descr_size_t     param_size;         /*!< Number of bytes in the parameter. */
    mb_parameter_opt_t  param_opts;         /*!< Parameter options used to check limits and etc. */
    mb_param_perms_t    access;             /*!< Access permissions based on mode */
    bool                bigendian;          /*!< Is this a 32bit Big Endian register? */
} mb_parameter_descriptor_t;
This just adds a true/false at the end of the each CID data row..

Code: Select all

    { CID_HOLD_DATA_3,   STR("Lat_int"),        STR("deg"),    MB_DEVICE_ADDR1, MB_PARAM_HOLDING,    0,    2, HOLD_OFFSET(holding_data3), PARAM_TYPE_U32,   4,   OPTS( 0, 900000, 1 ),   PAR_PERMS_READ_TRIGGER, true},
Any suggestions would be welcomed. Thanks again.

ESP_alisitsyn
Posts: 211
Joined: Fri Feb 01, 2019 4:02 pm
Contact:

Re: Modbus byte ordering

Postby ESP_alisitsyn » Wed Sep 15, 2021 7:36 am

@breshead,

Thanks for update.

Your code looks good to me to use the mentioned approach. The modifications to

Code: Select all

 mb_parameter_descriptor_t
look good as well. This type was constructed to address some common data handling that can be used for processing of Modbus data and to integrate the stack with other protocols/networks and can be castomized.
Do I need to specify if it is FLOAT or UINT32 ??
It depends on your project and your Modbus slaves. There are several variants for storing the data in Modbus slaves and even IEEE754 floating point can be mapped differently in some slaves. The place I showed allows to hook data and handle it differently, even for some custom slaves, as in your case.

Who is online

Users browsing this forum: No registered users and 102 guests