modbus add extended float/integer support for custom and third party devices

This commit is contained in:
Alex Lisitsyn
2024-04-19 17:11:08 +08:00
parent 430217563f
commit 0cf6bb2c6a
49 changed files with 3124 additions and 388 deletions
+4 -1
View File
@@ -13,17 +13,20 @@
# http://doxygen.nl/manual/config.html
PROJECT_NAME = "IDF Programming Guide"
PROJECT_NAME = "ESP-MODBUS Programming Guide"
## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
## to automatically generate API reference list files heder_file.inc
## These files are placed in '_inc' directory
## and used to include in API reference documentation
##
INPUT = \
$(PROJECT_PATH)/freemodbus/common/include/esp_modbus_common.h \
$(PROJECT_PATH)/freemodbus/common/include/esp_modbus_slave.h \
$(PROJECT_PATH)/freemodbus/common/include/esp_modbus_master.h \
$(PROJECT_PATH)/freemodbus/common/include/mb_endianness_utils.h
## Get warnings for functions that have no documentation for their parameters or return value
##
+37
View File
@@ -0,0 +1,37 @@
# Modbus float_abcd frame structure diagram
blockdiag mb_float_frame {
# global properties
span_width = 2;
span_height = 5;
node_height = 25;
default_fontsize = 16;
default_group_color = lightgrey;
class spacer [shape=none, width=10];
# tuning node properties and connections
0,1,2 [class=spacer];
0; note
1; header
2; response -- uid -- cmd -- len -- fl_abcd -- crc
group float_abcd_packet {
label = "PDU";
color = gray;
shape = line;
style = dashed;
group{uid,resp_uid};group{cmd,resp_cmd};group{len,resp_len};group{crc,resp_crc};
group float_abcd{
color = blue;
shape = line;
style = dashed;
fl_abcd;dt_abcd;
}
}
note[label="1: Unit Identificator, 2: Function code, 3: Data length, 4: Float data array, 5: Checksum",colwidth=6,color=lightyellow,shape=roundedbox]
header[label="FLOAT_ABCD = 0x4640e400 = 12345.0",colwidth=6,color=lightgreen]
response[label="RX:",color=yellow];
uid[label="UID",numbered=1];cmd[label="FC",numbered=2];
len[label="LENGTH",numbered=3];crc[label="CRC",numbered=5];
resp_uid[label="0x01"];resp_cmd[label="0x03"];resp_len[label="0x08"];resp_crc[label="0x9065"];
fl_abcd[label="FLOAT_ABCD",color=lightgreen,numbered=4];
dt_abcd[label="0xE4004640",shape=note];
}
+61
View File
@@ -0,0 +1,61 @@
# Modbus frame packaging examples
blockdiag mb_master_frames {
# global properties
span_width = 5;
span_height = 5;
node_height = 25;
default_group_color = lightgrey;
default_fontsize = 15;
# tuning node properties and connections
group 16bit_packets {
label = "16bit frame";
color = red;
shape = line;
style = dashed;
16bit_notes;
}
group 32bit_packets {
label = "32bit frame";
color = green;
shape = line;
style = dashed;
group{32bit_notes};
}
group 64bit_packets {
label = "64bit frame";
color = blue;
shape = line;
style = dashed;
64bit_notes;
}
16bit_notes[label="UINT16, INT16 VALUE = 0x3039 = (uint16_t)12345", width=600, color=orange, shape = roundedbox];
req_u16_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=16bit_packets];
req_u16_frm1[label="TX:| 01 | 03 | 00 04 | 00 02 | 85 CA |", color=lightgrey, width=380, colwidth=2,group=16bit_packets];
rsp_u16_hd1[label= "RX:| UID | FC | LEN | UINT16_AB1 | UINT16_AB2 | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=16bit_packets];
rsp_u16_frm1[label="RX:| 01 | 03 | 04 | 30 39 | 30 39 | F1 2C |", color=lightgrey, width=380, colwidth=2,group=16bit_packets];
rsp_u16_hd2[label= "RX:| UID | FC | LEN | UINT16_BA1 | UINT16_BA2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=16bit_packets];
rsp_u16_frm2[label="RX:| 01 | 03 | 04 | 39 30 | 39 30 | E4 E4 |\n", color=lightgrey, width=380, colwidth=2,group=16bit_packets];
32bit_notes[label="(UINT32, INT32) FLOAT32 VALUE = 0x4640e400 = 12345.0", width=600, color=lightgreen, shape = roundedbox];
req_fl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=32bit_packets];
req_fl_frm1[label="TX:| 01 | 03 | 00 XX | 00 04 | C5 CB |", color=lightgrey, width=380, colwidth=2,group=32bit_packets];
rsp_fl_hd1[label= "RX:| UID | FC | LEN | FLOAT_ABCD1 | FLOAT_ABCD2 | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=32bit_packets];
rsp_fl_frm1[label="RX:| 01 | 03 | 08 | E4 00 46 40 | E4 00 46 40 | 90 65 |", color=lightgrey, width=380, colwidth=2,group=32bit_packets];
rsp_fl_hd2[label= "RX:| UID | FC | LEN | FLOAT_CDAB1 | FLOAT_CDAB2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm2[label="RX:| 01 | 03 | 08 | 46 40 E4 00 | 46 40 E4 00 | 18 71 |\n", color=lightgrey, width=380, colwidth=2,group=32bit_packets];
rsp_fl_hd3[label= "RX:| UID | FC | LEN | FLOAT_BADC1 | FLOAT_BADC2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm3[label="RX:| 01 | 03 | 08 | 00 E4 40 46 | 00 E4 40 46 | 46 D3 |\n", color=lightgrey, width=380, colwidth=2,group=32bit_packets];
rsp_fl_hd4[label= "RX:| UID | FC | LEN | FLOAT_DCAB1 | FLOAT_DCAB2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm4[label="RX:| 01 | 03 | 08 | 40 46 00 E4 | 40 46 00 E4 | 32 6B |\n", color=lightgrey, width=380, colwidth=2,group=32bit_packets];
64bit_notes[label="(UINT64, INT64) FLOAT64 VALUE = 0x40c81c8000000000 = 12345.0", width=600, color=lightblue, shape = roundedbox];
req_dbl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=64bit_packets];
req_dbl_frm1[label="TX:| 01 | 03 | 00 28 | 00 08 | C4 04 |", color=lightgrey, width=380, colwidth=2,group=64bit_packets];
rsp_dbl_hd1[label= "RX:| UID | FC | LEN | DOUBLE_ABCDEFGH1 | DOUBLE_ABCDEFGH2 | CRC |", color=lightyellow, width=380, colwidth=2, shape = roundedbox ,group=64bit_packets];
rsp_dbl_frm1[label="RX:| 01 | 03 | 10 | 00 00 00 00 1C 80 40 C8 | 00 00 00 00 1C 80 40 C8 | 9F 4B |", color=lightgrey, width=380, colwidth=2,group=64bit_packets];
rsp_dbl_hd2[label= "RX:| UID | FC | LEN | DOUBLE_HGFEDCBA1 | DOUBLE_HGFEDCBA2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm2[label="RX:| 01 | 03 | 10 | C8 40 80 1C 00 00 00 00 | C8 40 80 1C 00 00 00 00 | DF D3 |\n", color=lightgrey, width=380, colwidth=2,group=64bit_packets];
rsp_dbl_hd3[label= "RX:| UID | FC | LEN | DOUBLE_GHEFCDAB1 | DOUBLE_GHEFCDAB2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm3[label="RX:| 01 | 03 | 10 | 40 C8 1C 80 00 00 00 00 | 40 C8 1C 80 00 00 00 00 | B1 9C |\n", color=lightgrey, width=380, colwidth=2,group=64bit_packets];
rsp_dbl_hd4[label= "RX:| UID | FC | LEN | DOUBLE_BADCFEHG1 | DOUBLE_BADCFEHG2 | CRC |\n ", color=lightyellow, width=380, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm4[label="RX:| 01 | 03 | 10 | 00 00 00 00 80 1C C8 40 | 00 00 00 00 80 1C C8 40 | 86 94 |\n", color=lightgrey, width=380, colwidth=2,group=64bit_packets];
}
+5
View File
@@ -70,4 +70,9 @@ API Reference
.. include-build-file:: inc/esp_modbus_master.inc
.. include-build-file:: inc/esp_modbus_slave.inc
.. _modbus_api_endianness_conversion:
Modbus Endianness Conversion API Reference
------------------------------------------
.. include-build-file:: inc/mb_endianness_utils.inc
+1 -1
View File
@@ -11,4 +11,4 @@ The Modbus is a data communications protocol originally published by Modicon (no
Modbus Port Initialization <port_initialization>
Modbus Master API <master_api_overview>
Modbus Slave API <slave_api_overview>
Applications and References <applications_and_references>
Applications and References <applications_and_references>
+143 -15
View File
@@ -16,10 +16,7 @@ The following overview describes how to setup Modbus master communication. The o
Configuring Master Data Access
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The architectural approach of ESP_Modbus includes one level above standard Modbus IO driver.
The additional layer is called Modbus controller and its goal is to add an abstraction such as CID - characteristic identifier.
The CID is linked to a corresponding Modbus registers through the table called Data Dictionary and represents device physical parameter (such as temperature, humidity, etc.) in specific Modbus slave device.
This approach allows the upper layer (e.g., MESH or MQTT) to be isolated from Modbus specifics thus simplify Modbus integration with other protocols/networks.
The architectural approach of ESP_Modbus includes one level above standard Modbus IO driver. The additional layer is called Modbus controller and its goal is to add an abstraction such as CID - characteristic identifier. The CID is linked to a corresponding Modbus registers through the table called Data Dictionary and represents device physical parameter (such as temperature, humidity, etc.) in specific Modbus slave device. This approach allows the upper layer (e.g., MESH or MQTT) to be isolated from Modbus specifics thus simplify Modbus integration with other protocols/networks.
The Data Dictionary is the list in the Modbus master which shall be defined by user to link each CID to its corresponding Modbus registers representation using Register Mapping table of the Modbus slave being used.
Each element in this data dictionary is of type :cpp:type:`mb_parameter_descriptor_t` and represents the description of one physical characteristic:
@@ -52,20 +49,17 @@ Each element in this data dictionary is of type :cpp:type:`mb_parameter_descript
- Relative register address of the characteristic in the register area.
* - ``mb_size``
- Modbus Register Size
- Length of characteristic in registers.
- Length of characteristic in registers (two bytes).
* - ``param_offset``
- Instance Offset
- Offset to instance of the characteristic in bytes. It is used to calculate the absolute address to the characteristic in the storage structure.
It is optional field and can be set to zero if the parameter is not used in the application.
* - ``param_type``
- Data Type
- Specifies type of the characteristic.
:cpp:enumerator:`PARAM_TYPE_U8`, :cpp:enumerator:`PARAM_TYPE_U16`, :cpp:enumerator:`PARAM_TYPE_U32` - Unsigned integer 8/16/32 bit type;
:cpp:enumerator:`PARAM_TYPE_FLOAT` - IEEE754 floating point format;
:cpp:enumerator:`PARAM_TYPE_ASCII` - ASCII string or binary data;
- Data Type
- Specifies type of the characteristic. Possible types are described in the section :ref:`modbus_mapping_complex_data_types`.
* - ``param_size``
- Data Size
- The storage size of the characteristic (bytes).
- The storage size of the characteristic (in bytes) describes the size of data to keep into data instance during mapping. For the :ref:`modbus_mapping_complex_data_types` this allows to define the data container of the corresponded type.
* - ``param_opts``
- Parameter Options
- Limits, options of characteristic used during processing of alarm in user application (optional)
@@ -76,6 +70,13 @@ Each element in this data dictionary is of type :cpp:type:`mb_parameter_descript
.. note:: The ``cid`` and ``param_key`` have to be unique. Please use the prefix to the parameter key if you have several similar parameters in your register map table.
Examples Of Mapping
@@@@@@@@@@@@@@@@@@@
Please refer to section :ref:`modbus_mapping_complex_data_types` for more information about used data types.
Example 1: Configure access to legacy parameter types is described below.
.. list-table:: Table 2 Example Register mapping table of Modbus slave
:widths: 5 5 2 10 5 5 68
:header-rows: 1
@@ -115,6 +116,7 @@ Each element in this data dictionary is of type :cpp:type:`mb_parameter_descript
- ASCII or binary array
- Not defined
- Device name (16 bytes) ASCII string. The type of `PARAM_TYPE_ASCII` allows to read/write complex parameter (string or binary data) that corresponds to one CID.
.. code:: c
// Enumeration of modbus slave addresses accessed by master device
@@ -160,10 +162,139 @@ Each element in this data dictionary is of type :cpp:type:`mb_parameter_descript
// Calculate number of parameters in the table
uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
Example 2: Configure access using extended parameter types for third-party devices.
.. list-table:: Table 3 Example Register mapping table of Modbus slave
:widths: 2 4 2 10 3 68
:header-rows: 1
* - CID
- Register
- Length
- Range
- Units
- Description
* - 0
- 40000
- 4
- 0 ... 255
- No units
- :cpp:enumerator:`PARAM_TYPE_U8_A` - unsigned integer 8-bit
* - 1
- 40002
- 4
- 0 ... 65535
- No Units
- :cpp:enumerator:`PARAM_TYPE_U16_AB` uinsigned integer 16-bit
* - 3
- 40004
- 8
- 0 ... Unsigned integer 32-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_U32_ABCD` - unsigned integer 32-bit in ABCD format
* - 4
- 40008
- 8
- 0 ... Unsigned integer 32-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB` - FLOAT 32-bit value in CDAB format
* - 5
- 400012
- 16
- 0 ... Unsigned integer 64-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH` - Unsigned integer 64-bit value in ABCDEFGH format
* - 6
- 400020
- 16
- 0 ... Unsigned integer 64-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA` - Double precision 64-bit value in HGFEDCBA format
.. code:: c
#include "limits.h"
#include "mbcontroller.h"
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
#define HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
#define HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
#pragma pack(push, 1)
// Example structure that contains parameter arrays of different types
// with different options of endianness.
typedef struct
{
uint16_t holding_u8_a[2];
uint16_t holding_u16_ab[2];
uint32_t holding_uint32_abcd[2];
float holding_float_cdab[2];
double holding_uint64_abcdefgh[2];
double holding_double_hgfedcba[2];
} holding_reg_params_t;
#pragma pack(pop)
// Enumeration of modbus slave addresses accessed by master device
enum {
MB_DEVICE_ADDR1 = 1, // Short address of Modbus slave device
MB_SLAVE_COUNT
};
// Enumeration of all supported CIDs for device (used in parameter definition table)
enum {
CID_HOLD_U8_A = 0,
CID_HOLD_U16_AB,
CID_HOLD_UINT32_ABCD,
CID_HOLD_FLOAT_CDAB,
CID_HOLD_UINT64_ABCDEFGH,
CID_HOLD_DOUBLE_HGFEDCBA,
CID_COUNT
};
// Example Data Dictionary for to address parameters from slaves with different options of endianness
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_HOLD_U8_A, STR("U8_A"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_u8_a), HOLD_REG_SIZE(holding_u8_a),
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (HOLD_REG_SIZE(holding_u8_a) << 1),
OPTS( 0, UCHAR_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_AB, STR("U16_AB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_u16_ab), HOLD_REG_SIZE(holding_u16_ab),
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, USHRT_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_uint32_abcd), HOLD_REG_SIZE(holding_uint32_abcd),
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (HOLD_REG_SIZE(holding_uint32_abcd) << 1),
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_float_cdab), HOLD_REG_SIZE(holding_float_cdab),
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (HOLD_REG_SIZE(holding_float_cdab) << 1),
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT64_ABCDEFGH, STR("UINT64_ABCDEFGH"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_uint64_abcdefgh), HOLD_REG_SIZE(holding_uint64_abcdefgh),
HOLD_OFFSET(holding_uint64_abcdefgh), PARAM_TYPE_UINT64_ABCDEFGH, (HOLD_REG_SIZE(holding_uint64_abcdefgh) << 1),
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_double_hgfedcba), HOLD_REG_SIZE(holding_double_hgfedcba),
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
};
uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
The example above describes the definition of just several extended types. The types described in the :ref:`modbus_mapping_complex_data_types` allow to address the most useful value formats from devices of known third-party vendors.
Once the type of characteristic is defined in data dictionary the stack is responsible for conversion of values to/from the corresponding type option into the format recognizable by compiler.
.. note:: Please refer to your vendor device manual and its mapping table to select the types suitable for your device.
The Modbus stack contains also the :ref:`modbus_api_endianness_conversion` - endianness conversion API functions that allow to convert values from/to each extended type into compiler representation.
During initialization of the Modbus stack, a pointer to the Data Dictionary (called descriptor) must be provided as the parameter of the function below.
:cpp:func:`mbc_master_set_descriptor`: Initialization of master descriptor.
Initialization of master descriptor. The descriptor represents an array of type :cpp:type:`mb_parameter_descriptor_t` and describes all the characteristics accessed by master.
.. code:: c
ESP_ERROR_CHECK(mbc_master_set_descriptor(&device_parameters[0], num_device_parameters));
@@ -226,7 +357,6 @@ Refer to :ref:`example TCP master <example_mb_tcp_master>` for more information.
.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to the `UART communication section <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#uart-api-running-uart-communication>`__ in documentation.
.. _modbus_api_master_start_communication:
Master Communication
@@ -265,7 +395,7 @@ Example:
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
err = mbc_master_get_parameter(param_descriptor->cid, (char*)param_descriptor->param_key, (uint8_t*)temp_data, &type);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.",
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
param_descriptor->cid,
(char*)param_descriptor->param_key,
(char*)param_descriptor->param_units,
@@ -281,7 +411,6 @@ Example:
ESP_LOGE(TAG, "Could not get information for characteristic %d.", cid);
}
:cpp:func:`mbc_master_set_parameter`
The function writes characteristic's value defined as a name and cid parameter in corresponded slave device. The additional data for parameter request is taken from master parameter description table.
@@ -298,7 +427,6 @@ The function writes characteristic's value defined as a name and cid parameter i
ESP_LOGE(TAG, "Set data fail, err = 0x%x (%s).", (int)err, (char*)esp_err_to_name(err));
}
.. _modbus_api_master_destroy:
Modbus Master Teardown
+204 -1
View File
@@ -39,9 +39,212 @@ The Modbus protocol allows devices to map data to four types of registers (Holdi
Modbus data mapping
.. _modbus_mapping_complex_data_types:
Mapping Of Complex Data Types
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
As per section 4.2 of Modbus specification, "MODBUS uses a ``big-Endian`` representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first". The biggest official structure defined by the Modbus specification is a 16-bit word register, which is 2 bytes. However, vendors sometimes group two or even four 16-bit registers together to be interpretted as 32-bit or 64-bit values, respectively. It is also possible when the Modbus vendors group many registers together for serial numbers, text strings, time/date, etc. Regardless of how the vendor intends the data to be interpreted, the Modbus protocol itself simply transfers 16-bit word registers. These values grouped from registers may use either little-endian or big-endian register order.
.. note:: Each individual 16-bit register, is encoded in big-endian order (assuming the Modbus device abides by the Modbus specification). However, the 32-bit and 64-bit types naming conventions like ABCD or ABCDEFGH, does not take into account the network format byte order of frame. For example: the ABCD prefix for 32-bit values means the common Modbus mapping format and corresponds to the CDAB on network format (order in the frame).
Common Data Types Supported By Modbus Vendors
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.. list-table:: Table 1 basic types used by Modbus vendors
:widths: 8 3 20
:header-rows: 1
* - Type
- Range
- Format description
* - U8, I8 - Unsigned/Signed 8-bit type
- (0 .. 255)/(-128 .. 127)
- Common unsigned 8-bit type that is stored usually in one Modbus register. The value can be stored in HI or LO byte of the register or packed with the next byte into one 16 - bit register.
* - U16 - Unsigned integer 16-bit type
- 0 - 65535
- Stored in one 16-bit register. The values can be stored with AB or BA endianness.
* - I16 - Signed integer 16-bit type
- -32768 to 32767 is allowed.
- Stored in one 16-bit register. The values can be stored with AB or BA forendiannessmat.
* - I32 - Signed long integer 32-bit type
- -2147483648 to 2147483647 is allowed.
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness (see below).
* - U32 - Unsigned long integer 32-bit type
- 0 to 4294967295 is allowed.
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness.
* - U64 Unsigned Long long integers (Unsigned integer 64)
- 0 to 18446744073709551615 is allowed.
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
* - I64 Signed Long long integers (Signed integer 64)
- -9223372036854775808 to 9223372036854775807 is allowed.
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
* - Floating point single precision 32-bit
- 1.17549435E-38 to 3.40282347E+38 is allowed.
- Stored in two consecutive 16-bit register per IEEE754. The values can be stored with ABCD - DCBA endianness.
* - Floating point double precision 64-bit
- +/-5.0E-324 to +/-1.7E+308 is allowed.
- Stored in four consecutive 16-bit register per IEEE754. The values can be stored with ABCDEFGH - BADCFEHG endianness.
As showed in the table above the float and double types do not fit to the 16-bit register and reguire several consecutive registers be used to store the value. However, different manufacturers store the consecutive bytes in different order (not standardized). For example: The DCBA prefix means inversed Modbus format (BADC order on network format).
.. list-table:: Table 2 Modbus byte order for extended types
:widths: 3 28
:header-rows: 1
* - Postfix
- Format description
* - ABCD
- Big endian, high order byte first
* - CDAB
- Big endian, reversed register order (Little endian with byte swap)
* - BADC
- Little endian, reversed register order (Big endian with byte swap)
* - DCBA
- Little endian (Low order byte first)
The extended data types are used to define all possible combinations of groupped values are represented below and correspond to ``param_type`` field of the data dictionary as described in the table below:
.. list-table:: Table 3 Modbus extended data types of characteristics
:widths: 6 28 10
:header-rows: 1
* - Type
- Format type description (common format)
- Format type (network format)
* - :cpp:enumerator:`PARAM_TYPE_U8`
- compatibility type corresponds to :cpp:enumerator:`PARAM_TYPE_U8_A`
- Unsigned integer 8 bit type
* - :cpp:enumerator:`PARAM_TYPE_U16`
- Unsigned integer 16 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U16_AB`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32`
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT`
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_ASCII`
- Default ASCII string format
- Packed ASCII string data
* - :cpp:enumerator:`PARAM_TYPE_BIN`
- Binary data type
- Default type for binary packed data
* - :cpp:enumerator:`PARAM_TYPE_I8_A`
- I8 signed integer in low byte of register, high byte is zero
- I8 signed integer LO
* - :cpp:enumerator:`PARAM_TYPE_I8_B`
- I8 signed integer in high byte of register, low byte is zero
- I8 signed integer HI
* - :cpp:enumerator:`PARAM_TYPE_U8_A`
- U8 unsigned integer written to low byte of register, high byte is zero
- U8 unsigned integer LO
* - :cpp:enumerator:`PARAM_TYPE_U8_B`
- U8 unsigned integer written to hi byte of register, low byte is zero
- U8 unsigned integer HI
* - :cpp:enumerator:`PARAM_TYPE_I16_AB`
- I16 signed integer, big endian
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I16_BA`
- I16 signed integer, little endian
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U16_AB`
- U16 unsigned integer, big endian
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U16_BA`
- U16 unsigned integer, little endian
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_I32_ABCD`
- I32 ABCD signed integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I32_CDAB`
- I32 CDAB signed integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I32_BADC`
- I32 BADC signed integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_I32_DCBA`
- I32 DCBA signed integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
- U32 ABCD unsigned integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32_CDAB`
- U32 CDAB unsigned integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U32_BADC`
- U32 BADC unsigned integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U32_DCBA`
- U32 DCBA unsigned integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
- Float ABCD floating point, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB`
- Float CDAB floating point, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_BADC`
- Float BADC floating point, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_DCBA`
- Float DCBA floating point, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_ABCDEFGH`
- I64, ABCDEFGH signed integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_HGFEDCBA`
- I64, HGFEDCBA signed integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_GHEFCDAB`
- I64, GHEFCDAB signed integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I64_BADCFEHG`
- I64, BADCFEHG signed integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH`
- U64, ABCDEFGH unsigned integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U64_HGFEDCBA`
- U64, HGFEDCBA unsigned integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U64_GHEFCDAB`
- U64, GHEFCDAB unsigned integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U64_BADCFEHG`
- U64, BADCFEHG unsigned integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_ABCDEFGH`
- Double ABCDEFGH floating point, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA`
- Double HGFEDCBA floating point, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_GHEFCDAB`
- Double GHEFCDAB floating point, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_BADCFEHG`
- Double BADCFEHG floating point, little endian, reversed register order
- Little endian
.. note:: The support for the extended data types should be enabled using the option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` in kconfig menu.
The below diagrams show how the extended data types appear on network layer.
.. blockdiag:: /../_static/diag_frame.diag
:scale: 80%
:caption: Modbus master response with ABCD frame
:align: center
.. blockdiag:: /../_static/modbus_frame_examples.diag
:scale: 80%
:caption: Modbus frame packaging examples (16-bit, 32-bit, 64-bit data)
:align: center
The approach showed above can be used to pack the data into MBAP frames used by Modbus TCP as well as for other types with similar size.
The following sections give an overview of how to use the ESP_Modbus component found under `components/freemodbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly:
- :ref:`modbus_api_port_initialization`
- :ref:`modbus_api_slave_overview`
- :ref:`modbus_api_master_overview`
+1 -1
View File
@@ -32,4 +32,4 @@ This example code to initialize slave port:
if (slave_handler == NULL || err != ESP_OK) {
// Error handling is performed here
ESP_LOGE(TAG, "mb controller initialization fail.");
}
}
+29 -4
View File
@@ -77,6 +77,31 @@ Direct access to register area from user application must be protected by critic
holding_reg_area[2] += 10;
portEXIT_CRITICAL(&param_lock);
The stack supports the extended data types when enabled through the the option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` in kconfig menu.
In this case the mapped data values can be initialized to specific format using :ref:`modbus_api_endianness_conversion`.
Please refer to secton :ref:`modbus_mapping_complex_data_types` for more information about data types.
Example initialization of mapped values:
.. code:: c
#include "mbcontroller.h" // for mbcontroller defines and api
val_32_arr holding_float_abcd[2] = {0};
val_64_arr holding_double_ghefcdab[2] = {0};
...
// set the Modbus parameter to specific format
portENTER_CRITICAL(&param_lock); // critical section is required if the stack is active
mb_set_float_abcd(&holding_float_abcd[0], (float)12345.0);
mb_set_float_abcd(&holding_float_abcd[1], (float)12345.0);
mb_set_double_ghefcdab(&holding_double_ghefcdab[0], (double)12345.0);
portEXIT_CRITICAL(&param_lock);
...
// The actual abcd formatted value can be converted to actual float represenatation as below
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[0]));
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[1]));
ESP_LOGI("TEST", "Test value ghefcdab: %lf", mb_get_double_ghefcdab(&holding_double_ghefcdab[0]));
...
.. _modbus_api_slave_setup_communication_options:
@@ -176,14 +201,14 @@ Example to get event when holding or input registers accessed in the slave:
....
// The function blocks while waiting for register access
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
(void)mbc_slave_check_event(MB_READ_WRITE_MASK);
// Get information about data accessed from master
ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
// Filter events and process them accordingly
if (event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(uint32_t)reg_info.time_stamp,
@@ -191,7 +216,7 @@ Example to get event when holding or input registers accessed in the slave:
(uint32_t)reg_info.type,
(uint32_t)reg_info.address,
(uint32_t)reg_info.size);
} else if (event & (MB_EVENT_INPUT_REG_RD)) {
} else if (reg_info.type & (MB_EVENT_INPUT_REG_RD)) {
ESP_LOGI(TAG, "INPUT %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(uint32_t)reg_info.time_stamp,
+1 -1
View File
@@ -1 +1 @@
esp-docs==0.2.4
esp-docs>=1.8,<2.0