diff --git a/.flake8 b/.flake8 index c578027ed9..5a3f626b75 100644 --- a/.flake8 +++ b/.flake8 @@ -153,6 +153,7 @@ exclude = components/tinyusb, components/unity/unity, components/spiffs/spiffs, + components/freemodbus, examples/build_system/cmake/import_lib/main/lib/tinyxml2, examples/peripherals/secure_element/atecc608_ecdsa/components/esp-cryptoauthlib, # autogenerated scripts @@ -168,4 +169,5 @@ exclude = per-file-ignores = # Sphinx conf.py files use star imports to setup config variables - docs/conf_common.py: F405 + docs/conf_common.py: F405, + components/freemodbus/docs/conf_common.py: F405 diff --git a/components/freemodbus/.github/workflows/issue_comment.yml b/components/freemodbus/.github/workflows/issue_comment.yml new file mode 100644 index 0000000000..983586b924 --- /dev/null +++ b/components/freemodbus/.github/workflows/issue_comment.yml @@ -0,0 +1,20 @@ +name: Sync issue comments to JIRA + +# This workflow will be triggered when new issue comment is created (including PR comments) +on: issue_comment + +jobs: + sync_issue_comments_to_jira: + name: Sync Issue Comments to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Sync issue comments to JIRA + uses: espressif/github-actions/sync_issues_to_jira@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: IDFGH + JIRA_COMPONENT: modbus + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/components/freemodbus/.github/workflows/new_issues.yml b/components/freemodbus/.github/workflows/new_issues.yml new file mode 100644 index 0000000000..ad23c11c22 --- /dev/null +++ b/components/freemodbus/.github/workflows/new_issues.yml @@ -0,0 +1,20 @@ +name: Sync issues to Jira + +# This workflow will be triggered when a new issue is opened +on: issues + +jobs: + sync_issues_to_jira: + name: Sync issues to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Sync GitHub issues to Jira project + uses: espressif/github-actions/sync_issues_to_jira@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: IDFGH + JIRA_COMPONENT: modbus + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/components/freemodbus/.github/workflows/new_prs.yml b/components/freemodbus/.github/workflows/new_prs.yml new file mode 100644 index 0000000000..a2329bda2c --- /dev/null +++ b/components/freemodbus/.github/workflows/new_prs.yml @@ -0,0 +1,25 @@ +name: Sync remain PRs to Jira + +# This workflow will be triggered every hour, to sync remaining PRs (i.e. PRs with zero comment) to Jira project +# Note that, PRs can also get synced when new PR comment is created +on: + schedule: + - cron: "0 * * * *" + +jobs: + sync_prs_to_jira: + name: Sync PRs to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Sync PRs to Jira project + uses: espressif/github-actions/sync_issues_to_jira@master + with: + cron_job: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: IDFGH + JIRA_COMPONENT: modbus + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/components/freemodbus/.gitignore b/components/freemodbus/.gitignore new file mode 100644 index 0000000000..46ceedef71 --- /dev/null +++ b/components/freemodbus/.gitignore @@ -0,0 +1,49 @@ +.config +*.o +*.pyc + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Test files +test/build +test/sdkconfig +test/sdkconfig.old + +# Doc build artifacts +docs/_build/ +docs/doxygen-warning-log.txt +docs/sphinx-warning-log.txt +docs/sphinx-warning-log-sanitized.txt +docs/xml/ +docs/xml_in/ +docs/man/ +docs/doxygen_sqlite3.db + +TEST_LOGS + + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +# VS Code Settings +.vscode/ diff --git a/components/freemodbus/.gitlab-ci.yml b/components/freemodbus/.gitlab-ci.yml new file mode 100644 index 0000000000..50d6c9d97f --- /dev/null +++ b/components/freemodbus/.gitlab-ci.yml @@ -0,0 +1,161 @@ +stages: + - build + - deploy + +variables: + # System environment + ESP_DOCS_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env-v5.0:2-2" + ESP_DOCS_PATH: "$CI_PROJECT_DIR" + + # GitLab-CI environment + GET_SOURCES_ATTEMPTS: "10" + ARTIFACT_DOWNLOAD_ATTEMPTS: "10" + GIT_SUBMODULE_STRATEGY: none + +.setup_idf_tools: &setup_idf_tools | + tools/idf_tools.py --non-interactive install && eval "$(tools/idf_tools.py --non-interactive export)" || exit 1 + +.add_gh_key_remote: &add_gh_key_remote | + command -v ssh-agent >/dev/null || exit 1 + eval $(ssh-agent -s) + printf '%s\n' "${GH_PUSH_KEY}" | tr -d '\r' | ssh-add - > /dev/null + mkdir -p ~/.ssh && chmod 700 ~/.ssh + [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config || ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + git remote remove github || true + git remote add github ${GH_PUSH_REPO} + +after_script: + # Just for cleaning space, no other causes + - git clean -ffdx + + +# This template gets expanded multiple times, once for every IDF version. +# IDF version is specified by setting the espressif/idf image tag. +# +# EXAMPLE_TARGETS sets the list of IDF_TARGET values to build examples for. +# It should be equal to the list of targets supported by the specific IDF version. +# +# TEST_TARGETS sets the list of IDF_TARGET values to build the test_app for. +# It should contain only the targets with optimized assembly implementations. +# +.build_template: + stage: build + tags: + - build + - internet + script: + - pip install idf-component-manager --upgrade + - ./build_all.sh + variables: + EXAMPLE_TARGETS: "esp32" + TEST_TARGETS: "esp32" + +build_idf_v4.1: + extends: .build_template + image: espressif/idf:release-v4.1 + +build_idf_v4.2: + extends: .build_template + image: espressif/idf:release-v4.2 + variables: + EXAMPLE_TARGETS: "esp32 esp32s2" + +build_idf_v4.3: + extends: .build_template + image: espressif/idf:release-v4.3 + variables: + EXAMPLE_TARGETS: "esp32 esp32s2 esp32c3" + +build_idf_v4.4: + extends: .build_template + image: espressif/idf:release-v4.4 + variables: + EXAMPLE_TARGETS: "esp32 esp32s2 esp32s3 esp32c3" + TEST_TARGETS: "esp32 esp32s3" + +build_idf_latest: + extends: .build_template + image: espressif/idf:latest + variables: + EXAMPLE_TARGETS: "esp32 esp32s2 esp32s3 esp32c3" + TEST_TARGETS: "esp32 esp32s3" + # GNU Make based build system is not supported starting from IDF v5.0 + SKIP_GNU_MAKE_BUILD: 1 + +build_docs: + stage: build + image: $ESP_DOCS_ENV_IMAGE + tags: + - build_docs + artifacts: + when: always + paths: + - docs/_build/*/*/*.txt + - docs/_build/*/*/html/* + expire_in: 4 days + # No cleaning when the artifacts + after_script: [] + script: + - cd docs + - pip install -r requirements.txt + - build-docs -l en -t esp32 + +.deploy_docs_template: + stage: deploy + image: $ESP_DOCS_ENV_IMAGE + tags: + - deploy_docs + needs: + - build_docs + only: + changes: + - "docs/**/*" + script: + - source ${CI_PROJECT_DIR}/docs/utils.sh + - add_doc_server_ssh_keys $DOCS_DEPLOY_PRIVATEKEY $DOCS_DEPLOY_SERVER $DOCS_DEPLOY_SERVER_USER + - export GIT_VER=$(git describe --always) + - pip install -r ${CI_PROJECT_DIR}/docs/requirements.txt + - deploy-docs + +deploy_docs_preview: + extends: + - .deploy_docs_template + except: + refs: + - master + variables: + TYPE: "preview" + DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/" + DOCS_DEPLOY_PRIVATEKEY: "$DOCS_DEPLOY_KEY" + DOCS_DEPLOY_SERVER: "$DOCS_SERVER" + DOCS_DEPLOY_SERVER_USER: "$DOCS_SERVER_USER" + DOCS_DEPLOY_PATH: "$DOCS_PATH" + DOCS_DEPLOY_URL_BASE: "https://$DOCS_PREVIEW_SERVER_URL/docs/esp-modbus" + +deploy_docs_production: + extends: + - .deploy_docs_template + only: + refs: + - master + variables: + TYPE: "production" + DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/" + DOCS_DEPLOY_PRIVATEKEY: "$DOCS_PROD_DEPLOY_KEY" + DOCS_DEPLOY_SERVER: "$DOCS_PROD_SERVER" + DOCS_DEPLOY_SERVER_USER: "$DOCS_PROD_SERVER_USER" + DOCS_DEPLOY_PATH: "$DOCS_PROD_PATH" + DOCS_DEPLOY_URL_BASE: "https://docs.espressif.com/projects/esp-modbus" + +upload_to_component_manager: + stage: deploy + image: python:3.10-alpine + tags: + - deploy + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + - if: '$FORCE_PUSH_COMPONENT == "1"' + script: + - pip install idf-component-manager + - export IDF_COMPONENT_API_TOKEN=${ESP_MODBUS_API_KEY} + - python -m idf_component_manager upload-component --allow-existing --name=esp-modbus --namespace=espressif diff --git a/components/freemodbus/.gitrepo b/components/freemodbus/.gitrepo new file mode 100644 index 0000000000..22d875fac0 --- /dev/null +++ b/components/freemodbus/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = https://github.com/espressif/esp-modbus.git + branch = master + commit = 0e1fe58fbf775edc5a7b6dcd46cc07d477120cf6 + parent = 24e597928cca93c926ffcae0ecaf0c99463a397f + method = merge + cmdver = 0.4.5 diff --git a/components/freemodbus/CMakeLists.txt b/components/freemodbus/CMakeLists.txt index aea9e8d900..a33ac7e25b 100644 --- a/components/freemodbus/CMakeLists.txt +++ b/components/freemodbus/CMakeLists.txt @@ -1,3 +1,5 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly set(srcs "common/esp_modbus_master.c" "common/esp_modbus_slave.c" @@ -44,14 +46,29 @@ set(srcs set(include_dirs common/include) set(priv_include_dirs common port modbus modbus/ascii modbus/functions - modbus/rtu modbus/tcp modbus/include) + modbus/rtu modbus/tcp modbus/include) list(APPEND priv_include_dirs serial_slave/port serial_slave/modbus_controller - serial_master/port serial_master/modbus_controller - tcp_slave/port tcp_slave/modbus_controller - tcp_master/port tcp_master/modbus_controller) + serial_master/port serial_master/modbus_controller + tcp_slave/port tcp_slave/modbus_controller + tcp_master/port tcp_master/modbus_controller) + +add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${srcs}) +add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${include_dirs}) +add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/freemodbus/" ${priv_include_dirs}) + +message(STATUS "DEBUG: Use esp-modbus component folder: ${CMAKE_CURRENT_LIST_DIR}.") + +set(requires driver lwip) + +# esp_timer component was introduced in v4.2 +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.1") + list(APPEND requires esp_timer) +endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS "${priv_include_dirs}" - REQUIRES driver) + REQUIRES ${requires} + PRIV_REQUIRES esp_netif) +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/Kconfig b/components/freemodbus/Kconfig index a28e5f498a..50b48f3285 100644 --- a/components/freemodbus/Kconfig +++ b/components/freemodbus/Kconfig @@ -193,44 +193,14 @@ menu "Modbus configuration" If this option is set the Modbus stack uses timer for T3.5 time measurement. Else the internal UART TOUT timeout is used for 3.5T symbol time measurement. - config FMB_TIMER_GROUP - int "Slave Timer group number" - range 0 1 - default 0 - help - Modbus slave Timer group number that is used for timeout measurement. - - config FMB_TIMER_INDEX - int "Slave Timer index in the group" - range 0 1 - default 0 - help - Modbus slave Timer Index in the group that is used for timeout measurement. - - config FMB_MASTER_TIMER_GROUP - int "Master Timer group number" - range 0 1 - default FMB_TIMER_GROUP - help - Modbus master Timer group number that is used for timeout measurement. - - config FMB_MASTER_TIMER_INDEX - int "Master Timer index" - range 0 1 - default FMB_TIMER_INDEX - help - Modbus master Timer Index in the group that is used for timeout measurement. - Note: Modbus master and slave should have different timer index to be able to work simultaneously. - - config FMB_TIMER_ISR_IN_IRAM - bool "Place timer interrupt handler into IRAM" + config FMB_TIMER_USE_ISR_DISPATCH_METHOD + bool "Modbus timer uses ISR dispatch method" default n + select ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD select UART_ISR_IN_IRAM help - This option places Modbus timer IRQ handler into IRAM. - This allows to avoid delays related to processing of non-IRAM-safe interrupts - during a flash write operation (NVS updating a value, or some other - flash API which has to perform an read/write operation and disable CPU cache). + If this option is set the Modbus stack uses ISR dispatch method + to send timeout events from the callback function called from ISR. 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. diff --git a/components/freemodbus/LICENSE b/components/freemodbus/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/components/freemodbus/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/freemodbus/README.md b/components/freemodbus/README.md new file mode 100644 index 0000000000..f04f4451bc --- /dev/null +++ b/components/freemodbus/README.md @@ -0,0 +1,63 @@ +# ESP-Modbus Library + +## Overview + +An Espressif ESP-Modbus Library (esp-modbus) is a library to support Modbus communication in the networks based on RS485, WiFi, Ethernet interfaces. The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). + +* [ESP-Modbus component on GitHub](https://www.github.com/espressif/esp-modbus) + +This library is to be used with Espressif’s IoT Development Framework, [ESP_IDF](https://github.com/espressif/esp-idf). The packages from this repository are uploaded to Espressif’s component repository. + +* [esp-modbus component in component repository](https://components.espressif.com/component/espressif/esp-modbus) + +You can add the component to your project via `idf.py add-dependency`. More information about idf-component-manager can be found in [Espressif API guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html) or [PyPi registry](https://pypi.org/project/idf-component-manager). + +The ESP-Modbus library can be used with ESP-IDF v4.1 and later. ESP-IDF v4.x releases include an earlier version of ESP-Modbus library inside freemodbus component. To use ESP-Modbus with these releases, users need to exclude the built-in freemodbus component from the build process, and update application components to depend on esp-modbus component instead. To exclude freemodbus component from compilation, add the following line to the project CMakeLists.txt file: + +``` +set(EXCLUDE_COMPONENTS freemodbus) +``` + +ESP-IDF v5.x and later releases do not include freemodbus component, so no extra steps are necessary when adding esp-modbus component. + +## Documentation + +The documentation can be found on the link below: + +* [ESP-Modbus documentation (English)](https://docs.espressif.com/projects/esp-modbus) + +## Application Examples + +The examples below demonstrate the ESP-Modbus library of serial, TCP ports for slave and master implementations accordingly. + +- [Modbus serial slave example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/serial/mb_slave) + +- [Modbus serial master example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/serial/mb_master) + +- [Modbus TCP master example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/tcp/mb_tcp_master) + +- [Modbus TCP slave example](https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus/tcp/mb_tcp_slave) + +Please refer to the specific example README.md for details. + +## Protocol References + +- [Modbus Organization with protocol specifications](https://modbus.org/specs.php) + +## Contributing + +We welcome contributions to this project in the form of bug reports, feature requests and pull requests. + +Issue reports and feature requests can be submitted using Github Issues: https://github.com/espressif/esp-modbus/issues. Please check if the issue has already been reported before opening a new one. + +Contributions in the form of pull requests should follow ESP-IDF project's [contribution guidelines](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/index.html). We kindly ask developers to start a discussion on an issue before proposing large changes to the project. + +## Licence + +ESP-Modbus project is based on [FreeMODBUS library](https://github.com/cwalter-at/freemodbus), Copyright (c) 2006 Christian Walter and licensed under the BSD 3-clause license. + +Modbus Master related code is Copyright (c) 2013 Armink and licensed under BSD 3-clause license. + +All original code in this repository is Copyright (c) 2016-2022 Espressif Systems (Shanghai) Co. Ltd. + +The project is distributed under Apache 2.0 license. See the accompanying [LICENSE file](https://github.com/espressif/esp-modbus/blob/master/LICENSE) for a copy. \ No newline at end of file diff --git a/components/freemodbus/build_all.sh b/components/freemodbus/build_all.sh new file mode 100755 index 0000000000..5881abbc8d --- /dev/null +++ b/components/freemodbus/build_all.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# +# Build the test app and all examples from the examples directory. +# Expects TEST_TARGETS environment variables to be set. +# Each variable is the list of IDF_TARGET values to build the examples and +# the test app for, respectively. +# +# ----------------------------------------------------------------------------- +# Safety settings (see https://gist.github.com/ilg-ul/383869cbb01f61a51c4d). + +if [[ -n "${DEBUG_SHELL}" ]] +then + set -x # Activate the expand mode if DEBUG is anything but empty. +fi + +if [[ -z "${EXAMPLE_TARGETS}" || -z "${TEST_TARGETS}" ]] +then + echo "EXAMPLE_TARGETS and TEST_TARGETS environment variables must be set before calling this script" + exit 1 +fi + +if [[ -z "${SKIP_GNU_MAKE_BUILD}" ]] +then + echo "SKIP_GNU_MAKE_BUILD not set, will build with GNU Make based build system as well." + export SKIP_GNU_MAKE_BUILD=0 +fi + +set -o errexit # Exit if command failed. +set -o pipefail # Exit if pipe failed. +set -o nounset # Exit if variable not set. + + +STARS='***************************************************' + +# ----------------------------------------------------------------------------- + +die() { + echo "${1:-"Unknown Error"}" 1>&2 + exit 1 +} + +# build_for_targets +# call this in the project directory +function build_for_targets +{ + target_list="$1" + for IDF_TARGET in ${target_list} + do + export IDF_TARGET + if [[ "${IDF_TARGET}" = "esp32" ]] && [[ "${SKIP_GNU_MAKE_BUILD}" = "0" ]] + then + echo "${STARS}" + echo "Building in $PWD with Make" + # -j option will be set via MAKEFLAGS in .gitlab-ci.yml + # shellcheck disable=SC2015 + make defconfig && make || die "Make build in ${PWD} has failed" + rm -rf build + fi + + echo "${STARS}" + echo "Building in $PWD with CMake for ${IDF_TARGET}" + if [[ ${IDF_TARGET} != "esp32" ]] + then + # IDF 4.0 doesn't support idf.py set-target, and only supports esp32. + idf.py set-target "${IDF_TARGET}" + fi + idf.py build || die "CMake build in ${PWD} has failed for ${IDF_TARGET}" + idf.py fullclean + done +} + +function build_folders +{ + pushd "$1" + EXAMPLES=$(find . -maxdepth 1 -mindepth 1 -type d | cut -d '/' -f 2) + for NAME in ${EXAMPLES} + do + cd "${NAME}" + build_for_targets "$2" + cd .. + done + popd +} + +echo "${STARS}" +# Build the tests +build_folders test/serial "${TEST_TARGETS}" +echo "${STARS}" +# Build the tests +build_folders test/tcp "${TEST_TARGETS}" +echo "${STARS}" diff --git a/components/freemodbus/common/esp_modbus_callbacks.h b/components/freemodbus/common/esp_modbus_callbacks.h deleted file mode 100644 index d9875a6266..0000000000 --- a/components/freemodbus/common/esp_modbus_callbacks.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -// Stack callback functions prototypes - -#ifndef _ESP_MODBUS_CALLBACKS_H_ -#define _ESP_MODBUS_CALLBACKS_H_ - -#include "mb.h" -#include "mb_m.h" - -typedef eMBErrorCode (*reg_input_cb)(UCHAR*, USHORT, USHORT); -typedef eMBErrorCode (*reg_holding_cb)(UCHAR*, USHORT, USHORT, eMBRegisterMode); -typedef eMBErrorCode (*reg_coils_cb)(UCHAR*, USHORT, USHORT, eMBRegisterMode); -typedef eMBErrorCode (*reg_discrete_cb)(UCHAR*, USHORT, USHORT); - -#endif /* _ESP_MODBUS_CALLBACKS_H_ */ diff --git a/components/freemodbus/common/esp_modbus_master_tcp.c b/components/freemodbus/common/esp_modbus_master_tcp.c deleted file mode 100644 index 4bedfe84d6..0000000000 --- a/components/freemodbus/common/esp_modbus_master_tcp.c +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "esp_err.h" // for esp_err_t -#include "esp_modbus_master.h" // for public interface defines -#include "mbc_tcp_master.h" // for public interface defines - -/** - * Initialization of Modbus TCP Master controller interface - */ -esp_err_t mbc_master_init_tcp(void** handler) -{ - void* port_handler = NULL; - esp_err_t error = mbc_tcp_master_create(&port_handler); - - if ((port_handler != NULL) && (error == ESP_OK)) { - mbc_master_init_iface(port_handler); - *handler = port_handler; - } - return error; -} diff --git a/components/freemodbus/common/esp_modbus_slave_tcp.c b/components/freemodbus/common/esp_modbus_slave_tcp.c deleted file mode 100644 index 238469045f..0000000000 --- a/components/freemodbus/common/esp_modbus_slave_tcp.c +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "esp_err.h" // for esp_err_t -#include "esp_modbus_slave.h" // for public slave defines -#include "mbc_tcp_slave.h" // for public interface defines - -/** - * Initialization of Modbus TCP Slave controller - */ -esp_err_t mbc_slave_init_tcp(void** handler) -{ - void* port_handler = NULL; - esp_err_t error = mbc_tcp_slave_create(&port_handler); - - if ((port_handler != NULL) && (error == ESP_OK)) { - mbc_slave_init_iface(port_handler); - *handler = port_handler; - } - return error; -} diff --git a/components/freemodbus/component.mk b/components/freemodbus/component.mk index c90a5dc720..14d428f024 100644 --- a/components/freemodbus/component.mk +++ b/components/freemodbus/component.mk @@ -1,14 +1,26 @@ -COMPONENT_ADD_INCLUDEDIRS := common/include -COMPONENT_PRIV_INCLUDEDIRS := common port modbus modbus/ascii modbus/functions -COMPONENT_PRIV_INCLUDEDIRS += modbus/rtu modbus/tcp modbus/include -COMPONENT_PRIV_INCLUDEDIRS += serial_slave/port serial_slave/modbus_controller -COMPONENT_PRIV_INCLUDEDIRS += serial_master/port serial_master/modbus_controller -COMPONENT_PRIV_INCLUDEDIRS += tcp_slave/port tcp_slave/modbus_controller -COMPONENT_PRIV_INCLUDEDIRS += tcp_master/port tcp_master/modbus_controller -COMPONENT_SRCDIRS := common -COMPONENT_SRCDIRS += modbus modbus/ascii modbus/functions modbus/rtu modbus/tcp -COMPONENT_SRCDIRS += serial_slave/port serial_slave/modbus_controller -COMPONENT_SRCDIRS += serial_master/port serial_master/modbus_controller -COMPONENT_SRCDIRS += tcp_slave/port tcp_slave/modbus_controller -COMPONENT_SRCDIRS += tcp_master/port tcp_master/modbus_controller -COMPONENT_SRCDIRS += port +INCLUDEDIRS := common/include +PRIV_INCLUDEDIRS := common port modbus modbus/ascii modbus/functions +PRIV_INCLUDEDIRS += modbus/rtu modbus/tcp modbus/include +PRIV_INCLUDEDIRS += serial_slave/port serial_slave/modbus_controller +PRIV_INCLUDEDIRS += serial_master/port serial_master/modbus_controller +PRIV_INCLUDEDIRS += tcp_slave/port tcp_slave/modbus_controller +PRIV_INCLUDEDIRS += tcp_master/port tcp_master/modbus_controller +SRCDIRS := common +SRCDIRS += modbus modbus/ascii modbus/functions modbus/rtu modbus/tcp +SRCDIRS += serial_slave/port serial_slave/modbus_controller +SRCDIRS += serial_master/port serial_master/modbus_controller +SRCDIRS += tcp_slave/port tcp_slave/modbus_controller +SRCDIRS += tcp_master/port tcp_master/modbus_controller +SRCDIRS += port + +COMPONENT_PRIV_INCLUDEDIRS = $(addprefix freemodbus/, \ + $(PRIV_INCLUDEDIRS) \ + ) + +COMPONENT_SRCDIRS = $(addprefix freemodbus/, \ + $(SRCDIRS) \ + ) + +COMPONENT_ADD_INCLUDEDIRS = $(addprefix freemodbus/, \ + $(INCLUDEDIRS) \ + ) diff --git a/components/freemodbus/docs/Doxyfile b/components/freemodbus/docs/Doxyfile new file mode 100644 index 0000000000..c3c933c75e --- /dev/null +++ b/components/freemodbus/docs/Doxyfile @@ -0,0 +1,57 @@ +# This is Doxygen configuration file +# +# Doxygen provides over 260 configuration statements +# To make this file easier to follow, +# it contains only statements that are non-default +# +# NOTE: +# It is recommended not to change defaults unless specifically required +# Test any changes how they affect generated documentation +# Make sure that correct warnings are generated to flag issues with documented code +# +# For the complete list of configuration statements see: +# http://doxygen.nl/manual/config.html + + +PROJECT_NAME = "IDF 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 \ + +## Get warnings for functions that have no documentation for their parameters or return value +## +WARN_NO_PARAMDOC = YES + +## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files +## +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +PREDEFINED = \ + $(ENV_DOXYGEN_DEFINES) \ + +## Do not complain about not having dot +## +HAVE_DOT = NO + +## Generate XML that is required for Breathe +## +GENERATE_XML = YES +XML_OUTPUT = xml + +GENERATE_HTML = NO +HAVE_DOT = NO +GENERATE_LATEX = NO +GENERATE_MAN = YES +GENERATE_RTF = NO + +## Skip distracting progress messages +## +QUIET = YES diff --git a/components/freemodbus/docs/README.md b/components/freemodbus/docs/README.md new file mode 100644 index 0000000000..daec0623dc --- /dev/null +++ b/components/freemodbus/docs/README.md @@ -0,0 +1,11 @@ +# ESP-Modbus Library + +This folder represents the official documentation for the ESP-Modbus library (**esp-modbus component documentation**). The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). The Modbus has become a de facto standard communication protocol and is now a commonly available means of connecting industrial electronic devices. This library supports Modbus communication in the networks that are based on RS485 or Ethernet interfaces. + +# Hosted Documentation + +* English: https://docs.espressif.com/projects/esp-modbus/ + +# Building Documentation + +The documentation is built using the python package `esp-docs`, which can be installed by running `pip install esp-docs`. Running `build-docs --help` will give a summary of available options. For more information see the `esp-docs` documentation at https://github.com/espressif/esp-docs/blob/master/README.md \ No newline at end of file diff --git a/components/freemodbus/docs/_static/404-page__en.svg b/components/freemodbus/docs/_static/404-page__en.svg new file mode 100644 index 0000000000..928ec63115 --- /dev/null +++ b/components/freemodbus/docs/_static/404-page__en.svg @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/freemodbus/docs/_static/modbus-data-mapping.png b/components/freemodbus/docs/_static/modbus-data-mapping.png new file mode 100644 index 0000000000..18fd2cf4fe Binary files /dev/null and b/components/freemodbus/docs/_static/modbus-data-mapping.png differ diff --git a/components/freemodbus/docs/_static/modbus-segment.png b/components/freemodbus/docs/_static/modbus-segment.png new file mode 100644 index 0000000000..80fb6bfe6a Binary files /dev/null and b/components/freemodbus/docs/_static/modbus-segment.png differ diff --git a/components/freemodbus/docs/_static/modbus_docs_versions.js b/components/freemodbus/docs/_static/modbus_docs_versions.js new file mode 100644 index 0000000000..2827286f27 --- /dev/null +++ b/components/freemodbus/docs/_static/modbus_docs_versions.js @@ -0,0 +1,16 @@ +var DOCUMENTATION_VERSIONS = { + DEFAULTS: { has_targets: true, + supported_targets: [ "esp32", "esp32s2", "esp32s3", "esp32c3" ] + }, + VERSIONS: [ + { name: "latest" }, + { name: "v1.0.1", old:false }, + { name: "v1.0.0", old:true } + ], + IDF_TARGETS: [ + { text: "ESP32", value: "esp32"}, + { text: "ESP32-S2", value: "esp32s2"}, + { text: "ESP32-S3", value: "esp32s3"}, + { text: "ESP32-C3", value: "esp32c3"} + ] +}; diff --git a/components/freemodbus/docs/conf_common.py b/components/freemodbus/docs/conf_common.py new file mode 100644 index 0000000000..896d2c5c38 --- /dev/null +++ b/components/freemodbus/docs/conf_common.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# Common (non-language-specific) configuration for Sphinx +# + +# type: ignore +# pylint: disable=wildcard-import +# pylint: disable=undefined-variable + +from __future__ import print_function, unicode_literals + +from esp_docs.conf_docs import * # noqa: F403,F401 + +extensions += ['sphinx_copybutton', + # Needed as a trigger for running doxygen + 'esp_docs.esp_extensions.dummy_build_system', + 'esp_docs.esp_extensions.run_doxygen' + ] + +# link roles config +github_repo = 'espressif/esp-modbus' + +# context used by sphinx_idf_theme +html_context['github_user'] = 'espressif' +html_context['github_repo'] = 'esp-modbus' +html_static_path = ['../_static'] + +# Extra options required by sphinx_idf_theme +project_slug = 'esp-modbus' +versions_url = './_static/modbus_docs_versions.js' + +idf_targets = ['esp32', 'esp32s2', 'esp32c3'] +languages = ['en'] diff --git a/components/freemodbus/docs/en/applications_and_references.rst b/components/freemodbus/docs/en/applications_and_references.rst new file mode 100644 index 0000000000..ca5dbf192c --- /dev/null +++ b/components/freemodbus/docs/en/applications_and_references.rst @@ -0,0 +1,72 @@ +Possible Communication Issues And Solutions +------------------------------------------- + +If the examples do not work as expected and slave and master boards are not able to communicate correctly, it is possible to find the reason for errors. The most important errors are described in master example output and formatted as below: + +.. highlight:: none + +:: + + E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT). + + +.. list-table:: Table 5 Modbus error codes and troubleshooting + :widths: 5 30 65 + :header-rows: 1 + + * - Error + - Description + - Possible solution + * - 0x106 + - ``ESP_ERR_NOT_SUPPORTED`` - Invalid register request - slave returned an exception because the requested register is not supported. + - Refer to slave register map. Check the master data dictionary for correctness. + * - 0x107 + - ``ESP_ERR_TIMEOUT`` - Slave response timeout - Modbus slave did not send response during configured slave response timeout. + - Measure and increase the maximum slave response timeout `idf.py menuconfig`, option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND``. + Check physical connection or network configuration and make sure that the slave response can reach the master side. + If the application has some high performance tasks with higher priority than ``CONFIG_FMB_PORT_TASK_PRIO`` it is recommended to place Modbus tasks on the other core using an option ``CONFIG_FMB_PORT_TASK_AFFINITY``. + Configure the Modbus task's priority ``CONFIG_FMB_PORT_TASK_PRIO`` to ensure that the task gets sufficient processing time to handle Modbus stack events. + * - 0x108 + - ``ESP_ERR_INVALID_RESPONSE`` - Received unsupported response from slave or frame check failure. Master can not execute command handler because the command is either not supported or is incorrect. + - Check the physical connection then refer to register map of your slave to configure the master data dictionary properly. + * - 0x103 + - ``ESP_ERR_INVALID_STATE`` - Critical failure or FSM sequence failure or master FSM is busy processing previous request. + - Make sure your physical connection is working properly. Increase task stack size and check Modbus initialization sequence. + +Application Example +------------------- + +The examples below use the FreeModbus library port for serial TCP slave and master implementations accordingly. The selection of stack is performed through KConfig menu option "Enable Modbus stack support ..." for appropriate communication mode and related configuration keys. + +.. _example_mb_slave: + +- `Modbus serial slave example `__ + +.. _example_mb_master: + +- `Modbus serial master example `__ + +.. _example_mb_tcp_master: + +- `Modbus TCP master example `__ + +.. _example_mb_tcp_slave: + +- `Modbus TCP slave example `__ + +Please refer to the specific example README.md for details. + +.. _modbus_organization: + +Protocol References +------------------- + + - `Modbus Organization with protocol specifications `__ + +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 + diff --git a/components/freemodbus/docs/en/conf.py b/components/freemodbus/docs/en/conf.py new file mode 100644 index 0000000000..90f2495f6f --- /dev/null +++ b/components/freemodbus/docs/en/conf.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# English Language RTD & Sphinx config file +# +# Uses ../conf_common.py for most non-language-specific settings. + +# Importing conf_common adds all the non-language-specific +# parts to this conf module +try: + from conf_common import * # noqa: F403,F401 +except ImportError: + import os + import sys + sys.path.insert(0, os.path.abspath('../')) + from conf_common import * # noqa: F403,F401 + +import datetime + +current_year = datetime.datetime.now().year + +# General information about the project. +project = u'ESP-Modbus Programming Guide' +copyright = u'2019 - {}, Espressif Systems (Shanghai) Co., Ltd'.format(current_year) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' diff --git a/components/freemodbus/docs/en/index.rst b/components/freemodbus/docs/en/index.rst new file mode 100644 index 0000000000..a392044166 --- /dev/null +++ b/components/freemodbus/docs/en/index.rst @@ -0,0 +1,14 @@ +ESP-Modbus Library +================== + +An Espressif ESP-Modbus Library (esp-modbus) is a library to support Modbus communication in the networks based on RS485 or Ethernet interfaces. +The Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). + +.. toctree:: + :maxdepth: 1 + + The Overview, Messaging Model And Data Mapping + Modbus Port Initialization + Modbus Master API + Modbus Slave API + Applications and References \ No newline at end of file diff --git a/components/freemodbus/docs/en/master_api_overview.rst b/components/freemodbus/docs/en/master_api_overview.rst new file mode 100644 index 0000000000..ba49481d12 --- /dev/null +++ b/components/freemodbus/docs/en/master_api_overview.rst @@ -0,0 +1,300 @@ +.. _modbus_api_master_overview: + +Modbus Master API Overview +-------------------------- + +The following overview describes how to setup Modbus master communication. The overview reflects a typical programming workflow and is broken down into the sections provided below: + +1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface for the selected port. +2. :ref:`modbus_api_master_configure_descriptor` - Configure data descriptors to access slave parameters. +3. :ref:`modbus_api_master_setup_communication_options` - Allows to setup communication options for selected port. +4. :ref:`modbus_api_master_start_communication` - Start stack and sending / receiving data. +5. :ref:`modbus_api_master_destroy` - Destroy Modbus controller and its resources. + +.. _modbus_api_master_configure_descriptor: + +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 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: + +.. list-table:: Table 1 Modbus master Data Dictionary description + :widths: 8 10 82 + :header-rows: 1 + + * - Field + - Description + - Detailed information + * - ``cid`` + - Characteristic ID + - The identifier of characteristic (must be unique). + * - ``param_key`` + - Characteristic Name + - String description of the characteristic. + * - ``param_units`` + - Characteristic Units + - Physical Units of the characteristic. + * - ``mb_slave_addr`` + - Modbus Slave Address + - The short address of the device with correspond parameter UID. + * - ``mb_param_type`` + - Modbus Register Type + - Type of Modbus register area. + :cpp:enumerator:`MB_PARAM_INPUT`, :cpp:enumerator:`MB_PARAM_HOLDING`, :cpp:enumerator:`MB_PARAM_COIL`, :cpp:enumerator:`MB_PARAM_DISCRETE` - represents Input , Holding, Coil and Discrete input register area accordingly; + * - ``mb_reg_start`` + - Modbus Register Start + - Relative register address of the characteristic in the register area. + * - ``mb_size`` + - Modbus Register Size + - Length of characteristic in registers. + * - ``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; + * - ``param_size`` + - Data Size + - The storage size of the characteristic (bytes). + * - ``param_opts`` + - Parameter Options + - Limits, options of characteristic used during processing of alarm in user application (optional) + * - ``access`` + - Parameter access type + - Can be used in user application to define the behavior of the characteristic during processing of data in user application; + :cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`, :cpp:enumerator:`PAR_PERMS_READ`, :cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`; + +.. 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. + +.. list-table:: Table 2 Example Register mapping table of Modbus slave + :widths: 5 5 2 10 5 5 68 + :header-rows: 1 + + * - CID + - Register + - Length + - Range + - Type + - Units + - Description + * - 0 + - 30000 + - 4 + - MAX_UINT + - U32 + - Not defined + - Serial number of device (4 bytes) read-only + * - 1 + - 30002 + - 2 + - MAX_UINT + - U16 + - Not defined + - Software version (4 bytes) read-only + * - 2 + - 40000 + - 4 + - -20..40 + - FLOAT + - DegC + - Room temperature in DegC. Writing a temperature value to this register for single point calibration. + +.. code:: c + + // Enumeration of modbus slave addresses accessed by master device + enum { + MB_DEVICE_ADDR1 = 1, + MB_DEVICE_ADDR2, + MB_SLAVE_COUNT + }; + + // Enumeration of all supported CIDs for device + enum { + CID_SER_NUM1 = 0, + CID_SW_VER1, + CID_TEMP_DATA_1, + CID_SER_NUM2, + CID_SW_VER2, + CID_TEMP_DATA_2 + }; + + // Example Data Dictionary for Modbus parameters in 2 slaves in the segment + 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_SER_NUM1, STR("Serial_number_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, + 0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_SW_VER1, STR("Software_version_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 1, + 0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_TEMP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, + 0, PARAM_TYPE_FLOAT, 4, OPTS( 16, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_SER_NUM2, STR("Serial_number_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 0, 2, + 0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_SW_VER2, STR("Software_version_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 2, 1, + 0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_TEMP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 0, 2, + 0, PARAM_TYPE_FLOAT, 4, OPTS( 20, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + }; + // Calculate number of parameters in the table + uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0])); + +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. + +.. code:: c + + ESP_ERROR_CHECK(mbc_master_set_descriptor(&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. + +.. _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: + +Master Communication +^^^^^^^^^^^^^^^^^^^^ + +The starting of the Modbus controller is the final step in enabling communication. This is performed using function below: + +:cpp:func:`mbc_master_start` + +.. code:: c + + esp_err_t err = mbc_master_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "mb controller start fail, err=%x.", err); + } + +The list of functions below are used by the Modbus master stack from a user's application: + +:cpp:func:`mbc_master_send_request`: This function executes a blocking Modbus request. The master sends a data request (as defined in parameter request structure :cpp:type:`mb_param_request_t`) and then blocks until a response from corresponding slave and returns the status of command execution. This function provides a standard way for read/write access to Modbus devices in the network. + +:cpp:func:`mbc_master_get_cid_info`: The function gets information about each characteristic supported in the data dictionary and returns the characteristic's description in the form of the :cpp:type:`mb_parameter_descriptor_t` structure. Each characteristic is accessed using its CID. + +:cpp:func:`mbc_master_get_parameter`: The function reads the data of a characteristic defined in the parameters of a Modbus slave device. The additional data for request is taken from parameter description table. + +Example: + +.. code:: c + + const mb_parameter_descriptor_t* param_descriptor = NULL; + uint8_t temp_data[4] = {0}; // temporary buffer to hold maximum CID size + uint8_t type = 0; + .... + + // Get the information for characteristic cid from data dictionary + esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); + 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.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint32_t*)temp_data); + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } else { + 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. + +.. code:: c + + uint8_t type = 0; // Type of parameter + uint8_t temp_data[4] = {0}; // temporary buffer + + esp_err_t err = mbc_master_set_parameter(CID_TEMP_DATA_2, "Temperature_2", (uint8_t*)temp_data, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Set parameter data successfully."); + } else { + 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 +^^^^^^^^^^^^^^^^^^^^^^ + +This function stops Modbus communication stack and destroys controller interface and free all used active objects. + +:cpp:func:`mbc_master_destroy` + +.. code:: c + + ESP_ERROR_CHECK(mbc_master_destroy()); diff --git a/components/freemodbus/docs/en/overview_messaging_and_mapping.rst b/components/freemodbus/docs/en/overview_messaging_and_mapping.rst new file mode 100644 index 0000000000..f62ca32ba8 --- /dev/null +++ b/components/freemodbus/docs/en/overview_messaging_and_mapping.rst @@ -0,0 +1,47 @@ +ESP-Modbus +========== + +Overview +-------- + +The Modbus serial communication protocol is de facto standard protocol widely used to connect industrial electronic devices. Modbus allows communication among many devices connected to the same network, for example, a system that measures temperature and humidity and communicates the results to a computer. The Modbus protocol uses several types of data: Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs. Versions of the Modbus protocol exist for serial port and for Ethernet and other protocols that support the Internet protocol suite. There are many variants of Modbus protocols, some of them are: + + * ``Modbus RTU`` — This is used in serial communication and makes use of a compact, binary representation of the data for protocol communication. The RTU format follows the commands/data with a cyclic redundancy check checksum as an error check mechanism to ensure the reliability of data. Modbus RTU is the most common implementation available for Modbus. A Modbus RTU message must be transmitted continuously without inter-character hesitations. Modbus messages are framed (separated) by idle (silent) periods. The RS-485 interface communication is usually used for this type. + * ``Modbus ASCII`` — This is used in serial communication and makes use of ASCII characters for protocol communication. The ASCII format uses a longitudinal redundancy check checksum. Modbus ASCII messages are framed by leading colon (":") and trailing newline (CR/LF). + * ``Modbus TCP/IP or Modbus TCP`` — This is a Modbus variant used for communications over TCP/IP networks, connecting over port 502. It does not require a checksum calculation, as lower layers already provide checksum protection. + +.. note:: This documentation (and included code snippets) requires some familiarity with the Modbus protocol. Refer to the Modbus Organization's with protocol specifications for specifics :ref:`modbus_organization`. + +Messaging Model And Data Mapping +-------------------------------- + +Modbus is an application protocol that defines rules for messaging structure and data organization that are independent of the data transmission medium. Traditional serial Modbus is a register-based protocol that defines message transactions that occur between master(s) and slave devices (multiple masters are allowed on using Modbus TCP/IP). The slave devices listen for communication from the master and simply respond as instructed. The master(s) always controls communication and may communicate directly to one slave, or all connected slaves, but the slaves cannot communicate directly with each other. + +.. figure:: ../_static/modbus-segment.png + :align: center + :scale: 80% + :alt: Modbus segment diagram + :figclass: align-center + + Modbus segment diagram + +.. note:: It is assumed that the number of slaves and their register maps are known by the Modbus master before the start of stack. + +The register map of each slave device is usually part of its device manual. A Slave device usually permits configuration of its short slave address and communication options that are used within the device's network segment. + +The Modbus protocol allows devices to map data to four types of registers (Holding, Input, Discrete, Coil). The figure below illustrates an example mapping of a device's data to the four types of registers. + +.. figure:: ../_static/modbus-data-mapping.png + :align: center + :scale: 80% + :alt: Modbus data mapping + :figclass: align-center + + Modbus data mapping + +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/components/freemodbus/docs/en/port_initialization.rst b/components/freemodbus/docs/en/port_initialization.rst new file mode 100644 index 0000000000..668c5f445e --- /dev/null +++ b/components/freemodbus/docs/en/port_initialization.rst @@ -0,0 +1,35 @@ +.. _modbus_api_port_initialization: + +Modbus Port Initialization +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ESP_Modbus supports Modbus SERIAL and TCP ports and a port must be initialized before calling any other Modbus API. The functions below are used to create and then initialize Modbus controller interface (either master or slave) over a particular transmission medium (either Serial or TCP/IP): + +- :cpp:func:`mbc_slave_init` +- :cpp:func:`mbc_master_init` +- :cpp:func:`mbc_slave_init_tcp` +- :cpp:func:`mbc_master_init_tcp` + +The API call uses the first parameter to recognize the type of port being initialized. Supported enumeration for different ports: :cpp:enumerator:`MB_PORT_SERIAL_MASTER`, :cpp:enumerator:`MB_PORT_SERIAL_SLAVE` accordingly. +The parameters :cpp:enumerator:`MB_PORT_TCP_MASTER`, :cpp:enumerator:`MB_PORT_TCP_SLAVE` are reserved for internal usage. + +.. code:: c + + void* master_handler = NULL; // Pointer to allocate interface structure + // Initialization of Modbus master for serial port + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + if (master_handler == NULL || err != ESP_OK) { + ESP_LOGE(TAG, "mb controller initialization fail."); + } + +This example code to initialize slave port: + +.. code:: c + + void* slave_handler = NULL; // Pointer to allocate interface structure + // Initialization of Modbus slave for TCP + esp_err_t err = mbc_slave_init_tcp(&slave_handler); + if (slave_handler == NULL || err != ESP_OK) { + // Error handling is performed here + ESP_LOGE(TAG, "mb controller initialization fail."); + } \ No newline at end of file diff --git a/components/freemodbus/docs/en/slave_api_overview.rst b/components/freemodbus/docs/en/slave_api_overview.rst new file mode 100644 index 0000000000..bc46d0dbb8 --- /dev/null +++ b/components/freemodbus/docs/en/slave_api_overview.rst @@ -0,0 +1,215 @@ +.. _modbus_api_slave_overview: + +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. +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. +5. :ref:`modbus_api_slave_destroy` - Destroy Modbus controller and its resources. + +.. _modbus_api_slave_configure_descriptor: + +Configuring Slave Data Access +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following functions must be called when the Modbus controller slave port is already initialized. Refer to :ref:`modbus_api_port_initialization`. + +The slave stack requires the user to define structures (memory storage areas) that store the Modbus parameters accessed by stack. These structures should be prepared by the user and be assigned to the Modbus controller interface using :cpp:func:`mbc_slave_set_descriptor` API call before the start of communication. The slave task can call the :cpp:func:`mbc_slave_check_event` function which will block until the Modbus master access the slave. The slave task can then get information about the data being accessed. + +.. note:: One slave can define several area descriptors per each type of Modbus register area with different start_offset. + +Register area is defined by using the :cpp:type:`mb_register_area_descriptor_t` structure. + +.. list-table:: Table 3 Modbus register area descriptor + :widths: 8 92 + :header-rows: 1 + + * - Field + - Description + * - ``start_offset`` + - Zero based register relative offset for defined register area. Example: register address = 40002 ( 4x register area - Function 3 - holding register ), start_offset = 2 + * - ``type`` + - Type of the Modbus register area. Refer to :cpp:type:`mb_param_type_t` for more information. + * - ``address`` + - A pointer to the memory area which is used to store the register data for this area descriptor. + * - ``size`` + - The size of the memory area in bytes which is used to store register data. + +:cpp:func:`mbc_slave_set_descriptor` + +The function initializes Modbus communication descriptors for each type of Modbus register area (Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs). Once areas are initialized and the :cpp:func:`mbc_slave_start()` API is called the Modbus stack can access the data in user data structures by request from master. + +.. code:: c + + #define MB_REG_INPUT_START_AREA0 (0) + #define MB_REG_HOLDING_START_AREA0 (0) + #define MB_REG_HOLD_CNT (100) + #define MB_REG_INPUT_CNT (100) + + mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure + unit16_t holding_reg_area[MB_REG_HOLD_CNT] = {0}; // storage area for holding registers + unit16_t input_reg_area[MB_REG_INPUT_CNT] = {0}; // storage area for input registers + + reg_area.type = MB_PARAM_HOLDING; // Set type of register area + reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol + reg_area.address = (void*)&holding_reg_area[0]; // Set pointer to storage instance + reg_area.size = sizeof(holding_reg_area) << 1; // Set the size of register storage area in bytes + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + reg_area.type = MB_PARAM_INPUT; + reg_area.start_offset = MB_REG_INPUT_START_AREA0; + reg_area.address = (void*)&input_reg_area[0]; + reg_area.size = sizeof(input_reg_area) << 1; + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + +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. + +Direct access to register area from user application must be protected by critical section: + +.. code:: c + + portENTER_CRITICAL(¶m_lock); + holding_reg_area[2] += 10; + portEXIT_CRITICAL(¶m_lock); + + +.. _modbus_api_slave_setup_communication_options: + +Slave Communication Options +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The function initializes the Modbus controller interface and its active context (tasks, RTOS objects and other resources). + +:cpp:func:`mbc_slave_setup` + +The function is used to setup communication parameters of the Modbus stack. + +Example initialization of Modbus TCP communication: + +.. code:: c + + esp_netif_init(); + ... + + 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 = NULL, // This field keeps the client IP address to bind, NULL - bind to any client + .ip_netif_ptr = esp_netif_ptr // esp_netif_ptr - pointer to the corresponding network interface + }; + + // Setup communication parameters and start stack + ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); + +Example initialization of Modbus serial communication: + +.. code:: c + + #define MB_SLAVE_DEV_SPEED 9600 + #define MB_SLAVE_ADDR 1 + #define MB_SLAVE_PORT_NUM 2 + ... + + // Setup communication parameters and start stack + mb_communication_info_t comm_info = { + .mode = MB_MODE_RTU, // Communication type + .slave_addr = MB_SLAVE_ADDR, // Short address of the slave + .port = MB_SLAVE_PORT_NUM, // UART physical port number + .baudrate = MB_SLAVE_DEV_SPEED, // Baud rate for communication + .parity = MB_PARITY_NONE // Parity option + }; + + ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); + +.. _modbus_api_slave_communication: + +Slave Communication +^^^^^^^^^^^^^^^^^^^ + +The function below is used to start Modbus controller interface and allows communication. + +:cpp:func:`mbc_slave_start` + +.. code:: c + + ESP_ERROR_CHECK(mbc_slave_start()); + +:cpp:func:`mbc_slave_check_event` + +The blocking call to function waits for a event specified (represented as an event mask parameter). Once the master accesses the parameter and the event mask matches the parameter type, the application task will be unblocked and function will return the corresponding event :cpp:type:`mb_event_group_t` which describes the type of register access being done. + +:cpp:func:`mbc_slave_get_param_info` + +The function gets information about accessed parameters from the Modbus controller event queue. The KConfig ``CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE`` key can be used to configure the notification queue size. The timeout parameter allows a timeout to be specified when waiting for a notification. The :cpp:type:`mb_param_info_t` structure contains information about accessed parameter. + +.. list-table:: Table 4 Description of the register info structure: :cpp:type:`mb_param_info_t` + :widths: 10 90 + :header-rows: 1 + + * - Field + - Description + * - ``time_stamp`` + - the time stamp of the event when defined parameter is accessed + * - ``mb_offset`` + - start Modbus register accessed by master + * - ``type`` + - type of the Modbus register area being accessed (See the :cpp:type:`mb_event_group_t` for more information) + * - ``address`` + - memory address that corresponds to accessed register in defined area descriptor + * - ``size`` + - number of registers being accessed by master + +Example to get event when holding or input registers accessed in the slave: + +.. code:: c + + #define MB_READ_MASK (MB_EVENT_INPUT_REG_RD | MB_EVENT_HOLDING_REG_RD) + #define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR) + #define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK) + #define MB_PAR_INFO_GET_TOUT (10 / portTICK_RATE_MS) + .... + + // The function blocks while waiting for register access + mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK); + + // Get information about data accessed from master + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE"; + + // Filter events and process them accordingly + if (event & (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, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } else if (event & (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, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } + +.. _modbus_api_slave_destroy: + +Modbus Slave Teardown +^^^^^^^^^^^^^^^^^^^^^ + +This function stops the Modbus communication stack, destroys the controller interface, and frees all used active objects allocated for the slave. + +:cpp:func:`mbc_slave_destroy` + +.. code:: c + + ESP_ERROR_CHECK(mbc_slave_destroy()); \ No newline at end of file diff --git a/components/freemodbus/docs/requirements.txt b/components/freemodbus/docs/requirements.txt new file mode 100644 index 0000000000..32df3ba804 --- /dev/null +++ b/components/freemodbus/docs/requirements.txt @@ -0,0 +1 @@ +esp-docs==0.2.4 diff --git a/components/freemodbus/docs/utils.sh b/components/freemodbus/docs/utils.sh new file mode 100644 index 0000000000..84f3748997 --- /dev/null +++ b/components/freemodbus/docs/utils.sh @@ -0,0 +1,18 @@ +# Bash helper functions for adding SSH keys + +function add_ssh_keys() { + local key_string="${1}" + mkdir -p ~/.ssh + chmod 700 ~/.ssh + echo -n "${key_string}" >~/.ssh/id_rsa_base64 + base64 --decode --ignore-garbage ~/.ssh/id_rsa_base64 >~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa +} + +function add_doc_server_ssh_keys() { + local key_string="${1}" + local server_url="${2}" + local server_user="${3}" + add_ssh_keys "${key_string}" + echo -e "Host ${server_url}\n\tStrictHostKeyChecking no\n\tUser ${server_user}\n" >>~/.ssh/config +} diff --git a/components/freemodbus/freemodbus/common/esp_modbus_callbacks.h b/components/freemodbus/freemodbus/common/esp_modbus_callbacks.h new file mode 100644 index 0000000000..6bcd15f43c --- /dev/null +++ b/components/freemodbus/freemodbus/common/esp_modbus_callbacks.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Stack callback functions prototypes + +#ifndef _ESP_MODBUS_CALLBACKS_H_ +#define _ESP_MODBUS_CALLBACKS_H_ + +#include "mb.h" +#include "mb_m.h" + +typedef eMBErrorCode (*reg_input_cb)(UCHAR*, USHORT, USHORT); +typedef eMBErrorCode (*reg_holding_cb)(UCHAR*, USHORT, USHORT, eMBRegisterMode); +typedef eMBErrorCode (*reg_coils_cb)(UCHAR*, USHORT, USHORT, eMBRegisterMode); +typedef eMBErrorCode (*reg_discrete_cb)(UCHAR*, USHORT, USHORT); + +#endif /* _ESP_MODBUS_CALLBACKS_H_ */ diff --git a/components/freemodbus/common/esp_modbus_master.c b/components/freemodbus/freemodbus/common/esp_modbus_master.c similarity index 93% rename from components/freemodbus/common/esp_modbus_master.c rename to components/freemodbus/freemodbus/common/esp_modbus_master.c index bd63c4c348..e3015818f9 100644 --- a/components/freemodbus/common/esp_modbus_master.c +++ b/components/freemodbus/freemodbus/common/esp_modbus_master.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #include "esp_err.h" // for esp_err_t diff --git a/components/freemodbus/common/esp_modbus_master_serial.c b/components/freemodbus/freemodbus/common/esp_modbus_master_serial.c similarity index 56% rename from components/freemodbus/common/esp_modbus_master_serial.c rename to components/freemodbus/freemodbus/common/esp_modbus_master_serial.c index 26179746f9..9ea355bbfb 100644 --- a/components/freemodbus/common/esp_modbus_master_serial.c +++ b/components/freemodbus/freemodbus/common/esp_modbus_master_serial.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #include "esp_err.h" // for esp_err_t diff --git a/components/freemodbus/freemodbus/common/esp_modbus_master_tcp.c b/components/freemodbus/freemodbus/common/esp_modbus_master_tcp.c new file mode 100644 index 0000000000..8350f73255 --- /dev/null +++ b/components/freemodbus/freemodbus/common/esp_modbus_master_tcp.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_err.h" // for esp_err_t +#include "esp_modbus_master.h" // for public interface defines +#include "mbc_tcp_master.h" // for public interface defines + +/** + * Initialization of Modbus TCP Master controller interface + */ +esp_err_t mbc_master_init_tcp(void** handler) +{ + void* port_handler = NULL; + esp_err_t error = mbc_tcp_master_create(&port_handler); + + if ((port_handler != NULL) && (error == ESP_OK)) { + mbc_master_init_iface(port_handler); + *handler = port_handler; + } + return error; +} diff --git a/components/freemodbus/common/esp_modbus_slave.c b/components/freemodbus/freemodbus/common/esp_modbus_slave.c similarity index 96% rename from components/freemodbus/common/esp_modbus_slave.c rename to components/freemodbus/freemodbus/common/esp_modbus_slave.c index 1faff76d90..24e2c59c0e 100644 --- a/components/freemodbus/common/esp_modbus_slave.c +++ b/components/freemodbus/freemodbus/common/esp_modbus_slave.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #include "esp_err.h" // for esp_err_t @@ -78,7 +69,7 @@ static void mbc_slave_free_descriptors(void) { mb_slave_options_t* mbs_opts = &slave_interface_ptr->opts; for (int descr_type = 0; descr_type < MB_PARAM_COUNT; descr_type++) { - for (it = LIST_FIRST(&mbs_opts->mbs_area_descriptors[descr_type]); it != NULL; it = LIST_NEXT(it, entries)) { + while ((it = LIST_FIRST(&mbs_opts->mbs_area_descriptors[descr_type]))) { LIST_REMOVE(it, entries); free(it); } diff --git a/components/freemodbus/common/esp_modbus_slave_serial.c b/components/freemodbus/freemodbus/common/esp_modbus_slave_serial.c similarity index 61% rename from components/freemodbus/common/esp_modbus_slave_serial.c rename to components/freemodbus/freemodbus/common/esp_modbus_slave_serial.c index 89b75f4cdf..eb08dfdc06 100644 --- a/components/freemodbus/common/esp_modbus_slave_serial.c +++ b/components/freemodbus/freemodbus/common/esp_modbus_slave_serial.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #include "esp_err.h" // for esp_err_t diff --git a/components/freemodbus/freemodbus/common/esp_modbus_slave_tcp.c b/components/freemodbus/freemodbus/common/esp_modbus_slave_tcp.c new file mode 100644 index 0000000000..c3b3ee6fcd --- /dev/null +++ b/components/freemodbus/freemodbus/common/esp_modbus_slave_tcp.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_err.h" // for esp_err_t +#include "esp_modbus_slave.h" // for public slave defines +#include "mbc_tcp_slave.h" // for public interface defines + +/** + * Initialization of Modbus TCP Slave controller + */ +esp_err_t mbc_slave_init_tcp(void** handler) +{ + void* port_handler = NULL; + esp_err_t error = mbc_tcp_slave_create(&port_handler); + + if ((port_handler != NULL) && (error == ESP_OK)) { + mbc_slave_init_iface(port_handler); + *handler = port_handler; + } + return error; +} diff --git a/components/freemodbus/common/include/esp_modbus_common.h b/components/freemodbus/freemodbus/common/include/esp_modbus_common.h similarity index 87% rename from components/freemodbus/common/include/esp_modbus_common.h rename to components/freemodbus/freemodbus/common/include/esp_modbus_common.h index 9c65f08b90..f443286ae8 100644 --- a/components/freemodbus/common/include/esp_modbus_common.h +++ b/components/freemodbus/freemodbus/common/include/esp_modbus_common.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef _MB_IFACE_COMMON_H @@ -24,6 +15,7 @@ extern "C" { #if __has_include("esp_check.h") #include "esp_check.h" +#include "esp_log.h" #define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) ESP_RETURN_ON_FALSE(a, err_code, tag, format __VA_OPT__(,) __VA_ARGS__) @@ -44,10 +36,10 @@ extern "C" { #define MB_CONTROLLER_PRIORITY (CONFIG_FMB_PORT_TASK_PRIO - 1) // priority of MB controller task // Default port defines -#define MB_DEVICE_ADDRESS (1) // Default slave device address in Modbus -#define MB_DEVICE_SPEED (115200) // Default Modbus speed for now hard defined +#define MB_DEVICE_ADDRESS (1) // Default slave device address in Modbus +#define MB_DEVICE_SPEED (115200) // Default Modbus speed for now hard defined #define MB_UART_PORT (UART_NUM_MAX - 1) // Default UART port number -#define MB_PAR_INFO_TOUT (10) // Timeout for get parameter info +#define MB_PAR_INFO_TOUT (10) // Timeout for get parameter info #define MB_PARITY_NONE (UART_PARITY_DISABLE) // The Macros below handle the endianness while transfer N byte data into buffer diff --git a/components/freemodbus/common/include/esp_modbus_master.h b/components/freemodbus/freemodbus/common/include/esp_modbus_master.h similarity index 95% rename from components/freemodbus/common/include/esp_modbus_master.h rename to components/freemodbus/freemodbus/common/include/esp_modbus_master.h index 8084e68902..d11ade7a4d 100644 --- a/components/freemodbus/common/include/esp_modbus_master.h +++ b/components/freemodbus/freemodbus/common/include/esp_modbus_master.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef _ESP_MB_MASTER_INTERFACE_H diff --git a/components/freemodbus/common/include/esp_modbus_slave.h b/components/freemodbus/freemodbus/common/include/esp_modbus_slave.h similarity index 88% rename from components/freemodbus/common/include/esp_modbus_slave.h rename to components/freemodbus/freemodbus/common/include/esp_modbus_slave.h index 040d18265b..7d79b513a6 100644 --- a/components/freemodbus/common/include/esp_modbus_slave.h +++ b/components/freemodbus/freemodbus/common/include/esp_modbus_slave.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef _ESP_MB_SLAVE_INTERFACE_H diff --git a/components/freemodbus/common/include/mbcontroller.h b/components/freemodbus/freemodbus/common/include/mbcontroller.h similarity index 51% rename from components/freemodbus/common/include/mbcontroller.h rename to components/freemodbus/freemodbus/common/include/mbcontroller.h index 08b3c183c8..10205f8951 100644 --- a/components/freemodbus/common/include/mbcontroller.h +++ b/components/freemodbus/freemodbus/common/include/mbcontroller.h @@ -1,17 +1,8 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ + * SPDX-License-Identifier: Apache-2.0 + */ // mbcontroller.h // mbcontroller - common Modbus controller header file diff --git a/components/freemodbus/common/mbc_master.h b/components/freemodbus/freemodbus/common/mbc_master.h similarity index 89% rename from components/freemodbus/common/mbc_master.h rename to components/freemodbus/freemodbus/common/mbc_master.h index 086270385f..4ffe4ffcec 100644 --- a/components/freemodbus/common/mbc_master.h +++ b/components/freemodbus/freemodbus/common/mbc_master.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef _MB_CONTROLLER_MASTER_H diff --git a/components/freemodbus/common/mbc_slave.h b/components/freemodbus/freemodbus/common/mbc_slave.h similarity index 87% rename from components/freemodbus/common/mbc_slave.h rename to components/freemodbus/freemodbus/common/mbc_slave.h index c0e16b544c..5b54038d1d 100644 --- a/components/freemodbus/common/mbc_slave.h +++ b/components/freemodbus/freemodbus/common/mbc_slave.h @@ -1,17 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ + #ifndef _MB_CONTROLLER_SLAVE_H #define _MB_CONTROLLER_SLAVE_H diff --git a/components/freemodbus/modbus/ascii/mbascii.c b/components/freemodbus/freemodbus/modbus/ascii/mbascii.c similarity index 93% rename from components/freemodbus/modbus/ascii/mbascii.c rename to components/freemodbus/freemodbus/modbus/ascii/mbascii.c index 132711e324..3650d53d31 100644 --- a/components/freemodbus/modbus/ascii/mbascii.c +++ b/components/freemodbus/freemodbus/modbus/ascii/mbascii.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -144,26 +151,33 @@ eMBErrorCode eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { eMBErrorCode eStatus = MB_ENOERR; + UCHAR *pucMBASCIIFrame = ( UCHAR* ) ucASCIIBuf; + USHORT usFrameLength = usRcvBufferPos; + + if( xMBPortSerialGetRequest( &pucMBASCIIFrame, &usFrameLength ) == FALSE ) + { + return MB_EIO; + } ENTER_CRITICAL_SECTION( ); - assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); + assert( usFrameLength < MB_SER_PDU_SIZE_MAX ); /* Length and CRC check */ - if( ( usRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN ) - && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) ) + if( ( usFrameLength >= MB_ASCII_SER_PDU_SIZE_MIN ) + && ( prvucMBLRC( ( UCHAR * ) pucMBASCIIFrame, usFrameLength ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed * and the decision if a frame is used is done there. */ - *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF]; + *pucRcvAddress = pucMBASCIIFrame[MB_SER_PDU_ADDR_OFF]; /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus * size of address field and CRC checksum. */ - *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); + *pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); /* Return the start of the Modbus PDU to the caller. */ - *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF]; + *pucFrame = ( UCHAR * ) & pucMBASCIIFrame[MB_SER_PDU_PDU_OFF]; } else { @@ -179,13 +193,14 @@ eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) eMBErrorCode eStatus = MB_ENOERR; UCHAR usLRC; - ENTER_CRITICAL_SECTION( ); + /* Check if the receiver is still in idle state. If not we where too * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_RX_IDLE ) { + ENTER_CRITICAL_SECTION( ); /* First byte before the Modbus-PDU is the slave address. */ pucSndBufferCur = ( UCHAR * ) pucFrame - 1; usSndBufferCount = 1; @@ -200,6 +215,13 @@ eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) /* Activate the transmitter. */ eSndState = STATE_TX_START; + EXIT_CRITICAL_SECTION( ); + + if ( xMBPortSerialSendResponse( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ) == FALSE ) + { + eStatus = MB_EIO; + } + vMBPortSerialEnable( FALSE, TRUE ); } else diff --git a/components/freemodbus/modbus/ascii/mbascii.h b/components/freemodbus/freemodbus/modbus/ascii/mbascii.h similarity index 95% rename from components/freemodbus/modbus/ascii/mbascii.h rename to components/freemodbus/freemodbus/modbus/ascii/mbascii.h index 93cc68870e..e4a2c6a508 100644 --- a/components/freemodbus/modbus/ascii/mbascii.h +++ b/components/freemodbus/freemodbus/modbus/ascii/mbascii.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/ascii/mbascii_m.c b/components/freemodbus/freemodbus/modbus/ascii/mbascii_m.c similarity index 91% rename from components/freemodbus/modbus/ascii/mbascii_m.c rename to components/freemodbus/freemodbus/modbus/ascii/mbascii_m.c index 94c04215a8..2e1656788b 100644 --- a/components/freemodbus/modbus/ascii/mbascii_m.c +++ b/components/freemodbus/freemodbus/modbus/ascii/mbascii_m.c @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -149,30 +154,35 @@ eMBErrorCode eMBMasterASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { eMBErrorCode eStatus = MB_ENOERR; + UCHAR *pucMBASCIIFrame = ( UCHAR* ) ucMasterASCIIRcvBuf; + USHORT usFrameLength = usMasterRcvBufferPos; + if( xMBMasterPortSerialGetResponse( &pucMBASCIIFrame, &usFrameLength ) == FALSE ) + { + return MB_EIO; + } ENTER_CRITICAL_SECTION( ); - assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX ); + assert( usFrameLength < MB_SER_PDU_SIZE_MAX ); + assert( pucMBASCIIFrame ); /* Length and CRC check */ - if( ( usMasterRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN ) - && ( prvucMBLRC( ( UCHAR * ) ucMasterASCIIRcvBuf, usMasterRcvBufferPos ) == 0 ) ) + if( ( usFrameLength >= MB_ASCII_SER_PDU_SIZE_MIN ) + && ( prvucMBLRC( ( UCHAR * ) pucMBASCIIFrame, usFrameLength ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed - * and the decision if a frame is used is done there. - */ - *pucRcvAddress = ucMasterASCIIRcvBuf[MB_SER_PDU_ADDR_OFF]; + * and the decision if a frame is used is done there. + */ + *pucRcvAddress = pucMBASCIIFrame[MB_SER_PDU_ADDR_OFF]; - /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus - * size of address field and CRC checksum. - */ - *pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); + /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus + * size of address field and CRC checksum. + */ + *pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); - /* Return the start of the Modbus PDU to the caller. */ - *pucFrame = ( UCHAR * ) & ucMasterASCIIRcvBuf[MB_SER_PDU_PDU_OFF]; - } - else - { - eStatus = MB_EIO; + /* Return the start of the Modbus PDU to the caller. */ + *pucFrame = ( UCHAR * ) & pucMBASCIIFrame[MB_SER_PDU_PDU_OFF]; + } else { + eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; @@ -186,13 +196,13 @@ eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLengt if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL; - ENTER_CRITICAL_SECTION( ); /* Check if the receiver is still in idle state. If not we where too * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if(eRcvState == STATE_M_RX_IDLE) { + ENTER_CRITICAL_SECTION( ); /* First byte before the Modbus-PDU is the slave address. */ pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1; usMasterSndBufferCount = 1; @@ -203,17 +213,22 @@ eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLengt /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */ usLRC = prvucMBLRC( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ); - ucMasterASCIISndBuf[usMasterSndBufferCount++] = usLRC; + pucMasterSndBufferCur[usMasterSndBufferCount++] = usLRC; /* Activate the transmitter. */ eSndState = STATE_M_TX_START; + EXIT_CRITICAL_SECTION( ); + + if ( xMBMasterPortSerialSendRequest( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ) == FALSE ) + { + eStatus = MB_EIO; + } vMBMasterPortSerialEnable( FALSE, TRUE ); } else { eStatus = MB_EIO; } - EXIT_CRITICAL_SECTION( ); return eStatus; } @@ -422,7 +437,8 @@ xMBMasterASCIITransmitFSM( void ) /* Notify the task which called eMBMasterASCIISend that the frame has * been sent. */ case STATE_M_TX_NOTIFY: - xFrameIsBroadcast = ( ucMasterASCIISndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; + xFrameIsBroadcast = ( ucMasterASCIISndBuf[MB_SEND_BUF_PDU_OFF - MB_SER_PDU_PDU_OFF] + == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; vMBMasterRequestSetType( xFrameIsBroadcast ); eSndState = STATE_M_TX_XFWR; /* If the frame is broadcast ,master will enable timer of convert delay, @@ -442,7 +458,7 @@ xMBMasterASCIITransmitFSM( void ) return xNeedPoll; } -BOOL +BOOL MB_PORT_ISR_ATTR xMBMasterASCIITimerT1SExpired( void ) { BOOL xNeedPoll = FALSE; @@ -452,7 +468,6 @@ xMBMasterASCIITimerT1SExpired( void ) /* Timer t35 expired. Startup phase is finished. */ case STATE_M_RX_INIT: xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY); - ESP_EARLY_LOGI("xMBMasterASCIITimerT1SExpired", "RX_INIT_EXPIRED"); break; /* Start of message is not received during respond timeout. diff --git a/components/freemodbus/modbus/functions/mbfunccoils.c b/components/freemodbus/freemodbus/modbus/functions/mbfunccoils.c similarity index 98% rename from components/freemodbus/modbus/functions/mbfunccoils.c rename to components/freemodbus/freemodbus/modbus/functions/mbfunccoils.c index 40ca1c1a12..600c7564d1 100644 --- a/components/freemodbus/modbus/functions/mbfunccoils.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfunccoils.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/functions/mbfunccoils_m.c b/components/freemodbus/freemodbus/modbus/functions/mbfunccoils_m.c similarity index 98% rename from components/freemodbus/modbus/functions/mbfunccoils_m.c rename to components/freemodbus/freemodbus/modbus/functions/mbfunccoils_m.c index 931f5369f4..cbd5e3c3cd 100644 --- a/components/freemodbus/modbus/functions/mbfunccoils_m.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfunccoils_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink diff --git a/components/freemodbus/modbus/functions/mbfuncdiag.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncdiag.c similarity index 90% rename from components/freemodbus/modbus/functions/mbfuncdiag.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncdiag.c index 8f7b9675b0..b988cb8daf 100644 --- a/components/freemodbus/modbus/functions/mbfuncdiag.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncdiag.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/functions/mbfuncdisc.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncdisc.c similarity index 100% rename from components/freemodbus/modbus/functions/mbfuncdisc.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncdisc.c diff --git a/components/freemodbus/modbus/functions/mbfuncdisc_m.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncdisc_m.c similarity index 97% rename from components/freemodbus/modbus/functions/mbfuncdisc_m.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncdisc_m.c index 21ce0cbfbe..3a079ba339 100644 --- a/components/freemodbus/modbus/functions/mbfuncdisc_m.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncdisc_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink @@ -28,8 +35,6 @@ * File: $Id: mbfuncdisc_m.c,v 1.60 2013/10/15 8:48:20 Armink Add Master Functions Exp $ */ - - /* ----------------------- System includes ----------------------------------*/ #include "stdlib.h" #include "string.h" diff --git a/components/freemodbus/modbus/functions/mbfuncholding.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncholding.c similarity index 98% rename from components/freemodbus/modbus/functions/mbfuncholding.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncholding.c index 6038738cce..723ba1a17a 100644 --- a/components/freemodbus/modbus/functions/mbfuncholding.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncholding.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/functions/mbfuncholding_m.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncholding_m.c similarity index 99% rename from components/freemodbus/modbus/functions/mbfuncholding_m.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncholding_m.c index 9c3d69949b..48b7c7b957 100644 --- a/components/freemodbus/modbus/functions/mbfuncholding_m.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncholding_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink diff --git a/components/freemodbus/modbus/functions/mbfuncinput.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncinput.c similarity index 96% rename from components/freemodbus/modbus/functions/mbfuncinput.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncinput.c index b713d866e2..2880bd95bc 100644 --- a/components/freemodbus/modbus/functions/mbfuncinput.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncinput.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/functions/mbfuncinput_m.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncinput_m.c similarity index 97% rename from components/freemodbus/modbus/functions/mbfuncinput_m.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncinput_m.c index 37135f7dee..2db16d6297 100644 --- a/components/freemodbus/modbus/functions/mbfuncinput_m.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncinput_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink diff --git a/components/freemodbus/modbus/functions/mbfuncother.c b/components/freemodbus/freemodbus/modbus/functions/mbfuncother.c similarity index 95% rename from components/freemodbus/modbus/functions/mbfuncother.c rename to components/freemodbus/freemodbus/modbus/functions/mbfuncother.c index 0c2323a8cb..59d6bdab31 100644 --- a/components/freemodbus/modbus/functions/mbfuncother.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbfuncother.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/functions/mbutils.c b/components/freemodbus/freemodbus/modbus/functions/mbutils.c similarity index 96% rename from components/freemodbus/modbus/functions/mbutils.c rename to components/freemodbus/freemodbus/modbus/functions/mbutils.c index 0f0928b527..1332c02d0b 100644 --- a/components/freemodbus/modbus/functions/mbutils.c +++ b/components/freemodbus/freemodbus/modbus/functions/mbutils.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/include/mb.h b/components/freemodbus/freemodbus/modbus/include/mb.h similarity index 99% rename from components/freemodbus/modbus/include/mb.h rename to components/freemodbus/freemodbus/modbus/include/mb.h index bd0f24ade4..964e3e167e 100644 --- a/components/freemodbus/modbus/include/mb.h +++ b/components/freemodbus/freemodbus/modbus/include/mb.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/include/mb_m.h b/components/freemodbus/freemodbus/modbus/include/mb_m.h similarity index 98% rename from components/freemodbus/modbus/include/mb_m.h rename to components/freemodbus/freemodbus/modbus/include/mb_m.h index cb088e990b..0b720a44a6 100644 --- a/components/freemodbus/modbus/include/mb_m.h +++ b/components/freemodbus/freemodbus/modbus/include/mb_m.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink @@ -401,6 +408,7 @@ BOOL xMBMasterRequestIsBroadcast( void ); eMBMasterErrorEventType eMBMasterGetErrorType( void ); void vMBMasterSetErrorType( eMBMasterErrorEventType errorType ); eMBMasterReqErrCode eMBMasterWaitRequestFinish( void ); +eMBMode ucMBMasterGetCommMode( void ); /* ----------------------- Callback -----------------------------------------*/ diff --git a/components/freemodbus/modbus/include/mbconfig.h b/components/freemodbus/freemodbus/modbus/include/mbconfig.h similarity index 94% rename from components/freemodbus/modbus/include/mbconfig.h rename to components/freemodbus/freemodbus/modbus/include/mbconfig.h index b029786a64..268b748244 100644 --- a/components/freemodbus/modbus/include/mbconfig.h +++ b/components/freemodbus/freemodbus/modbus/include/mbconfig.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -34,6 +41,10 @@ #include "sdkconfig.h" // for KConfig options +#if __has_include("esp_idf_version.h") +#include "esp_idf_version.h" +#endif + #ifdef __cplusplus PR_BEGIN_EXTERN_C #endif @@ -66,6 +77,15 @@ PR_BEGIN_EXTERN_C #error "None of Modbus communication mode is enabled. Please enable one of (ASCII, RTU, TCP) mode in Kconfig." #endif +#ifdef ESP_IDF_VERSION + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) +// Features supported from 4.4 +#define MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD 1 +#endif + +#endif + /*! \brief This option defines the number of data bits per ASCII character. * * A parity bit is added before the stop bit which keeps the actual byte size at 10 bits. diff --git a/components/freemodbus/modbus/include/mbframe.h b/components/freemodbus/freemodbus/modbus/include/mbframe.h similarity index 93% rename from components/freemodbus/modbus/include/mbframe.h rename to components/freemodbus/freemodbus/modbus/include/mbframe.h index 5b7d6d7e40..9df0b1dc20 100644 --- a/components/freemodbus/modbus/include/mbframe.h +++ b/components/freemodbus/freemodbus/modbus/include/mbframe.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -80,6 +87,12 @@ PR_BEGIN_EXTERN_C #define MB_TCP_UID 6 #define MB_TCP_FUNC 7 +#if MB_MASTER_TCP_ENABLED +#define MB_SEND_BUF_PDU_OFF MB_TCP_FUNC +#else +#define MB_SEND_BUF_PDU_OFF MB_SER_PDU_PDU_OFF +#endif + #define MB_TCP_PSEUDO_ADDRESS 255 /* ----------------------- Prototypes 0-------------------------------------*/ diff --git a/components/freemodbus/modbus/include/mbfunc.h b/components/freemodbus/freemodbus/modbus/include/mbfunc.h similarity index 94% rename from components/freemodbus/modbus/include/mbfunc.h rename to components/freemodbus/freemodbus/modbus/include/mbfunc.h index 48d7b390a4..fcb88bbc99 100644 --- a/components/freemodbus/modbus/include/mbfunc.h +++ b/components/freemodbus/freemodbus/modbus/include/mbfunc.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/include/mbport.h b/components/freemodbus/freemodbus/modbus/include/mbport.h similarity index 93% rename from components/freemodbus/modbus/include/mbport.h rename to components/freemodbus/freemodbus/modbus/include/mbport.h index 412d171051..4b072d571d 100644 --- a/components/freemodbus/modbus/include/mbport.h +++ b/components/freemodbus/freemodbus/modbus/include/mbport.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -138,6 +145,10 @@ BOOL xMBPortSerialGetByte( CHAR * pucByte ); BOOL xMBPortSerialPutByte( CHAR ucByte ); +BOOL xMBPortSerialGetRequest( UCHAR **ppucMBSerialFrame, USHORT * pusSerialLength ) __attribute__ ((weak)); + +BOOL xMBPortSerialSendResponse( UCHAR *pucMBSerialFrame, USHORT usSerialLength ) __attribute__ ((weak)); + #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED BOOL xMBMasterPortSerialInit( UCHAR ucPort, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ); @@ -151,6 +162,13 @@ void vMBMasterPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ); BOOL xMBMasterPortSerialGetByte( CHAR * pucByte ); BOOL xMBMasterPortSerialPutByte( CHAR ucByte ); + +BOOL xMBMasterPortSerialGetResponse( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength ); + +BOOL xMBMasterPortSerialSendRequest( UCHAR *pucMBSerialFrame, USHORT usSerialLength ); + +void vMBMasterRxFlush( void ); + #endif /* ----------------------- Timers functions ---------------------------------*/ diff --git a/components/freemodbus/modbus/include/mbproto.h b/components/freemodbus/freemodbus/modbus/include/mbproto.h similarity index 95% rename from components/freemodbus/modbus/include/mbproto.h rename to components/freemodbus/freemodbus/modbus/include/mbproto.h index 3c76517281..da303a6fc5 100644 --- a/components/freemodbus/modbus/include/mbproto.h +++ b/components/freemodbus/freemodbus/modbus/include/mbproto.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/include/mbutils.h b/components/freemodbus/freemodbus/modbus/include/mbutils.h similarity index 95% rename from components/freemodbus/modbus/include/mbutils.h rename to components/freemodbus/freemodbus/modbus/include/mbutils.h index 1d96ddcc0b..de64c4a964 100644 --- a/components/freemodbus/modbus/include/mbutils.h +++ b/components/freemodbus/freemodbus/modbus/include/mbutils.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/mb.c b/components/freemodbus/freemodbus/modbus/mb.c similarity index 98% rename from components/freemodbus/modbus/mb.c rename to components/freemodbus/freemodbus/modbus/mb.c index 25722011a9..60ba538ec2 100644 --- a/components/freemodbus/modbus/mb.c +++ b/components/freemodbus/freemodbus/modbus/mb.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/mb_m.c b/components/freemodbus/freemodbus/modbus/mb_m.c similarity index 81% rename from components/freemodbus/modbus/mb_m.c rename to components/freemodbus/freemodbus/modbus/mb_m.c index f23a577a0a..5bfad5621a 100644 --- a/components/freemodbus/modbus/mb_m.c +++ b/components/freemodbus/freemodbus/modbus/mb_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (C) 2013 Armink @@ -63,18 +70,18 @@ /* ----------------------- Static variables ---------------------------------*/ -static UCHAR ucMBMasterDestAddress; -static BOOL xMBRunInMasterMode = FALSE; +static UCHAR ucMBMasterDestAddress; +static BOOL xMBRunInMasterMode = FALSE; static volatile eMBMasterErrorEventType eMBMasterCurErrorType; -static volatile USHORT usMasterSendPDULength; +static volatile USHORT usMasterSendPDULength; static volatile eMBMode eMBMasterCurrentMode; /*------------------------ Shared variables ---------------------------------*/ -volatile UCHAR ucMasterSndBuf[MB_SERIAL_BUF_SIZE]; -volatile UCHAR ucMasterRcvBuf[MB_SERIAL_BUF_SIZE]; +volatile UCHAR ucMasterSndBuf[MB_SERIAL_BUF_SIZE]; +volatile UCHAR ucMasterRcvBuf[MB_SERIAL_BUF_SIZE]; volatile eMBMasterTimerMode eMasterCurTimerMode; -volatile BOOL xFrameIsBroadcast = FALSE; +volatile BOOL xFrameIsBroadcast = FALSE; static enum { @@ -145,7 +152,7 @@ static xMBFunctionHandler xMasterFuncHandlers[MB_FUNC_HANDLERS_MAX] = { }; /* ----------------------- Start implementation -----------------------------*/ -#if MB_MASTER_TCP_ENABLED > 0 +#if MB_MASTER_TCP_ENABLED eMBErrorCode eMBMasterTCPInit( USHORT ucTCPPort ) { @@ -303,11 +310,13 @@ eMBMasterDisable( void ) eMBErrorCode eMBMasterPoll( void ) { - static UCHAR *ucMBFrame = NULL; + static UCHAR *ucMBSendFrame = NULL; + static UCHAR *ucMBRcvFrame = NULL; static UCHAR ucRcvAddress; static UCHAR ucFunctionCode; static USHORT usLength; static eMBException eException; + static BOOL xTransactionIsActive = FALSE; int i; int j; eMBErrorCode eStatus = MB_ENOERR; @@ -333,49 +342,61 @@ eMBMasterPoll( void ) } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_TRANSMIT ) ) { ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_TRANSMIT", __func__); /* Master is busy now. */ - vMBMasterGetPDUSndBuf( &ucMBFrame ); - ESP_LOG_BUFFER_HEX_LEVEL("POLL transmit buffer", (void*)ucMBFrame, usMBMasterGetPDUSndLength(), ESP_LOG_DEBUG); - eStatus = peMBMasterFrameSendCur( ucMBMasterGetDestAddress(), ucMBFrame, usMBMasterGetPDUSndLength() ); + vMBMasterGetPDUSndBuf( &ucMBSendFrame ); + ESP_LOG_BUFFER_HEX_LEVEL("POLL transmit buffer", (void*)ucMBSendFrame, usMBMasterGetPDUSndLength(), ESP_LOG_DEBUG); + eStatus = peMBMasterFrameSendCur( ucMBMasterGetDestAddress(), ucMBSendFrame, usMBMasterGetPDUSndLength() ); if (eStatus != MB_ENOERR) { + vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); + ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); ESP_LOGE( MB_PORT_TAG, "%s:Frame send error. %d", __func__, eStatus ); } MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_TRANSMIT ); } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_SENT ) ) { ESP_LOGD( MB_PORT_TAG, "%s:EV_MASTER_FRAME_SENT", __func__ ); - ESP_LOG_BUFFER_HEX_LEVEL("POLL sent buffer", (void*)ucMBFrame, usMBMasterGetPDUSndLength(), ESP_LOG_DEBUG); + ESP_LOG_BUFFER_HEX_LEVEL("POLL sent buffer", (void*)ucMBSendFrame, usMBMasterGetPDUSndLength(), ESP_LOG_DEBUG); + xTransactionIsActive = TRUE; MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_SENT ); } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_FRAME_RECEIVED ) ) { - eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength); + if (xTransactionIsActive) { + eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBRcvFrame, &usLength); + MB_PORT_CHECK(ucMBRcvFrame, MB_EILLSTATE, "Receive buffer initialization fail."); + MB_PORT_CHECK(ucMBSendFrame, MB_EILLSTATE, "Send buffer initialization fail."); + // Check if the frame is for us. If not ,send an error process event. + if ( ( eStatus == MB_ENOERR ) && ( ( ucRcvAddress == ucMBMasterGetDestAddress() ) + || ( ucRcvAddress == MB_TCP_PSEUDO_ADDRESS) ) ) { + if ( ( ucMBRcvFrame[MB_PDU_FUNC_OFF] & ~MB_FUNC_ERROR ) == ( ucMBSendFrame[MB_PDU_FUNC_OFF] ) ) { + ESP_LOGD(MB_PORT_TAG, "%s: Packet data received successfully (%u).", __func__, eStatus); + ESP_LOG_BUFFER_HEX_LEVEL("POLL receive buffer", (void*)ucMBRcvFrame, (uint16_t)usLength, ESP_LOG_DEBUG); - // Check if the frame is for us. If not ,send an error process event. - if ( ( eStatus == MB_ENOERR ) && ( ( ucRcvAddress == ucMBMasterGetDestAddress() ) - || ( ucRcvAddress == MB_TCP_PSEUDO_ADDRESS ) ) ) - { - ( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE ); - ESP_LOGD(MB_PORT_TAG, "%s: Packet data received successfully (%u).", __func__, eStatus); - ESP_LOG_BUFFER_HEX_LEVEL("POLL receive buffer", (void*)ucMBFrame, (uint16_t)usLength, ESP_LOG_DEBUG); - } - else - { - vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); - ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); - ESP_LOGD( MB_PORT_TAG, "%s: Packet data receive failed (addr=%u)(%u).", - __func__, ucRcvAddress, eStatus); + ( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE ); + } else { + ESP_LOGE( MB_PORT_TAG, "Drop incorrect frame, receive_func(%u) != send_func(%u)", + ucMBRcvFrame[MB_PDU_FUNC_OFF], ucMBSendFrame[MB_PDU_FUNC_OFF]); + vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); + ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); + } + } else { + vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); + ( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); + ESP_LOGD( MB_PORT_TAG, "%s: Packet data receive failed (addr=%u)(%u).", + __func__, ucRcvAddress, eStatus); + } + } else { + // Ignore the `EV_MASTER_FRAME_RECEIVED` event because the respond timeout occurred + // and this is likely respond to previous transaction + ESP_LOGE( MB_PORT_TAG, "Drop data received outside of transaction."); } MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_FRAME_RECEIVED ); } else if ( MB_PORT_CHECK_EVENT( eEvent, EV_MASTER_EXECUTE ) ) { - if ( !ucMBFrame ) - { - return MB_EILLSTATE; - } + MB_PORT_CHECK(ucMBRcvFrame, MB_EILLSTATE, "receive buffer initialization fail."); ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_EXECUTE", __func__); - ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; + ucFunctionCode = ucMBRcvFrame[MB_PDU_FUNC_OFF]; eException = MB_EX_ILLEGAL_FUNCTION; /* If receive frame has exception. The receive function code highest bit is 1.*/ if (ucFunctionCode & MB_FUNC_ERROR) { - eException = (eMBException)ucMBFrame[MB_PDU_DATA_OFF]; + eException = (eMBException)ucMBRcvFrame[MB_PDU_DATA_OFF]; } else { for ( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) { @@ -396,12 +417,12 @@ eMBMasterPoll( void ) for(j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++) { vMBMasterSetDestAddress(j); - eException = xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength); + eException = xMasterFuncHandlers[i].pxHandler(ucMBRcvFrame, &usLength); } } else { - eException = xMasterFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); + eException = xMasterFuncHandlers[i].pxHandler( ucMBRcvFrame, &usLength ); } vMBMasterSetCBRunInMasterMode( FALSE ); break; @@ -427,20 +448,20 @@ eMBMasterPoll( void ) ESP_LOGD( MB_PORT_TAG, "%s:EV_MASTER_ERROR_PROCESS", __func__ ); /* Execute specified error process callback function. */ errorType = eMBMasterGetErrorType( ); - vMBMasterGetPDUSndBuf( &ucMBFrame ); + vMBMasterGetPDUSndBuf( &ucMBSendFrame ); switch ( errorType ) { case EV_ERROR_RESPOND_TIMEOUT: vMBMasterErrorCBRespondTimeout( ucMBMasterGetDestAddress( ), - ucMBFrame, usMBMasterGetPDUSndLength( ) ); + ucMBSendFrame, usMBMasterGetPDUSndLength( ) ); break; case EV_ERROR_RECEIVE_DATA: vMBMasterErrorCBReceiveData( ucMBMasterGetDestAddress( ), - ucMBFrame, usMBMasterGetPDUSndLength( ) ); + ucMBSendFrame, usMBMasterGetPDUSndLength( ) ); break; case EV_ERROR_EXECUTE_FUNCTION: vMBMasterErrorCBExecuteFunction( ucMBMasterGetDestAddress( ), - ucMBFrame, usMBMasterGetPDUSndLength( ) ); + ucMBSendFrame, usMBMasterGetPDUSndLength( ) ); break; case EV_ERROR_OK: vMBMasterCBRequestSuccess( ); @@ -449,6 +470,7 @@ eMBMasterPoll( void ) ESP_LOGE( MB_PORT_TAG, "%s: incorrect error type = %d.", __func__, errorType); break; } + vMBMasterPortTimersDisable( ); vMBMasterSetErrorType( EV_ERROR_INIT ); MB_PORT_CLEAR_EVENT( eEvent, EV_MASTER_ERROR_PROCESS ); vMBMasterRunResRelease( ); @@ -501,7 +523,7 @@ void IRAM_ATTR vMBMasterSetErrorType( eMBMasterErrorEventType errorType ) /* Get Modbus Master send PDU's buffer address pointer.*/ void vMBMasterGetPDUSndBuf( UCHAR ** pucFrame ) { - *pucFrame = ( UCHAR * ) &ucMasterSndBuf[MB_SER_PDU_PDU_OFF]; + *pucFrame = ( UCHAR * ) &ucMasterSndBuf[MB_SEND_BUF_PDU_OFF]; } /* Set Modbus Master send PDU's buffer length.*/ @@ -535,8 +557,15 @@ BOOL MB_PORT_ISR_ATTR xMBMasterRequestIsBroadcast( void ) } /* The master request is broadcast? */ -void vMBMasterRequestSetType( BOOL xIsBroadcast ){ +void vMBMasterRequestSetType( BOOL xIsBroadcast ) +{ xFrameIsBroadcast = xIsBroadcast; } +// Get Modbus Master communication mode. +eMBMode ucMBMasterGetCommMode(void) +{ + return eMBMasterCurrentMode; +} + #endif // MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED diff --git a/components/freemodbus/modbus/rtu/mbcrc.c b/components/freemodbus/freemodbus/modbus/rtu/mbcrc.c similarity index 95% rename from components/freemodbus/modbus/rtu/mbcrc.c rename to components/freemodbus/freemodbus/modbus/rtu/mbcrc.c index bb896764f6..f413453492 100644 --- a/components/freemodbus/modbus/rtu/mbcrc.c +++ b/components/freemodbus/freemodbus/modbus/rtu/mbcrc.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -32,7 +39,7 @@ #include "port.h" #include "mbconfig.h" -#if MB_MASTER_RTU_ENABLED || MB_SLAVE_RTU_ENABLED +#if (MB_MASTER_RTU_ENABLED || MB_SLAVE_RTU_ENABLED || CONFIG_MB_UTEST) static const UCHAR aucCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, diff --git a/components/freemodbus/modbus/rtu/mbcrc.h b/components/freemodbus/freemodbus/modbus/rtu/mbcrc.h similarity index 90% rename from components/freemodbus/modbus/rtu/mbcrc.h rename to components/freemodbus/freemodbus/modbus/rtu/mbcrc.h index 5ed3f22896..d74d9eb3a6 100644 --- a/components/freemodbus/modbus/rtu/mbcrc.h +++ b/components/freemodbus/freemodbus/modbus/rtu/mbcrc.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/rtu/mbrtu.c b/components/freemodbus/freemodbus/modbus/rtu/mbrtu.c similarity index 90% rename from components/freemodbus/modbus/rtu/mbrtu.c rename to components/freemodbus/freemodbus/modbus/rtu/mbrtu.c index c4d77f6f38..77a2c2f48e 100644 --- a/components/freemodbus/modbus/rtu/mbrtu.c +++ b/components/freemodbus/freemodbus/modbus/rtu/mbrtu.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter @@ -148,26 +155,33 @@ eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { eMBErrorCode eStatus = MB_ENOERR; + UCHAR *pucMBRTUFrame = ( UCHAR* ) ucRTUBuf; + USHORT usFrameLength = usRcvBufferPos; + + if( xMBPortSerialGetRequest( &pucMBRTUFrame, &usFrameLength ) == FALSE ) + { + return MB_EIO; + } ENTER_CRITICAL_SECTION( ); - assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); + assert( usFrameLength < MB_SER_PDU_SIZE_MAX ); /* Length and CRC check */ - if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) - && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) + if( ( usFrameLength >= MB_SER_PDU_SIZE_MIN ) + && ( usMBCRC16( ( UCHAR * ) pucMBRTUFrame, usFrameLength ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed * and the decision if a frame is used is done there. */ - *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; + *pucRcvAddress = pucMBRTUFrame[MB_SER_PDU_ADDR_OFF]; /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus * size of address field and CRC checksum. */ - *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); + *pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); /* Return the start of the Modbus PDU to the caller. */ - *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; + *pucFrame = ( UCHAR * ) & pucMBRTUFrame[MB_SER_PDU_PDU_OFF]; } else { @@ -184,14 +198,13 @@ eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; - ENTER_CRITICAL_SECTION( ); - /* Check if the receiver is still in idle state. If not we where to * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_RX_IDLE ) { + ENTER_CRITICAL_SECTION( ); /* First byte before the Modbus-PDU is the slave address. */ pucSndBufferCur = ( UCHAR * ) pucFrame - 1; usSndBufferCount = 1; @@ -207,13 +220,19 @@ eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) /* Activate the transmitter. */ eSndState = STATE_TX_XMIT; + EXIT_CRITICAL_SECTION( ); + + if( xMBPortSerialSendResponse( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ) == FALSE ) + { + eStatus = MB_EIO; + } + vMBPortSerialEnable( FALSE, TRUE ); } else { eStatus = MB_EIO; } - EXIT_CRITICAL_SECTION( ); return eStatus; } @@ -265,7 +284,7 @@ xMBRTUReceiveFSM( void ) case STATE_RX_RCV: if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) { - if ( xStatus ) { + if( xStatus ) { ucRTUBuf[usRcvBufferPos++] = ucByte; } } diff --git a/components/freemodbus/modbus/rtu/mbrtu.h b/components/freemodbus/freemodbus/modbus/rtu/mbrtu.h similarity index 94% rename from components/freemodbus/modbus/rtu/mbrtu.h rename to components/freemodbus/freemodbus/modbus/rtu/mbrtu.h index 676f67d1df..69e0f4535f 100644 --- a/components/freemodbus/modbus/rtu/mbrtu.h +++ b/components/freemodbus/freemodbus/modbus/rtu/mbrtu.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/rtu/mbrtu_m.c b/components/freemodbus/freemodbus/modbus/rtu/mbrtu_m.c similarity index 88% rename from components/freemodbus/modbus/rtu/mbrtu_m.c rename to components/freemodbus/freemodbus/modbus/rtu/mbrtu_m.c index ce22c8e861..85b19809b4 100644 --- a/components/freemodbus/modbus/rtu/mbrtu_m.c +++ b/components/freemodbus/freemodbus/modbus/rtu/mbrtu_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2013 China Beijing Armink @@ -154,26 +161,34 @@ eMBErrorCode eMBMasterRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { eMBErrorCode eStatus = MB_ENOERR; + UCHAR *pucMBRTUFrame = ( UCHAR* ) ucMasterRTURcvBuf; + USHORT usFrameLength = usMasterRcvBufferPos; + + if( xMBMasterPortSerialGetResponse( &pucMBRTUFrame, &usFrameLength ) == FALSE ) + { + return MB_EIO; + } ENTER_CRITICAL_SECTION( ); - assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX ); + assert( usFrameLength < MB_SER_PDU_SIZE_MAX ); + assert( pucMBRTUFrame ); /* Length and CRC check */ - if( ( usMasterRcvBufferPos >= MB_RTU_SER_PDU_SIZE_MIN ) - && ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 ) ) + if( ( usFrameLength >= MB_RTU_SER_PDU_SIZE_MIN ) + && ( usMBCRC16( ( UCHAR * ) pucMBRTUFrame, usFrameLength ) == 0 ) ) { - /* Save the address field. All frames are passed to the upper layed + /* Save the address field. All frames are passed to the upper layer * and the decision if a frame is used is done there. */ - *pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF]; + *pucRcvAddress = pucMBRTUFrame[MB_SER_PDU_ADDR_OFF]; /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus * size of address field and CRC checksum. */ - *pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); + *pusLength = ( USHORT )( usFrameLength - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); /* Return the start of the Modbus PDU to the caller. */ - *pucFrame = ( UCHAR * ) & ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF]; + *pucFrame = ( UCHAR * ) & pucMBRTUFrame[MB_SER_PDU_PDU_OFF]; } else { @@ -192,14 +207,13 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL; - ENTER_CRITICAL_SECTION( ); - /* Check if the receiver is still in idle state. If not we where to * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_M_RX_IDLE ) { + ENTER_CRITICAL_SECTION( ); /* First byte before the Modbus-PDU is the slave address. */ pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1; usMasterSndBufferCount = 1; @@ -210,11 +224,18 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ usCRC16 = usMBCRC16( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ); - ucMasterRTUSndBuf[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); - ucMasterRTUSndBuf[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); + pucMasterSndBufferCur[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); + pucMasterSndBufferCur[usMasterSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); + EXIT_CRITICAL_SECTION( ); /* Activate the transmitter. */ eSndState = STATE_M_TX_XMIT; + + if ( xMBMasterPortSerialSendRequest( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ) == FALSE ) + { + eStatus = MB_EIO; + } + // The place to enable RS485 driver vMBMasterPortSerialEnable( FALSE, TRUE ); } @@ -222,7 +243,6 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength { eStatus = MB_EIO; } - EXIT_CRITICAL_SECTION( ); return eStatus; } @@ -266,11 +286,15 @@ xMBMasterRTUReceiveFSM( void ) eSndState = STATE_M_TX_IDLE; usMasterRcvBufferPos = 0; - ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte; - eRcvState = STATE_M_RX_RCV; + if( xStatus && ucByte ) { + ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte; + eRcvState = STATE_M_RX_RCV; + } /* Enable t3.5 timers. */ +#if CONFIG_FMB_TIMER_PORT_ENABLED vMBMasterPortTimersT35Enable( ); +#endif break; /* We are currently receiving a frame. Reset the timer after @@ -289,7 +313,9 @@ xMBMasterRTUReceiveFSM( void ) { eRcvState = STATE_M_RX_ERROR; } +#if CONFIG_FMB_TIMER_PORT_ENABLED vMBMasterPortTimersT35Enable( ); +#endif break; } return xStatus; @@ -324,7 +350,8 @@ xMBMasterRTUTransmitFSM( void ) } else { - xFrameIsBroadcast = ( ucMasterRTUSndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; + xFrameIsBroadcast = ( ucMasterRTUSndBuf[MB_SEND_BUF_PDU_OFF - MB_SER_PDU_PDU_OFF] + == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; vMBMasterRequestSetType( xFrameIsBroadcast ); eSndState = STATE_M_TX_XFWR; /* If the frame is broadcast ,master will enable timer of convert delay, diff --git a/components/freemodbus/modbus/tcp/mbtcp.c b/components/freemodbus/freemodbus/modbus/tcp/mbtcp.c similarity index 96% rename from components/freemodbus/modbus/tcp/mbtcp.c rename to components/freemodbus/freemodbus/modbus/tcp/mbtcp.c index 624d909344..3502454eb8 100644 --- a/components/freemodbus/modbus/tcp/mbtcp.c +++ b/components/freemodbus/freemodbus/modbus/tcp/mbtcp.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/tcp/mbtcp.h b/components/freemodbus/freemodbus/modbus/tcp/mbtcp.h similarity index 93% rename from components/freemodbus/modbus/tcp/mbtcp.h rename to components/freemodbus/freemodbus/modbus/tcp/mbtcp.h index 89e970a08c..842ce19e54 100644 --- a/components/freemodbus/modbus/tcp/mbtcp.h +++ b/components/freemodbus/freemodbus/modbus/tcp/mbtcp.h @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/tcp/mbtcp_m.c b/components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.c similarity index 96% rename from components/freemodbus/modbus/tcp/mbtcp_m.c rename to components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.c index 7e1f8950cb..4a9fc01395 100644 --- a/components/freemodbus/modbus/tcp/mbtcp_m.c +++ b/components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.c @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/modbus/tcp/mbtcp_m.h b/components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.h similarity index 95% rename from components/freemodbus/modbus/tcp/mbtcp_m.h rename to components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.h index 34aaa027ce..901cd0e790 100644 --- a/components/freemodbus/modbus/tcp/mbtcp_m.h +++ b/components/freemodbus/freemodbus/modbus/tcp/mbtcp_m.h @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ /* * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. * Copyright (c) 2006 Christian Walter diff --git a/components/freemodbus/port/port.c b/components/freemodbus/freemodbus/port/port.c similarity index 74% rename from components/freemodbus/port/port.c rename to components/freemodbus/freemodbus/port/port.c index 1959104cb9..ca8eb5bcac 100644 --- a/components/freemodbus/port/port.c +++ b/components/freemodbus/freemodbus/port/port.c @@ -38,6 +38,7 @@ /* ----------------------- Modbus includes ----------------------------------*/ #include "freertos/FreeRTOS.h" +#include "freertos/queue.h" #include "sys/lock.h" #include "port.h" @@ -72,6 +73,61 @@ vMBPortSetMode( UCHAR ucMode ) EXIT_CRITICAL_SECTION(); } +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED + +BOOL xMBPortSerialWaitEvent(QueueHandle_t xMbUartQueue, uart_event_t* pxEvent, ULONG xTimeout) +{ + BOOL xResult = (BaseType_t)xQueueReceive(xMbUartQueue, (void*)pxEvent, (TickType_t) xTimeout); + ESP_LOGD(MB_PORT_TAG, "%s, UART event: %d ", __func__, pxEvent->type); + return xResult; +} + +#endif + +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED + +/* + * The function is called from ASCII/RTU module to get processed data buffer. Sets the + * received buffer and its length using parameters. + */ +__attribute__ ((weak)) +BOOL xMBMasterPortSerialGetResponse( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength ) +{ + ESP_LOGD(MB_PORT_TAG, " %s default", __func__); + return TRUE; +} + +/* + * The function is called from ASCII/RTU module to set processed data buffer + * to be sent in transmitter state machine. + */ +__attribute__ ((weak)) +BOOL xMBMasterPortSerialSendRequest( UCHAR *pucMBSerialFrame, USHORT usSerialLength ) +{ + ESP_LOGD(MB_PORT_TAG, "%s default", __func__); + return TRUE; +} + +#endif + +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED + +__attribute__ ((weak)) +BOOL xMBPortSerialGetRequest( UCHAR **ppucMBSerialFrame, USHORT * usSerialLength ) +{ + ESP_LOGD(MB_PORT_TAG, "%s default", __func__); + return TRUE; +} + +__attribute__ ((weak)) +BOOL xMBPortSerialSendResponse( UCHAR *pucMBSerialFrame, USHORT usSerialLength ) +{ + ESP_LOGD(MB_PORT_TAG, "%s default", __func__); + return TRUE; +} + +#endif + #if MB_TCP_DEBUG // This function is kept to realize legacy freemodbus frame logging functionality diff --git a/components/freemodbus/port/port.h b/components/freemodbus/freemodbus/port/port.h similarity index 91% rename from components/freemodbus/port/port.h rename to components/freemodbus/freemodbus/port/port.h index c08c011242..b5d88583b8 100644 --- a/components/freemodbus/port/port.h +++ b/components/freemodbus/freemodbus/port/port.h @@ -38,7 +38,18 @@ #define PORT_COMMON_H_ #include "freertos/FreeRTOS.h" +#include "freertos/queue.h" // for queue + #include "esp_log.h" // for ESP_LOGE macro +#include "esp_timer.h" +#include "driver/uart.h" // for uart_event_t + +#if __has_include("driver/gptimer.h") +#include "driver/gptimer.h" +#else +#include "driver/timer.h" +#endif + #include "mbconfig.h" #define INLINE inline @@ -86,6 +97,7 @@ // Define number of timer reloads per 1 mS #define MB_TIMER_TICS_PER_MS (20UL) +#define MB_TIMER_TICK_TIME_US (1000 / MB_TIMER_TICS_PER_MS) // 50uS = one discreet for timer #define MB_TCP_DEBUG (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) // Enable legacy debug output in TCP module. @@ -141,6 +153,12 @@ typedef enum { MB_PORT_IPV6 = 1 /*!< TCP IPV6 addressing */ } eMBPortIpVer; +typedef struct { + esp_timer_handle_t xTimerIntHandle; + USHORT usT35Ticks; + BOOL xTimerState; +} xTimerContext_t; + void vMBPortEnterCritical(void); void vMBPortExitCritical(void); @@ -166,6 +184,8 @@ void prvvMBTCPLogFrame( const CHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen void vMBPortSetMode( UCHAR ucMode ); UCHAR ucMBPortGetMode( void ); +BOOL xMBPortSerialWaitEvent(QueueHandle_t xMbUartQueue, uart_event_t* pxEvent, ULONG xTimeout); + #ifdef __cplusplus PR_END_EXTERN_C #endif /* __cplusplus */ diff --git a/components/freemodbus/port/portevent.c b/components/freemodbus/freemodbus/port/portevent.c similarity index 85% rename from components/freemodbus/port/portevent.c rename to components/freemodbus/freemodbus/port/portevent.c index 20e2bce5b0..229635bc53 100644 --- a/components/freemodbus/port/portevent.c +++ b/components/freemodbus/freemodbus/port/portevent.c @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2010 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Port Demo Application @@ -54,7 +47,7 @@ #include "sdkconfig.h" #include "port_serial_slave.h" /* ----------------------- Variables ----------------------------------------*/ -static xQueueHandle xQueueHdl; +static QueueHandle_t xQueueHdl; #define MB_EVENT_QUEUE_SIZE (6) #define MB_EVENT_QUEUE_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_EVENT_QUEUE_TIMEOUT)) @@ -120,7 +113,7 @@ xMBPortEventGet(eMBEventType * peEvent) return xEventHappened; } -xQueueHandle +QueueHandle_t xMBPortEventGetHandle(void) { if(xQueueHdl != NULL) diff --git a/components/freemodbus/port/portevent_m.c b/components/freemodbus/freemodbus/port/portevent_m.c similarity index 97% rename from components/freemodbus/port/portevent_m.c rename to components/freemodbus/freemodbus/port/portevent_m.c index 79dbca3783..0f680be887 100644 --- a/components/freemodbus/port/portevent_m.c +++ b/components/freemodbus/freemodbus/port/portevent_m.c @@ -187,13 +187,16 @@ BOOL xMBMasterRunResTake( LONG lTimeOut ) /** * This function is release Modbus Master running resource. - * Note:The resource is define by Operating System.If you not use OS this function can be empty. + * Note:The resource is define by Operating System. If you not use OS this function can be empty. */ void vMBMasterRunResRelease( void ) { EventBits_t uxBits = xEventGroupSetBits( xResourceMasterHdl, MB_EVENT_RESOURCE ); - MB_PORT_CHECK((uxBits == MB_EVENT_RESOURCE), ; , "Resource release failure."); - ESP_LOGD(MB_PORT_TAG,"%s: Release resource (%x).", __func__, uxBits); + if (uxBits != MB_EVENT_RESOURCE) { + // The returned resource mask may be = 0, if the task waiting for it is unblocked. + // This is not an error but expected behavior. + ESP_LOGD(MB_PORT_TAG,"%s: Release resource (%x) fail.", __func__, uxBits); + } } /** diff --git a/components/freemodbus/port/portother.c b/components/freemodbus/freemodbus/port/portother.c similarity index 80% rename from components/freemodbus/port/portother.c rename to components/freemodbus/freemodbus/port/portother.c index 0ff251c850..180de5ba6c 100644 --- a/components/freemodbus/port/portother.c +++ b/components/freemodbus/freemodbus/port/portother.c @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2010 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Demo Application diff --git a/components/freemodbus/port/portother_m.c b/components/freemodbus/freemodbus/port/portother_m.c similarity index 80% rename from components/freemodbus/port/portother_m.c rename to components/freemodbus/freemodbus/port/portother_m.c index bdc471cf70..fa2d421998 100644 --- a/components/freemodbus/port/portother_m.c +++ b/components/freemodbus/freemodbus/port/portother_m.c @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2006 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Demo Application diff --git a/components/freemodbus/port/portserial.c b/components/freemodbus/freemodbus/port/portserial.c similarity index 91% rename from components/freemodbus/port/portserial.c rename to components/freemodbus/freemodbus/port/portserial.c index 67b56bf6fc..dbc148d75b 100644 --- a/components/freemodbus/port/portserial.c +++ b/components/freemodbus/freemodbus/port/portserial.c @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2010 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Port @@ -40,6 +33,8 @@ * * File: $Id: portother.c,v 1.1 2010/06/06 13:07:20 wolti Exp $ */ + +#include "driver/uart.h" #include "port.h" #include "driver/uart.h" #include "freertos/queue.h" // for queue support @@ -92,15 +87,14 @@ static USHORT usMBPortSerialRxPoll(size_t xEventSize) if (bRxStateEnabled) { // Get received packet into Rx buffer - while(xReadStatus && (usCnt++ <= MB_SERIAL_BUF_SIZE)) { + while(xReadStatus && (usCnt++ <= xEventSize)) { // Call the Modbus stack callback function and let it fill the buffers. xReadStatus = pxMBFrameCBByteReceived(); // callback to execute receive FSM } uart_flush_input(ucUartNumber); // Send event EV_FRAME_RECEIVED to allow stack process packet #if !CONFIG_FMB_TIMER_PORT_ENABLED - // Let the stack know that T3.5 time is expired and data is received - (void)pxMBPortCBTimerExpired(); // calls callback xMBRTUTimerT35Expired(); + pxMBPortCBTimerExpired(); #endif ESP_LOGD(TAG, "RX: %d bytes\n", usCnt); } @@ -133,7 +127,7 @@ static void vUartTask(void *pvParameters) uart_event_t xEvent; USHORT usResult = 0; for(;;) { - if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) { + if (xMBPortSerialWaitEvent(xMbUartQueue, (void*)&xEvent, portMAX_DELAY)) { ESP_LOGD(TAG, "MB_uart[%d] event:", ucUartNumber); switch(xEvent.type) { //Event of UART receving data @@ -142,6 +136,8 @@ static void vUartTask(void *pvParameters) // This flag set in the event means that no more // data received during configured timeout and UART TOUT feature is triggered if (xEvent.timeout_flag) { + // Get buffered data length + ESP_ERROR_CHECK(uart_get_buffered_data_len(ucUartNumber, &xEvent.size)); // Read received data and send it to modbus stack usResult = usMBPortSerialRxPoll(xEvent.size); ESP_LOGD(TAG,"Timeout occured, processed: %d bytes", usResult); @@ -226,7 +222,7 @@ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .rx_flow_ctrl_thresh = 2, - .source_clk = UART_SCLK_APB, + .source_clk = UART_SCLK_APB }; // Set UART config xErr = uart_param_config(ucUartNumber, &xUartConfig); diff --git a/components/freemodbus/port/portserial_m.c b/components/freemodbus/freemodbus/port/portserial_m.c similarity index 77% rename from components/freemodbus/port/portserial_m.c rename to components/freemodbus/freemodbus/port/portserial_m.c index bcb74f29cb..cc37f510dc 100644 --- a/components/freemodbus/port/portserial_m.c +++ b/components/freemodbus/freemodbus/port/portserial_m.c @@ -52,6 +52,9 @@ #include "port_serial_master.h" /* ----------------------- Defines ------------------------------------------*/ +#define MB_SERIAL_RX_SEMA_TOUT_MS (1000) +#define MB_SERIAL_RX_SEMA_TOUT (pdMS_TO_TICKS(MB_SERIAL_RX_SEMA_TOUT_MS)) +#define MB_SERIAL_RX_FLUSH_RETRY (2) /* ----------------------- Static variables ---------------------------------*/ static const CHAR *TAG = "MB_MASTER_SERIAL"; @@ -66,16 +69,67 @@ static UCHAR ucUartNumber = UART_NUM_MAX - 1; static BOOL bRxStateEnabled = FALSE; // Receiver enabled flag static BOOL bTxStateEnabled = FALSE; // Transmitter enabled flag +static SemaphoreHandle_t xMasterSemaRxHandle; // Rx blocking semaphore handle + +static BOOL xMBMasterPortRxSemaInit( void ) +{ + xMasterSemaRxHandle = xSemaphoreCreateBinary(); + MB_PORT_CHECK((xMasterSemaRxHandle != NULL), FALSE , "%s: RX semaphore create failure.", __func__); + return TRUE; +} + +static BOOL xMBMasterPortRxSemaTake( LONG lTimeOut ) +{ + BaseType_t xStatus = pdTRUE; + xStatus = xSemaphoreTake(xMasterSemaRxHandle, lTimeOut ); + MB_PORT_CHECK((xStatus == pdTRUE), FALSE , "%s: RX semaphore take failure.", __func__); + ESP_LOGV(MB_PORT_TAG,"%s:Take RX semaphore (%lu ticks).", __func__, lTimeOut); + return TRUE; +} + +static void vMBMasterRxSemaRelease( void ) +{ + BaseType_t xStatus = pdFALSE; + xStatus = xSemaphoreGive(xMasterSemaRxHandle); + if (xStatus != pdTRUE) { + ESP_LOGD(MB_PORT_TAG,"%s:RX semaphore is free.", __func__); + } +} + +static BOOL vMBMasterRxSemaIsBusy( void ) +{ + BaseType_t xStatus = pdFALSE; + xStatus = (uxSemaphoreGetCount(xMasterSemaRxHandle) == 0) ? TRUE : FALSE; + return xStatus; +} + +void vMBMasterRxFlush( void ) +{ + size_t xSize = 1; + esp_err_t xErr = ESP_OK; + for (int xCount = 0; (xCount < MB_SERIAL_RX_FLUSH_RETRY) && xSize; xCount++) { + xErr = uart_get_buffered_data_len(ucUartNumber, &xSize); + MB_PORT_CHECK((xErr == ESP_OK), ; , "mb flush serial fail, error = 0x%x.", xErr); + BaseType_t xStatus = xQueueReset(xMbUartQueue); + if (xStatus) { + xErr = uart_flush_input(ucUartNumber); + MB_PORT_CHECK((xErr == ESP_OK), ; , "mb flush serial fail, error = 0x%x.", xErr); + } + } +} + void vMBMasterPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) { // This function can be called from xMBRTUTransmitFSM() of different task if (bTxEnable) { + vMBMasterRxFlush(); bTxStateEnabled = TRUE; } else { bTxStateEnabled = FALSE; } if (bRxEnable) { bRxStateEnabled = TRUE; + vMBMasterRxSemaRelease(); vTaskResume(xMbTaskHandle); // Resume receiver task } else { vTaskSuspend(xMbTaskHandle); // Block receiver task @@ -85,17 +139,26 @@ void vMBMasterPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) static USHORT usMBMasterPortSerialRxPoll(size_t xEventSize) { - BOOL xReadStatus = TRUE; + BOOL xStatus = TRUE; USHORT usCnt = 0; - if (bRxStateEnabled) { - while(xReadStatus && (usCnt++ <= MB_SERIAL_BUF_SIZE)) { + xStatus = xMBMasterPortRxSemaTake(MB_SERIAL_RX_SEMA_TOUT); + if (xStatus) { + while(xStatus && (usCnt++ <= xEventSize)) { // Call the Modbus stack callback function and let it fill the stack buffers. - xReadStatus = pxMBMasterFrameCBByteReceived(); // callback to receive FSM + xStatus = pxMBMasterFrameCBByteReceived(); // callback to receive FSM } // The buffer is transferred into Modbus stack and is not needed here any more uart_flush_input(ucUartNumber); ESP_LOGD(TAG, "Received data: %d(bytes in buffer)\n", (uint32_t)usCnt); +#if !CONFIG_FMB_TIMER_PORT_ENABLED + vMBMasterSetCurTimerMode(MB_TMODE_T35); + xStatus = pxMBMasterPortCBTimerExpired(); + if (!xStatus) { + xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED); + ESP_LOGD(TAG, "Send additional RX ready event."); + } +#endif } else { ESP_LOGE(TAG, "%s: bRxState disabled but junk data (%d bytes) received. ", __func__, xEventSize); } @@ -129,7 +192,7 @@ static void vUartTask(void* pvParameters) uart_event_t xEvent; USHORT usResult = 0; for(;;) { - if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) { + if (xMBPortSerialWaitEvent(xMbUartQueue, (void*)&xEvent, portMAX_DELAY)) { ESP_LOGD(TAG, "MB_uart[%d] event:", ucUartNumber); switch(xEvent.type) { //Event of UART receiving data @@ -138,11 +201,17 @@ static void vUartTask(void* pvParameters) // This flag set in the event means that no more // data received during configured timeout and UART TOUT feature is triggered if (xEvent.timeout_flag) { + // Response is received but previous packet processing is pending + // Do not wait completion of processing and just discard received data as incorrect + if (vMBMasterRxSemaIsBusy()) { + vMBMasterRxFlush(); + break; + } + // Get buffered data length + ESP_ERROR_CHECK(uart_get_buffered_data_len(ucUartNumber, &xEvent.size)); // Read received data and send it to modbus stack usResult = usMBMasterPortSerialRxPoll(xEvent.size); ESP_LOGD(TAG,"Timeout occured, processed: %d bytes", usResult); - // Block receiver task until data is not processed - vTaskSuspend(NULL); } break; //Event of HW FIFO overflow detected @@ -242,7 +311,8 @@ BOOL xMBMasterPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, // Set always timeout flag to trigger timeout interrupt even after rx fifo full uart_set_always_rx_timeout(ucUartNumber, true); - + MB_PORT_CHECK((xMBMasterPortRxSemaInit()), FALSE, + "mb serial RX semaphore create fail."); // Create a task to handle UART events BaseType_t xStatus = xTaskCreatePinnedToCore(vUartTask, "uart_queue_task", MB_SERIAL_TASK_STACK_SIZE, diff --git a/components/freemodbus/freemodbus/port/porttimer.c b/components/freemodbus/freemodbus/port/porttimer.c new file mode 100644 index 0000000000..4fbfbfda71 --- /dev/null +++ b/components/freemodbus/freemodbus/port/porttimer.c @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2010 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: ESP32 Port Demo Application + * Copyright (C) 2010 Christian Walter + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: portother.c,v 1.1 2010/06/06 13:07:20 wolti Exp $ + */ +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +#include "mb.h" +#include "mbport.h" +#include "sdkconfig.h" + +#if CONFIG_FMB_TIMER_PORT_ENABLED + +static const char *TAG = "MBS_TIMER"; + +static xTimerContext_t* pxTimerContext = NULL; + +/* ----------------------- Start implementation -----------------------------*/ +static void IRAM_ATTR vTimerAlarmCBHandler(void *param) +{ + pxMBPortCBTimerExpired(); // Timer expired callback function + pxTimerContext->xTimerState = TRUE; + ESP_EARLY_LOGD(TAG, "Slave timeout triggered."); +} +#endif + +BOOL xMBPortTimersInit(USHORT usTimeOut50us) +{ +#if CONFIG_FMB_TIMER_PORT_ENABLED + MB_PORT_CHECK((usTimeOut50us > 0), FALSE, + "Modbus timeout discreet is incorrect."); + MB_PORT_CHECK(!pxTimerContext, FALSE, + "Modbus timer is already created."); + pxTimerContext = calloc(1, sizeof(xTimerContext_t)); + if (!pxTimerContext) { + return FALSE; + } + pxTimerContext->xTimerIntHandle = NULL; + // Save timer reload value for Modbus T35 period + pxTimerContext->usT35Ticks = usTimeOut50us; + esp_timer_create_args_t xTimerConf = { + .callback = vTimerAlarmCBHandler, + .arg = NULL, +#if (MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD && CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD) + .dispatch_method = ESP_TIMER_ISR, +#else + .dispatch_method = ESP_TIMER_TASK, +#endif + .name = "MBS_T35timer" + }; + // Create Modbus timer + esp_err_t xErr = esp_timer_create(&xTimerConf, &(pxTimerContext->xTimerIntHandle)); + if (xErr) { + return FALSE; + } +#endif + return TRUE; +} + +void vMBPortTimersEnable(void) +{ +#if CONFIG_FMB_TIMER_PORT_ENABLED + MB_PORT_CHECK((pxTimerContext && pxTimerContext->xTimerIntHandle), ; , + "timer is not initialized."); + uint64_t xToutUs = (pxTimerContext->usT35Ticks * MB_TIMER_TICK_TIME_US); + esp_timer_stop(pxTimerContext->xTimerIntHandle); + esp_timer_start_once(pxTimerContext->xTimerIntHandle, xToutUs); + pxTimerContext->xTimerState = FALSE; +#endif +} + +void MB_PORT_ISR_ATTR +vMBPortTimersDisable(void) +{ +#if CONFIG_FMB_TIMER_PORT_ENABLED + // Disable timer alarm + esp_timer_stop(pxTimerContext->xTimerIntHandle); +#endif +} + +void vMBPortTimerClose(void) +{ +#if CONFIG_FMB_TIMER_PORT_ENABLED + // Delete active timer + if (pxTimerContext) { + if (pxTimerContext->xTimerIntHandle) { + esp_timer_stop(pxTimerContext->xTimerIntHandle); + esp_timer_delete(pxTimerContext->xTimerIntHandle); + } + free(pxTimerContext); + pxTimerContext = NULL; + } +#endif +} diff --git a/components/freemodbus/freemodbus/port/porttimer_m.c b/components/freemodbus/freemodbus/port/porttimer_m.c new file mode 100644 index 0000000000..f1595a6a27 --- /dev/null +++ b/components/freemodbus/freemodbus/port/porttimer_m.c @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: ESP32 Port + * Copyright (C) 2013 Armink + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: porttimer_m.c,v 1.60 2013/08/13 15:07:05 Armink add Master Functions$ + */ + +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +#include "mb_m.h" +#include "mbport.h" +#include "sdkconfig.h" + +static const char *TAG = "MBM_TIMER"; + +/* ----------------------- Variables ----------------------------------------*/ +static xTimerContext_t* pxTimerContext = NULL; + +/* ----------------------- Start implementation -----------------------------*/ +static void IRAM_ATTR vTimerAlarmCBHandler(void *param) +{ + pxMBMasterPortCBTimerExpired(); // Timer expired callback function + pxTimerContext->xTimerState = TRUE; + ESP_EARLY_LOGD(TAG, "Timer mode: (%d) triggered", xMBMasterGetCurTimerMode()); +} + +BOOL xMBMasterPortTimersInit(USHORT usTimeOut50us) +{ + MB_PORT_CHECK((usTimeOut50us > 0), FALSE, + "Modbus timeout discreet is incorrect."); + MB_PORT_CHECK(!pxTimerContext, FALSE, + "Modbus timer is already created."); + pxTimerContext = calloc(1, sizeof(xTimerContext_t)); + if (!pxTimerContext) { + return FALSE; + } + pxTimerContext->xTimerIntHandle = NULL; + // Save timer reload value for Modbus T35 period + pxTimerContext->usT35Ticks = usTimeOut50us; + esp_timer_create_args_t xTimerConf = { + .callback = vTimerAlarmCBHandler, + .arg = NULL, +#if (MB_TIMER_SUPPORTS_ISR_DISPATCH_METHOD && CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD) + .dispatch_method = ESP_TIMER_ISR, +#else + .dispatch_method = ESP_TIMER_TASK, +#endif + .name = "MBM_T35timer" + }; + // Create Modbus timer + esp_err_t xErr = esp_timer_create(&xTimerConf, &(pxTimerContext->xTimerIntHandle)); + if (xErr) { + return FALSE; + } + return TRUE; +} + +// Set timer alarm value +static BOOL xMBMasterPortTimersEnable(uint64_t xToutUs) +{ + MB_PORT_CHECK(pxTimerContext && (pxTimerContext->xTimerIntHandle), FALSE, + "timer is not initialized."); + MB_PORT_CHECK((xToutUs > 0), FALSE, + "incorrect tick value for timer = (0x%llu).", xToutUs); + esp_timer_stop(pxTimerContext->xTimerIntHandle); + esp_timer_start_once(pxTimerContext->xTimerIntHandle, xToutUs); + pxTimerContext->xTimerState = FALSE; + return TRUE; +} + +void vMBMasterPortTimersT35Enable(void) +{ +#if CONFIG_FMB_TIMER_PORT_ENABLED + uint64_t xToutUs = (pxTimerContext->usT35Ticks * MB_TIMER_TICK_TIME_US); + + // Set current timer mode, don't change it. + vMBMasterSetCurTimerMode(MB_TMODE_T35); + // Set timer alarm + (void)xMBMasterPortTimersEnable(xToutUs); +#endif +} + +void vMBMasterPortTimersConvertDelayEnable(void) +{ + // Covert time in milliseconds into ticks + uint64_t xToutUs = (MB_MASTER_DELAY_MS_CONVERT * 1000); + + // Set current timer mode + vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY); + ESP_LOGD(MB_PORT_TAG,"%s Convert delay enable.", __func__); + (void)xMBMasterPortTimersEnable(xToutUs); +} + +void vMBMasterPortTimersRespondTimeoutEnable(void) +{ + uint64_t xToutUs = (MB_MASTER_TIMEOUT_MS_RESPOND * 1000); + + vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT); + ESP_LOGD(MB_PORT_TAG,"%s Respond enable timeout.", __func__); + (void)xMBMasterPortTimersEnable(xToutUs); +} + +void MB_PORT_ISR_ATTR +vMBMasterPortTimersDisable() +{ + // Disable timer alarm + esp_timer_stop(pxTimerContext->xTimerIntHandle); +} + +void vMBMasterPortTimerClose(void) +{ + // Delete active timer + if (pxTimerContext) { + if (pxTimerContext->xTimerIntHandle) { + esp_timer_stop(pxTimerContext->xTimerIntHandle); + esp_timer_delete(pxTimerContext->xTimerIntHandle); + } + free(pxTimerContext); + pxTimerContext = NULL; + } +} diff --git a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c b/components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.c similarity index 97% rename from components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c rename to components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.c index 1127650c1e..94bec3fcd5 100644 --- a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c +++ b/components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ // mbc_serial_master.c @@ -39,7 +30,7 @@ extern BOOL xMBMasterPortSerialTxPoll(void); #define MB_RESPONSE_TICS pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND + 10) -static mb_master_interface_t* mbm_interface_ptr = NULL; //&default_interface_inst; +static mb_master_interface_t* mbm_interface_ptr = NULL; static const char *TAG = "MB_CONTROLLER_MASTER"; // Modbus event processing task @@ -110,7 +101,7 @@ static esp_err_t mbc_serial_master_start(void) "mb stack initialization failure, eMBInit() returns (0x%x).", status); status = eMBMasterEnable(); MB_MASTER_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE, - "mb stack set slave ID failure, eMBEnable() returned (0x%x).", (uint32_t)status); + "mb stack set slave ID failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status); // Set the mbcontroller start flag EventBits_t flag = xEventGroupSetBits(mbm_opts->mbm_event_group, (EventBits_t)MB_EVENT_STACK_STARTED); diff --git a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.h b/components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.h similarity index 56% rename from components/freemodbus/serial_master/modbus_controller/mbc_serial_master.h rename to components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.h index 5ed72c9b8b..28aec539c7 100644 --- a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.h +++ b/components/freemodbus/freemodbus/serial_master/modbus_controller/mbc_serial_master.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ // mbc_serial_master.h Modbus controller serial master implementation header file diff --git a/components/freemodbus/serial_master/port/port_serial_master.h b/components/freemodbus/freemodbus/serial_master/port/port_serial_master.h similarity index 76% rename from components/freemodbus/serial_master/port/port_serial_master.h rename to components/freemodbus/freemodbus/serial_master/port/port_serial_master.h index c639743aaf..e389b8198c 100644 --- a/components/freemodbus/serial_master/port/port_serial_master.h +++ b/components/freemodbus/freemodbus/serial_master/port/port_serial_master.h @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2010 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Port Demo Application @@ -46,7 +39,6 @@ /* ----------------------- Platform includes --------------------------------*/ #include "driver/uart.h" -#include "driver/timer.h" #include "esp_log.h" // for ESP_LOGE macro #include "mb_m.h" #include "port.h" diff --git a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c b/components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c similarity index 94% rename from components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c rename to components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c index d389c68dd5..cae13616cd 100644 --- a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c +++ b/components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c @@ -1,18 +1,8 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ - // mbc_serial_slave.c // Implementation of the Modbus controller serial slave diff --git a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h b/components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h similarity index 62% rename from components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h rename to components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h index e8c5c3d71c..9a515bac40 100644 --- a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h +++ b/components/freemodbus/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.h @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ // mbc_serial_slave.h Modbus controller serial slave implementation header file diff --git a/components/freemodbus/serial_slave/port/port_serial_slave.h b/components/freemodbus/freemodbus/serial_slave/port/port_serial_slave.h similarity index 76% rename from components/freemodbus/serial_slave/port/port_serial_slave.h rename to components/freemodbus/freemodbus/serial_slave/port/port_serial_slave.h index 16d299cb1d..880876c11d 100644 --- a/components/freemodbus/serial_slave/port/port_serial_slave.h +++ b/components/freemodbus/freemodbus/serial_slave/port/port_serial_slave.h @@ -1,16 +1,9 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2010 Christian Walter * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * SPDX-License-Identifier: BSD-3-Clause * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 Port Demo Application @@ -47,7 +40,6 @@ /* ----------------------- Platform includes --------------------------------*/ #include "driver/uart.h" -#include "driver/timer.h" #include "esp_log.h" #include "port.h" diff --git a/components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c b/components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c similarity index 97% rename from components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c rename to components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c index 17e47f0c8c..279caad629 100644 --- a/components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c +++ b/components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.c @@ -1,16 +1,7 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ // mbc_tcp_master.c @@ -90,7 +81,7 @@ static void mbc_tcp_master_free_slave_list(void) // Initialize interface properties mb_master_options_t* mbm_opts = &mbm_interface_ptr->opts; - LIST_FOREACH(it, &mbm_opts->mbm_slave_list, entries) { + while ((it = LIST_FIRST(&mbm_opts->mbm_slave_list))) { LIST_REMOVE(it, entries); mbm_opts->mbm_slave_list_count--; free(it); @@ -168,7 +159,7 @@ static esp_err_t mbc_tcp_master_start(void) status = eMBMasterEnable(); MB_MASTER_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE, - "mb stack set slave ID failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status); + "mb stack enable failure, eMBMasterEnable() returned (0x%x).", (uint32_t)status); // Add slave IP address for each slave to initialize connection mb_slave_addr_entry_t *p_slave_info; @@ -181,13 +172,12 @@ static esp_err_t mbc_tcp_master_start(void) // Add end of list condition (void)xMBTCPPortMasterAddSlaveIp(0xFF, NULL, 0xFF); - // Wait for connection done event bool start = (bool)xMBTCPPortMasterWaitEvent(mbm_opts->mbm_event_group, - (EventBits_t)MB_EVENT_STACK_STARTED, MB_TCP_CONNECTION_TOUT); + (EventBits_t)MB_EVENT_STACK_STARTED, MB_TCP_CONNECTION_TOUT); MB_MASTER_CHECK((start), ESP_ERR_INVALID_STATE, - "mb stack could not connect to slaves for %d seconds.", - CONFIG_FMB_TCP_CONNECTION_TOUT_SEC); + "mb stack could not connect to slaves for %d seconds.", + CONFIG_FMB_TCP_CONNECTION_TOUT_SEC); return ESP_OK; } diff --git a/components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h b/components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h similarity index 66% rename from components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h rename to components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h index 9593f77a1d..b0731dfdf3 100644 --- a/components/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h +++ b/components/freemodbus/freemodbus/tcp_master/modbus_controller/mbc_tcp_master.h @@ -1,17 +1,8 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ + * SPDX-License-Identifier: Apache-2.0 + */ // mbc_tcp_master.h Modbus controller TCP master implementation header file diff --git a/components/freemodbus/tcp_master/port/port_tcp_master.c b/components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.c similarity index 98% rename from components/freemodbus/tcp_master/port/port_tcp_master.c rename to components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.c index ec0210bb2b..390026d5b7 100644 --- a/components/freemodbus/tcp_master/port/port_tcp_master.c +++ b/components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-3-Clause * - * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 TCP Port @@ -281,23 +281,27 @@ static int vMBTCPPortMasterRxCheck(int xSd, fd_set* pxFdSet, int xTimeMs) return xRes; } -static int xMBTCPPortMasterGetBuf(MbSlaveInfo_t* pxInfo, UCHAR* pucDstBuf, USHORT usLength) +static int xMBTCPPortMasterGetBuf(MbSlaveInfo_t* pxInfo, UCHAR* pucDstBuf, USHORT usLength, uint16_t xTimeMs) { int xLength = 0; UCHAR* pucBuf = pucDstBuf; USHORT usBytesLeft = usLength; + struct timeval xTime; MB_PORT_CHECK((pxInfo && pxInfo->xSockId > -1), -1, "Try to read incorrect socket = #%d.", pxInfo->xSockId); + // Set receive timeout for socket <= slave respond time + xTime.tv_sec = xTimeMs / 1000; + xTime.tv_usec = (xTimeMs % 1000) * 1000; + setsockopt(pxInfo->xSockId, SOL_SOCKET, SO_RCVTIMEO, &xTime, sizeof(xTime)); + // Receive data from connected client while (usBytesLeft > 0) { xMBTCPPortMasterCheckShutdown(); - // none blocking read from socket with timeout - xLength = recv(pxInfo->xSockId, pucBuf, usBytesLeft, MSG_DONTWAIT); + xLength = recv(pxInfo->xSockId, pucBuf, usBytesLeft, 0); if (xLength < 0) { if (errno == EAGAIN) { - // Read timeout occurred, continue reading - continue; + // Read timeout occurred, check the timeout and return } else if (errno == ENOTCONN) { // Socket connection closed ESP_LOGE(TAG, "Socket(#%d)(%s) connection closed.", @@ -316,7 +320,6 @@ static int xMBTCPPortMasterGetBuf(MbSlaveInfo_t* pxInfo, UCHAR* pucDstBuf, USHOR if (xMBTCPPortMasterGetRespTimeLeft(pxInfo) == 0) { return ERR_TIMEOUT; } - vTaskDelay(1); } return usLength; } @@ -331,7 +334,8 @@ static int vMBTCPPortMasterReadPacket(MbSlaveInfo_t* pxInfo) if (pxInfo) { MB_PORT_CHECK((pxInfo->xSockId > 0), -1, "Try to read incorrect socket = #%d.", pxInfo->xSockId); // Read packet header - xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[0], MB_TCP_UID); + xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[0], + MB_TCP_UID, xMBTCPPortMasterGetRespTimeLeft(pxInfo)); if (xRet < 0) { pxInfo->xRcvErr = xRet; return xRet; @@ -344,7 +348,8 @@ static int vMBTCPPortMasterReadPacket(MbSlaveInfo_t* pxInfo) // If we have received the MBAP header we can analyze it and calculate // the number of bytes left to complete the current request. xLength = (int)MB_TCP_GET_FIELD(pxInfo->pucRcvBuf, MB_TCP_LEN); - xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[MB_TCP_UID], xLength); + xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[MB_TCP_UID], + xLength, xMBTCPPortMasterGetRespTimeLeft(pxInfo)); if (xRet < 0) { pxInfo->xRcvErr = xRet; return xRet; @@ -847,12 +852,13 @@ static void vMBTCPPortMasterTask(void *pvParameters) pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); } else if ((xRet == ERR_TIMEOUT) || (xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo) == 0)) { // Timeout occurred when receiving frame, process respond timeout + xMBTCPPortMasterFsmSetError(EV_ERROR_RESPOND_TIMEOUT, EV_MASTER_ERROR_PROCESS); ESP_LOGD(TAG, MB_SLAVE_FMT(", frame read timeout."), pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); } else if (xRet == ERR_BUF) { // After retries a response with incorrect TID received, process failure. xMBTCPPortMasterFsmSetError(EV_ERROR_RECEIVE_DATA, EV_MASTER_ERROR_PROCESS); - ESP_LOGD(TAG, MB_SLAVE_FMT(", frame error."), + ESP_LOGW(TAG, MB_SLAVE_FMT(", frame error."), pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); } else { ESP_LOGE(TAG, MB_SLAVE_FMT(", critical error=%d."), diff --git a/components/freemodbus/tcp_master/port/port_tcp_master.h b/components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.h similarity index 98% rename from components/freemodbus/tcp_master/port/port_tcp_master.h rename to components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.h index fd714d36c7..9867beef56 100644 --- a/components/freemodbus/tcp_master/port/port_tcp_master.h +++ b/components/freemodbus/freemodbus/tcp_master/port/port_tcp_master.h @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-3-Clause * - * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 TCP Port diff --git a/components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c b/components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c similarity index 94% rename from components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c rename to components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c index 29b8678d8c..45078b4bc4 100644 --- a/components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c +++ b/components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.c @@ -1,17 +1,8 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ + * SPDX-License-Identifier: Apache-2.0 + */ // mbc_tcp_slave.c // Implementation of the Modbus controller TCP slave diff --git a/components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h b/components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h similarity index 61% rename from components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h rename to components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h index 0987610092..56ece0d97d 100644 --- a/components/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h +++ b/components/freemodbus/freemodbus/tcp_slave/modbus_controller/mbc_tcp_slave.h @@ -1,17 +1,8 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ + * SPDX-License-Identifier: Apache-2.0 + */ // mbc_tcp_slave.h Modbus controller TCP slave implementation header file diff --git a/components/freemodbus/tcp_slave/port/port_tcp_slave.c b/components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.c similarity index 96% rename from components/freemodbus/tcp_slave/port/port_tcp_slave.c rename to components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.c index b64ad8efd9..c6485f47d7 100644 --- a/components/freemodbus/tcp_slave/port/port_tcp_slave.c +++ b/components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-3-Clause * - * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 TCP Port @@ -61,7 +61,7 @@ /* ----------------------- Defines -----------------------------------------*/ #define MB_TCP_DISCONNECT_TIMEOUT ( CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000000 ) // disconnect timeout in uS -#define MB_TCP_RESP_TIMEOUT_MS ( MB_MASTER_TIMEOUT_MS_RESPOND - 2 ) // slave response time limit +#define MB_TCP_RESP_TIMEOUT_MS ( MB_MASTER_TIMEOUT_MS_RESPOND - 1 ) // slave response time limit #define MB_TCP_NET_LISTEN_BACKLOG ( SOMAXCONN ) /* ----------------------- Prototypes ---------------------------------------*/ @@ -87,19 +87,19 @@ static void vxMBTCPPortMStoTimeVal(USHORT usTimeoutMs, struct timeval *pxTimeout pxTimeout->tv_usec = (usTimeoutMs - (pxTimeout->tv_sec * 1000)) * 1000; } -static xQueueHandle xMBTCPPortRespQueueCreate(void) +static QueueHandle_t xMBTCPPortRespQueueCreate(void) { - xQueueHandle xRespQueueHandle = xQueueCreate(2, sizeof(void*)); + QueueHandle_t xRespQueueHandle = xQueueCreate(2, sizeof(void*)); MB_PORT_CHECK((xRespQueueHandle != NULL), NULL, "TCP respond queue creation failure."); return xRespQueueHandle; } -static void vMBTCPPortRespQueueDelete(xQueueHandle xRespQueueHandle) +static void vMBTCPPortRespQueueDelete(QueueHandle_t xRespQueueHandle) { vQueueDelete(xRespQueueHandle); } -static void* vxMBTCPPortRespQueueRecv(xQueueHandle xRespQueueHandle) +static void* vxMBTCPPortRespQueueRecv(QueueHandle_t xRespQueueHandle) { void* pvResp = NULL; MB_PORT_CHECK(xRespQueueHandle != NULL, NULL, "Response queue is not initialized."); @@ -111,7 +111,7 @@ static void* vxMBTCPPortRespQueueRecv(xQueueHandle xRespQueueHandle) return pvResp; } -static BOOL vxMBTCPPortRespQueueSend(xQueueHandle xRespQueueHandle, void* pvResp) +static BOOL vxMBTCPPortRespQueueSend(QueueHandle_t xRespQueueHandle, void* pvResp) { MB_PORT_CHECK(xRespQueueHandle != NULL, FALSE, "Response queue is not initialized."); BaseType_t xStatus = xQueueSend(xConfig.xRespQueueHandle, @@ -150,12 +150,13 @@ xMBTCPPortInit( USHORT usTCPPort ) xConfig.pcBindAddr = NULL; // Create task for packet processing - BaseType_t xErr = xTaskCreate(vMBTCPPortServerTask, - "tcp_server_task", + BaseType_t xErr = xTaskCreatePinnedToCore(vMBTCPPortServerTask, + "tcp_slave_task", MB_TCP_STACK_SIZE, NULL, MB_TCP_TASK_PRIO, - &xConfig.xMbTcpTaskHandle); + &xConfig.xMbTcpTaskHandle, + MB_PORT_TASK_AFFINITY); vTaskSuspend(xConfig.xMbTcpTaskHandle); if (xErr != pdTRUE) { @@ -204,6 +205,10 @@ static int xMBTCPPortAcceptConnection(int xListenSockId, char** pcIPAddr) inet6_ntoa_r(((struct sockaddr_in6 *)&xSrcAddr)->sin6_addr, cAddrStr, sizeof(cAddrStr) - 1); } #endif + else { + // Make sure ss_family is valid + abort(); + } ESP_LOGI(TAG, "Socket (#%d), accept client connection from address: %s", xSockId, cAddrStr); pcStr = calloc(1, strlen(cAddrStr) + 1); if (pcStr && pcIPAddr) { @@ -560,6 +565,12 @@ static void vMBTCPPortServerTask(void *pvParameters) pxClientInfo->xSockId, pxClientInfo->pcIpAddr, xErr); break; } + + if (xShutdownSemaphore) { + xSemaphoreGive(xShutdownSemaphore); + vTaskDelete(NULL); + } + // Close client connection xMBTCPPortCloseConnection(pxClientInfo); diff --git a/components/freemodbus/tcp_slave/port/port_tcp_slave.h b/components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.h similarity index 96% rename from components/freemodbus/tcp_slave/port/port_tcp_slave.h rename to components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.h index e9fc5d9edb..efac0fb514 100644 --- a/components/freemodbus/tcp_slave/port/port_tcp_slave.h +++ b/components/freemodbus/freemodbus/tcp_slave/port/port_tcp_slave.h @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-3-Clause * - * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD */ /* * FreeModbus Libary: ESP32 TCP Port @@ -76,7 +76,7 @@ typedef struct { typedef struct { TaskHandle_t xMbTcpTaskHandle; /*!< Server task handle */ - xQueueHandle xRespQueueHandle; /*!< Response queue handle */ + QueueHandle_t xRespQueueHandle; /*!< Response queue handle */ MbClientInfo_t* pxCurClientInfo; /*!< Current client info */ MbClientInfo_t** pxMbClientInfo; /*!< Pointers to information about connected clients */ USHORT usPort; /*!< TCP/UDP port number */ diff --git a/components/freemodbus/idf_component.yml b/components/freemodbus/idf_component.yml new file mode 100644 index 0000000000..aa6d96e49b --- /dev/null +++ b/components/freemodbus/idf_component.yml @@ -0,0 +1,11 @@ +version: "1.0.7" +description: ESP-MODBUS is the official Modbus library for Espressif SoCs. +url: https://github.com/espressif/esp-modbus +dependencies: + idf: ">=4.1" +files: + exclude: + - "docs/_build/**/*" + - "docs/_build" + - "test/**/build/**/*" + - "test/**/build" diff --git a/components/freemodbus/port/porttimer.c b/components/freemodbus/port/porttimer.c deleted file mode 100644 index 47d9b73607..0000000000 --- a/components/freemodbus/port/porttimer.c +++ /dev/null @@ -1,154 +0,0 @@ -/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * FreeModbus Libary: ESP32 Port Demo Application - * Copyright (C) 2010 Christian Walter - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * File: $Id: portother.c,v 1.1 2010/06/06 13:07:20 wolti Exp $ - */ -/* ----------------------- Platform includes --------------------------------*/ -#include "port.h" - -/* ----------------------- Modbus includes ----------------------------------*/ -#include "sdkconfig.h" -#include "mb.h" -#include "mbport.h" -#include "driver/timer.h" -#include "port_serial_slave.h" -#include "sdkconfig.h" - -#if CONFIG_FMB_TIMER_PORT_ENABLED - -#define MB_US50_FREQ (20000) // 20kHz 1/20000 = 50mks -#define MB_DISCR_TIME_US (50) // 50uS = one discreet for timer - -#define MB_TIMER_PRESCALLER ((TIMER_BASE_CLK / MB_US50_FREQ) - 1) -#define MB_TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // convert counter value to seconds -#define MB_TIMER_DIVIDER ((TIMER_BASE_CLK / 1000000UL) * MB_DISCR_TIME_US - 1) // divider for 50uS -#define MB_TIMER_WITH_RELOAD (1) - -static const USHORT usTimerIndex = CONFIG_FMB_TIMER_INDEX; // Modbus Timer index used by stack -static const USHORT usTimerGroupIndex = CONFIG_FMB_TIMER_GROUP; // Modbus Timer group index used by stack -static timer_isr_handle_t xTimerIntHandle; // Timer interrupt handle - -/* ----------------------- Start implementation -----------------------------*/ -static void IRAM_ATTR vTimerGroupIsr(void *param) -{ - assert((int)param == usTimerIndex); - // Retrieve the counter value from the timer that reported the interrupt - timer_group_clr_intr_status_in_isr(usTimerGroupIndex, usTimerIndex); - (void)pxMBPortCBTimerExpired(); // Timer callback function - // Enable alarm - timer_group_enable_alarm_in_isr(usTimerGroupIndex, usTimerIndex); -} -#endif - -BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) -{ -#if CONFIG_FMB_TIMER_PORT_ENABLED - MB_PORT_CHECK((usTim1Timerout50us > 0), FALSE, - "Modbus timeout discreet is incorrect."); - esp_err_t xErr; - timer_config_t config = { - .alarm_en = TIMER_ALARM_EN, - .auto_reload = MB_TIMER_WITH_RELOAD, - .counter_dir = TIMER_COUNT_UP, - .divider = MB_TIMER_PRESCALLER, - .intr_type = TIMER_INTR_LEVEL, - .counter_en = TIMER_PAUSE, - }; - // Configure timer - xErr = timer_init(usTimerGroupIndex, usTimerIndex, &config); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer init failure, timer_init() returned (0x%x).", xErr); - // Stop timer counter - xErr = timer_pause(usTimerGroupIndex, usTimerIndex); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "stop timer failure, timer_pause() returned (0x%x).", xErr); - // Reset counter value - xErr = timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0x00000000ULL); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set value failure, timer_set_counter_value() returned (0x%x).", xErr); - // wait3T5_us = 35 * 11 * 100000 / baud; // the 3.5T symbol time for baudrate - // Set alarm value for usTim1Timerout50us * 50uS - xErr = timer_set_alarm_value(usTimerGroupIndex, usTimerIndex, (uint32_t)(usTim1Timerout50us)); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "failure to set alarm failure, timer_set_alarm_value() returned (0x%x).", - (uint32_t)xErr); - // Register ISR for timer - xErr = timer_isr_register(usTimerGroupIndex, usTimerIndex, vTimerGroupIsr, - (void*)(uint32_t)usTimerIndex, MB_PORT_TIMER_ISR_FLAG, &xTimerIntHandle); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set value failure, timer_isr_register() returned (0x%x).", - (uint32_t)xErr); -#endif - return TRUE; -} - -void vMBPortTimersEnable(void) -{ -#if CONFIG_FMB_TIMER_PORT_ENABLED - ESP_ERROR_CHECK(timer_pause(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0ULL)); - ESP_ERROR_CHECK(timer_enable_intr(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(timer_start(usTimerGroupIndex, usTimerIndex)); -#endif -} - -void MB_PORT_ISR_ATTR -vMBPortTimersDisable(void) -{ -#if CONFIG_FMB_TIMER_PORT_ENABLED - if( (BOOL)xPortInIsrContext() ) { - timer_group_set_counter_enable_in_isr(usTimerGroupIndex, usTimerIndex, TIMER_PAUSE); - } else { - ESP_ERROR_CHECK(timer_pause(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0ULL)); - // Disable timer interrupt - ESP_ERROR_CHECK(timer_disable_intr(usTimerGroupIndex, usTimerIndex)); - } -#endif -} - -void vMBPortTimerClose(void) -{ -#ifdef CONFIG_FMB_TIMER_PORT_ENABLED - ESP_ERROR_CHECK(timer_deinit(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(esp_intr_free(xTimerIntHandle)); -#endif -} diff --git a/components/freemodbus/port/porttimer_m.c b/components/freemodbus/port/porttimer_m.c deleted file mode 100644 index 18fa7f640d..0000000000 --- a/components/freemodbus/port/porttimer_m.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2013 Armink - * - * SPDX-License-Identifier: BSD-3-Clause - * - * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD - */ -/* - * FreeModbus Libary: ESP32 Port - * Copyright (C) 2013 Armink - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * File: $Id: porttimer_m.c,v 1.60 2013/08/13 15:07:05 Armink add Master Functions$ - */ - -/* ----------------------- Platform includes --------------------------------*/ -#include "port.h" - -/* ----------------------- Modbus includes ----------------------------------*/ -#include "mb_m.h" -#include "mbport.h" -#include "port_serial_master.h" -#include "sdkconfig.h" - -#define MB_US50_FREQ (20000) // 20kHz 1/20000 = 50mks -#define MB_TICK_TIME_US (50) // 50uS = one tick for timer - -#define MB_TIMER_PRESCALLER ((TIMER_BASE_CLK / MB_US50_FREQ) - 1) -#define MB_TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) -#define MB_TIMER_DIVIDER ((TIMER_BASE_CLK / 1000000UL) * MB_TICK_TIME_US - 1) // divider for 50uS -#define MB_TIMER_WITH_RELOAD (1) - -/* ----------------------- Variables ----------------------------------------*/ -static USHORT usT35TimeOut50us; - -// Initialize Modbus Timer group and index used by stack -static const USHORT usTimerIndex = CONFIG_FMB_MASTER_TIMER_INDEX; -static const USHORT usTimerGroupIndex = CONFIG_FMB_MASTER_TIMER_GROUP; -static timer_isr_handle_t xTimerIntHandle; // Timer interrupt handle - -/* ----------------------- static functions ---------------------------------*/ -static void IRAM_ATTR vTimerGroupIsr(void *param) -{ - assert((int)param == usTimerIndex); - // Retrieve the the counter value from the timer that reported the interrupt - timer_group_clr_intr_status_in_isr(usTimerGroupIndex, usTimerIndex); - (void)pxMBMasterPortCBTimerExpired(); // Timer expired callback function - // Enable alarm - timer_group_enable_alarm_in_isr(usTimerGroupIndex, usTimerIndex); -} - -/* ----------------------- Start implementation -----------------------------*/ -BOOL xMBMasterPortTimersInit(USHORT usTimeOut50us) -{ - MB_PORT_CHECK((usTimeOut50us > 0), FALSE, - "Modbus timeout discreet is incorrect."); - // Save timer reload value for Modbus T35 period - usT35TimeOut50us = usTimeOut50us; - esp_err_t xErr; - timer_config_t config = { - .alarm_en = TIMER_ALARM_EN, - .auto_reload = MB_TIMER_WITH_RELOAD, - .counter_dir = TIMER_COUNT_UP, - .divider = MB_TIMER_PRESCALLER, - .intr_type = TIMER_INTR_LEVEL, - .counter_en = TIMER_PAUSE, - }; - // Configure timer - xErr = timer_init(usTimerGroupIndex, usTimerIndex, &config); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer init failure, timer_init() returned (0x%x).", (uint32_t)xErr); - // Stop timer counter - xErr = timer_pause(usTimerGroupIndex, usTimerIndex); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "stop timer failure, timer_pause() returned (0x%x).", (uint32_t)xErr); - // Reset counter value - xErr = timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0x00000000ULL); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set value failure, timer_set_counter_value() returned (0x%x).", - (uint32_t)xErr); - // wait3T5_us = 35 * 11 * 100000 / baud; // the 3.5T symbol time for baudrate - // Set alarm value for usTimeOut50us * 50uS - xErr = timer_set_alarm_value(usTimerGroupIndex, usTimerIndex, (uint32_t)(usTimeOut50us)); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "failure to set alarm failure, timer_set_alarm_value() returned (0x%x).", - (uint32_t)xErr); - // Register ISR for timer - xErr = timer_isr_register(usTimerGroupIndex, usTimerIndex, - vTimerGroupIsr, (void*)(uint32_t)usTimerIndex, MB_PORT_TIMER_ISR_FLAG, &xTimerIntHandle); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set value failure, timer_isr_register() returned (0x%x).", - (uint32_t)xErr); - return TRUE; -} - -// Set alarm value for usTimerTimeOut50us * 50uS -static BOOL xMBMasterPortTimersEnable(USHORT usTimerTics50us) -{ - MB_PORT_CHECK((usTimerTics50us > 0), FALSE, - "incorrect tick value for timer = (0x%x).", - (uint32_t)usTimerTics50us); - esp_err_t xErr; - xErr = timer_pause(usTimerGroupIndex, usTimerIndex); // stop timer - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer pause failure, timer_pause() returned (0x%x).", - (uint32_t)xErr); - xErr = timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0ULL); // reset timer - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set counter failure, timer_set_counter_value() returned (0x%x).", - (uint32_t)xErr); - // Set alarm value to number of 50uS ticks - xErr = timer_set_alarm_value(usTimerGroupIndex, usTimerIndex, - (uint32_t)(usTimerTics50us)); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set alarm failure, timer_set_alarm_value() returned (0x%x).", - (uint32_t)xErr); - xErr = timer_enable_intr(usTimerGroupIndex, usTimerIndex); - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer enable interrupt failure, timer_enable_intr() returned (0x%x).", - (uint32_t)xErr); - xErr = timer_start(usTimerGroupIndex, usTimerIndex); // start timer - MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer start failure, timer_start() returned (0x%x).", - (uint32_t)xErr); - return TRUE; -} - -void vMBMasterPortTimersT35Enable(void) -{ - USHORT usTimerTicks = usT35TimeOut50us; - - // Set current timer mode, don't change it. - vMBMasterSetCurTimerMode(MB_TMODE_T35); - // Set timer period - (void)xMBMasterPortTimersEnable(usTimerTicks); -} - -void vMBMasterPortTimersConvertDelayEnable(void) -{ - // Covert time in milliseconds into ticks - USHORT usTimerTicks = ((MB_MASTER_DELAY_MS_CONVERT * 1000) / MB_TICK_TIME_US); - - // Set current timer mode - vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY); - ESP_LOGD(MB_PORT_TAG,"%s Convert delay enable.", __func__); - (void)xMBMasterPortTimersEnable(usTimerTicks); -} - -void vMBMasterPortTimersRespondTimeoutEnable(void) -{ - USHORT usTimerTicks = (MB_MASTER_TIMEOUT_MS_RESPOND * 1000 / MB_TICK_TIME_US); - - vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT); - ESP_LOGD(MB_PORT_TAG,"%s Respond enable timeout.", __func__); - (void)xMBMasterPortTimersEnable(usTimerTicks); -} - -void MB_PORT_ISR_ATTR -vMBMasterPortTimersDisable() -{ - if( (BOOL)xPortInIsrContext() ) { - timer_group_set_counter_enable_in_isr(usTimerGroupIndex, usTimerIndex, TIMER_PAUSE); - } else { - // Stop timer and then reload timer counter value - ESP_ERROR_CHECK(timer_pause(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0ULL)); - // Disable timer interrupt - ESP_ERROR_CHECK(timer_disable_intr(usTimerGroupIndex, usTimerIndex)); - } -} - -void vMBMasterPortTimerClose(void) -{ - ESP_ERROR_CHECK(timer_deinit(usTimerGroupIndex, usTimerIndex)); - ESP_ERROR_CHECK(esp_intr_free(xTimerIntHandle)); -} diff --git a/components/freemodbus/test/CMakeLists.txt b/components/freemodbus/test/CMakeLists.txt new file mode 100644 index 0000000000..a96dced59e --- /dev/null +++ b/components/freemodbus/test/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register() diff --git a/components/freemodbus/test/README.md b/components/freemodbus/test/README.md new file mode 100644 index 0000000000..e8931d46ae --- /dev/null +++ b/components/freemodbus/test/README.md @@ -0,0 +1,9 @@ +# Test implementation + +This directory contains a set of ESP-IDF projects to be used as tests only, which aim to exercise various +configuration of components to check completely arbitrary functionality should it be building only, executing under +various conditions or combination with other components, including custom test frameworks. + +The tests in this folder are not intended to demonstrate the ESP-IDF functionality in any way. + +The examples can be found here: https://github.com/espressif/esp-idf/tree/master/examples/protocols/modbus diff --git a/components/freemodbus/test/component.mk b/components/freemodbus/test/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/freemodbus/test/mb_example_common/CMakeLists.txt b/components/freemodbus/test/mb_example_common/CMakeLists.txt new file mode 100644 index 0000000000..828fc58151 --- /dev/null +++ b/components/freemodbus/test/mb_example_common/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +idf_component_register(SRCS "modbus_params.c" + INCLUDE_DIRS "include") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/test/mb_example_common/README.md b/components/freemodbus/test/mb_example_common/README.md new file mode 100644 index 0000000000..4a08b7e86c --- /dev/null +++ b/components/freemodbus/test/mb_example_common/README.md @@ -0,0 +1,10 @@ +# Modbus Example Common + +This directory contains component that is common for Modbus master and slave examples. The component defines Modbus parameters that are shared between examples and provide code that you can copy and adapt into your own projects. +For more information please refer to Modbus example README.md files located in the folders: + +* `examples/protocols/modbus/serial/mb_master` Modbus serial master implementation (RTU and ASCII) +* `examples/protocols/modbus/serial/mb_slave` Modbus serial slave implementation (RTU and ASCII) +* `examples/protocols/modbus/serial/mb_master` Modbus serial master implementation (RTU and ASCII) +* `examples/protocols/modbus/tcp/mb_tcp_slave` Modbus serial slave implementation (TCP) +* `examples/protocols/modbus/tcp/mb_tcp_master` Modbus serial master implementation (TCP) \ No newline at end of file diff --git a/components/freemodbus/test/mb_example_common/component.mk b/components/freemodbus/test/mb_example_common/component.mk new file mode 100644 index 0000000000..f0dc18cceb --- /dev/null +++ b/components/freemodbus/test/mb_example_common/component.mk @@ -0,0 +1,5 @@ +# +# Component Makefile +# +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := . diff --git a/components/freemodbus/test/mb_example_common/include/modbus_params.h b/components/freemodbus/test/mb_example_common/include/modbus_params.h new file mode 100644 index 0000000000..6060fa83a8 --- /dev/null +++ b/components/freemodbus/test/mb_example_common/include/modbus_params.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/*===================================================================================== + * Description: + * The Modbus parameter structures used to define Modbus instances that + * can be addressed by Modbus protocol. Define these structures per your needs in + * your application. Below is just an example of possible parameters. + *====================================================================================*/ +#ifndef _DEVICE_PARAMS +#define _DEVICE_PARAMS + +// This file defines structure of modbus parameters which reflect correspond modbus address space +// for each modbus register type (coils, discreet inputs, holding registers, input registers) +#pragma pack(push, 1) +typedef struct +{ + uint8_t discrete_input0:1; + uint8_t discrete_input1:1; + uint8_t discrete_input2:1; + uint8_t discrete_input3:1; + uint8_t discrete_input4:1; + uint8_t discrete_input5:1; + uint8_t discrete_input6:1; + uint8_t discrete_input7:1; + uint8_t discrete_input_port1:8; +} discrete_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + uint8_t coils_port0; + uint8_t coils_port1; +} coil_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + float input_data0; // 0 + float input_data1; // 2 + float input_data2; // 4 + float input_data3; // 6 + uint16_t data[150]; // 8 + 150 = 158 + float input_data4; // 158 + float input_data5; + float input_data6; + float input_data7; + uint16_t data_block1[150]; +} input_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + float holding_data0; + float holding_data1; + float holding_data2; + float holding_data3; + uint16_t test_regs[150]; + float holding_data4; + float holding_data5; + float holding_data6; + float holding_data7; +} holding_reg_params_t; +#pragma pack(pop) + +extern holding_reg_params_t holding_reg_params; +extern input_reg_params_t input_reg_params; +extern coil_reg_params_t coil_reg_params; +extern discrete_reg_params_t discrete_reg_params; + +#endif // !defined(_DEVICE_PARAMS) diff --git a/components/freemodbus/test/mb_example_common/modbus_params.c b/components/freemodbus/test/mb_example_common/modbus_params.c new file mode 100644 index 0000000000..8a5bbd45f1 --- /dev/null +++ b/components/freemodbus/test/mb_example_common/modbus_params.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/*===================================================================================== + * Description: + * C file to define parameter storage instances + *====================================================================================*/ +#include +#include "modbus_params.h" + +// Here are the user defined instances for device parameters packed by 1 byte +// These are keep the values that can be accessed from Modbus master +holding_reg_params_t holding_reg_params = { 0 }; + +input_reg_params_t input_reg_params = { 0 }; + +coil_reg_params_t coil_reg_params = { 0 }; + +discrete_reg_params_t discrete_reg_params = { 0 }; diff --git a/components/freemodbus/test/serial/mb_serial_master/CMakeLists.txt b/components/freemodbus/test/serial/mb_serial_master/CMakeLists.txt new file mode 100644 index 0000000000..c4188f9684 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/CMakeLists.txt @@ -0,0 +1,13 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "../../../") +set(EXCLUDE_COMPONENTS examples test_app test freemodbus) + +# Include parameters from common modbus folder +set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common") +list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_master) diff --git a/components/freemodbus/test/serial/mb_serial_master/Makefile b/components/freemodbus/test/serial/mb_serial_master/Makefile new file mode 100644 index 0000000000..1159aeac21 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := modbus_master + +EXTRA_COMPONENT_DIRS := ../../../ +EXTRA_COMPONENT_DIRS += ../../mb_example_common +EXCLUDE_COMPONENTS := examples test freemodbus + +include $(IDF_PATH)/make/project.mk diff --git a/components/freemodbus/test/serial/mb_serial_master/README.md b/components/freemodbus/test/serial/mb_serial_master/README.md new file mode 100644 index 0000000000..107d72aa17 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/README.md @@ -0,0 +1,156 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# Modbus Master Example + +This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device. +This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file. +The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment. +The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of holding_data0 parameter exceeded limit. +The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder. + +Example parameters definition: +-------------------------------------------------------------------------------------------------- +| Slave Address | Characteristic ID | Characteristic name | Description | +|---------------------|----------------------|----------------------|----------------------------| +| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 | +| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off | +| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off | +-------------------------------------------------------------------------------------------------- +Note: The Slave Address is the same for all parameters for example test but it can be changed in the ```Example Data (Object) Dictionary``` table of master example to address parameters from other slaves. +The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment. + +Simplified Modbus connection schematic for example test: + ``` + MB_DEVICE_ADDR1 + ------------- ------------- + | | RS485 network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` +Modbus multi slave segment connection schematic: +``` + MB_DEVICE_ADDR1 + ------------- + | | + | Slave 1 |---<>--+ + | | | + ------------- | + MB_DEVICE_ADDR2 | + ------------- | ------------- + | | | | | + | Slave 2 |---<>--+---<>---| Master | + | | | | | + ------------- | ------------- + MB_DEVICE_ADDR3 | + ------------- RS485 network + | | | + | Slave 3 |---<>--+ + | | + ------------- +``` + +## Hardware required : +Option 1: +PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board + +Option 2: +Several ESP32 boards flashed with modbus_slave example software to represent slave device with specific slave address (See CONFIG_MB_SLAVE_ADDR). The slave addresses for each board have to be configured as defined in "connection schematic" above. +One ESP32 board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below). + +The MAX485 line driver is used as an example below but other similar chips can be used as well. +RS485 example circuit schematic for connection of master and slave devices into segment: +``` + VCC ---------------+ +--------------- VCC + | | + +-------x-------+ +-------x-------+ + RXD <------| RO | DIFFERENTIAL | RO|-----> RXD + | B|---------------|B | + TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD +ESP32 BOARD | | RS-485 side | | External PC (emulator) with USB to serial or + RTS --+--->| DE | / \ | DE|---+ ESP32 BOARD (slave) + | | A|---------------|A | | + +----| /RE | PAIR | /RE|---+-- RTS + +-------x-------+ +-------x-------+ + | | + --- --- + Modbus Master device Modbus Slave device + +``` + +## How to setup and use an example: + +### Configure the application +Start the command below to setup configuration: +``` +idf.py menuconfig +``` +Configure the UART pins used for modbus communication using and table below. +Define the communication mode parameter for master and slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave devices in one segment). +Configure the slave address for each slave in the Modbus segment (the CONFIG_MB_SLAVE_ADDR in Kconfig). +``` + -------------------------------------------------------------------------------------------------------------------------- + | UART Interface | #define | Default ESP32 Pin | Default pins for | External RS485 Driver Pin | + | | | | ESP32-S2(S3, C3) | | + | ----------------------|--------------------|-----------------------|-----------------------|---------------------------| + | Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI | + | Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO | + | Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE | + | Ground | n/a | GND | GND | GND | + -------------------------------------------------------------------------------------------------------------------------- +``` +Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information. + +Connect a USB-to-RS485 adapter to a computer, then connect the adapter's A/B output lines with the corresponding A/B output lines of the RS485 line driver connected to the ESP32 chip (see figure above). + +The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings. +See the help string of parameters for more information. + +### Setup external Modbus slave devices or emulator +Option 1: +Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices. + +Option 2: +Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above. + +### Build and flash software of master device +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (9035) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9045) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.539999 (0x40b147ac) read successful. +I (9045) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9055) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9065) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9075) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9085) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9095) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful. +I (9605) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9615) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.739999 (0x40b7ae12) read successful. +I (9615) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9625) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9635) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9645) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9655) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9665) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful. +I (10175) MASTER_TEST: Alarm triggered by cid #7. +I (10175) MASTER_TEST: Destroy master... + +``` +The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex). + diff --git a/components/freemodbus/test/serial/mb_serial_master/main/CMakeLists.txt b/components/freemodbus/test/serial/mb_serial_master/main/CMakeLists.txt new file mode 100644 index 0000000000..5d314a3471 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(PROJECT_NAME "modbus_master") + +idf_component_register(SRCS "master.c" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/test/serial/mb_serial_master/main/Kconfig.projbuild b/components/freemodbus/test/serial/mb_serial_master/main/Kconfig.projbuild new file mode 100644 index 0000000000..b923e15ca4 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/main/Kconfig.projbuild @@ -0,0 +1,73 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 22 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_TXD + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 23 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_RTS + int "UART RTS pin number" + range 0 34 if IDF_TARGET_ESP32 + default 18 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + +endmenu diff --git a/components/freemodbus/test/serial/mb_serial_master/main/component.mk b/components/freemodbus/test/serial/mb_serial_master/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/components/freemodbus/test/serial/mb_serial_master/main/master.c b/components/freemodbus/test/serial/mb_serial_master/main/master.c new file mode 100644 index 0000000000..bbec346878 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/main/master.c @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "string.h" +#include "esp_log.h" +#include "modbus_params.h" // for modbus parameters structures +#include "mbcontroller.h" +#include "sdkconfig.h" + +#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection +#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART + +// Note: Some pins on target chip cannot be assigned for UART communication. +// See UART documentation for selected board and target to configure pins using Kconfig. + +// The number of parameters that intended to be used in the particular control process +#define MASTER_MAX_CIDS num_device_parameters + +// Number of reading of parameters from slave +#define MASTER_MAX_RETRY 30 + +// Timeout to update cid over Modbus +#define UPDATE_CIDS_TIMEOUT_MS (500) +#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS) + +// Timeout between polls +#define POLL_TIMEOUT_MS (1) +#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS) + +// The macro to get offset for parameter in the appropriate structure +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1)) +#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1)) +// Discrete offset macro +#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1)) + +#define STR(fieldname) ((const char*)( fieldname )) +// Options can be used as bit masks or parameter limits +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +static const char *TAG = "MASTER_TEST"; + +// Enumeration of modbus device addresses accessed by master device +enum { + MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here) +}; + +// Enumeration of all supported CIDs for device (used in parameter definition table) +enum { + CID_INP_DATA_0 = 0, + CID_HOLD_DATA_0, + CID_INP_DATA_1, + CID_HOLD_DATA_1, + CID_INP_DATA_2, + CID_HOLD_DATA_2, + CID_HOLD_TEST_REG, + CID_RELAY_P1, + CID_RELAY_P2, + CID_COUNT +}; + +// Example Data (Object) Dictionary for Modbus parameters: +// The CID field in the table must be unique. +// Modbus Slave Addr field defines slave address of the device with correspond parameter. +// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such). +// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly. +// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value. +// Data Type, Data Size specify type of the characteristic and its data size. +// Parameter Options field specifies the options that can be used to process parameter value (limits or masks). +// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc). +const mb_parameter_descriptor_t device_parameters[] = { + // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode} + { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, + INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, + HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2, + INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2, + HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2, + INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2, + HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58, + HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8, + COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8, + COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER } +}; + +// Calculate number of parameters in the table +const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0])); + +// The function to get pointer to parameter storage (instance) according to parameter description table +static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor) +{ + assert(param_descriptor != NULL); + void* instance_ptr = NULL; + if (param_descriptor->param_offset != 0) { + switch(param_descriptor->mb_param_type) + { + case MB_PARAM_HOLDING: + instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_INPUT: + instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_COIL: + instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_DISCRETE: + instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1); + break; + default: + instance_ptr = NULL; + break; + } + } else { + ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid); + assert(instance_ptr != NULL); + } + return instance_ptr; +} + +// User operation function to read slave values and check alarm +static void master_operation_func(void *arg) +{ + esp_err_t err = ESP_OK; + float value = 0; + bool alarm_state = false; + const mb_parameter_descriptor_t* param_descriptor = NULL; + + ESP_LOGI(TAG, "Start modbus test..."); + + for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) { + // Read all found characteristics from slave(s) + for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) + { + // Get data from parameters description table + // and use this information to fill the characteristics description table + // and having all required fields in just one table + err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + void* temp_data_ptr = master_get_param_data(param_descriptor); + assert(temp_data_ptr); + uint8_t type = 0; + if ((param_descriptor->param_type == PARAM_TYPE_ASCII) && + (param_descriptor->cid == CID_HOLD_TEST_REG)) { + // Check for long array of registers of type PARAM_TYPE_ASCII + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + // Initialize data of test array and write to slave + if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) { + memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size); + *(uint32_t*)temp_data_ptr = 0xAAAAAAAA; + err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x), write successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } else { + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)&value, &type); + if (err == ESP_OK) { + *(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%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + value, + *(uint32_t*)temp_data_ptr); + if (((value > param_descriptor->param_opts.max) || + (value < param_descriptor->param_opts.min))) { + alarm_state = true; + break; + } + } else { + uint16_t state = *(uint16_t*)temp_data_ptr; + const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF"; + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + (const char*)rw_str, + *(uint16_t*)temp_data_ptr); + if (state & param_descriptor->param_opts.opt1) { + alarm_state = true; + break; + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls + } + } + vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); // + } + + if (alarm_state) { + ESP_LOGI(TAG, "Alarm triggered by cid #%d.", + param_descriptor->cid); + } else { + ESP_LOGE(TAG, "Alarm is not triggered after %d retries.", + MASTER_MAX_RETRY); + } + ESP_LOGI(TAG, "Destroy master..."); + ESP_ERROR_CHECK(mbc_master_destroy()); +} + +// Modbus master initialization +static esp_err_t master_init(void) +{ + // Initialize and start Modbus controller + mb_communication_info_t comm = { + .port = MB_PORT_NUM, +#if CONFIG_MB_COMM_MODE_ASCII + .mode = MB_MODE_ASCII, +#elif CONFIG_MB_COMM_MODE_RTU + .mode = MB_MODE_RTU, +#endif + .baudrate = MB_DEV_SPEED, + .parity = MB_PARITY_NONE + }; + void* master_handler = NULL; + + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail, returns(0x%x).", + (uint32_t)err); + err = mbc_master_setup((void*)&comm); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller setup fail, returns(0x%x).", + (uint32_t)err); + + // Set UART pin numbers + err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD, + CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE); + + err = mbc_master_start(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller start fail, returns(0x%x).", + (uint32_t)err); + + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err); + // Set driver mode to Half Duplex + err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + + vTaskDelay(5); + err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller set descriptor fail, returns(0x%x).", + (uint32_t)err); + ESP_LOGI(TAG, "Modbus master stack initialized..."); + return err; +} + +void app_main(void) +{ + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(master_init()); + vTaskDelay(10); + + master_operation_func(NULL); +} diff --git a/components/freemodbus/test/serial/mb_serial_master/sdkconfig.defaults b/components/freemodbus/test/serial/mb_serial_master/sdkconfig.defaults new file mode 100644 index 0000000000..3c9c6de0ed --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_master/sdkconfig.defaults @@ -0,0 +1,10 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=n +CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400 +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y diff --git a/components/freemodbus/test/serial/mb_serial_slave/CMakeLists.txt b/components/freemodbus/test/serial/mb_serial_slave/CMakeLists.txt new file mode 100644 index 0000000000..f4117d0425 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/CMakeLists.txt @@ -0,0 +1,14 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "../../../") +set(EXCLUDE_COMPONENTS examples test_app test freemodbus) + +# Include parameters from common modbus folder +set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common") +list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(modbus_slave) diff --git a/components/freemodbus/test/serial/mb_serial_slave/Makefile b/components/freemodbus/test/serial/mb_serial_slave/Makefile new file mode 100644 index 0000000000..8abdf516c5 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := modbus_slave + +EXTRA_COMPONENT_DIRS := ../../../ +EXTRA_COMPONENT_DIRS += ../../mb_example_common +EXCLUDE_COMPONENTS := examples test freemodbus + +include $(IDF_PATH)/make/project.mk diff --git a/components/freemodbus/test/serial/mb_serial_slave/README.md b/components/freemodbus/test/serial/mb_serial_slave/README.md new file mode 100644 index 0000000000..be327faa3a --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/README.md @@ -0,0 +1,97 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# Modbus Slave Example + +This example demonstrates using the port of the FreeModbus stack on an ESP32 target where the ESP32 target is operating as a network slave. The example allows an external Modbus host to read/write device parameters on the ESP32 target using the Modbus protocol. The parameters accessible through Modbus are located in `mb_example_common/modbus_params.h\c` source/header files that users can update to add/remove their own custom parameters. +These are represented in structures `holding_reg_params`, `input_reg_params`, `coil_reg_params`, `discrete_reg_params` for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system. +The FreeModbus stack located in `components/freemodbus` folder and contains the `/port` folder where the stack's port to the ESP32 is situated. There are some parameters of the port that can be configured in KConfig file to start stack correctly (See description below for more information). + +The slave example uses shared parameter structures defined in `examples/protocols/modbus/mb_example_common` folder. + +## Hardware required : +Option 1: +PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board. +The MAX485 line driver is used as an example below but other similar chips can be used as well. + +Option 2: +The modbus_master example application configured as described in its README.md file and flashed into ESP32 based board. +Note: The ```Example Data (Object) Dictionary``` in the modbus_master example can be edited to address parameters from other slaves connected into Modbus segment. + +RS485 example circuit schematic: +``` + VCC ---------------+ +--------------- VCC + | | + +-------x-------+ +-------x-------+ + RXD <------| RO | DIFFERENTIAL | RO|-----> RXD + | B|---------------|B | + TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD +ESP32 board | | RS-485 side | | Modbus master + RTS --+--->| DE | / \ | DE|---+ + | | A|---------------|A | | + +----| /RE | PAIR | /RE|---+-- RTS + +-------x--------+ +-------x-------+ + | | + --- --- +``` + +## How to setup and use an example: + +### Configure the application +Start the command below to show the configuration menu: +``` +idf.py menuconfig +``` +Select Modbus Example Configuration menu item. +Configure the UART pins used for modbus communication using the command and table below. +``` + -------------------------------------------------------------------------------------------------------------------------- + | UART Interface | #define | Default ESP32 Pin | Default pins for | External RS485 Driver Pin | + | | | | ESP32-S2(S3, C3) | | + | ----------------------|--------------------|-----------------------|-----------------------|---------------------------| + | Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI | + | Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO | + | Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE | + | Ground | n/a | GND | GND | GND | + -------------------------------------------------------------------------------------------------------------------------- +``` +Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information. + +Define the ```Modbus communiction mode``` for slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave application). +Set ```Modbus slave address``` for the example application (by default for example script is set to 1). +The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings. +See the help strings of parameters for more information. + +### Setup external Modbus master software +Option 1: +Configure the external Modbus master software according to port configuration parameters used in application. +As an example the Modbus Poll application can be used with this example. +Option 2: +Setup ESP32 based board and set modbus_master example configuration as described in its README.md file. +Setup one or more slave boards with different slave addresses and connect them into the same Modbus segment (See configuration above). +Note: The ```Modbus communiction mode``` parameter must be the same for master and slave example application to be able to communicate with each other. + +### Build and flash software +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (13941) SLAVE_TEST: INPUT READ (13651163 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffb2fd0, SIZE:2 +I (13951) SLAVE_TEST: HOLDING READ (13656431 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2fe0, SIZE:2 +I (13961) SLAVE_TEST: INPUT READ (13665877 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffb2fd4, SIZE:2 +I (13971) SLAVE_TEST: HOLDING READ (13676010 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffb2fe4, SIZE:2 +I (13981) SLAVE_TEST: INPUT READ (13686130 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffb2fd8, SIZE:2 +I (13991) SLAVE_TEST: HOLDING READ (13696267 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffb2fe8, SIZE:2 +I (14001) SLAVE_TEST: COILS READ (13706331 us), ADDR:0, TYPE:32, INST_ADDR:0x3ffb2fcc, SIZE:8 +I (14001) SLAVE_TEST: Modbus controller destroyed. +``` +The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly. + diff --git a/components/freemodbus/test/serial/mb_serial_slave/main/CMakeLists.txt b/components/freemodbus/test/serial/mb_serial_slave/main/CMakeLists.txt new file mode 100644 index 0000000000..da27238004 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(PROJECT_NAME "modbus_slave") + +idf_component_register(SRCS "slave.c" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/test/serial/mb_serial_slave/main/Kconfig.projbuild b/components/freemodbus/test/serial/mb_serial_slave/main/Kconfig.projbuild new file mode 100644 index 0000000000..884a1b8b32 --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/main/Kconfig.projbuild @@ -0,0 +1,82 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 22 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_TXD + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 23 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_RTS + int "UART RTS pin number" + range 0 34 if IDF_TARGET_ESP32 + default 18 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + + config MB_SLAVE_ADDR + int "Modbus slave address" + range 1 127 + default 1 + help + This is the Modbus slave address in the network. + It is used to organize Modbus network with several slaves connected into the same segment. + + +endmenu diff --git a/components/freemodbus/test/serial/mb_serial_slave/main/component.mk b/components/freemodbus/test/serial/mb_serial_slave/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/components/freemodbus/test/serial/mb_serial_slave/main/slave.c b/components/freemodbus/test/serial/mb_serial_slave/main/slave.c new file mode 100644 index 0000000000..e300b63fff --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/main/slave.c @@ -0,0 +1,233 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// FreeModbus Slave Example ESP32 + +#include +#include +#include "esp_err.h" +#include "mbcontroller.h" // for mbcontroller defines and api +#include "modbus_params.h" // for modbus parameters structures +#include "esp_log.h" // for log_write +#include "sdkconfig.h" + +#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection +#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR) // The address of device in Modbus network +#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART + +// Note: Some pins on target chip cannot be assigned for UART communication. +// Please refer to documentation for selected board and target to configure pins using Kconfig. + +// Defines below are used to define register start address for each type of Modbus registers +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1)) +#define MB_REG_DISCRETE_INPUT_START (0x0000) +#define MB_REG_COILS_START (0x0000) +#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0 +#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1 +#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0)) +#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4)) + +#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info +#define MB_CHAN_DATA_MAX_VAL (6) +#define MB_CHAN_DATA_OFFSET (0.2f) +#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \ + | MB_EVENT_HOLDING_REG_RD \ + | MB_EVENT_DISCRETE_RD \ + | MB_EVENT_COILS_RD) +#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \ + | MB_EVENT_COILS_WR) +#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK) + +static const char *TAG = "SLAVE_TEST"; + +static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED; + +// Set register values into known state +static void setup_reg_data(void) +{ + // Define initial state of parameters + discrete_reg_params.discrete_input0 = 1; + discrete_reg_params.discrete_input1 = 0; + discrete_reg_params.discrete_input2 = 1; + discrete_reg_params.discrete_input3 = 0; + discrete_reg_params.discrete_input4 = 1; + discrete_reg_params.discrete_input5 = 0; + discrete_reg_params.discrete_input6 = 1; + discrete_reg_params.discrete_input7 = 0; + + holding_reg_params.holding_data0 = 1.34; + holding_reg_params.holding_data1 = 2.56; + holding_reg_params.holding_data2 = 3.78; + holding_reg_params.holding_data3 = 4.90; + + holding_reg_params.holding_data4 = 5.67; + holding_reg_params.holding_data5 = 6.78; + holding_reg_params.holding_data6 = 7.79; + holding_reg_params.holding_data7 = 8.80; + + coil_reg_params.coils_port0 = 0x55; + coil_reg_params.coils_port1 = 0xAA; + + input_reg_params.input_data0 = 1.12; + input_reg_params.input_data1 = 2.34; + input_reg_params.input_data2 = 3.56; + input_reg_params.input_data3 = 4.78; + + input_reg_params.input_data4 = 1.12; + input_reg_params.input_data5 = 2.34; + input_reg_params.input_data6 = 3.56; + input_reg_params.input_data7 = 4.78; +} + +// An example application of Modbus slave. It is based on freemodbus stack. +// See deviceparams.h file for more information about assigned Modbus parameters. +// These parameters can be accessed from main application and also can be changed +// by external Modbus master host. +void app_main(void) +{ + mb_param_info_t reg_info; // keeps the Modbus registers access information + mb_communication_info_t comm_info; // Modbus communication parameters + mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure + + // Set UART log level + esp_log_level_set(TAG, ESP_LOG_INFO); + void* mbc_slave_handler = NULL; + + ESP_ERROR_CHECK(mbc_slave_init(MB_PORT_SERIAL_SLAVE, &mbc_slave_handler)); // Initialization of Modbus controller + + // Setup communication parameters and start stack +#if CONFIG_MB_COMM_MODE_ASCII + comm_info.mode = MB_MODE_ASCII, +#elif CONFIG_MB_COMM_MODE_RTU + comm_info.mode = MB_MODE_RTU, +#endif + comm_info.slave_addr = MB_SLAVE_ADDR; + comm_info.port = MB_PORT_NUM; + comm_info.baudrate = MB_DEV_SPEED; + comm_info.parity = MB_PARITY_NONE; + ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); + + // The code below initializes Modbus register area descriptors + // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs + // Initialization should be done for each supported Modbus register area according to register map. + // When external master trying to access the register in the area that is not initialized + // by mbc_slave_set_descriptor() API call then Modbus stack + // will send exception response for this register area. + reg_area.type = MB_PARAM_HOLDING; // Set type of register area + reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol + reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance + // Set the size of register storage instance = 150 holding registers + reg_area.size = (size_t)(HOLD_OFFSET(holding_data4) - HOLD_OFFSET(test_regs)); + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + reg_area.type = MB_PARAM_HOLDING; // Set type of register area + reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol + reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance + reg_area.size = sizeof(float) << 2; // Set the size of register storage instance + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + // Initialization of Input Registers area + reg_area.type = MB_PARAM_INPUT; + reg_area.start_offset = MB_REG_INPUT_START_AREA0; + reg_area.address = (void*)&input_reg_params.input_data0; + reg_area.size = sizeof(float) << 2; + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + reg_area.type = MB_PARAM_INPUT; + reg_area.start_offset = MB_REG_INPUT_START_AREA1; + reg_area.address = (void*)&input_reg_params.input_data4; + reg_area.size = sizeof(float) << 2; + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + // Initialization of Coils register area + reg_area.type = MB_PARAM_COIL; + reg_area.start_offset = MB_REG_COILS_START; + reg_area.address = (void*)&coil_reg_params; + reg_area.size = sizeof(coil_reg_params); + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + // Initialization of Discrete Inputs register area + reg_area.type = MB_PARAM_DISCRETE; + reg_area.start_offset = MB_REG_DISCRETE_INPUT_START; + reg_area.address = (void*)&discrete_reg_params; + reg_area.size = sizeof(discrete_reg_params); + ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); + + setup_reg_data(); // Set values into known state + + // Starts of modbus controller and stack + ESP_ERROR_CHECK(mbc_slave_start()); + + // Set UART pin numbers + ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, + CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS, + UART_PIN_NO_CHANGE)); + + // Set UART driver mode to Half Duplex + ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX)); + + ESP_LOGI(TAG, "Modbus slave stack initialized."); + ESP_LOGI(TAG, "Start modbus test..."); + + // The cycle below will be terminated when parameter holdingRegParams.dataChan0 + // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value. + for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) { + // Check for read/write events of Modbus master for certain events + mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK); + const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE"; + + // Filter events and process them accordingly + if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) { + // Get parameter information from parameter queue + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + 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, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0) + { + portENTER_CRITICAL(¶m_lock); + holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET; + if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) { + coil_reg_params.coils_port1 = 0xFF; + } + portEXIT_CRITICAL(¶m_lock); + } + } else if (event & MB_EVENT_INPUT_REG_RD) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } else if (event & MB_EVENT_DISCRETE_RD) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + rw_str, + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + if (coil_reg_params.coils_port1 == 0xFF) break; + } + } + // Destroy of Modbus controller on alarm + ESP_LOGI(TAG,"Modbus controller destroyed."); + vTaskDelay(100); + ESP_ERROR_CHECK(mbc_slave_destroy()); +} diff --git a/components/freemodbus/test/serial/mb_serial_slave/sdkconfig.defaults b/components/freemodbus/test/serial/mb_serial_slave/sdkconfig.defaults new file mode 100644 index 0000000000..14806eb02b --- /dev/null +++ b/components/freemodbus/test/serial/mb_serial_slave/sdkconfig.defaults @@ -0,0 +1,9 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_SLAVE_ADDR=1 +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=n +CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y diff --git a/components/freemodbus/test/tcp/mb_tcp_master/CMakeLists.txt b/components/freemodbus/test/tcp/mb_tcp_master/CMakeLists.txt new file mode 100644 index 0000000000..194d5b2369 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/CMakeLists.txt @@ -0,0 +1,16 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "../../../") +set(EXCLUDE_COMPONENTS examples test_app test freemodbus) + +# Include parameters from common modbus folder +set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common") +list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}") + +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_tcp_master) diff --git a/components/freemodbus/test/tcp/mb_tcp_master/Makefile b/components/freemodbus/test/tcp/mb_tcp_master/Makefile new file mode 100644 index 0000000000..bc15e1a09c --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/Makefile @@ -0,0 +1,13 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := modbus_tcp_master + +EXTRA_COMPONENT_DIRS := ../../../ +EXTRA_COMPONENT_DIRS += ../../mb_example_common +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/protocol_examples_common +EXCLUDE_COMPONENTS := test freemodbus + +include $(IDF_PATH)/make/project.mk diff --git a/components/freemodbus/test/tcp/mb_tcp_master/README.md b/components/freemodbus/test/tcp/mb_tcp_master/README.md new file mode 100644 index 0000000000..8673580ead --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/README.md @@ -0,0 +1,146 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# Modbus TCP Master Example + +This example demonstrates using of FreeModbus stack port implementation for ESP32 targets as a TCP master device. +This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file. +The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment. +The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of parameter exceeded limit. +The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder. + +Example parameters definition: +-------------------------------------------------------------------------------------------------- +| Slave Address | Characteristic ID | Characteristic name | Description | +|---------------------|----------------------|----------------------|----------------------------| +| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 | +| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off | +| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off | +-------------------------------------------------------------------------------------------------- +Note: The Slave Address is the same for all parameters for example test but it can be changed in the `Example Data (Object) Dictionary` table of master example to address parameters from other slaves. +The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment. + +Simplified Modbus connection schematic for example test: + ``` + MB_DEVICE_ADDR1 + ------------- ------------- + | | Network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` +Modbus multi slave segment connection schematic: +``` + MB_DEVICE_ADDR1 + ------------- + | | + | Slave 1 |---<>--+ + | | | + ------------- | + MB_DEVICE_ADDR2 | + ------------- | ------------- + | | | | | + | Slave 2 |---<>--+---<>---| Master | + | | | | | + ------------- | ------------- + MB_DEVICE_ADDR3 | + ------------- Network (Ethernet or WiFi connection) + | | | + | Slave 3 |---<>--+ + | | + ------------- +``` + +## Hardware required : +Option 1: +PC (Modbus TCP Slave application) + ESP32 based development board with modbus_tcp_slave example. + +Option 2: +Several ESP32 based boards flashed with modbus_tcp_slave example software to represent slave devices. The IP slave addresses for each board have to be configured in `Modbus Example Configuration` menu according to the communication table of example. +One ESP32 based development board should be flashed with modbus_master example and connected to the same network. All the boards require configuration of network settings as described in `examples/common_components/protocol_examples_common`. + +## How to setup and use an example: + +### Configure the application +Start the command below to setup configuration: +``` +idf.py menuconfig +``` + +The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings. +See the help string of parameters for more information. +There are three ways to configure how the master example will obtain slave IP addresses in the network: +* Enable CONFIG_MB_MDNS_IP_RESOLVER option allows to query for modbus services provided by Modbus slaves in the network and automatically configure IP table. This requires to activate the same option for each slave with unique modbus slave address configured in `Modbus Example Configuration` menu. +* Enable CONFIG_MB_SLAVE_IP_FROM_STDIN option to define IP addresses of slaves manually. In order to enter the IP addresses wait for the prompt and type the string with IP address following format. Prompt: "Waiting IPN from stdin:", then enter the IP address of the slave to connect: "IPN=192.168.1.21", where N = (configured slave address - 1). +* Configure slave addresses manually as below: +``` +char* slave_ip_address_table[MB_DEVICE_COUNT] = { + "192.168.1.21", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user + "192.168.1.22", // Address corresponds to MB_DEVICE_ADDR2 of slave device in the Modbus data dictionary + NULL // Marker of end of list + }; +``` + +### Setup external Modbus slave devices or emulator +Option 1: +Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices. + +Option 2: +Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above. + +### Build and flash software of master device +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (4644) esp_netif_handlers: example_connect: sta ip: 192.168.1.39, mask: 255.255.255.0, gw: 192.168.1.1 +I (4644) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.39 +I (5644) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:bedd:c2ff:fed1:b210, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5644) example_connect: Connected to example_connect: sta +I (5654) example_connect: - IPv4 address: 192.168.1.39 +I (5664) example_connect: - IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed1:b210, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5674) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (5684) MASTER_TEST: Leave IP(0) = [192.168.1.21] set by user. +I (5694) MASTER_TEST: IP(1) is not set in the table. +I (5694) MASTER_TEST: Configured 1 IP addresse(s). +I (5704) MASTER_TEST: Modbus master stack initialized... +I (5704) MB_TCP_MASTER_PORT: TCP master stack initialized. +I (5724) MB_TCP_MASTER_PORT: Host[IP]: "192.168.1.21"[192.168.1.21] +I (5724) MB_TCP_MASTER_PORT: Add slave IP: 192.168.1.21 +I (5734) MB_TCP_MASTER_PORT: Connecting to slaves... +-.-.-.I (5844) MB_TCP_MASTER_PORT: Connected 1 slaves, start polling... +I (6004) MASTER_TEST: Start modbus test... +I (6044) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (6054) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 1.340000 (0x3fab851f) read successful. +I (6074) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (6084) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (6094) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (6104) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (6124) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (6134) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful. +I (6854) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (7064) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 1.740000 (0x3fdeb852) read successful. +I (7264) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +... +I (45974) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (46174) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (46384) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (46584) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful. +I (47094) MASTER_TEST: Alarm triggered by cid #7. +I (47094) MASTER_TEST: Destroy master... +``` +The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex data). + diff --git a/components/freemodbus/test/tcp/mb_tcp_master/main/CMakeLists.txt b/components/freemodbus/test/tcp/mb_tcp_master/main/CMakeLists.txt new file mode 100644 index 0000000000..203b5cf822 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(PROJECT_NAME "modbus_tcp_master") + +idf_component_register(SRCS "tcp_master.c" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/test/tcp/mb_tcp_master/main/Kconfig.projbuild b/components/freemodbus/test/tcp/mb_tcp_master/main/Kconfig.projbuild new file mode 100644 index 0000000000..31e15aeb8c --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/main/Kconfig.projbuild @@ -0,0 +1,16 @@ +menu "Modbus TCP Example Configuration" + + choice MB_SLAVE_IP_RESOLVER + prompt "Select method to resolve slave IP addresses" + help + Select method which is used to resolve slave IP addresses + and configure Master TCP IP stack. + + config MB_MDNS_IP_RESOLVER + bool "Resolve Modbus slave addresses using mDNS service." + + config MB_SLAVE_IP_FROM_STDIN + bool "Configure Modbus slave addresses from stdin" + endchoice + +endmenu diff --git a/components/freemodbus/test/tcp/mb_tcp_master/main/component.mk b/components/freemodbus/test/tcp/mb_tcp_master/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/components/freemodbus/test/tcp/mb_tcp_master/main/idf_component.yml b/components/freemodbus/test/tcp/mb_tcp_master/main/idf_component.yml new file mode 100644 index 0000000000..1b4743e5e8 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: ">=4.1" + espressif/mdns: + version: "^1.0.0" + rules: + - if: "idf_version >=5.0" diff --git a/components/freemodbus/test/tcp/mb_tcp_master/main/tcp_master.c b/components/freemodbus/test/tcp/mb_tcp_master/main/tcp_master.c new file mode 100644 index 0000000000..0bbb0e4701 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/main/tcp_master.c @@ -0,0 +1,758 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// FreeModbus Master Example ESP32 + +#include +#include +#include "esp_log.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" + +#if __has_include("esp_mac.h") +#include "esp_mac.h" +#endif + +#include "mdns.h" +#include "protocol_examples_common.h" + +#include "modbus_params.h" // for modbus parameters structures +#include "mbcontroller.h" +#include "sdkconfig.h" + +#define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example + +// The number of parameters that intended to be used in the particular control process +#define MASTER_MAX_CIDS num_device_parameters + +// Number of reading of parameters from slave +#define MASTER_MAX_RETRY (30) + +// Timeout to update cid over Modbus +#define UPDATE_CIDS_TIMEOUT_MS (500) +#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS) + +// Timeout between polls +#define POLL_TIMEOUT_MS (1) +#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS) +#define MB_MDNS_PORT (502) + +// The macro to get offset for parameter in the appropriate structure +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1)) +#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1)) +#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1)) +#define STR(fieldname) ((const char*)( fieldname )) + +// Options can be used as bit masks or parameter limits +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +#define MB_ID_BYTE0(id) ((uint8_t)(id)) +#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF)) +#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF)) +#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF)) + +#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id) + +#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT +#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID +#else +#define MB_DEVICE_ID (uint32_t)0x00112233 +#endif + +#define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp" +static const char *TAG = "MASTER_TEST"; + +// Enumeration of modbus device addresses accessed by master device +// Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table +enum { + MB_DEVICE_ADDR1 = 1, // Slave address 1 + MB_DEVICE_ADDR2 = 200, + MB_DEVICE_ADDR3 = 35 +}; + +// Enumeration of all supported CIDs for device (used in parameter definition table) +enum { + CID_INP_DATA_0 = 0, + CID_HOLD_DATA_0, + CID_INP_DATA_1, + CID_HOLD_DATA_1, + CID_INP_DATA_2, + CID_HOLD_DATA_2, + CID_HOLD_TEST_REG, + CID_RELAY_P1, + CID_RELAY_P2, + CID_COUNT +}; + +// Example Data (Object) Dictionary for Modbus parameters: +// The CID field in the table must be unique. +// Modbus Slave Addr field defines slave address of the device with correspond parameter. +// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such). +// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly. +// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value. +// Data Type, Data Size specify type of the characteristic and its data size. +// Parameter Options field specifies the options that can be used to process parameter value (limits or masks). +// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc). +const mb_parameter_descriptor_t device_parameters[] = { + // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode} + { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, + INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, + HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2, + INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 2, 2, + HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 4, 2, + INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR3, MB_PARAM_HOLDING, 4, 2, + HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 8, 100, + HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 200, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8, + COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 1, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8, + COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 1, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER } +}; + +// Calculate number of parameters in the table +const uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0])); + +// This table represents slave IP addresses that correspond to the short address field of the slave in device_parameters structure +// Modbus TCP stack shall use these addresses to be able to connect and read parameters from slave +char* slave_ip_address_table[] = { +#if CONFIG_MB_SLAVE_IP_FROM_STDIN + "FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user + "FROM_STDIN", // Corresponds to characteristic MB_DEVICE_ADDR2 + "FROM_STDIN", // Corresponds to characteristic MB_DEVICE_ADDR3 + NULL // End of table condition (must be included) +#elif CONFIG_MB_MDNS_IP_RESOLVER + NULL, + NULL, + NULL, + NULL +#endif +}; + +const size_t ip_table_sz = (size_t)(sizeof(slave_ip_address_table) / sizeof(slave_ip_address_table[0])); + +#if CONFIG_MB_SLAVE_IP_FROM_STDIN + +// Scan IP address according to IPV settings +char* master_scan_addr(int* index, char* buffer) +{ + char* ip_str = NULL; + unsigned int a[8] = {0}; + int buf_cnt = 0; +#if !CONFIG_EXAMPLE_CONNECT_IPV6 + buf_cnt = sscanf(buffer, "IP%d="IPSTR, index, &a[0], &a[1], &a[2], &a[3]); + if (buf_cnt == 5) { + if (-1 == asprintf(&ip_str, IPSTR, a[0], a[1], a[2], a[3])) { + abort(); + } + } +#else + buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]); + if (buf_cnt == 9) { + if (-1 == asprintf(&ip_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) { + abort(); + } + } +#endif + return ip_str; +} + +static int master_get_slave_ip_stdin(char** addr_table) +{ + char buf[128]; + int index; + char* ip_str = NULL; + int buf_cnt = 0; + int ip_cnt = 0; + + if (!addr_table) { + return 0; + } + + ESP_ERROR_CHECK(example_configure_stdin_stdout()); + while(1) { + if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) { + printf("Waiting IP%d from stdin:\r\n", ip_cnt); + while (fgets(buf, sizeof(buf), stdin) == NULL) { + fputs(buf, stdout); + } + buf_cnt = strlen(buf); + buf[buf_cnt - 1] = '\0'; + fputc('\n', stdout); + ip_str = master_scan_addr(&index, buf); + if (ip_str != NULL) { + ESP_LOGI(TAG, "IP(%d) = [%s] set from stdin.", ip_cnt, ip_str); + if ((ip_cnt >= ip_table_sz) || (index != ip_cnt)) { + addr_table[ip_cnt] = NULL; + break; + } + addr_table[ip_cnt++] = ip_str; + } else { + // End of configuration + addr_table[ip_cnt++] = NULL; + break; + } + } else { + if (addr_table[ip_cnt]) { + ESP_LOGI(TAG, "Leave IP(%d) = [%s] set manually.", ip_cnt, addr_table[ip_cnt]); + ip_cnt++; + } else { + ESP_LOGI(TAG, "IP(%d) is not set in the table.", ip_cnt); + break; + } + } + } + return ip_cnt; +} + +#elif CONFIG_MB_MDNS_IP_RESOLVER + +typedef struct slave_addr_entry_s { + uint16_t index; + char* ip_address; + uint8_t slave_addr; + void* p_data; + LIST_ENTRY(slave_addr_entry_s) entries; +} slave_addr_entry_t; + +LIST_HEAD(slave_addr_, slave_addr_entry_s) slave_addr_list = LIST_HEAD_INITIALIZER(slave_addr_list); + +// convert MAC from binary format to string +static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str) +{ + sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac)); + return mac_str; +} + +static inline char* gen_id_str(char* service_name, char* slave_id_str) +{ + sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID)); + return slave_id_str; +} + +static void master_start_mdns_service() +{ + char temp_str[32] = {0}; + uint8_t sta_mac[6] = {0}; + ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA)); + char* hostname = gen_mac_str(sta_mac, MB_MDNS_INSTANCE("")"_", temp_str); + // initialize mDNS + ESP_ERROR_CHECK(mdns_init()); + // set mDNS hostname (required if you want to advertise services) + ESP_ERROR_CHECK(mdns_hostname_set(hostname)); + ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname); + + // set default mDNS instance name + ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_"))); + + // structure with TXT records + mdns_txt_item_t serviceTxtData[] = { + {"board","esp32"} + }; + + // initialize service + ESP_ERROR_CHECK(mdns_service_add(MB_MDNS_INSTANCE(""), "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1)); + // add mac key string text item + ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str))); + // add slave id key txt item + ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str))); +} + +static char* master_get_slave_ip_str(mdns_ip_addr_t* address, mb_tcp_addr_type_t addr_type) +{ + mdns_ip_addr_t* a = address; + char* slave_ip_str = NULL; + + while (a) { + if ((a->addr.type == ESP_IPADDR_TYPE_V6) && (addr_type == MB_IPV6)) { + if (-1 == asprintf(&slave_ip_str, IPV6STR, IPV62STR(a->addr.u_addr.ip6))) { + abort(); + } + } else if ((a->addr.type == ESP_IPADDR_TYPE_V4) && (addr_type == MB_IPV4)) { + if (-1 == asprintf(&slave_ip_str, IPSTR, IP2STR(&(a->addr.u_addr.ip4)))) { + abort(); + } + } + if (slave_ip_str) { + break; + } + a = a->next; + } + return slave_ip_str; +} + +static esp_err_t master_resolve_slave(uint8_t short_addr, mdns_result_t* result, char** resolved_ip, + mb_tcp_addr_type_t addr_type) +{ + if (!short_addr || !result || !resolved_ip) { + return ESP_ERR_INVALID_ARG; + } + mdns_result_t* r = result; + int t; + char* slave_ip = NULL; + char slave_name[22] = {0}; + + if (sprintf(slave_name, "mb_slave_tcp_%02X", short_addr) < 0) { + ESP_LOGE(TAG, "Fail to create instance name for index: %d", short_addr); + abort(); + } + for (; r ; r = r->next) { + if ((r->ip_protocol == MDNS_IP_PROTOCOL_V4) && (addr_type == MB_IPV6)) { + continue; + } else if ((r->ip_protocol == MDNS_IP_PROTOCOL_V6) && (addr_type == MB_IPV4)) { + continue; + } + // Check host name for Modbus short address and + // append it into slave ip address table + if ((strcmp(r->instance_name, slave_name) == 0) && (r->port == CONFIG_FMB_TCP_PORT_DEFAULT)) { + printf(" PTR : %s\n", r->instance_name); + if (r->txt_count) { + printf(" TXT : [%u] ", r->txt_count); + for ( t = 0; t < r->txt_count; t++) { + printf("%s=%s; ", r->txt[t].key, r->txt[t].value?r->txt[t].value:"NULL"); + } + printf("\n"); + } + slave_ip = master_get_slave_ip_str(r->addr, addr_type); + if (slave_ip) { + ESP_LOGI(TAG, "Resolved slave %s[%s]:%u", r->hostname, slave_ip, r->port); + *resolved_ip = slave_ip; + return ESP_OK; + } + } + } + *resolved_ip = NULL; + ESP_LOGD(TAG, "Fail to resolve slave: %s", slave_name); + return ESP_ERR_NOT_FOUND; +} + +static int master_create_slave_list(mdns_result_t* results, char** addr_table, + int addr_table_size, mb_tcp_addr_type_t addr_type) +{ + if (!results) { + return -1; + } + int i, slave_addr, cid_resolve_cnt = 0; + int ip_index = 0; + const mb_parameter_descriptor_t* pdescr = &device_parameters[0]; + char** ip_table = addr_table; + char* slave_ip = NULL; + slave_addr_entry_t *it; + + for (i = 0; (i < num_device_parameters && pdescr); i++, pdescr++) + { + slave_addr = pdescr->mb_slave_addr; + + it = NULL; + // Is the slave address already registered? + LIST_FOREACH(it, &slave_addr_list, entries) { + if (slave_addr == it->slave_addr) { + break; + } + } + if (!it) { + // Resolve new slave IP address using its short address + esp_err_t err = master_resolve_slave(slave_addr, results, &slave_ip, addr_type); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Index: %d, sl_addr: %d, failed to resolve!", i, slave_addr); + // Set correspond index to NULL indicate host not resolved + ip_table[ip_index] = NULL; + continue; + } + // Register new slave address information + slave_addr_entry_t* new_slave_entry = (slave_addr_entry_t*) heap_caps_malloc(sizeof(slave_addr_entry_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + MB_RETURN_ON_FALSE((new_slave_entry != NULL), ESP_ERR_NO_MEM, + TAG, "Can not allocate memory for slave entry."); + new_slave_entry->index = i; + new_slave_entry->ip_address = slave_ip; + new_slave_entry->slave_addr = slave_addr; + new_slave_entry->p_data = NULL; + LIST_INSERT_HEAD(&slave_addr_list, new_slave_entry, entries); + ip_table[ip_index] = slave_ip; + ESP_LOGI(TAG, "Index: %d, sl_addr: %d, resolved to IP: [%s]", + i, slave_addr, slave_ip); + cid_resolve_cnt++; + if (ip_index < addr_table_size) { + ip_index++; + } + } else { + ip_table[ip_index] = it ? it->ip_address : ip_table[ip_index]; + ESP_LOGI(TAG, "Index: %d, sl_addr: %d, set to IP: [%s]", + i, slave_addr, ip_table[ip_index]); + cid_resolve_cnt++; + } + } + ESP_LOGI(TAG, "Resolved %d cids, with %d IP addresses", cid_resolve_cnt, ip_index); + return cid_resolve_cnt; +} + +static int master_query_slave_service(const char * service_name, const char * proto, + mb_tcp_addr_type_t addr_type) +{ + ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto); + + mdns_result_t* results = NULL; + int count = 0; + + esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results); + if(err){ + ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err)); + return count; + } + if(!results){ + ESP_LOGW(TAG, "No results found!"); + return count; + } + + count = master_create_slave_list(results, slave_ip_address_table, ip_table_sz, addr_type); + + mdns_query_results_free(results); + return count; +} +#endif + +static void master_destroy_slave_list(char** table, size_t ip_table_size) +{ +#if CONFIG_MB_MDNS_IP_RESOLVER + slave_addr_entry_t *it; + while ((it = LIST_FIRST(&slave_addr_list))) { + LIST_REMOVE(it, entries); + free(it); + } +#endif + for (int i = 0; ((i < ip_table_size) && table[i] != NULL); i++) { + if (table[i]) { +#if CONFIG_MB_SLAVE_IP_FROM_STDIN + free(table[i]); + table[i] = "FROM_STDIN"; +#elif CONFIG_MB_MDNS_IP_RESOLVER + table[i] = NULL; +#endif + } + } +} + +// The function to get pointer to parameter storage (instance) according to parameter description table +static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor) +{ + assert(param_descriptor != NULL); + void* instance_ptr = NULL; + if (param_descriptor->param_offset != 0) { + switch(param_descriptor->mb_param_type) + { + case MB_PARAM_HOLDING: + instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_INPUT: + instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_COIL: + instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_DISCRETE: + instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1); + break; + default: + instance_ptr = NULL; + break; + } + } else { + ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid); + assert(instance_ptr != NULL); + } + return instance_ptr; +} + +// User operation function to read slave values and check alarm +static void master_operation_func(void *arg) +{ + esp_err_t err = ESP_OK; + float value = 0; + bool alarm_state = false; + const mb_parameter_descriptor_t* param_descriptor = NULL; + + ESP_LOGI(TAG, "Start modbus test..."); + + for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) { + // Read all found characteristics from slave(s) + for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) + { + // Get data from parameters description table + // and use this information to fill the characteristics description table + // and having all required fields in just one table + err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + void* temp_data_ptr = master_get_param_data(param_descriptor); + assert(temp_data_ptr); + uint8_t type = 0; + if ((param_descriptor->param_type == PARAM_TYPE_ASCII) && + (param_descriptor->cid == CID_HOLD_TEST_REG)) { + // Check for long array of registers of type PARAM_TYPE_ASCII + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + // Initialize data of test array and write to slave + if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) { + memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size); + *(uint32_t*)temp_data_ptr = 0xAAAAAAAA; + err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x), write successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } else { + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) || + (param_descriptor->mb_param_type == MB_PARAM_INPUT)) { + value = *(float*)temp_data_ptr; + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + value, + *(uint32_t*)temp_data_ptr); + if (((value > param_descriptor->param_opts.max) || + (value < param_descriptor->param_opts.min))) { + alarm_state = true; + break; + } + } else { + uint8_t state = *(uint8_t*)temp_data_ptr; + const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF"; + ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + (const char*)rw_str, + *(uint8_t*)temp_data_ptr); + if (state & param_descriptor->param_opts.opt1) { + alarm_state = true; + break; + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls + } + } + vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); + } + + if (alarm_state) { + ESP_LOGI(TAG, "Alarm triggered by cid #%d.", + param_descriptor->cid); + } else { + ESP_LOGE(TAG, "Alarm is not triggered after %d retries.", + MASTER_MAX_RETRY); + } + ESP_LOGI(TAG, "Destroy master..."); + vTaskDelay(100); +} + +static esp_err_t init_services(mb_tcp_addr_type_t ip_addr_type) +{ + esp_err_t result = nvs_flash_init(); + if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + result = nvs_flash_init(); + } + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "nvs_flash_init fail, returns(0x%x).", + (uint32_t)result); + result = esp_netif_init(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_netif_init fail, returns(0x%x).", + (uint32_t)result); + result = esp_event_loop_create_default(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_event_loop_create_default fail, returns(0x%x).", + (uint32_t)result); +#if CONFIG_MB_MDNS_IP_RESOLVER + // Start mdns service and register device + master_start_mdns_service(); +#endif + // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + // Read "Establishing Wi-Fi or Ethernet Connection" section in + // examples/protocols/README.md for more information about this function. + result = example_connect(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "example_connect fail, returns(0x%x).", + (uint32_t)result); +#if CONFIG_EXAMPLE_CONNECT_WIFI + result = esp_wifi_set_ps(WIFI_PS_NONE); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_wifi_set_ps fail, returns(0x%x).", + (uint32_t)result); +#endif +#if CONFIG_MB_MDNS_IP_RESOLVER + int res = 0; + for (int retry = 0; (res < num_device_parameters) && (retry < 10); retry++) { + res = master_query_slave_service("_modbus", "_tcp", ip_addr_type); + } + if (res < num_device_parameters) { + ESP_LOGE(TAG, "Could not resolve one or more slave IP addresses, resolved: %d out of %d.", res, num_device_parameters ); + ESP_LOGE(TAG, "Make sure you configured all slaves according to device parameter table and they alive in the network."); + return ESP_ERR_NOT_FOUND; + } + mdns_free(); +#elif CONFIG_MB_SLAVE_IP_FROM_STDIN + int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table); + if (ip_cnt) { + ESP_LOGI(TAG, "Configured %d IP addresse(s).", ip_cnt); + } else { + ESP_LOGE(TAG, "Fail to get IP address from stdin. Continue."); + return ESP_ERR_NOT_FOUND; + } +#endif + return ESP_OK; +} + +static esp_err_t destroy_services(void) +{ + esp_err_t err = ESP_OK; + master_destroy_slave_list(slave_ip_address_table, ip_table_sz); + + err = example_disconnect(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "example_disconnect fail, returns(0x%x).", + (uint32_t)err); + err = esp_event_loop_delete_default(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_event_loop_delete_default fail, returns(0x%x).", + (uint32_t)err); + err = esp_netif_deinit(); + MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE, + TAG, + "esp_netif_deinit fail, returns(0x%x).", + (uint32_t)err); + err = nvs_flash_deinit(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "nvs_flash_deinit fail, returns(0x%x).", + (uint32_t)err); + return err; +} + +// Modbus master initialization +static esp_err_t master_init(mb_communication_info_t* comm_info) +{ + void* master_handler = NULL; + + esp_err_t err = mbc_master_init_tcp(&master_handler); + MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, + TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mb controller initialization fail, returns(0x%x).", + (uint32_t)err); + + err = mbc_master_setup((void*)comm_info); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mb controller setup fail, returns(0x%x).", + (uint32_t)err); + + err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mb controller set descriptor fail, returns(0x%x).", + (uint32_t)err); + ESP_LOGI(TAG, "Modbus master stack initialized..."); + + err = mbc_master_start(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mb controller start fail, returns(0x%x).", + (uint32_t)err); + vTaskDelay(5); + return err; +} + +static esp_err_t master_destroy(void) +{ + esp_err_t err = mbc_master_destroy(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_master_destroy fail, returns(0x%x).", + (uint32_t)err); + ESP_LOGI(TAG, "Modbus master stack destroy..."); + return err; +} + +void app_main(void) +{ + mb_tcp_addr_type_t ip_addr_type; +#if !CONFIG_EXAMPLE_CONNECT_IPV6 + ip_addr_type = MB_IPV4; +#else + ip_addr_type = MB_IPV6; +#endif + ESP_ERROR_CHECK(init_services(ip_addr_type)); + + mb_communication_info_t comm_info = { 0 }; + comm_info.ip_port = MB_TCP_PORT; + comm_info.ip_addr_type = ip_addr_type; + comm_info.ip_mode = MB_MODE_TCP; + comm_info.ip_addr = (void*)slave_ip_address_table; + comm_info.ip_netif_ptr = (void*)get_example_netif(); + + ESP_ERROR_CHECK(master_init(&comm_info)); + vTaskDelay(50); + + master_operation_func(NULL); + ESP_ERROR_CHECK(master_destroy()); + ESP_ERROR_CHECK(destroy_services()); +} diff --git a/components/freemodbus/test/tcp/mb_tcp_master/sdkconfig.defaults b/components/freemodbus/test/tcp/mb_tcp_master/sdkconfig.defaults new file mode 100644 index 0000000000..dc1172d492 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_master/sdkconfig.defaults @@ -0,0 +1,18 @@ +# +# Modbus configuration +# +CONFIG_FMB_COMM_MODE_TCP_EN=y +CONFIG_FMB_TCP_PORT_DEFAULT=502 +CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20 +CONFIG_FMB_PORT_TASK_STACK_SIZE=4096 +CONFIG_FMB_PORT_TASK_PRIO=10 +CONFIG_FMB_COMM_MODE_RTU_EN=n +CONFIG_FMB_COMM_MODE_ASCII_EN=n +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000 +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_MB_MDNS_IP_RESOLVER=n +CONFIG_MB_SLAVE_IP_FROM_STDIN=y +CONFIG_EXAMPLE_CONNECT_IPV6=n +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/CMakeLists.txt b/components/freemodbus/test/tcp/mb_tcp_slave/CMakeLists.txt new file mode 100644 index 0000000000..bdd736e302 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/CMakeLists.txt @@ -0,0 +1,17 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "../../../") +set(EXCLUDE_COMPONENTS examples test_app test freemodbus) + +# Include parameters from common modbus folder +set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common") +list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}") + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_tcp_slave) diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/Makefile b/components/freemodbus/test/tcp/mb_tcp_slave/Makefile new file mode 100644 index 0000000000..f4b3e9b2b3 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := modbus_tcp_slave + +EXTRA_COMPONENT_DIRS := ../../../ +EXTRA_COMPONENT_DIRS += ../../mb_example_common +EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/protocol_examples_common +EXCLUDE_COMPONENTS := test freemodbus + +include $(IDF_PATH)/make/project.mk diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/README.md b/components/freemodbus/test/tcp/mb_tcp_slave/README.md new file mode 100644 index 0000000000..0fcebb8a41 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/README.md @@ -0,0 +1,86 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# Modbus Slave Example + +This example demonstrates using of FreeModbus TCP slave stack port implementation for supported ESP32 target chips. The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in `mb_example_common/modbus_params.h\c` files and can be updated by user. +These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system. +The FreeModbus stack located in `components/freemodbus` folder and contain `/port` folder inside which contains FreeModbus stack port for ESP32. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information). + +The slave example uses shared parameter structures defined in ```examples/protocols/modbus/mb_example_common``` folder. + +## Hardware required : +Option 1: +The ESP32 based development board flashed with modbus_tcp_slave example + external Modbus master host software. + +Option 2: +The modbus_tcp_master example application configured as described in its README.md file and flashed into ESP32 based board. +Note: The ```Example Data (Object) Dictionary``` in the modbus_tcp_master example can be edited to address parameters from other slaves connected into Modbus segment. + +## How to setup and use an example: + +### Configure the application +Start the command below to show the configuration menu: +``` +idf.py menuconfig +``` + +To configure the example to use Wi-Fi or Ethernet connection, open the project configuration menu and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" in the "Connect using" choice. +Follow the instructions in `examples/common_components/protocol_examples_common` for further configuration. + +The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings. +See the help strings of parameters for more information. + +### Setup external Modbus master software +Option 1: +Configure the external Modbus master software according to port configuration parameters used in application. +As an example the Modbus Poll application can be used with this example. +Option 2: +Setup ESP32 based development board and set modbus_tcp_master example configuration as described in its README.md file. +Setup one or more slave boards and connect them into the same Modbus segment (See configuration above). + +### Build and flash software +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (4235) esp_netif_handlers: example_connect: sta ip: 192.168.1.21, mask: 255.255.255.0, gw: 192.168.1.1 +I (4235) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.21 +I (4465) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:7edf:a1ff:fe00:4039, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (4465) example_connect: Connected to example_connect: sta +I (4475) example_connect: - IPv4 address: 192.168.1.21 +I (4475) example_connect: - IPv6 address: fe80:0000:0000:0000:7edf:a1ff:fe00:4039, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (4495) MB_TCP_SLAVE_PORT: Socket (#54), listener on port: 502, errno=0 +I (4495) MB_TCP_SLAVE_PORT: Protocol stack initialized. +I (4505) SLAVE_TEST: Modbus slave stack initialized. +I (4505) SLAVE_TEST: Start modbus test... +I (41035) MB_TCP_SLAVE_PORT: Socket (#55), accept client connection from address: 192.168.1.39 +I (41225) SLAVE_TEST: INPUT READ (41704766 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffcb878, SIZE:2 +I (41235) SLAVE_TEST: HOLDING READ (41719746 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffcb9b4, SIZE:2 +I (41255) SLAVE_TEST: INPUT READ (41732965 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffcb87c, SIZE:2 +I (41265) SLAVE_TEST: HOLDING READ (41745923 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffcb9b8, SIZE:2 +I (41275) SLAVE_TEST: INPUT READ (41759563 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffcb880, SIZE:2 +I (41295) SLAVE_TEST: HOLDING READ (41772568 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2 +I (41305) SLAVE_TEST: COILS WRITE (41785889 us), ADDR:0, TYPE:16, INST_ADDR:0x3ffcb874, SIZE:8 +I (41315) SLAVE_TEST: COILS WRITE (41799175 us), ADDR:8, TYPE:16, INST_ADDR:0x3ffcb875, SIZE:8 +I (41945) SLAVE_TEST: INPUT READ (42421629 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffcb878, SIZE:2 +I (42145) SLAVE_TEST: HOLDING READ (42626497 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffcb9b4, SIZE:2 +I (42345) SLAVE_TEST: INPUT READ (42831315 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffcb87c, SIZE:2 +I (42555) SLAVE_TEST: HOLDING READ (43036111 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffcb9b8, SIZE:2 +I (42755) SLAVE_TEST: INPUT READ (43240950 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffcb880, SIZE:2 +I (42865) SLAVE_TEST: HOLDING READ (43343204 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2 +...... +I (81265) SLAVE_TEST: HOLDING READ (81743698 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffcb9bc, SIZE:2 +I (81465) SLAVE_TEST: COILS WRITE (81948482 us), ADDR:0, TYPE:16, INST_ADDR:0x3ffcb874, SIZE:8 +I (81465) SLAVE_TEST: Modbus controller destroyed. +``` +The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly. + diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/main/CMakeLists.txt b/components/freemodbus/test/tcp/mb_tcp_slave/main/CMakeLists.txt new file mode 100644 index 0000000000..1abefc16c5 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(PROJECT_NAME "modbus_tcp_slave") + +idf_component_register(SRCS "tcp_slave.c" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/main/Kconfig.projbuild b/components/freemodbus/test/tcp/mb_tcp_slave/main/Kconfig.projbuild new file mode 100644 index 0000000000..27a2979099 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/main/Kconfig.projbuild @@ -0,0 +1,18 @@ +menu "Modbus Example Configuration" + + config MB_SLAVE_ADDR + int "Modbus slave address" + range 1 255 + default 1 + help + This is the Modbus slave address in the network. + The address is used as an index to resolve slave ip address. + + config MB_MDNS_IP_RESOLVER + bool "Resolve slave addresses using mDNS service" + default y + help + This option allows to use mDNS service to resolve IP addresses of the Modbus slaves. + If the option is disabled the ip addresses of slaves are defined in static table. + +endmenu diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/main/component.mk b/components/freemodbus/test/tcp/mb_tcp_slave/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/main/idf_component.yml b/components/freemodbus/test/tcp/mb_tcp_slave/main/idf_component.yml new file mode 100644 index 0000000000..1b4743e5e8 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: ">=4.1" + espressif/mdns: + version: "^1.0.0" + rules: + - if: "idf_version >=5.0" diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/main/tcp_slave.c b/components/freemodbus/test/tcp/mb_tcp_slave/main/tcp_slave.c new file mode 100644 index 0000000000..e3725460c2 --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/main/tcp_slave.c @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// FreeModbus Slave Example ESP32 + +#include +#include "esp_err.h" +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "mdns.h" +#include "esp_netif.h" + +#if __has_include("esp_mac.h") +#include "esp_mac.h" +#endif + +#include "protocol_examples_common.h" + +#include "mbcontroller.h" // for mbcontroller defines and api +#include "modbus_params.h" // for modbus parameters structures + +#define MB_TCP_PORT_NUMBER (CONFIG_FMB_TCP_PORT_DEFAULT) +#define MB_MDNS_PORT (502) + +// Defines below are used to define register start address for each type of Modbus registers +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1)) +#define MB_REG_DISCRETE_INPUT_START (0x0000) +#define MB_REG_COILS_START (0x0000) +#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0 +#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1 +#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0)) +#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4)) + +#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info +#define MB_CHAN_DATA_MAX_VAL (10) +#define MB_CHAN_DATA_OFFSET (1.1f) + +#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \ + | MB_EVENT_HOLDING_REG_RD \ + | MB_EVENT_DISCRETE_RD \ + | MB_EVENT_COILS_RD) +#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \ + | MB_EVENT_COILS_WR) +#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK) + +static const char *TAG = "SLAVE_TEST"; + +static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED; + +#if CONFIG_MB_MDNS_IP_RESOLVER + +#define MB_ID_BYTE0(id) ((uint8_t)(id)) +#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF)) +#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF)) +#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF)) + +#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id) + +#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT +#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID +#endif + +#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR) + +#define MB_MDNS_INSTANCE(pref) pref"mb_slave_tcp" + +// convert mac from binary format to string +static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str) +{ + sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac)); + return mac_str; +} + +static inline char* gen_id_str(char* service_name, char* slave_id_str) +{ + sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID)); + return slave_id_str; +} + +static inline char* gen_host_name_str(char* service_name, char* name) +{ + sprintf(name, "%s_%02X", service_name, MB_SLAVE_ADDR); + return name; +} + +static void start_mdns_service(void) +{ + char temp_str[32] = {0}; + uint8_t sta_mac[6] = {0}; + ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA)); + char* hostname = gen_host_name_str(MB_MDNS_INSTANCE(""), temp_str); + //initialize mDNS + ESP_ERROR_CHECK(mdns_init()); + //set mDNS hostname (required if you want to advertise services) + ESP_ERROR_CHECK(mdns_hostname_set(hostname)); + ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname); + + //set default mDNS instance name + ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_"))); + + //structure with TXT records + mdns_txt_item_t serviceTxtData[] = { + {"board","esp32"} + }; + + //initialize service + ESP_ERROR_CHECK(mdns_service_add(hostname, "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1)); + //add mac key string text item + ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str))); + //add slave id key txt item + ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str))); +} + +static void stop_mdns_service(void) +{ + mdns_free(); +} + +#endif + +// Set register values into known state +static void setup_reg_data(void) +{ + // Define initial state of parameters + discrete_reg_params.discrete_input0 = 1; + discrete_reg_params.discrete_input1 = 0; + discrete_reg_params.discrete_input2 = 1; + discrete_reg_params.discrete_input3 = 0; + discrete_reg_params.discrete_input4 = 1; + discrete_reg_params.discrete_input5 = 0; + discrete_reg_params.discrete_input6 = 1; + discrete_reg_params.discrete_input7 = 0; + + holding_reg_params.holding_data0 = 1.34; + holding_reg_params.holding_data1 = 2.56; + holding_reg_params.holding_data2 = 3.78; + holding_reg_params.holding_data3 = 4.90; + + holding_reg_params.holding_data4 = 5.67; + holding_reg_params.holding_data5 = 6.78; + holding_reg_params.holding_data6 = 7.79; + holding_reg_params.holding_data7 = 8.80; + coil_reg_params.coils_port0 = 0x55; + coil_reg_params.coils_port1 = 0xAA; + + input_reg_params.input_data0 = 1.12; + input_reg_params.input_data1 = 2.34; + input_reg_params.input_data2 = 3.56; + input_reg_params.input_data3 = 4.78; + input_reg_params.input_data4 = 1.12; + input_reg_params.input_data5 = 2.34; + input_reg_params.input_data6 = 3.56; + input_reg_params.input_data7 = 4.78; +} + +static void slave_operation_func(void *arg) +{ + mb_param_info_t reg_info; // keeps the Modbus registers access information + + ESP_LOGI(TAG, "Modbus slave stack initialized."); + ESP_LOGI(TAG, "Start modbus test..."); + // The cycle below will be terminated when parameter holding_data0 + // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value. + for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) { + // Check for read/write events of Modbus master for certain events + mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK); + const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE"; + // Filter events and process them accordingly + if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) { + // Get parameter information from parameter queue + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + 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, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0) + { + portENTER_CRITICAL(¶m_lock); + holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET; + if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) { + coil_reg_params.coils_port1 = 0xFF; + } + portEXIT_CRITICAL(¶m_lock); + } + } else if (event & MB_EVENT_INPUT_REG_RD) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } else if (event & MB_EVENT_DISCRETE_RD) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + } else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) { + ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); + ESP_LOGI(TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", + rw_str, + (uint32_t)reg_info.time_stamp, + (uint32_t)reg_info.mb_offset, + (uint32_t)reg_info.type, + (uint32_t)reg_info.address, + (uint32_t)reg_info.size); + if (coil_reg_params.coils_port1 == 0xFF) break; + } + } + // Destroy of Modbus controller on alarm + ESP_LOGI(TAG,"Modbus controller destroyed."); + vTaskDelay(100); +} + +static esp_err_t init_services(void) +{ + esp_err_t result = nvs_flash_init(); + if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + result = nvs_flash_init(); + } + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "nvs_flash_init fail, returns(0x%x).", + (uint32_t)result); + result = esp_netif_init(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_netif_init fail, returns(0x%x).", + (uint32_t)result); + result = esp_event_loop_create_default(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_event_loop_create_default fail, returns(0x%x).", + (uint32_t)result); +#if CONFIG_MB_MDNS_IP_RESOLVER + // Start mdns service and register device + start_mdns_service(); +#endif + // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + // Read "Establishing Wi-Fi or Ethernet Connection" section in + // examples/protocols/README.md for more information about this function. + result = example_connect(); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "example_connect fail, returns(0x%x).", + (uint32_t)result); +#if CONFIG_EXAMPLE_CONNECT_WIFI + result = esp_wifi_set_ps(WIFI_PS_NONE); + MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_wifi_set_ps fail, returns(0x%x).", + (uint32_t)result); +#endif + return ESP_OK; +} + +static esp_err_t destroy_services(void) +{ + esp_err_t err = ESP_OK; + + err = example_disconnect(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "example_disconnect fail, returns(0x%x).", + (uint32_t)err); + err = esp_event_loop_delete_default(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "esp_event_loop_delete_default fail, returns(0x%x).", + (uint32_t)err); + err = esp_netif_deinit(); + MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE, + TAG, + "esp_netif_deinit fail, returns(0x%x).", + (uint32_t)err); + err = nvs_flash_deinit(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "nvs_flash_deinit fail, returns(0x%x).", + (uint32_t)err); +#if CONFIG_MB_MDNS_IP_RESOLVER + stop_mdns_service(); +#endif + return err; +} + +// Modbus slave initialization +static esp_err_t slave_init(mb_communication_info_t* comm_info) +{ + mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure + + void* slave_handler = NULL; + + // Initialization of Modbus controller + esp_err_t err = mbc_slave_init_tcp(&slave_handler); + MB_RETURN_ON_FALSE((err == ESP_OK && slave_handler != NULL), ESP_ERR_INVALID_STATE, + TAG, + "mb controller initialization fail."); + + comm_info->ip_addr = NULL; // Bind to any address + comm_info->ip_netif_ptr = (void*)get_example_netif(); + + // Setup communication parameters and start stack + err = mbc_slave_setup((void*)comm_info); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_setup fail, returns(0x%x).", + (uint32_t)err); + + // The code below initializes Modbus register area descriptors + // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs + // Initialization should be done for each supported Modbus register area according to register map. + // When external master trying to access the register in the area that is not initialized + // by mbc_slave_set_descriptor() API call then Modbus stack + // will send exception response for this register area. + reg_area.type = MB_PARAM_HOLDING; // Set type of register area + reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol + reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance + reg_area.size = (MB_REG_HOLDING_START_AREA1 - MB_REG_HOLDING_START_AREA0) << 1; // Set the size of register storage instance + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + + reg_area.type = MB_PARAM_HOLDING; // Set type of register area + reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol + reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance + reg_area.size = sizeof(float) << 2; // Set the size of register storage instance + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + + // Initialization of Input Registers area + reg_area.type = MB_PARAM_INPUT; + reg_area.start_offset = MB_REG_INPUT_START_AREA0; + reg_area.address = (void*)&input_reg_params.input_data0; + reg_area.size = sizeof(float) << 2; + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + reg_area.type = MB_PARAM_INPUT; + reg_area.start_offset = MB_REG_INPUT_START_AREA1; + reg_area.address = (void*)&input_reg_params.input_data4; + reg_area.size = sizeof(float) << 2; + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + + // Initialization of Coils register area + reg_area.type = MB_PARAM_COIL; + reg_area.start_offset = MB_REG_COILS_START; + reg_area.address = (void*)&coil_reg_params; + reg_area.size = sizeof(coil_reg_params); + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + + // Initialization of Discrete Inputs register area + reg_area.type = MB_PARAM_DISCRETE; + reg_area.start_offset = MB_REG_DISCRETE_INPUT_START; + reg_area.address = (void*)&discrete_reg_params; + reg_area.size = sizeof(discrete_reg_params); + err = mbc_slave_set_descriptor(reg_area); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_set_descriptor fail, returns(0x%x).", + (uint32_t)err); + + // Set values into known state + setup_reg_data(); + + // Starts of modbus controller and stack + err = mbc_slave_start(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_start fail, returns(0x%x).", + (uint32_t)err); + vTaskDelay(5); + return err; +} + +static esp_err_t slave_destroy(void) +{ + esp_err_t err = mbc_slave_destroy(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, + TAG, + "mbc_slave_destroy fail, returns(0x%x).", + (uint32_t)err); + return err; +} + +// An example application of Modbus slave. It is based on freemodbus stack. +// See deviceparams.h file for more information about assigned Modbus parameters. +// These parameters can be accessed from main application and also can be changed +// by external Modbus master host. +void app_main(void) +{ + ESP_ERROR_CHECK(init_services()); + + // Set UART log level + esp_log_level_set(TAG, ESP_LOG_INFO); + + mb_communication_info_t comm_info = { 0 }; + +#if !CONFIG_EXAMPLE_CONNECT_IPV6 + comm_info.ip_addr_type = MB_IPV4; +#else + comm_info.ip_addr_type = MB_IPV6; +#endif + comm_info.ip_mode = MB_MODE_TCP; + + comm_info.ip_port = MB_TCP_PORT_NUMBER; + ESP_ERROR_CHECK(slave_init(&comm_info)); + + // The Modbus slave logic is located in this function (user handling of Modbus) + slave_operation_func(NULL); + + ESP_ERROR_CHECK(slave_destroy()); + ESP_ERROR_CHECK(destroy_services()); +} diff --git a/components/freemodbus/test/tcp/mb_tcp_slave/sdkconfig.defaults b/components/freemodbus/test/tcp/mb_tcp_slave/sdkconfig.defaults new file mode 100644 index 0000000000..5899a48fdf --- /dev/null +++ b/components/freemodbus/test/tcp/mb_tcp_slave/sdkconfig.defaults @@ -0,0 +1,20 @@ +# +# Modbus configuration +# +CONFIG_FMB_COMM_MODE_TCP_EN=y +CONFIG_FMB_TCP_PORT_DEFAULT=502 +CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20 +CONFIG_FMB_PORT_TASK_STACK_SIZE=4096 +CONFIG_FMB_PORT_TASK_PRIO=10 +CONFIG_FMB_COMM_MODE_RTU_EN=n +CONFIG_FMB_COMM_MODE_ASCII_EN=n +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=1000 +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_MB_MDNS_IP_RESOLVER=n +CONFIG_MB_SLAVE_IP_FROM_STDIN=y +CONFIG_MB_SLAVE_ADDR=1 +CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y +CONFIG_EXAMPLE_CONNECT_IPV6=n +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 06b7eada19..859796b9e6 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -212,9 +212,6 @@ INPUT = \ $(PROJECT_PATH)/components/esp_ringbuf/include/freertos/ringbuf.h \ $(PROJECT_PATH)/components/esp_common/include/esp_err.h \ $(PROJECT_PATH)/components/esp_common/include/esp_check.h \ - $(PROJECT_PATH)/components/freemodbus/common/include/esp_modbus_common.h \ - $(PROJECT_PATH)/components/freemodbus/common/include/esp_modbus_slave.h \ - $(PROJECT_PATH)/components/freemodbus/common/include/esp_modbus_master.h \ $(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_access.h \ $(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_apis.h \ $(PROJECT_PATH)/components/perfmon/include/xtensa_perfmon_masks.h \ diff --git a/docs/en/api-reference/protocols/modbus.rst b/docs/en/api-reference/protocols/modbus.rst index c646147431..756374bc7e 100644 --- a/docs/en/api-reference/protocols/modbus.rst +++ b/docs/en/api-reference/protocols/modbus.rst @@ -1,645 +1,66 @@ ESP-Modbus ========== -Overview --------- +The Espressif ESP-Modbus Library (esp-modbus) supports Modbus communication in the networks based on RS485, Wi-Fi, Ethernet interfaces. -The Modbus serial communication protocol is de facto standard protocol widely used to connect industrial electronic devices. Modbus allows communication among many devices connected to the same network, for example, a system that measures temperature and humidity and communicates the results to a computer. The Modbus protocol uses several types of data: Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs. Versions of the Modbus protocol exist for serial port and for Ethernet and other protocols that support the Internet protocol suite. -There are many variants of Modbus protocols, some of them are: +Options Of Usage +---------------- +There are two options for using the ``ESP-Modbus`` library in your projects: - * ``Modbus RTU`` — This is used in serial communication and makes use of a compact, binary representation of the data for protocol communication. The RTU format follows the commands/data with a cyclic redundancy check checksum as an error check mechanism to ensure the reliability of data. Modbus RTU is the most common implementation available for Modbus. A Modbus RTU message must be transmitted continuously without inter-character hesitations. Modbus messages are framed (separated) by idle (silent) periods. The RS-485 interface communication is usually used for this type. - * ``Modbus ASCII`` — This is used in serial communication and makes use of ASCII characters for protocol communication. The ASCII format uses a longitudinal redundancy check checksum. Modbus ASCII messages are framed by leading colon (":") and trailing newline (CR/LF). - * ``Modbus TCP/IP or Modbus TCP`` — This is a Modbus variant used for communications over TCP/IP networks, connecting over port 502. It does not require a checksum calculation, as lower layers already provide checksum protection. + 1. Using the ``freemodbus`` component included in the current release of ESP-IDF. + + The ESP-IDF component ``freemodbus`` included in this release reflects the ``esp-modbus`` component of version v1.0.7. This release of the component is used by default in the existing examples mentioned below on this page. -The following document (and included code snippets) requires some familiarity with the Modbus protocol. Refer to the Modbus Organization's with protocol specifications for specifics. + The downside of this option is that the ``freemodbus`` component may not contain the latest fixes from esp-modbus repository. Because of this, it is recommended to use the original ``esp-modbus`` component for new projects (the option 2). -Messaging Model And Data Mapping --------------------------------- + 2. Using the ``esp-modbus`` component via the component manager instead of ``freemodbus``. + + Additional information for the ``ESP-Modbus`` component can be found in the separate repository: -Modbus is an application protocol that defines rules for messaging structure and data organization that are independent of the data transmission medium. Traditional serial Modbus is a register-based protocol that defines message transactions that occur between master(s) and slave devices (multiple masters are allowed on using Modbus TCP/IP). The slave devices listen for communication from the master and simply respond as instructed. The master(s) always controls communication and may communicate directly to one slave, or all connected slaves, but the slaves cannot communicate directly with each other. + * `ESP-Modbus component on GitHub `__ -.. figure:: ../../../_static/modbus-segment.png - :align: center - :scale: 80% - :alt: Modbus segment diagram - :figclass: align-center + In order to use ``esp-modbus`` component, your application must include the statement below in its ``CMakeLists.txt`` file to exclude the ``freemodbus`` component from the build. The excluding of the ``freemodbus`` component is required to avoid compilation issues when ``esp-modbus`` is used in conjunction with the component manager. - Modbus segment diagram + .. highlight:: none -.. note:: It is assumed that the number of slaves and their register maps are known by the Modbus master before the start of stack. + :: -The register map of each slave device is usually part of its device manual. A Slave device usually permits configuration of its short slave address and communication options that are used within the device's network segment. + set(EXCLUDE_COMPONENTS freemodbus) + + If your project uses the make build system, the statement below needs to be added into project Makefile: -The Modbus protocol allows devices to map data to four types of registers (Holding, Input, Discrete, Coil). The figure below illustrates an example mapping of a device's data to the four types of registers. + .. highlight:: none -.. figure:: ../../../_static/modbus-data-mapping.png - :align: center - :scale: 80% - :alt: Modbus data mapping - :figclass: align-center + :: - Modbus data mapping + EXCLUDE_COMPONENTS := freemodbus -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: + The ``main`` component folder of the new application shall include the component manager manifest file ``idf_component.yml`` as below: -- :ref:`modbus_api_port_initialization` -- :ref:`modbus_api_slave_overview` -- :ref:`modbus_api_master_overview` + .. highlight:: none -.. _modbus_api_port_initialization: + :: -Modbus Port Initialization -^^^^^^^^^^^^^^^^^^^^^^^^^^ + dependencies: + idf: ">=4.1" + espressif/esp-modbus: + version: "^1.0" -The ESP_Modbus supports Modbus SERIAL and TCP ports and a port must be initialized before calling any other Modbus API. The functions below are used to create and then initialize Modbus controller interface (either master or slave) over a particular transmission medium (either Serial or TCP/IP): + This option allows to use the latest ``esp-modbus`` component updates in any projects based on ESP-IDF versions from v4.1 up to v5.0. + Refer to :doc:`component manager documentation <../../api-guides/tools/idf-component-manager>` for more information on how to set up the component manager. The ``esp-modbus`` component can be found in `component manager registry `__. -- :cpp:func:`mbc_slave_init` -- :cpp:func:`mbc_master_init` -- :cpp:func:`mbc_slave_init_tcp` -- :cpp:func:`mbc_master_init_tcp` +Managed Documentation +--------------------- -The API call uses the first parameter to recognize the type of port being initialized. Supported enumeration for different ports: :cpp:enumerator:`MB_PORT_SERIAL_MASTER`, :cpp:enumerator:`MB_PORT_SERIAL_SLAVE` accordingly. -The parameters :cpp:enumerator:`MB_PORT_TCP_MASTER`, :cpp:enumerator:`MB_PORT_TCP_SLAVE` are reserved for internal usage. +The component documentation can be found on the link below: -.. code:: c - - void* master_handler = NULL; // Pointer to allocate interface structure - // Initialization of Modbus master for serial port - esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); - if (master_handler == NULL || err != ESP_OK) { - ESP_LOGE(TAG, "mb controller initialization fail."); - } - -This example code to initialize slave port: - -.. code:: c - - void* slave_handler = NULL; // Pointer to allocate interface structure - // Initialization of Modbus slave for TCP - esp_err_t err = mbc_slave_init_tcp(&slave_handler); - if (slave_handler == NULL || err != ESP_OK) { - // Error handling is performed here - ESP_LOGE(TAG, "mb controller initialization fail."); - } - -.. _modbus_api_master_overview: - -Modbus Master API Overview --------------------------- - -The following overview describes how to setup Modbus master communication. The overview reflects a typical programming workflow and is broken down into the sections provided below: - -1. :ref:`modbus_api_port_initialization` - Initialization of Modbus controller interface for the selected port. -2. :ref:`modbus_api_master_configure_descriptor` - Configure data descriptors to access slave parameters. -3. :ref:`modbus_api_master_setup_communication_options` - Allows to setup communication options for selected port. -4. :ref:`modbus_api_master_start_communication` - Start stack and sending / receiving data. -5. :ref:`modbus_api_master_destroy` - Destroy Modbus controller and its resources. - -.. _modbus_api_master_configure_descriptor: - -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 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: - -.. list-table:: Table 1 Modbus master Data Dictionary description - :widths: 8 10 82 - :header-rows: 1 - - * - Field - - Description - - Detailed information - * - ``cid`` - - Characteristic ID - - The identifier of characteristic (must be unique). - * - ``param_key`` - - Characteristic Name - - String description of the characteristic. - * - ``param_units`` - - Characteristic Units - - Physical Units of the characteristic. - * - ``mb_slave_addr`` - - Modbus Slave Address - - The short address of the device with correspond parameter UID. - * - ``mb_param_type`` - - Modbus Register Type - - Type of Modbus register area. - :cpp:enumerator:`MB_PARAM_INPUT`, :cpp:enumerator:`MB_PARAM_HOLDING`, :cpp:enumerator:`MB_PARAM_COIL`, :cpp:enumerator:`MB_PARAM_DISCRETE` - represents Input , Holding, Coil and Discrete input register area accordingly; - * - ``mb_reg_start`` - - Modbus Register Start - - Relative register address of the characteristic in the register area. - * - ``mb_size`` - - Modbus Register Size - - Length of characteristic in registers. - * - ``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; - * - ``param_size`` - - Data Size - - The storage size of the characteristic (bytes). - * - ``param_opts`` - - Parameter Options - - Limits, options of characteristic used during processing of alarm in user application (optional) - * - ``access`` - - Parameter access type - - Can be used in user application to define the behavior of the characteristic during processing of data in user application; - :cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`, :cpp:enumerator:`PAR_PERMS_READ`, :cpp:enumerator:`PAR_PERMS_READ_WRITE_TRIGGER`; - -.. 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. - -.. list-table:: Table 2 Example Register mapping table of Modbus slave - :widths: 5 5 2 10 5 5 68 - :header-rows: 1 - - * - CID - - Register - - Length - - Range - - Type - - Units - - Description - * - 0 - - 30000 - - 4 - - MAX_UINT - - U32 - - Not defined - - Serial number of device (4 bytes) read-only - * - 1 - - 30002 - - 2 - - MAX_UINT - - U16 - - Not defined - - Software version (4 bytes) read-only - * - 2 - - 40000 - - 4 - - -20..40 - - FLOAT - - DegC - - Room temperature in DegC. Writing a temperature value to this register for single point calibration. - -.. code:: c - - // Enumeration of modbus slave addresses accessed by master device - enum { - MB_DEVICE_ADDR1 = 1, - MB_DEVICE_ADDR2, - MB_SLAVE_COUNT - }; - - // Enumeration of all supported CIDs for device - enum { - CID_SER_NUM1 = 0, - CID_SW_VER1, - CID_TEMP_DATA_1, - CID_SER_NUM2, - CID_SW_VER2, - CID_TEMP_DATA_2 - }; - - // Example Data Dictionary for Modbus parameters in 2 slaves in the segment - 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_SER_NUM1, STR("Serial_number_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, - 0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_SW_VER1, STR("Software_version_1"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 1, - 0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_TEMP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, - 0, PARAM_TYPE_FLOAT, 4, OPTS( 16, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_SER_NUM2, STR("Serial_number_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 0, 2, - 0, PARAM_TYPE_U32, 4, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_SW_VER2, STR("Software_version_2"), STR("--"), MB_DEVICE_ADDR2, MB_PARAM_INPUT, 2, 1, - 0, PARAM_TYPE_U16, 2, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_TEMP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 0, 2, - 0, PARAM_TYPE_FLOAT, 4, OPTS( 20, 30, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - }; - // Calculate number of parameters in the table - uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0])); - -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. - -.. code:: c - - ESP_ERROR_CHECK(mbc_master_set_descriptor(&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. - -.. _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 :doc:`esp_netif component <../network/esp_netif>` 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 :example:`protocols/modbus/tcp/mb_tcp_master` for more information. - -.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to :ref:`uart-api-running-uart-communication` section of UART documentation. - - -.. _modbus_api_master_start_communication: - -Master Communication -^^^^^^^^^^^^^^^^^^^^ - -The starting of the Modbus controller is the final step in enabling communication. This is performed using function below: - -:cpp:func:`mbc_master_start` - -.. code:: c - - esp_err_t err = mbc_master_start(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "mb controller start fail, err=%x.", err); - } - -The list of functions below are used by the Modbus master stack from a user's application: - -:cpp:func:`mbc_master_send_request`: This function executes a blocking Modbus request. The master sends a data request (as defined in parameter request structure :cpp:type:`mb_param_request_t`) and then blocks until a response from corresponding slave and returns the status of command execution. This function provides a standard way for read/write access to Modbus devices in the network. - -:cpp:func:`mbc_master_get_cid_info`: The function gets information about each characteristic supported in the data dictionary and returns the characteristic's description in the form of the :cpp:type:`mb_parameter_descriptor_t` structure. Each characteristic is accessed using its CID. - -:cpp:func:`mbc_master_get_parameter`: The function reads the data of a characteristic defined in the parameters of a Modbus slave device. The additional data for request is taken from parameter description table. - -Example: - -.. code:: c - - const mb_parameter_descriptor_t* param_descriptor = NULL; - uint8_t temp_data[4] = {0}; // temporary buffer to hold maximum CID size - uint8_t type = 0; - .... - - // Get the information for characteristic cid from data dictionary - esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); - 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.", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (char*)param_descriptor->param_units, - *(uint32_t*)temp_data); - } else { - ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (int)err, - (char*)esp_err_to_name(err)); - } - } else { - 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. - -.. code:: c - - uint8_t type = 0; // Type of parameter - uint8_t temp_data[4] = {0}; // temporary buffer - - esp_err_t err = mbc_master_set_parameter(CID_TEMP_DATA_2, "Temperature_2", (uint8_t*)temp_data, &type); - if (err == ESP_OK) { - ESP_LOGI(TAG, "Set parameter data successfully."); - } else { - 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 -^^^^^^^^^^^^^^^^^^^^^^ - -This function stops Modbus communication stack and destroys controller interface and free all used active objects. - -:cpp:func:`mbc_master_destroy` - -.. code:: c - - ESP_ERROR_CHECK(mbc_master_destroy()); - - -.. _modbus_api_slave_overview: - -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. -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. -5. :ref:`modbus_api_slave_destroy` - Destroy Modbus controller and its resources. - -.. _modbus_api_slave_configure_descriptor: - -Configuring Slave Data Access -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following functions must be called when the Modbus controller slave port is already initialized. Refer to :ref:`modbus_api_port_initialization`. - -The slave stack requires the user to define structures (memory storage areas) that store the Modbus parameters accessed by stack. These structures should be prepared by the user and be assigned to the Modbus controller interface using :cpp:func:`mbc_slave_set_descriptor` API call before the start of communication. The slave task can call the :cpp:func:`mbc_slave_check_event` function which will block until the Modbus master access the slave. The slave task can then get information about the data being accessed. - -.. note:: One slave can define several area descriptors per each type of Modbus register area with different start_offset. - -Register area is defined by using the :cpp:type:`mb_register_area_descriptor_t` structure. - -.. list-table:: Table 3 Modbus register area descriptor - :widths: 8 92 - :header-rows: 1 - - * - Field - - Description - * - ``start_offset`` - - Zero based register relative offset for defined register area. Example: register address = 40002 ( 4x register area - Function 3 - holding register ), start_offset = 2 - * - ``type`` - - Type of the Modbus register area. Refer to :cpp:type:`mb_param_type_t` for more information. - * - ``address`` - - A pointer to the memory area which is used to store the register data for this area descriptor. - * - ``size`` - - The size of the memory area in bytes which is used to store register data. - -:cpp:func:`mbc_slave_set_descriptor` - -The function initializes Modbus communication descriptors for each type of Modbus register area (Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs). Once areas are initialized and the :cpp:func:`mbc_slave_start()` API is called the Modbus stack can access the data in user data structures by request from master. - -.. code:: c - - #define MB_REG_INPUT_START_AREA0 (0) - #define MB_REG_HOLDING_START_AREA0 (0) - #define MB_REG_HOLD_CNT (100) - #define MB_REG_INPUT_CNT (100) - - mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure - unit16_t holding_reg_area[MB_REG_HOLD_CNT] = {0}; // storage area for holding registers - unit16_t input_reg_area[MB_REG_INPUT_CNT] = {0}; // storage area for input registers - - reg_area.type = MB_PARAM_HOLDING; // Set type of register area - reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol - reg_area.address = (void*)&holding_reg_area[0]; // Set pointer to storage instance - reg_area.size = sizeof(holding_reg_area) << 1; // Set the size of register storage area in bytes - ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); - - reg_area.type = MB_PARAM_INPUT; - reg_area.start_offset = MB_REG_INPUT_START_AREA0; - reg_area.address = (void*)&input_reg_area[0]; - reg_area.size = sizeof(input_reg_area) << 1; - ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area)); - - -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. - -Direct access to register area from user application must be protected by critical section: - -.. code:: c - - portENTER_CRITICAL(¶m_lock); - holding_reg_area[2] += 10; - portEXIT_CRITICAL(¶m_lock); - - -.. _modbus_api_slave_setup_communication_options: - -Slave Communication Options -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The function initializes the Modbus controller interface and its active context (tasks, RTOS objects and other resources). - -:cpp:func:`mbc_slave_setup` - -The function is used to setup communication parameters of the Modbus stack. - -Example initialization of Modbus TCP communication: - -.. code:: c - - esp_netif_init(); - ... - - 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 = NULL, // This field keeps the client IP address to bind, NULL - bind to any client - .ip_netif_ptr = esp_netif_ptr // esp_netif_ptr - pointer to the corresponding network interface - }; - - // Setup communication parameters and start stack - ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); - -Example initialization of Modbus serial communication: - -.. code:: c - - #define MB_SLAVE_DEV_SPEED 9600 - #define MB_SLAVE_ADDR 1 - #define MB_SLAVE_PORT_NUM 2 - ... - - // Setup communication parameters and start stack - mb_communication_info_t comm_info = { - .mode = MB_MODE_RTU, // Communication type - .slave_addr = MB_SLAVE_ADDR, // Short address of the slave - .port = MB_SLAVE_PORT_NUM, // UART physical port number - .baudrate = MB_SLAVE_DEV_SPEED, // Baud rate for communication - .parity = MB_PARITY_NONE // Parity option - }; - - ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); - -.. _modbus_api_slave_communication: - -Slave Communication -^^^^^^^^^^^^^^^^^^^ - -The function below is used to start Modbus controller interface and allows communication. - -:cpp:func:`mbc_slave_start` - -.. code:: c - - ESP_ERROR_CHECK(mbc_slave_start()); - -:cpp:func:`mbc_slave_check_event` - -The blocking call to function waits for a event specified (represented as an event mask parameter). Once the master accesses the parameter and the event mask matches the parameter type, the application task will be unblocked and function will return the corresponding event :cpp:type:`mb_event_group_t` which describes the type of register access being done. - -:cpp:func:`mbc_slave_get_param_info` - -The function gets information about accessed parameters from the Modbus controller event queue. The KConfig :ref:`CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE` key can be used to configure the notification queue size. The timeout parameter allows a timeout to be specified when waiting for a notification. The :cpp:type:`mb_param_info_t` structure contains information about accessed parameter. - -.. list-table:: Table 4 Description of the register info structure: :cpp:type:`mb_param_info_t` - :widths: 10 90 - :header-rows: 1 - - * - Field - - Description - * - ``time_stamp`` - - the time stamp of the event when defined parameter is accessed - * - ``mb_offset`` - - start Modbus register accessed by master - * - ``type`` - - type of the Modbus register area being accessed (See the :cpp:type:`mb_event_group_t` for more information) - * - ``address`` - - memory address that corresponds to accessed register in defined area descriptor - * - ``size`` - - number of registers being accessed by master - -Example to get event when holding or input registers accessed in the slave: - -.. code:: c - - #define MB_READ_MASK (MB_EVENT_INPUT_REG_RD | MB_EVENT_HOLDING_REG_RD) - #define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR) - #define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK) - #define MB_PAR_INFO_GET_TOUT (10 / portTICK_RATE_MS) - .... - - // The function blocks while waiting for register access - mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK); - - // Get information about data accessed from master - ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); - const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE"; - - // Filter events and process them accordingly - if (event & (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, - (uint32_t)reg_info.mb_offset, - (uint32_t)reg_info.type, - (uint32_t)reg_info.address, - (uint32_t)reg_info.size); - } else if (event & (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, - (uint32_t)reg_info.mb_offset, - (uint32_t)reg_info.type, - (uint32_t)reg_info.address, - (uint32_t)reg_info.size); - } - -.. _modbus_api_slave_destroy: - -Modbus Slave Teardown -^^^^^^^^^^^^^^^^^^^^^ - -This function stops the Modbus communication stack, destroys the controller interface, and frees all used active objects allocated for the slave. - -:cpp:func:`mbc_slave_destroy` - -.. code:: c - - ESP_ERROR_CHECK(mbc_slave_destroy()); - -Possible Communication Issues And Solutions -------------------------------------------- - -If the examples do not work as expected and slave and master boards are not able to communicate correctly, it is possible to find the reason for errors. The most important errors are described in master example output and formatted as below: - -.. highlight:: none - -:: - - E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT). - - -.. list-table:: Table 5 Modbus error codes and troubleshooting - :widths: 5 30 65 - :header-rows: 1 - - * - Error - - Description - - Possible solution - * - 0x106 - - ``ESP_ERR_NOT_SUPPORTED`` - Invalid register request - slave returned an exception because the requested register is not supported. - - Refer to slave register map. Check the master data dictionary for correctness. - * - 0x107 - - ``ESP_ERR_TIMEOUT`` - Slave response timeout - Modbus slave did not send response during configured slave response timeout. - - Measure and increase the maximum slave response timeout `idf.py menuconfig`, option :ref:`CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`. - Check physical connection or network configuration and make sure that the slave response can reach the master side. - If the application has some high performance tasks with higher priority than :ref:`CONFIG_FMB_PORT_TASK_PRIO` it is recommended to place Modbus tasks on the other core using an option :ref:`CONFIG_FMB_PORT_TASK_AFFINITY`. - Configure the Modbus task's priority :ref:`CONFIG_FMB_PORT_TASK_PRIO` to ensure that the task gets sufficient processing time to handle Modbus stack events. - * - 0x108 - - ``ESP_ERR_INVALID_RESPONSE`` - Received unsupported response from slave or frame check failure. Master can not execute command handler because the command is either not supported or is incorrect. - - Check the physical connection then refer to register map of your slave to configure the master data dictionary properly. - * - 0x103 - - ``ESP_ERR_INVALID_STATE`` - Critical failure or FSM sequence failure or master FSM is busy processing previous request. - - Make sure your physical connection is working properly. Increase task stack size and check Modbus initialization sequence. +* `ESP-Modbus documentation (English) `__ Application Example ------------------- -The examples below use the FreeModbus library port for serial TCP slave and master implementations accordingly. The selection of stack is performed through KConfig menu option "Enable Modbus stack support ..." for appropriate communication mode and related configuration keys. +The examples below demonstrate the ESP-Modbus library of serial, TCP ports for slave and master implementations accordingly. - :example:`protocols/modbus/serial/mb_slave` - :example:`protocols/modbus/serial/mb_master` @@ -651,12 +72,5 @@ Please refer to the specific example README.md for details. Protocol References ------------------- - - ``https://modbus.org/specs.php``: Modbus Organization with protocol specifications. - -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 Organization with protocol specifications `__ diff --git a/examples/protocols/modbus/mb_example_common/CMakeLists.txt b/examples/protocols/modbus/mb_example_common/CMakeLists.txt index 9b126758b8..c4854e99a6 100644 --- a/examples/protocols/modbus/mb_example_common/CMakeLists.txt +++ b/examples/protocols/modbus/mb_example_common/CMakeLists.txt @@ -2,5 +2,4 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) idf_component_register(SRCS "modbus_params.c" - INCLUDE_DIRS "include" - PRIV_REQUIRES freemodbus) + INCLUDE_DIRS "include") diff --git a/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults b/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults index 7b669af0b2..3c9c6de0ed 100644 --- a/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults +++ b/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults @@ -3,9 +3,8 @@ # CONFIG_MB_COMM_MODE_ASCII=y CONFIG_MB_UART_BAUD_RATE=115200 -CONFIG_FMB_TIMER_PORT_ENABLED=y -CONFIG_FMB_TIMER_GROUP=0 -CONFIG_FMB_TIMER_INDEX=0 +CONFIG_FMB_TIMER_PORT_ENABLED=n CONFIG_FMB_TIMER_ISR_IN_IRAM=y CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 -CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150 +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400 +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y diff --git a/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults b/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults index 334a4213af..300f170d0c 100644 --- a/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults +++ b/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults @@ -5,6 +5,4 @@ CONFIG_MB_COMM_MODE_ASCII=y CONFIG_MB_SLAVE_ADDR=1 CONFIG_MB_UART_BAUD_RATE=115200 CONFIG_FMB_TIMER_PORT_ENABLED=y -CONFIG_FMB_TIMER_GROUP=0 -CONFIG_FMB_TIMER_INDEX=0 CONFIG_FMB_TIMER_ISR_IN_IRAM=y diff --git a/examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c b/examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c index f7cdef4d20..a6b24c93e3 100644 --- a/examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c +++ b/examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c @@ -16,8 +16,12 @@ #include "nvs_flash.h" #include "esp_netif.h" #include "mdns.h" -#include "protocol_examples_common.h" +#if __has_include("esp_mac.h") +#include "esp_mac.h" +#endif + +#include "protocol_examples_common.h" #include "modbus_params.h" // for modbus parameters structures #include "mbcontroller.h" #include "sdkconfig.h" diff --git a/examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c b/examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c index e97f593b06..a3aa0fc9d0 100644 --- a/examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c +++ b/examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c @@ -18,6 +18,11 @@ #include "mdns.h" #include "esp_netif.h" + +#if __has_include("esp_mac.h") +#include "esp_mac.h" +#endif + #include "protocol_examples_common.h" #include "mbcontroller.h" // for mbcontroller defines and api diff --git a/tools/ci/check_ut_cmake_make.sh b/tools/ci/check_ut_cmake_make.sh index 6f9c754716..1d00f770d9 100755 --- a/tools/ci/check_ut_cmake_make.sh +++ b/tools/ci/check_ut_cmake_make.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash +EXCLUDE_UT_LIST_FILE="$( dirname -- "$0" )/exclude_check_ut_cmake_make_list.txt" + # While we support GNU Make & CMake together, check that unit tests support both -CMAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name CMakeLists.txt | grep "/test/" | grep -v "mbedtls/programs") -MAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name component.mk | grep "/test/" ) +CMAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name CMakeLists.txt | grep "/test/" | grep -v -F -f ${EXCLUDE_UT_LIST_FILE} ) +MAKE_UT_PATHS=$( find ${IDF_PATH}/components/ -type f -name component.mk | grep "/test/" | grep -v -F -f ${EXCLUDE_UT_LIST_FILE} ) CMAKE_UT_PATHS="$(/usr/bin/dirname $CMAKE_UT_PATHS | sort -n)" MAKE_UT_PATHS="$(/usr/bin/dirname $MAKE_UT_PATHS | sort -n)" diff --git a/tools/ci/exclude_check_ut_cmake_make_list.txt b/tools/ci/exclude_check_ut_cmake_make_list.txt new file mode 100644 index 0000000000..9a1cc51ff7 --- /dev/null +++ b/tools/ci/exclude_check_ut_cmake_make_list.txt @@ -0,0 +1,2 @@ +mbedtls/programs +freemodbus/test diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index bf34d0c705..3f955dcd52 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -7,6 +7,7 @@ components/espcoredump/espcoredump.py components/espcoredump/test/test_espcoredump.py components/espcoredump/test/test_espcoredump.sh components/espcoredump/test_apps/build_espcoredump.sh +components/freemodbus/build_all.sh components/heap/test_multi_heap_host/test_all_configs.sh components/mbedtls/esp_crt_bundle/gen_crt_bundle.py components/mbedtls/esp_crt_bundle/test_gen_crt_bundle/test_gen_crt_bundle.py