From 6fd7c4a1035d1594838210a5d787b94d6f96931d Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn Date: Fri, 19 Apr 2024 17:36:32 +0800 Subject: [PATCH] add extended float support to v2.0.x --- .gitlab-ci.yml | 1 + CMakeLists.txt | 4 + Kconfig | 17 +- docs/Doxyfile | 2 + docs/_static/diag_frame.diag | 37 + docs/_static/modbus_docs_versions.js | 12 +- docs/_static/modbus_frame_examples.diag | 61 ++ docs/conf_common.py | 2 +- docs/en/applications_and_references.rst | 7 + docs/en/master_api_overview.rst | 207 +++++- docs/en/overview_messaging_and_mapping.rst | 207 +++++- docs/en/port_initialization.rst | 21 + docs/en/slave_api_overview.rst | 46 +- docs/generate_docs | 2 +- docs/requirements.txt | 2 +- examples/conftest.py | 2 +- .../mb_example_common/include/modbus_params.h | 21 + .../mb_serial_master/main/serial_master.c | 402 ++++++++--- .../mb_serial_master/sdkconfig.ci.ascii | 1 + .../serial/mb_serial_master/sdkconfig.ci.rtu | 1 + .../mb_serial_slave/main/serial_slave.c | 71 +- .../serial/mb_serial_slave/sdkconfig.ci.ascii | 1 + .../serial/mb_serial_slave/sdkconfig.ci.rtu | 1 + .../serial/mb_serial_slave/sdkconfig.defaults | 1 + examples/tcp/mb_tcp_master/main/tcp_master.c | 419 ++++++++--- .../tcp/mb_tcp_master/sdkconfig.ci.ethernet | 2 + examples/tcp/mb_tcp_master/sdkconfig.ci.wifi | 2 + examples/tcp/mb_tcp_master/sdkconfig.defaults | 1 + .../mb_controller/common/esp_modbus_master.c | 278 +++++++ .../common/include/esp_modbus_common.h | 4 + .../common/include/esp_modbus_master.h | 69 +- .../common/include/mb_endianness_utils.h | 556 ++++++++++++++ .../common/mb_endianness_utils.c | 683 ++++++++++++++++++ .../mb_controller/serial/mbc_serial_master.c | 151 ++-- modbus/mb_controller/tcp/mbc_tcp_master.c | 56 +- .../unit_tests/mb_ext_types/CMakeLists.txt | 8 + test_apps/unit_tests/mb_ext_types/README.md | 3 + .../mb_ext_types/main/CMakeLists.txt | 7 + .../mb_ext_types/main/idf_component.yml | 6 + .../main/test_mb_endianness_utils.c | 149 ++++ .../pytest_mb_endianness_utils.py | 11 + .../mb_ext_types/sdkconfig.defaults | 2 + 42 files changed, 3168 insertions(+), 368 deletions(-) create mode 100644 docs/_static/diag_frame.diag create mode 100644 docs/_static/modbus_frame_examples.diag create mode 100644 modbus/mb_controller/common/include/mb_endianness_utils.h create mode 100644 modbus/mb_controller/common/mb_endianness_utils.c create mode 100644 test_apps/unit_tests/mb_ext_types/CMakeLists.txt create mode 100644 test_apps/unit_tests/mb_ext_types/README.md create mode 100644 test_apps/unit_tests/mb_ext_types/main/CMakeLists.txt create mode 100644 test_apps/unit_tests/mb_ext_types/main/idf_component.yml create mode 100644 test_apps/unit_tests/mb_ext_types/main/test_mb_endianness_utils.c create mode 100644 test_apps/unit_tests/mb_ext_types/pytest_mb_endianness_utils.py create mode 100644 test_apps/unit_tests/mb_ext_types/sdkconfig.defaults diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1248392..c53fa41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -75,6 +75,7 @@ after_script: --manifest-file .build-test-rules.yml \ --parallel-count ${CI_NODE_TOTAL:-1} \ --parallel-index ${CI_NODE_INDEX:-1} + echo "delete build folders:" $(find . -type d -regex '^\./.*build_esp32[a-z]+[0-9]+[_a-z]*' -print -exec rm -rf {} +) ls -lh > test_dir_${PWD##*/}.txt # This template gets expanded multiple times, once for every IDF version. diff --git a/CMakeLists.txt b/CMakeLists.txt index 808129f..61d8b6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ set(include_dirs mb_transports mb_controller/common/include mb_objects/include m set(priv_include_dirs mb_controller/serial mb_controller/tcp mb_controller/common mb_transports/rtu mb_transports/ascii mb_transports/tcp) +if(CONFIG_FMB_EXT_TYPE_SUPPORT) + list(APPEND srcs "mb_controller/common/mb_endianness_utils.c") +endif() + add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${srcs}) add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${include_dirs}) add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${priv_include_dirs}) diff --git a/Kconfig b/Kconfig index 1fd6dc4..ade1e31 100644 --- a/Kconfig +++ b/Kconfig @@ -16,7 +16,7 @@ menu "Modbus configuration" config FMB_TCP_PORT_MAX_CONN int "Maximum allowed connections for TCP stack" - range 1 6 + range 1 8 default 5 depends on FMB_COMM_MODE_TCP_EN help @@ -66,7 +66,7 @@ menu "Modbus configuration" config FMB_MASTER_DELAY_MS_CONVERT int "Slave conversion delay (Milliseconds)" default 200 - range 50 400 + range 150 2000 help If master sends a broadcast frame, it has to wait conversion time to delay, then master can send next frame. @@ -108,7 +108,7 @@ menu "Modbus configuration" config FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS int "Response timeout for ASCII communication mode (ms)" default 1000 - range 300 4000 + range 200 5000 depends on FMB_COMM_MODE_ASCII_EN help This option defines response timeout of slave in milliseconds for ASCII communication mode. @@ -116,7 +116,7 @@ menu "Modbus configuration" config FMB_PORT_TASK_PRIO int "Modbus port task priority" - range 3 21 + range 3 23 default 10 help Modbus port data processing task priority. @@ -206,4 +206,13 @@ menu "Modbus configuration" This option has dependency with the UART_ISR_IN_IRAM option which places UART interrupt handler into IRAM to prevent delays related to processing of UART events. + config FMB_EXT_TYPE_SUPPORT + bool "Modbus uses extended types to support third party devices" + default n + help + If this option is set the Modbus stack supports extended list of types + in data dictionary and conversion API to work with the extended types + otherwise the only legacy types are supported. The extended types include + integer, float, double types with different endianness and size. + endmenu diff --git a/docs/Doxyfile b/docs/Doxyfile index fce2024..888c0d3 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -21,8 +21,10 @@ PROJECT_NAME = "IDF Programming Guide" ## and used to include in API reference documentation INPUT = \ + $(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_common.h \ $(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_slave.h \ $(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_master.h \ + $(PROJECT_PATH)/modbus/mb_controller/common/include/mb_endianness_utils.h ## Get warnings for functions that have no documentation for their parameters or return value ## diff --git a/docs/_static/diag_frame.diag b/docs/_static/diag_frame.diag new file mode 100644 index 0000000..f27add3 --- /dev/null +++ b/docs/_static/diag_frame.diag @@ -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]; +} diff --git a/docs/_static/modbus_docs_versions.js b/docs/_static/modbus_docs_versions.js index 2827286..ab4e07f 100644 --- a/docs/_static/modbus_docs_versions.js +++ b/docs/_static/modbus_docs_versions.js @@ -1,16 +1,20 @@ var DOCUMENTATION_VERSIONS = { DEFAULTS: { has_targets: true, - supported_targets: [ "esp32", "esp32s2", "esp32s3", "esp32c3" ] + supported_targets: [ "esp32", "esp32s2", "esp32s3", "esp32c2","esp32c3", "esp32c6", "esp32s2", "esp32s3, esp32p4" ] }, VERSIONS: [ { name: "latest" }, - { name: "v1.0.1", old:false }, - { name: "v1.0.0", old:true } + { name: "v1.0.1", old:true }, + { name: "v2.0.0", old:false } ], IDF_TARGETS: [ { text: "ESP32", value: "esp32"}, { text: "ESP32-S2", value: "esp32s2"}, { text: "ESP32-S3", value: "esp32s3"}, - { text: "ESP32-C3", value: "esp32c3"} + { text: "ESP32-C2", value: "esp32c2"}, + { text: "ESP32-C3", value: "esp32c3"}, + { text: "ESP32-C6", value: "esp32c6"}, + { text: "ESP32-H2", value: "esp32h2"}, + { text: "ESP32-P4", value: "esp32p4"} ] }; diff --git a/docs/_static/modbus_frame_examples.diag b/docs/_static/modbus_frame_examples.diag new file mode 100644 index 0000000..acef030 --- /dev/null +++ b/docs/_static/modbus_frame_examples.diag @@ -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]; +} diff --git a/docs/conf_common.py b/docs/conf_common.py index dcc583e..9e090d1 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -29,5 +29,5 @@ html_static_path = ['../_static'] project_slug = 'esp-modbus' versions_url = './_static/modbus_docs_versions.js' -idf_targets = ['esp32', 'esp32c2', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3'] +idf_targets = [ 'esp32' ] languages = ['en'] diff --git a/docs/en/applications_and_references.rst b/docs/en/applications_and_references.rst index f4f7e3f..d76cce3 100644 --- a/docs/en/applications_and_references.rst +++ b/docs/en/applications_and_references.rst @@ -66,6 +66,13 @@ Protocol References API Reference ------------- +.. include-build-file:: inc/esp_modbus_common.inc .. 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 \ No newline at end of file diff --git a/docs/en/master_api_overview.rst b/docs/en/master_api_overview.rst index c705d95..41b6812 100644 --- a/docs/en/master_api_overview.rst +++ b/docs/en/master_api_overview.rst @@ -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,6 +162,133 @@ 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`: @@ -174,6 +303,62 @@ Initialization of master descriptor. The descriptor represents an array of type ESP_ERROR_CHECK(mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters)); The Data Dictionary can be initialized from SD card, MQTT or other source before start of stack. Once the initialization and setup is done, the Modbus controller allows the reading of complex parameters from any slave included in descriptor table using its CID. +Refer to :ref:`example TCP master `, :ref:`example Serial master ` for more information. + +.. _modbus_api_master_setup_communication_options: + +Master Communication Options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Calling the setup function allows for specific communication options to be defined for port. + +:cpp:func:`mbc_master_setup` + +The communication structure provided as a parameter is different for serial and TCP communication mode. + +Example setup for serial port: + +.. code:: c + + mb_communication_info_t comm_info = { + .port = MB_PORT_NUM, // Serial port number + .mode = MB_MODE_RTU, // Modbus mode of communication (MB_MODE_RTU or MB_MODE_ASCII) + .baudrate = 9600, // Modbus communication baud rate + .parity = MB_PARITY_NONE // parity option for serial port + }; + + ESP_ERROR_CHECK(mbc_master_setup((void*)&comm_info)); + +Modbus master TCP port requires additional definition of IP address table where number of addresses should be equal to number of unique slave addresses in master Modbus Data Dictionary: + +The order of IP address string corresponds to short slave address in the Data Dictionary. + +.. code:: c + + #define MB_SLAVE_COUNT 2 // Number of slaves in the segment being accessed (as defined in Data Dictionary) + + char* slave_ip_address_table[MB_SLAVE_COUNT] = { + "192.168.1.2", // Address corresponds to UID1 and set to predefined value by user + "192.168.1.3", // corresponds to UID2 in the segment + NULL // end of table + }; + + mb_communication_info_t comm_info = { + .ip_port = MB_TCP_PORT, // Modbus TCP port number (default = 502) + .ip_addr_type = MB_IPV4, // version of IP protocol + .ip_mode = MB_MODE_TCP, // Port communication mode + .ip_addr = (void*)slave_ip_address_table, // assign table of IP addresses + .ip_netif_ptr = esp_netif_ptr // esp_netif_ptr pointer to the corresponding network interface + }; + + ESP_ERROR_CHECK(mbc_master_setup((void*)&comm_info)); + +.. note:: Refer to `esp_netif component `__ for more information about network interface initialization. + +The slave IP addresses in the table can be assigned automatically using mDNS service as described in the example. +Refer to :ref:`example 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 `__ in documentation. .. _modbus_api_master_start_communication: diff --git a/docs/en/overview_messaging_and_mapping.rst b/docs/en/overview_messaging_and_mapping.rst index 6dbaa14..4a44cc1 100644 --- a/docs/en/overview_messaging_and_mapping.rst +++ b/docs/en/overview_messaging_and_mapping.rst @@ -39,9 +39,212 @@ The Modbus protocol allows devices to map data to four types of registers (Holdi Modbus data mapping -The following sections give an overview of how to use the ESP_Modbus component. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly: +.. _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` - diff --git a/docs/en/port_initialization.rst b/docs/en/port_initialization.rst index 259c9eb..d519080 100644 --- a/docs/en/port_initialization.rst +++ b/docs/en/port_initialization.rst @@ -18,6 +18,27 @@ Calling the constructor function allows to create communication object with the // is used later as a first parameter for each API call static void *master_handle = NULL; ESP_ERROR_CHECK(mbc_master_create_serial(&config, &master_handle)); + ... + +.. code:: c + + static void *master_handle = NULL; + ESP_ERROR_CHECK(mbc_master_create_tcp(&config, &master_handle)); + ... + +.. code:: c + + static void *slave_handle = NULL; + ESP_ERROR_CHECK(mbc_slave_create_tcp(&config, &slave_handle)); + ... + +.. code:: c + + static void *slave_handle = NULL; + ESP_ERROR_CHECK(mbc_slave_create_serial(&config, &slave_handle)); + ... + +Refer to :ref:`modbus_api_master_setup_communication_options` and :ref:`modbus_api_slave_setup_communication_options` for more information on how to configure communication options for the master and slave object accordingly. .. _modbus_api_master_setup_communication_options: diff --git a/docs/en/slave_api_overview.rst b/docs/en/slave_api_overview.rst index 2fe6e37..693d6f0 100644 --- a/docs/en/slave_api_overview.rst +++ b/docs/en/slave_api_overview.rst @@ -5,7 +5,7 @@ Modbus Slave API Overview The sections below represent typical programming workflow for the slave API which should be called in following order: -1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface for the selected port. +1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface using communication options. 2. :ref:`modbus_api_slave_configure_descriptor` - Configure data descriptors to access slave parameters. 3. :ref:`modbus_api_slave_setup_communication_options` - Allows to setup communication options for selected port. 4. :ref:`modbus_api_slave_communication` - Start stack and sending / receiving data. Filter events when master accesses the register areas. @@ -71,12 +71,37 @@ The function initializes Modbus communication descriptors for each type of Modbu At least one area descriptor per each Modbus register type must be set in order to provide register access to its area. If the master tries to access an undefined area, the stack will generate a Modbus exception. +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(¶m_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(¶m_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_communication: Slave Communication ^^^^^^^^^^^^^^^^^^^ -The function below is used to start Modbus controller interface and allows communication. +The function below is used to start Modbus controller interface and allows communication. :cpp:func:`mbc_slave_start` @@ -130,7 +155,7 @@ Example to get event when holding or input registers accessed in the slave: 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, @@ -138,7 +163,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, @@ -159,9 +184,20 @@ The direct access to slave register area from user application must be protected static void *slave_handle = NULL; // communication object handle ... (void)mbc_slave_lock(slave_handle); // ignore the returned error if the object is not actual - holding_reg_area[2] += 10; // the data is part of initialized register area accessed by slave + holding_reg_area[1] += 10; // the data is part of initialized register area accessed by slave (void)mbc_slave_unlock(slave_handle); +The access to registered area shared between several slave objects from user application must be protected by critical section base on spin lock: + +.. code:: c +#include "freertos/FreeRTOS.h" +... + static portMUX_TYPE g_spinlock = portMUX_INITIALIZER_UNLOCKED; +... + portENTER_CRITICAL(¶m_lock); + holding_reg_area[2] = 123; + portEXIT_CRITICAL(¶m_lock); + .. _modbus_api_slave_destroy: Modbus Slave Teardown diff --git a/docs/generate_docs b/docs/generate_docs index d9af465..f5bdd5d 100755 --- a/docs/generate_docs +++ b/docs/generate_docs @@ -1,7 +1,7 @@ #!/bin/bash rm -rf docs -build-docs --target esp32 --language en +build-docs --target esp32 --language en && true # Modifes target field of html files ELEMENT="