Merge branch 'release/v2.0' into 'main'

esp-modbus stack release v2.0.1

See merge request idf/esp-modbus!89
This commit is contained in:
Alex Lisitsyn
2024-12-20 16:15:05 +08:00
243 changed files with 30310 additions and 0 deletions

55
.gitignore vendored Normal file
View File

@@ -0,0 +1,55 @@
.config
*.o
*.pyc
# gtags
GTAGS
GRTAGS
GPATH
# emacs
.dir-locals.el
# emacs temp file suffixes
*~
.#*
\#*#
# eclipse setting
.settings
# MacOS directory files
.DS_Store
# Build related files
build*
sdkconfig
sdkconfig.old
managed_components
dependencies.lock
.vscode
doxygen_output/**
dist
# 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/
/.pytest_cache/

348
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,348 @@
stages:
- build
- target_test
- deploy
variables:
# System environment
ESP_DOCS_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env-v5.3:1-1"
ESP_DOCS_PATH: "$CI_PROJECT_DIR"
TEST_DIR: "$CI_PROJECT_DIR"
# GitLab-CI environment
GET_SOURCES_ATTEMPTS: "10"
ARTIFACT_DOWNLOAD_ATTEMPTS: "10"
GIT_SUBMODULE_STRATEGY: none
# Define a matrix for IDF versions and their corresponding targets
.options_list:
# versions:
# IDF_VER: ["latest", "v5.3", "v5.2", "v5.0"]
markers:
TEST_MARKER:
- "tcp"
- "serial"
- "generic"
# - "tcp_p4"
# - "serial_p4"
.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
.build_template:
stage: build
tags:
- build
variables:
SIZE_INFO_LOCATION: "${TEST_DIR}/size_info.txt"
IDF_CCACHE_ENABLE: "1"
after_script:
# Show ccache statistics if enabled globally
- test "$CI_CCACHE_STATS" == 1 && test -n "$(which ccache)" && ccache --show-stats || true
dependencies: []
.before_script_build_jobs:
before_script:
- pip install idf-component-manager --upgrade
- pip install "idf_build_apps~=1.0.1"
.check_idf_ver: &check_idf_ver |
export IDF_PATH=$(find /opt -type d -name "*idf*" \
\( -exec test -f '{}/tools/idf.py' \; -and -exec test -f '{}/tools/idf_tools.py' \; \
\) -print -quit)
if [ -z "${IDF_PATH}" ];then
echo "IDF version is not found."
else
cd ${IDF_PATH}
export IDF_DESCRIBE=$(git describe)
export IDF_VERSION=${IDF_DESCRIBE%-*}
echo "$IDF_VERSION" >> $TEST_DIR/idf_version_info.txt
echo "ESP-IDF: $IDF_VERSION"
fi
# Note: this script builds the folder against all targets and then deletes
# all other artifacts except the esp32 to decrease the size of artifacs (may cause failures)
.build_cur_folder: &build_cur_folder |
echo "Build job ${CI_JOB_NAME}, folder: ${PWD##*/}, targets: ${TEST_TARGETS}"
python -m idf_build_apps build -v -p . \
--recursive \
--target all \
--default-build-targets ${TEST_TARGETS} \
--config "sdkconfig.ci.*=" --build-dir "build_@t_@w" \
--check-warnings \
--ignore-warning-file ../tools/ignore_build_warnings.txt \
--collect-size-info $SIZE_INFO_LOCATION \
--manifest-rootpath . \
--manifest-file .build-test-rules.yml \
--parallel-count ${CI_NODE_TOTAL:-1} \
--parallel-index ${CI_NODE_INDEX:-1}
echo "delete build folders:" $(find . -type d -regex '^\./.*build_esp32[a-z]+[0-9]+[_a-z]*' -print -exec rm -rf {} +)
ls -lh > test_dir_${PWD##*/}.txt
# This template gets expanded multiple times, once for every IDF version.
# IDF version is specified by setting the espressif/idf image tag.
#
# TEST_TARGETS sets the list of IDF_TARGET values to build the test for.
# It should contain only the targets with optimized assembly implementations.
#
.build_pytest_template:
stage: build
extends:
- .build_template
- .before_script_build_jobs
script:
# The script below will build all test applications defined in environment variable $TEST_TARGETS
- *check_idf_ver
# This is workaround to build library under esp-idf v4.4
- pip install idf-component-manager --upgrade
- cd ${TEST_DIR}/test_apps
- *build_cur_folder
- cd ${TEST_DIR}/examples
- export TEST_TARGETS="esp32" # override to build only on esp32 target to decrease build time
- *build_cur_folder
variables:
TEST_TARGETS: "esp32"
artifacts:
name: artifacts_${CI_JOB_NAME}
paths:
- "**/build*/size.json"
- "**/build*/build.log"
- "**/build*/build_log.txt"
- "**/build*/*.bin"
- "**/build*/*.elf"
- "**/build*/*.map"
- "**/build*/flasher_args.json"
- "**/build*/flash_project_args"
- "**/build*/config/sdkconfig.json"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- "**/idf_version_info.txt"
- "**/test_dir*.txt"
- $SIZE_INFO_LOCATION
when: always
expire_in: 3 weeks
build_idf_latest:
extends: .build_pytest_template
image: espressif/idf:latest
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c5 esp32c6 esp32h2"
build_idf_v5.3:
extends: .build_pytest_template
image: espressif/idf:release-v5.3
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2"
build_idf_v5.2:
extends: .build_pytest_template
image: espressif/idf:release-v5.2
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3"
build_idf_v5.0:
extends: .build_pytest_template
image: espressif/idf:release-v5.0
variables:
TEST_TARGETS: "esp32 esp32s2 esp32s3 esp32c2 esp32c3"
.target_test_template:
stage: target_test
timeout: 1 hour
variables:
GIT_DEPTH: 1
SUBMODULES_TO_FETCH: "none"
cache:
# Usually do not need submodule-cache in target_test
- key: pip-cache
paths:
- .cache/pip
policy: pull
.before_script_pytest_jobs:
before_script:
# Install or upgrade pytest-embedded to perform test cases
- pip install -r ${TEST_DIR}/tools/test_requirements.txt
# Upgrade the packages (workaround for esp-idf v5.0)
- pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf --upgrade
.test_cur_folder: &test_cur_folder |
export IDF_VER=$(cat ${TEST_DIR}/idf_version_info.txt)
echo "Start test job: ${CI_JOB_NAME}, version: ${IDF_VER%-*}, folder: ${PWD##*/}"
python -m pytest --junit-xml=${TEST_DIR}/${PWD##*/}/results_${IDF_VER%-*}_${PWD##*/}.xml --target=${IDF_TARGET} -m multi_dut_modbus_${TEST_MARKER}
ls -lh > test_dir_${PWD##*/}.txt
.test_template:
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.3:1"
stage: target_test
extends:
- .before_script_pytest_jobs
tags:
- multi_dut_modbus_${TEST_MARKER}
variables:
IDF_TARGET: "esp32" # the only esp32 runners are available for now
script:
- cd ${TEST_DIR}/test_apps/
- *test_cur_folder
- cd ${TEST_DIR}/examples/
- *test_cur_folder
artifacts:
name: artifacts_${CI_JOB_NAME}
paths:
- "${TEST_DIR}/**/*.log"
- "${TEST_DIR}/**/*.xml"
- "${TEST_DIR}/**/results_*.xml"
- "${TEST_DIR}/**/pytest_embedded_log/"
- "${TEST_DIR}/**/test_dir*.txt"
- "${TEST_DIR}/**/idf_version_info.txt"
- "${TEST_DIR}/**/log.html"
- "${TEST_DIR}/**/report.html"
- "${TEST_DIR}/**/*.pcap"
reports:
junit: ${TEST_DIR}/results_${IDF_VER%-*}.xml
when: always
expire_in: 1 week
target_test_latest:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.4:1"
extends: .test_template
parallel:
matrix:
- !reference [.options_list, markers]
needs:
job: build_idf_latest
artifacts: true
after_script: []
target_test_v5.3:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.3:1"
extends: .test_template
parallel:
matrix:
- !reference [.options_list, markers]
needs:
job: build_idf_v5.3
artifacts: true
after_script: []
target_test_v5.2:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.2:2"
extends: .test_template
parallel:
matrix:
- !reference [.options_list, markers]
needs:
job: build_idf_v5.2
artifacts: true
after_script: []
target_test_v5.0:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.0:3"
extends: .test_template
parallel:
matrix:
- !reference [.options_list, markers]
needs:
job: build_idf_v5.0
artifacts: true
after_script: []
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
- ./generate_docs
.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)
- export GIT_TAGS=$(git fetch --tags; git tag -l;)
- echo "Deploy ${PWD##*/}, ${DOCS_BUILD_DIR}, Ref name; ${CI_COMMIT_REF_NAME}"
- echo "Version; ${GIT_VER}, tags; ${GIT_TAGS}"
- pip install -r ${CI_PROJECT_DIR}/docs/requirements.txt
- deploy-docs
deploy_docs_preview:
extends:
- .deploy_docs_template
except:
refs:
- main
variables:
TYPE: "preview"
DOCS_BUILD_DIR: "${CI_PROJECT_DIR}/docs/_build/"
DOCS_DEPLOY_PRIVATEKEY: "$DOCS_PREVIEW_PRIVATEKEY"
DOCS_DEPLOY_SERVER: "$DOCS_PREVIEW_SERVER"
DOCS_DEPLOY_SERVER_USER: "$DOCS_PREVIEW_SERVER_USER"
DOCS_DEPLOY_PATH: "$DOCS_PREVIEW_PATH"
DOCS_DEPLOY_URL_BASE: "$DOCS_PREVIEW_URL_BASE"
deploy_docs_production:
extends:
- .deploy_docs_template
only:
refs:
- main
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 == "main"' # the main branch is a component release branch for v2
- if: '$FORCE_PUSH_COMPONENT == "1"'
when: manual
script:
- pip install idf-component-manager
- export IDF_COMPONENT_API_TOKEN=${ESP_MODBUS_API_KEY}
- export COMP_VERSION=$(grep 'version:' idf_component.yml | head -n 1 | awk '{print $2}' | tr -d '"')
- compote component upload --namespace=espressif --name=esp-modbus --allow-existing --version=${COMP_VERSION}

19
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,19 @@
repos:
- repo: https://github.com/igrr/astyle_py.git
rev: master
hooks:
- id: astyle_py
args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
types_or: [c, c++]
- id: end-of-file-fixer
types_or: [c, c++]
- id: check-merge-conflict
- id: mixed-line-ending
types_or: [c, c++]
args: ['--fix=lf']
description: Forces to replace line ending by the UNIX 'lf' character

92
CMakeLists.txt Normal file
View File

@@ -0,0 +1,92 @@
# 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
"mb_controller/common/esp_modbus_master.c"
"mb_controller/common/esp_modbus_slave.c"
"mb_controller/common/esp_modbus_master_serial.c"
"mb_controller/common/esp_modbus_slave_serial.c"
"mb_controller/common/esp_modbus_master_tcp.c"
"mb_controller/common/esp_modbus_slave_tcp.c"
"mb_controller/serial/mbc_serial_master.c"
"mb_controller/serial/mbc_serial_slave.c"
"mb_controller/tcp/mbc_tcp_master.c"
"mb_controller/tcp/mbc_tcp_slave.c"
"mb_objects/mb_master.c"
"mb_objects/mb_slave.c"
"mb_objects/functions/mbfunccoils_master.c"
"mb_objects/functions/mbfunccoils.c"
"mb_objects/functions/mbfuncdiag.c"
"mb_objects/functions/mbfuncdisc_master.c"
"mb_objects/functions/mbfuncdisc.c"
"mb_objects/functions/mbfuncholding_master.c"
"mb_objects/functions/mbfuncholding.c"
"mb_objects/functions/mbfuncinput_master.c"
"mb_objects/functions/mbfuncinput.c"
"mb_objects/functions/mbfuncother.c"
"mb_objects/functions/mbutils.c"
"mb_ports/common/port_event.c"
"mb_ports/common/port_other.c"
"mb_ports/common/port_timer.c"
"mb_ports/common/mb_transaction.c"
"mb_ports/serial/port_serial.c"
"mb_ports/tcp/port_tcp_master.c"
"mb_ports/tcp/port_tcp_slave.c"
"mb_ports/tcp/port_tcp_driver.c"
"mb_ports/tcp/port_tcp_utils.c"
"mb_transports/rtu/rtu_master.c"
"mb_transports/rtu/rtu_slave.c"
"mb_transports/rtu/mbcrc.c"
"mb_transports/ascii/ascii_master.c"
"mb_transports/ascii/ascii_slave.c"
"mb_transports/ascii/ascii_lrc.c"
"mb_transports/tcp/tcp_master.c"
"mb_transports/tcp/tcp_slave.c"
)
set(include_dirs mb_transports mb_controller/common/include mb_objects/include mb_ports/common mb_ports/serial mb_ports/tcp)
set(priv_include_dirs mb_controller/serial mb_controller/tcp mb_controller/common mb_transports/rtu mb_transports/ascii mb_transports/tcp)
if(CONFIG_FMB_EXT_TYPE_SUPPORT)
list(APPEND srcs "mb_controller/common/mb_endianness_utils.c")
endif()
add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${srcs})
add_prefix(include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${include_dirs})
add_prefix(priv_include_dirs "${CMAKE_CURRENT_LIST_DIR}/modbus/" ${priv_include_dirs})
message(STATUS "DEBUG: Use esp-modbus component folder: ${CMAKE_CURRENT_LIST_DIR}.")
set(requires driver)
set(priv_requires esp_netif esp_event vfs)
# 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 ${requires}
PRIV_REQUIRES ${priv_requires}
LDFRAGMENTS linker.lf)
# This is an alternative of macro `idf_component_optional_requires(PUBLIC mdns)` to support all versions of esp-idf
set(optional_reqs mdns espressif__mdns)
idf_build_get_property(build_components BUILD_COMPONENTS)
message(STATUS "build_components = ${build_components}")
foreach(req ${optional_reqs} ${exclude_comps})
if(req IN_LIST build_components)
idf_component_get_property(req_lib ${req} COMPONENT_LIB)
target_link_libraries(${COMPONENT_LIB} PRIVATE ${req_lib})
message(STATUS "Req ${req} is found and added into ${COMPONENT_NAME} dependencies.")
target_compile_definitions(${COMPONENT_LIB} PUBLIC -DMB_MDNS_IS_INCLUDED)
endif()
endforeach()
# target_link_options(${COMPONENT_LIB} INTERFACE -fsanitize=undefined -fsanitize=alignment) #-fsanitize=address -fsanitize=undefined
# target_link_options(${COMPONENT_LIB} INTERFACE -fsanitize=address)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-strict-aliasing -Wno-write-strings -Werror)
message(STATUS "The mdns included is: ${MB_MDNS_IS_INCLUDED}")

218
Kconfig Normal file
View File

@@ -0,0 +1,218 @@
menu "Modbus configuration"
config FMB_COMM_MODE_TCP_EN
bool "Enable Modbus stack support for TCP communication mode"
default y
help
Enable Modbus TCP option for stack.
config FMB_TCP_PORT_DEFAULT
int "Modbus TCP port number"
range 0 65535
default 502
depends on FMB_COMM_MODE_TCP_EN
help
Modbus default port number used by Modbus TCP stack
config FMB_TCP_PORT_MAX_CONN
int "Maximum allowed connections for TCP stack"
range 1 8
default 5
depends on FMB_COMM_MODE_TCP_EN
help
Maximum allowed connections number for Modbus TCP stack.
This is used by Modbus master and slave port layer to establish connections.
This parameter may decrease performance of Modbus stack and can cause
increasing of processing time (increase only if absolutely necessary).
config FMB_TCP_CONNECTION_TOUT_SEC
int "Modbus TCP connection timeout"
range 1 7200
default 20
depends on FMB_COMM_MODE_TCP_EN
help
Modbus TCP connection timeout in seconds.
Once expired the current connection with the client will be closed
and Modbus slave will be waiting for new connection to accept.
config FMB_TCP_UID_ENABLED
bool "Modbus TCP enable UID (Unit Identifier) support"
default n
depends on FMB_COMM_MODE_TCP_EN
help
If this option is set the Modbus stack uses UID (Unit Identifier) field in MBAP frame.
Else the UID is ignored by master and slave.
config FMB_COMM_MODE_RTU_EN
bool "Enable Modbus stack support for RTU mode"
default y
help
Enable RTU Modbus communication mode option for Modbus serial stack.
config FMB_COMM_MODE_ASCII_EN
bool "Enable Modbus stack support for ASCII mode"
default y
help
Enable ASCII Modbus communication mode option for Modbus serial stack.
config FMB_MASTER_TIMEOUT_MS_RESPOND
int "Slave respond timeout (Milliseconds)"
default 10000
range 150 30000
help
If master sends a frame which is not broadcast, it has to wait some time for slave response.
if slave is not respond in this time, the master will process timeout error.
config FMB_MASTER_DELAY_MS_CONVERT
int "Slave conversion delay (Milliseconds)"
default 200
range 150 2000
help
If master sends a broadcast frame, it has to wait conversion time to delay,
then master can send next frame.
config FMB_QUEUE_LENGTH
int "Modbus serial task queue length"
range 0 200
default 20
help
Modbus serial driver queue length. It is used by event queue task.
See the serial driver API for more information.
config FMB_PORT_TASK_STACK_SIZE
int "Modbus port task stack size"
range 2048 16384
default 4096
help
Modbus port task stack size for rx/tx event processing.
It may be adjusted when debugging is enabled (for example).
config FMB_BUFFER_SIZE
int "Modbus RX/TX buffer size"
range 256 2048
default 260 if FMB_COMM_MODE_TCP_EN
default 256 if !FMB_COMM_MODE_TCP_EN
help
Modbus RX/TX buffer size for UART driver initialization.
This buffer is used for modbus frame transfer. The Modbus protocol maximum
frame size is 260 bytes (TCP). Bigger size can be used for non standard implementations.
config FMB_SERIAL_ASCII_BITS_PER_SYMB
int "Number of data bits per ASCII character"
default 8
range 7 8
depends on FMB_COMM_MODE_ASCII_EN
help
This option defines the number of data bits per ASCII character.
config FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS
int "Response timeout for ASCII communication mode (ms)"
default 1000
range 200 5000
depends on FMB_COMM_MODE_ASCII_EN
help
This option defines response timeout of slave in milliseconds for ASCII communication mode.
Thus the timeout will expire and allow the master program to handle the error.
config FMB_PORT_TASK_PRIO
int "Modbus port task priority"
range 3 23
default 10
help
Modbus port data processing task priority.
The priority of Modbus controller task is equal to (CONFIG_FMB_PORT_TASK_PRIO - 1).
choice FMB_PORT_TASK_AFFINITY
prompt "Modbus task affinity"
default FMB_PORT_TASK_AFFINITY_CPU0
depends on !FREERTOS_UNICORE
help
Allows setting the core affinity of the Modbus controller task, i.e. whether the task is pinned to
particular CPU, or allowed to run on any CPU.
config FMB_PORT_TASK_AFFINITY_NO_AFFINITY
bool "No affinity"
config FMB_PORT_TASK_AFFINITY_CPU0
bool "CPU0"
config FMB_PORT_TASK_AFFINITY_CPU1
bool "CPU1"
endchoice
config FMB_PORT_TASK_AFFINITY
hex
default FREERTOS_NO_AFFINITY if FMB_PORT_TASK_AFFINITY_NO_AFFINITY || FREERTOS_UNICORE
default 0x0 if FMB_PORT_TASK_AFFINITY_CPU0
default 0x1 if FMB_PORT_TASK_AFFINITY_CPU1
config FMB_CONTROLLER_SLAVE_ID_SUPPORT
bool "Modbus controller slave ID support"
default y
help
Modbus slave ID support enable.
When enabled the Modbus <Report Slave ID> command is supported by stack.
config FMB_CONTROLLER_SLAVE_ID
hex "Modbus controller slave ID"
range 0 4294967295
default 0x00112233
depends on FMB_CONTROLLER_SLAVE_ID_SUPPORT
help
Modbus slave ID value to identify modbus device
in the network using <Report Slave ID> command.
Most significant byte of ID is used as short device ID and
other three bytes used as long ID.
config FMB_CONTROLLER_NOTIFY_TIMEOUT
int "Modbus controller notification timeout (ms)"
range 0 200
default 20
help
Modbus controller notification timeout in milliseconds.
This timeout is used to send notification about accessed parameters.
config FMB_CONTROLLER_NOTIFY_QUEUE_SIZE
int "Modbus controller notification queue size"
range 0 200
default 20
help
Modbus controller notification queue size.
The notification queue is used to get information about accessed parameters.
config FMB_CONTROLLER_STACK_SIZE
int "Modbus controller stack size"
range 2048 32768
default 4096
help
Modbus controller task stack size. The Stack size may be adjusted when
debug mode is used which requires more stack size (for example).
config FMB_EVENT_QUEUE_TIMEOUT
int "Modbus stack event queue timeout (ms)"
range 10 500
default 20
help
Modbus stack event queue timeout in milliseconds. This may help to optimize
Modbus stack event processing time.
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
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.
config FMB_EXT_TYPE_SUPPORT
bool "Modbus uses extended types to support third party devices"
default n
help
If this option is set the Modbus stack supports extended list of types
in data dictionary and conversion API to work with the extended types
otherwise the only legacy types are supported. The extended types include
integer, float, double types with different endianness and size.
endmenu

202
LICENSE Normal file
View File

@@ -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.

70
README.md Normal file
View File

@@ -0,0 +1,70 @@
# 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://github.com/espressif/esp-modbus/tree/main)
This library is to be used with Espressifs IoT Development Framework, [ESP_IDF](https://github.com/espressif/esp-idf). The packages from this repository are uploaded to Espressifs 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 v5.0 and later. Some ESP-IDF 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.
## Peculiarities Of Current Release
The current release esp-modbus corresponds to the version `v2.x.x` (refer to idf_component.yml file) and supports creation of several instances of modbus master and slave objects. The public API interface is changed to allow creation of multiple communication objects with its own communication parameters, and the constructor API returns the handle to the interface structure that must be used as a first parameter for each API call for this particular object. For more information about the interface API and related changes see the official documentation for this release, described below. The goal of this beta release is to introduce new features and changes to the end users and get their feedback. The appropriate information or feature requests can be shared over on discussion page of the project.
* [Discussions](https://github.com/espressif/esp-modbus/discussions/categories/general)
* [Issues](https://github.com/espressif/esp-modbus/issues)
## Documentation
The documentation can be found on the link below:
* [ESP-Modbus documentation (English)](https://docs.espressif.com/projects/esp-modbus/en/stable/esp32/index.html)
## 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-modbus/tree/main/examples/serial/mb_serial_slave)
- [Modbus Serial master example](https://github.com/espressif/esp-modbus/tree/main/examples/serial/mb_serial_master)
- [Modbus TCP slave example](https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_slave)
- [Modbus TCP master example](https://github.com/espressif/esp-modbus/tree/main/examples/tcp/mb_tcp_master)
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
The initial ESP-Modbus project was 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.

29
component.mk Normal file
View File

@@ -0,0 +1,29 @@
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) \
)

58
docs/Doxyfile Normal file
View File

@@ -0,0 +1,58 @@
# 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)/modbus/mb_controller/common/include/esp_modbus_common.h \
$(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_slave.h \
$(PROJECT_PATH)/modbus/mb_controller/common/include/esp_modbus_master.h \
$(PROJECT_PATH)/modbus/mb_controller/common/include/mb_endianness_utils.h
## Get warnings for functions that have no documentation for their parameters or return value
##
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

11
docs/README.md Normal file
View File

@@ -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

260
docs/_static/404-page__en.svg vendored Normal file
View File

@@ -0,0 +1,260 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="图层_1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 580"
style="enable-background:new 0 0 1000 580;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:url(#polygon12_1_);}
.st2{fill:url(#polygon21_1_);}
.st3{opacity:0.27;fill:url(#circle42_1_);enable-background:new ;}
.st4{fill:url(#polygon58_1_);}
.st5{fill:#444444;stroke:#FFFFFF;stroke-width:0.834;stroke-miterlimit:10;}
.st6{fill:none;stroke:#FFFFFF;stroke-width:1.1033;stroke-miterlimit:10;}
.st7{fill:none;stroke:#353535;stroke-width:1.1033;stroke-miterlimit:10;}
.st8{fill:#FFFFFF;stroke:#444444;stroke-width:0.834;stroke-miterlimit:10;}
.st9{fill:#444444;stroke:#FFFFFF;stroke-width:0.8485;stroke-miterlimit:10;}
.st10{fill:none;stroke:#FFFFFF;stroke-width:1.1226;stroke-miterlimit:10;}
.st11{fill:none;stroke:#353535;stroke-width:1.1226;stroke-miterlimit:10;}
.st12{fill:#FFFFFF;stroke:#444444;stroke-width:0.8485;stroke-miterlimit:10;}
.st13{fill:#353535;}
.st14{fill:#444444;stroke:#FFFFFF;stroke-width:0.9321;stroke-miterlimit:10;}
.st15{fill:none;stroke:#FFFFFF;stroke-width:1.046;stroke-miterlimit:10;}
.st16{fill:none;stroke:#353535;stroke-width:1.046;stroke-miterlimit:10;}
.st17{fill:#FFFFFF;stroke:#444444;stroke-width:0.7906;stroke-miterlimit:10;}
.st18{opacity:0.59;fill:#E0E0E0;enable-background:new ;}
.st19{fill:#FFFFFF;stroke:#444444;stroke-width:2;stroke-miterlimit:10;}
.st20{fill:none;stroke:#444444;stroke-width:2;stroke-miterlimit:10;}
.st21{fill:none;stroke:#444444;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st22{enable-background:new ;}
.st23{fill:#4D4D4D;}
</style>
<rect id="BG_2_" x="-1" y="-9.5" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st0" width="1012.9" height="600.4">
</rect>
<linearGradient id="polygon12_1_" gradientUnits="userSpaceOnUse" x1="1014.7582" y1="90.2012" x2="1077.5918" y2="356.7023" gradientTransform="matrix(0.9556 0.295 -0.2974 0.9605 -400.3649 -336.724)">
<stop offset="4.835800e-02" style="stop-color:#9FA0A0"/>
<stop offset="0.5227" style="stop-color:#D7D8D8;stop-opacity:0.4381"/>
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<polygon id="polygon12" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st1" points="608.7,371.9
511.9,134.7 490.9,134.1 430.7,377.1 ">
</polygon>
<linearGradient id="polygon21_1_" gradientUnits="userSpaceOnUse" x1="197.9478" y1="434.8972" x2="282.0578" y2="791.6389" gradientTransform="matrix(0.9983 -5.887031e-02 5.887031e-02 0.9983 28.0536 -430.7623)">
<stop offset="4.835800e-02" style="stop-color:#898989"/>
<stop offset="0.5874" style="stop-color:#D7D7D7;stop-opacity:0.3616"/>
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<polygon id="polygon21" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st2" points="325.4,155.1
289.2,163.9 291.9,437 480.9,450.4 ">
</polygon>
<radialGradient id="circle42_1_" cx="836.3" cy="506.5986" r="65.7125" gradientTransform="matrix(1 0 0 1 12 -19.0997)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</radialGradient>
<circle id="circle42" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st3" cx="848.3" cy="487.5" r="68.3">
</circle>
<linearGradient id="polygon58_1_" gradientUnits="userSpaceOnUse" x1="1863.538" y1="415.4688" x2="1929.7196" y2="696.1702" gradientTransform="matrix(0.8607 0.5092 -0.5092 0.8607 -635.7186 -1225.6498)">
<stop offset="4.835800e-02" style="stop-color:#898989"/>
<stop offset="0.5874" style="stop-color:#D7D7D7;stop-opacity:0.3616"/>
<stop offset="0.8926" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<polygon id="polygon58" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st4" points="547.4,383
763.1,428.2 713.7,163.4 683.8,156 ">
</polygon>
<g id="g94" transform="rotate(9.0573675,796.06564,263.99283)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<path id="path60" inkscape:connector-curvature="0" class="st5" d="M520,160.2l-75.8,10.4c-5.6,0.8-10.8-3.2-11.5-8.7l0,0
c-0.8-5.6,3.2-10.8,8.7-11.5l75.8-10.4c5.6-0.8,10.8,3.2,11.5,8.7l0,0C529.5,154.3,525.6,159.4,520,160.2L520,160.2z"/>
<ellipse id="circle62" transform="matrix(0.1574 -0.9875 0.9875 0.1574 220.2397 577.3303)" class="st6" cx="448.4" cy="159.6" rx="5.6" ry="5.6"/>
<ellipse id="circle64" transform="matrix(0.1574 -0.9875 0.9875 0.1574 241.8959 596.7076)" class="st6" cx="470.6" cy="156.6" rx="5.6" ry="5.6"/>
<ellipse id="circle66" transform="matrix(0.1574 -0.9875 0.9875 0.1574 304.0202 547.2599)" class="st7" cx="472.7" cy="95.5" rx="3.9" ry="3.9"/>
<ellipse id="circle68" transform="matrix(0.1574 -0.9875 0.9875 0.1574 263.9857 616.4264)" class="st6" cx="493.2" cy="153.5" rx="5.6" ry="5.6"/>
<ellipse id="circle70" transform="matrix(0.1574 -0.9875 0.9875 0.1574 285.6079 635.2634)" class="st6" cx="515.1" cy="150.3" rx="5.6" ry="5.6"/>
<path id="path72" inkscape:connector-curvature="0" class="st8" d="M510,139.5l-61.3,8.4l-0.4-2.8c-0.2-1.5,0.8-2.9,2.3-3.1
l55.7-7.7c1.5-0.2,2.9,0.8,3.1,2.3L510,139.5z"/>
<path id="path74" inkscape:connector-curvature="0" class="st8" d="M519.9,161.7L444.9,172l0.3,2.4c0.3,1.7,1.8,2.8,3.4,2.6
l69.1-9.5c1.7-0.3,2.8-1.8,2.6-3.4L519.9,161.7z"/>
<path id="path76" inkscape:connector-curvature="0" class="st7" d="M508.2,170.4l-50.5,6.9l0.4,3c0.2,1.3,1.5,2.3,2.8,2.2l45.5-6.3
c1.3-0.2,2.3-1.5,2.2-2.8L508.2,170.4z"/>
<path id="path78" inkscape:connector-curvature="0" class="st7" d="M499.5,133.7l-43.7,6l-1.8-13.2c-1-7.3,4.1-14,11.3-15l17.3-2.4
c7.3-1,14,4.1,15,11.3L499.5,133.7z"/>
<line id="line80" class="st7" x1="473.5" y1="100.6" x2="474.6" y2="109.1"/>
<line id="line82" class="st7" x1="471.6" y1="119.4" x2="464.5" y2="131.7"/>
<line id="line84" class="st7" x1="485.9" y1="117.9" x2="478.7" y2="130.2"/>
<path id="path86" inkscape:connector-curvature="0" class="st8" d="M470.7,182.8L467,193c-0.2,0.5,0.5,0.9,0.8,0.5l10.5-11.3
c0.3-0.3,0-0.9-0.4-0.8l-6.8,1C471,182.5,470.8,182.6,470.7,182.8z"/>
<path id="path88" inkscape:connector-curvature="0" class="st8" d="M460.7,184l-6.3,9.2c-0.3,0.5,0.3,1,0.8,0.7l12.8-10.1
c0.4-0.3,0.2-0.9-0.4-0.9l-6.6,0.9C460.9,183.9,460.8,184,460.7,184L460.7,184z"/>
<path id="path90" inkscape:connector-curvature="0" class="st8" d="M496.6,179l6.3,8.8c0.3,0.4-0.2,1-0.7,0.8l-13.2-8
c-0.4-0.3-0.3-0.8,0.2-0.9l6.8-0.8C496.3,178.8,496.5,178.9,496.6,179L496.6,179z"/>
<path id="path92" inkscape:connector-curvature="0" class="st8" d="M506.7,177.5l8.5,7.2c0.4,0.3,0,1.1-0.5,0.8l-15.1-6.3
c-0.5-0.2-0.4-0.9,0.1-0.9l6.6-0.9C506.4,177.4,506.5,177.4,506.7,177.5L506.7,177.5z"/>
</g>
<g id="g130" transform="translate(-131.09867,-443.26745)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<path id="path96" inkscape:connector-curvature="0" class="st9" d="M875,594.1l-76.7-13.2c-5.6-0.9-9.4-6.4-8.5-12l0,0
c0.9-5.6,6.4-9.4,12-8.5l76.7,13.2c5.6,0.9,9.4,6.4,8.5,12l0,0C885.9,591.3,880.6,595.1,875,594.1z"/>
<circle id="circle98" class="st10" cx="805.6" cy="571.5" r="5.7"/>
<circle id="circle100" class="st10" cx="828.2" cy="575.4" r="5.7"/>
<circle id="circle102" class="st11" cx="849" cy="516.8" r="4"/>
<circle id="circle104" class="st10" cx="851" cy="579.3" r="5.7"/>
<circle id="circle106" class="st10" cx="873.2" cy="583" r="5.7"/>
<path id="path108" inkscape:connector-curvature="0" class="st12" d="M871.6,570.9l-61.9-10.7l0.5-2.8c0.3-1.5,1.7-2.5,3.2-2.3
l56.4,9.8c1.5,0.3,2.5,1.7,2.3,3.2L871.6,570.9z"/>
<path id="path110" inkscape:connector-curvature="0" class="st12" d="M874.4,595.5l-76-13.2l-0.4,2.5c-0.3,1.7,0.8,3.3,2.5,3.6
l69.8,12c1.7,0.3,3.3-0.8,3.6-2.5L874.4,595.5z"/>
<path id="path112" inkscape:connector-curvature="0" class="st13" d="M860.3,600.3l-51.1-8.8l-0.5,3.1c-0.3,1.4,0.7,2.7,2.1,3l46,8
c1.4,0.3,2.7-0.7,3-2.1L860.3,600.3z"/>
<path id="path114" inkscape:connector-curvature="0" class="st11" d="M863.2,562.2l-44.2-7.6l2.3-13.3c1.3-7.3,8.3-12.3,15.6-11
l17.5,3.1c7.3,1.3,12.3,8.3,11,15.6L863.2,562.2z"/>
<line id="line116" class="st11" x1="848.1" y1="522.1" x2="846.6" y2="530.6"/>
<line id="line118" class="st11" x1="840.5" y1="539.7" x2="829.8" y2="549.5"/>
<line id="line120" class="st11" x1="854.8" y1="542.6" x2="844.2" y2="552.3"/>
<path id="path122" inkscape:connector-curvature="0" class="st12" d="M820.1,600.8l-6.8,8.7c-0.3,0.4,0.2,1,0.7,0.8l13.7-7.6
c0.4-0.3,0.3-0.8-0.2-0.9l-7-1.1C820.5,600.6,820.3,600.7,820.1,600.8L820.1,600.8z"/>
<path id="path124" inkscape:connector-curvature="0" class="st12" d="M810,599l-8.9,7c-0.4,0.3-0.1,1.1,0.5,0.8l15.5-5.9
c0.5-0.2,0.4-0.8-0.1-1l-6.6-1.2C810.3,598.8,810.1,598.9,810,599L810,599z"/>
<path id="path126" inkscape:connector-curvature="0" class="st12" d="M846.4,605.1l3.5,10.5c0.2,0.5-0.5,0.9-0.8,0.5l-10.4-11.8
c-0.3-0.3,0-0.9,0.5-0.8l6.9,1.3C846.3,604.9,846.4,605,846.4,605.1L846.4,605.1z"/>
<path id="path128" inkscape:connector-curvature="0" class="st12" d="M856.6,606.8l6,9.6c0.3,0.5-0.3,1-0.8,0.7l-12.7-10.7
c-0.4-0.3-0.1-1,0.4-0.8l6.6,1.2C856.4,606.6,856.5,606.7,856.6,606.8L856.6,606.8z"/>
</g>
<g id="g166" transform="translate(6.564267,-535.67492)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<path id="path132" inkscape:connector-curvature="0" class="st14" d="M331.5,670.9l-70.2,18.3c-5.1,1.3-10.4-1.8-11.7-6.9l0,0
c-1.3-5.1,1.8-10.4,6.9-11.7l70.2-18.3c5.1-1.3,10.4,1.8,11.7,6.9l0,0C339.7,664.2,336.7,669.6,331.5,670.9z"/>
<circle id="circle134" class="st15" cx="264.1" cy="678.3" r="5.3"/>
<circle id="circle136" class="st15" cx="284.7" cy="673" r="5.3"/>
<circle id="circle138" class="st16" cx="279.7" cy="615.2" r="3.7"/>
<circle id="circle140" class="st15" cx="305.5" cy="667.5" r="5.3"/>
<circle id="circle142" class="st15" cx="325.8" cy="662.1" r="5.3"/>
<path id="path144" inkscape:connector-curvature="0" class="st17" d="M319.7,652.5L263,667.3l-0.7-2.5c-0.4-1.4,0.5-2.8,1.9-3.2
l51.6-13.4c1.4-0.4,2.8,0.5,3.2,1.9L319.7,652.5z"/>
<path id="path146" inkscape:connector-curvature="0" class="st17" d="M331.5,672.3L262,690.4l0.6,2.2c0.4,1.6,2,2.5,3.5,2.1
l63.9-16.7c1.6-0.4,2.5-2,2.1-3.5L331.5,672.3z"/>
<path id="path148" inkscape:connector-curvature="0" class="st13" d="M321.5,681.8L274.8,694l0.7,2.8c0.4,1.3,1.7,2.1,2.9,1.7
l42.1-11c1.3-0.4,2.1-1.7,1.7-2.9L321.5,681.8z"/>
<path id="path150" inkscape:connector-curvature="0" class="st16" d="M309.3,648.2l-40.5,10.5l-3.2-12.2c-1.8-6.7,2.3-13.6,9-15.4
l16-4.2c6.7-1.8,13.6,2.3,15.4,9L309.3,648.2z"/>
<line id="line152" class="st16" x1="281" y1="620.1" x2="283.1" y2="627.9"/>
<line id="line154" class="st16" x1="281.4" y1="637.9" x2="276.1" y2="650.3"/>
<line id="line156" class="st16" x1="294.6" y1="634.9" x2="289.3" y2="647.3"/>
<path id="path158" inkscape:connector-curvature="0" class="st17" d="M287.6,697.6l-2.3,10.1c-0.1,0.5,0.6,0.8,0.8,0.4l8.7-11.7
c0.3-0.4-0.1-0.8-0.5-0.7l-6.3,1.8C287.8,697.3,287.6,697.4,287.6,697.6L287.6,697.6z"/>
<path id="path160" inkscape:connector-curvature="0" class="st17" d="M278.3,699.9l-4.8,9.3c-0.3,0.5,0.4,0.9,0.7,0.6l10.9-10.9
c0.4-0.4,0-0.9-0.5-0.8l-6.1,1.6C278.5,699.8,278.4,699.8,278.3,699.9L278.3,699.9z"/>
<path id="path162" inkscape:connector-curvature="0" class="st17" d="M311.6,691.2l7,7.6c0.4,0.4-0.1,0.9-0.6,0.7l-13.3-6.1
c-0.4-0.2-0.4-0.7,0.1-0.9l6.3-1.6C311.3,691,311.5,691.1,311.6,691.2z"/>
<path id="path164" inkscape:connector-curvature="0" class="st17" d="M320.8,688.7l8.8,5.8c0.5,0.3,0.1,1-0.4,0.8l-14.9-4.2
c-0.5-0.1-0.5-0.8,0-0.9l6.1-1.6C320.6,688.6,320.7,688.6,320.8,688.7z"/>
</g>
<path id="path168" inkscape:connector-curvature="0" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st18" d="
M241.1,524.1h-1c0.4-1.2,0.6-2.4,0.6-3.7c0-7-5.7-12.7-12.7-12.7h-3.3c-2.7,0-5.2,0.9-7.2,2.3c-1.2-0.8-2.7-1.3-4.3-1.3
c-0.7,0-1.4,0.1-2.1,0.3c-0.1-3.9-3.3-7.1-7.2-7.1h-0.3c-2.2,0-4.1,1-5.4,2.5l0,0c2.7-3.8,4.4-8.5,4.4-13.6
c0-13-10.6-23.5-23.5-23.5c-13,0-23.5,10.6-23.5,23.5c0,0.7,0,1.3,0.1,1.9c-2.9,0.3-5.7,1.3-8,2.9c-3.2-4.3-8.2-7.1-13.9-7.1l0,0
c-9.5,0-17.3,7.8-17.3,17.3c0,0.1,0,0.3,0,0.4c-1.8-0.9-3.7-1.5-5.9-1.5c-6.9,0-12.6,5.7-12.6,12.6c0,0.7,0.1,1.5,0.2,2.2H92
c-3,0-5.5,1.9-6.5,4.5h-9.9c-2.5,0-4.5,2-4.5,4.5s2,4.5,4.5,4.5h149.2h3.3h13.1c2.5,0,4.5-2,4.5-4.5
C245.6,526.1,243.6,524.1,241.1,524.1L241.1,524.1z"/>
<path id="path170" inkscape:connector-curvature="0" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st18" d="
M898.4,516.3h-0.9c0.3-1,0.5-2.1,0.5-3.3c0-6.2-5.1-11.3-11.3-11.3h-2.9c-2.4,0-4.6,0.8-6.4,2c-1.1-0.7-2.4-1.1-3.8-1.1
c-0.6,0-1.2,0.1-1.8,0.3c-0.1-3.5-2.9-6.3-6.4-6.3H865c-1.9,0-3.6,0.9-4.8,2.2l0,0c2.4-3.4,3.9-7.6,3.9-12
c0-11.5-9.4-20.9-20.9-20.9s-20.9,9.4-20.9,20.9c0,0.6,0,1.2,0.1,1.7c-2.6,0.2-5,1.2-7.1,2.6c-2.8-3.8-7.3-6.3-12.3-6.3l0,0
c-8.5,0-15.4,6.9-15.4,15.4c0,0.1,0,0.2,0,0.4c-1.6-0.8-3.3-1.4-5.2-1.4c-6.2,0-11.2,5-11.2,11.2c0,0.7,0.1,1.3,0.2,1.9h-5.5
c-2.6,0-4.9,1.7-5.8,4h-8.8c-2.2,0-4,1.8-4,4s1.8,4,4,4h132.5h2.9h11.6c2.2,0,4-1.8,4-4C902.4,518.1,900.6,516.3,898.4,516.3
L898.4,516.3z"/>
<g id="g184" transform="translate(10.641067,-115.56078)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<g id="g178">
<path id="path172" inkscape:connector-curvature="0" class="st19" d="M149.5,586c2.4,4.6,6.2,8.3,10.8,10.8c3.6,1.9,7.6,3,11.9,3
c14.2,0,25.7-11.5,25.7-25.7c0-4.3-1.1-8.4-3-11.9c-2.4-4.6-6.2-8.3-10.8-10.8c-3.6-1.9-7.6-3-11.9-3c-14.2,0-25.7,11.5-25.7,25.7
C146.6,578.4,147.7,582.4,149.5,586z"/>
<path id="path174" inkscape:connector-curvature="0" class="st18" d="M194.1,562.5c-2.3-4.4-6-8.1-10.4-10.4
c-3.4-1.8-7.4-2.9-11.5-2.9c-9.5,0-17.7,5.3-21.8,13.1c4-2.8,8.9-4.4,14.1-4.4c4.2,0,8.1,1,11.5,2.9c4.4,2.3,8.1,6,10.4,10.4
c1.8,3.4,2.9,7.4,2.9,11.5c0,4.2-1.1,8.2-2.9,11.7c6.4-4.5,10.6-11.9,10.6-20.3C197,569.8,196,565.9,194.1,562.5L194.1,562.5z"/>
<path id="path176" inkscape:connector-curvature="0" class="st20" d="M149.5,586c-7.7,10-11.6,17.8-9.3,20.1s10.1-1.6,20.1-9.3
c5.6-4.4,12-9.9,18.3-16.3c6.4-6.4,11.9-12.7,16.3-18.3c7.7-10,11.6-17.8,9.3-20.1s-10.1,1.6-20.1,9.3"/>
</g>
<path id="path180" inkscape:connector-curvature="0" class="st21" d="M154,566.3c0.5-1.2,1.1-2.3,1.8-3.4c0.7-1.1,1.5-2,2.4-2.9
s1.9-1.7,2.9-2.4c1.1-0.7,2.2-1.3,3.4-1.8s2.4-0.9,3.7-1.2s2.6-0.4,4-0.4"/>
<path id="path182" inkscape:connector-curvature="0" class="st21" d="M152.4,574.1c0-1.4,0.1-2.7,0.4-4"/>
</g>
<g id="g192" transform="translate(-0.2304,235.22748)" inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<polygon id="polygon186" class="st19" points="843.4,216.1 850,204.1 856.6,216.1 868.7,222.7 856.6,229.3 850,241.4 843.4,229.3
831.4,222.7 "/>
<polygon id="polygon188" class="st19" points="868.4,248.1 873.4,239.1 878.3,248.1 887.4,253.1 878.3,258 873.4,267.1 868.4,258
859.4,253.1 "/>
<polygon id="polygon190" class="st19" points="884.1,207.8 887.4,201.7 890.7,207.8 896.7,211.1 890.7,214.4 887.4,220.4
884.1,214.4 878,211.1 "/>
</g>
<g inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476" class="st22">
<path class="st23" d="M332.8,346.5l65.5-89.9h24.1v89.6h17.5v20.8h-17.5v28.8h-27.9v-28.8h-61.7V346.5z M358.9,346.1h35.6v-34.4
c0-5.9,0.2-11.6,0.8-17h-0.9c-4.9,7.8-8,12.7-9.4,14.6L358.9,346.1z"/>
<path class="st23" d="M450.4,326.2c0-11.1,1.1-21,3.2-29.6c2.1-8.6,4.9-15.5,8.2-20.7c3.3-5.2,7.3-9.4,11.9-12.8
c4.6-3.3,9.1-5.6,13.5-6.8c4.4-1.2,9-1.8,13.7-1.8c16.3,0,28.8,6.4,37.5,19.1c8.7,12.7,13.1,30.3,13.1,52.6
c0,22.1-4.4,39.6-13.1,52.4c-8.7,12.8-21.2,19.2-37.4,19.2c-4.5,0-8.9-0.6-13.2-1.7c-4.3-1.1-8.7-3.3-13.4-6.6
c-4.6-3.2-8.7-7.4-12.1-12.5c-3.5-5.1-6.3-12-8.6-20.8C451.5,347.6,450.4,337.6,450.4,326.2z M479.4,326.2
c0,33.8,7.2,50.7,21.6,50.7c14.2,0,21.3-16.9,21.3-50.7c0-33.8-7.2-50.7-21.5-50.7C486.5,275.5,479.4,292.4,479.4,326.2z"/>
<path class="st23" d="M562.4,346.5l65.5-89.9h24.1v89.6h17.5v20.8h-17.5v28.8h-27.8v-28.8h-61.7V346.5z M588.5,346.1h35.6v-34.4
c0-5.9,0.2-11.6,0.8-17h-0.9c-4.9,7.8-8,12.7-9.4,14.6L588.5,346.1z"/>
</g>
<g inkscape:export-xdpi="96.009476" inkscape:export-ydpi="96.009476">
<path class="st23" d="M435.8,197.2c0-6.4,1.6-11.6,4.9-15.5c3.2-3.9,7.5-5.9,12.8-5.9c5.3,0,9.6,2,12.8,5.9
c3.2,3.9,4.8,9.1,4.8,15.5c0,6.4-1.6,11.6-4.8,15.4c-3.2,3.9-7.5,5.8-12.8,5.8c-5.3,0-9.6-1.9-12.8-5.8
C437.5,208.8,435.8,203.6,435.8,197.2z M443.6,197.2c0,4.6,0.9,8.2,2.6,10.8c1.7,2.7,4.2,4,7.3,4c3.1,0,5.5-1.3,7.2-4
c1.7-2.7,2.6-6.3,2.6-10.9c0-4.6-0.9-8.2-2.6-10.8c-1.7-2.7-4.1-4-7.2-4c-3.1,0-5.5,1.3-7.2,4C444.5,189,443.6,192.7,443.6,197.2z"
/>
<path class="st23" d="M474.8,202.7c0-4.4,1.2-8.2,3.5-11.2c2.3-3.1,5.8-4.6,10.4-4.6c3.1,0,5.7,0.8,7.9,2.3
c2.2,1.6,3.7,3.5,4.6,5.8c0.9,2.3,1.4,4.9,1.4,7.7c0,1.4-0.1,2.7-0.4,4c-0.2,1.3-0.7,2.7-1.3,4.2c-0.7,1.5-1.5,2.7-2.5,3.8
c-1,1.1-2.3,2-4,2.7c-1.7,0.7-3.6,1.1-5.7,1.1c-2.1,0-4-0.3-5.6-1c-1.7-0.7-3-1.5-4-2.6c-1-1.1-1.8-2.3-2.5-3.7
c-0.7-1.4-1.1-2.8-1.4-4.2C475,205.6,474.8,204.1,474.8,202.7z M482.3,202.7c0,3.4,0.6,5.8,1.9,7.3c1.3,1.5,2.8,2.3,4.5,2.3
c1.7,0,3.1-0.8,4.5-2.3c1.3-1.5,2-4,2-7.3c0-3.4-0.7-5.8-2-7.4c-1.3-1.5-2.8-2.3-4.5-2.3c-1.7,0-3.2,0.8-4.5,2.3
C482.9,196.8,482.3,199.3,482.3,202.7z"/>
<path class="st23" d="M507.9,229.5v-41.8h6.9v3.3c1.9-2.8,4.3-4.3,7.4-4.3c3.6,0,6.7,1.4,9.1,4.3c2.4,2.8,3.6,6.7,3.6,11.5
c0,2.8-0.4,5.2-1.2,7.3c-0.8,2.1-1.8,3.8-3.1,5c-1.3,1.2-2.6,2.1-4.1,2.7c-1.4,0.6-2.9,0.9-4.4,0.9c-1.8,0-3.3-0.4-4.5-1.2
c-1.2-0.8-2.1-1.7-2.7-2.7v14.9H507.9z M515,202.6c0,3,0.5,5.3,1.6,7.1c1.1,1.7,2.6,2.6,4.6,2.6c1.9,0,3.4-0.8,4.6-2.5
c1.1-1.7,1.7-4.1,1.7-7.2c0-3-0.6-5.3-1.7-7c-1.1-1.7-2.7-2.6-4.6-2.6c-2,0-3.5,0.9-4.6,2.7C515.6,197.4,515,199.8,515,202.6z"/>
<path class="st23" d="M537.8,211.3l5.1-3c2,3,4.6,4.5,7.9,4.5c1.5,0,2.6-0.3,3.4-0.9c0.8-0.6,1.2-1.4,1.2-2.3c0-0.3,0-0.6-0.1-0.8
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.4-0.6-0.6c-0.2-0.2-0.4-0.4-0.8-0.6c-0.4-0.2-0.7-0.4-0.9-0.5c-0.2-0.1-0.6-0.3-1.1-0.5
c-0.5-0.2-0.9-0.3-1.2-0.4c-0.3-0.1-0.7-0.2-1.3-0.4c-0.6-0.2-1-0.3-1.3-0.4c-2.6-0.8-4.6-1.9-6.2-3.2c-1.6-1.3-2.3-3.2-2.3-5.7
c0-2.7,1.1-4.8,3.4-6.4c2.3-1.6,5-2.4,8.3-2.4c2.5,0,4.8,0.6,7,1.9c2.2,1.3,3.8,3,4.9,5l-4.8,2.9c-2.1-2.7-4.5-4-7.1-4
c-1.4,0-2.5,0.3-3.2,0.8c-0.8,0.6-1.1,1.3-1.1,2.2c0,0.3,0,0.6,0.1,0.9c0.1,0.3,0.2,0.5,0.4,0.8c0.2,0.2,0.4,0.4,0.6,0.6
c0.2,0.2,0.5,0.4,0.8,0.6c0.4,0.2,0.7,0.4,0.9,0.5c0.3,0.1,0.6,0.3,1.1,0.4c0.5,0.2,0.8,0.3,1.1,0.4c0.3,0.1,0.7,0.2,1.2,0.4
c0.5,0.2,0.9,0.3,1.2,0.4c5.9,2,8.9,4.9,8.9,8.9c0,2.5-1,4.6-3.1,6.4c-2.1,1.7-5,2.6-8.8,2.6c-2.9,0-5.6-0.7-7.9-2
S539.1,213.4,537.8,211.3z"/>
<path class="st23" d="M568.2,217.7v-8.3h9.3v8.3H568.2z M576.4,205.9h-7l-0.5-29.4h8L576.4,205.9z"/>
</g>
<g>
<path class="st23" d="M333.4,464.8v-27.1h12c3.2,0,5.7,0.8,7.5,2.3c1.8,1.5,2.7,3.6,2.7,6.1c0,2.6-0.9,4.6-2.8,6.1
s-4.3,2.2-7.4,2.2H339v10.3H333.4z M339,450.2h5.7c1.6,0,2.9-0.4,3.8-1.1c0.9-0.7,1.4-1.7,1.4-3c0-1.3-0.4-2.3-1.3-3
c-0.9-0.7-2.2-1.1-3.8-1.1H339V450.2z"/>
<path class="st23" d="M354.1,464.8l10.8-27.1h6l10.8,27.1h-5.9l-2.3-6.4h-11.2l-2.3,6.4H354.1z M363.7,454.4h8.3l-2.2-6.4
c-0.5-1.5-1.1-3.3-1.8-5.5h-0.2c-0.2,0.7-0.5,1.6-0.9,2.8c-0.4,1.2-0.7,2.1-0.9,2.6L363.7,454.4z"/>
<path class="st23" d="M383,451.2c0-4.1,1.3-7.5,3.8-10.2c2.5-2.6,5.9-4,10-4c1.3,0,2.5,0.2,3.7,0.5c1.2,0.3,2.1,0.7,2.9,1.2
c0.8,0.5,1.5,1,2.2,1.7c0.7,0.6,1.2,1.2,1.5,1.7c0.4,0.5,0.7,1,0.9,1.5l-4.8,1.4c-0.4-0.5-0.8-1-1.1-1.3c-0.3-0.3-0.7-0.7-1.2-1.2
s-1.1-0.8-1.8-1s-1.4-0.3-2.3-0.3c-2.5,0-4.5,0.9-5.9,2.7c-1.4,1.8-2.1,4.2-2.1,7.2c0,3,0.7,5.4,2.2,7.2c1.5,1.8,3.3,2.7,5.6,2.7
c2,0,3.5-0.5,4.6-1.4c1.1-1,1.8-2.2,2-3.8c0.1-1.3,0.2-2,0.2-2.1h-7.1v-4.3h12.5v15.3H405l-0.5-2.4c-1.7,2-4.4,3-8,3
c-3.8,0-7-1.3-9.6-3.8C384.3,459,383,455.5,383,451.2z"/>
<path class="st23" d="M413.9,464.8v-27.1H434v4.3h-14.6v6.7h13.1v4.2h-13.1v7.6h14.9v4.3H413.9z"/>
<path class="st23" d="M448.1,464.8v-27.1h5.8l8.5,14.1l3.3,5.5h0.2c-0.4-2.6-0.6-5.2-0.6-7.9v-11.7h5.6v27.1h-5.8l-8.5-13.8
l-3.3-5.7h-0.2c0.4,2.5,0.6,5.1,0.6,7.9v11.6H448.1z"/>
<path class="st23" d="M475.4,451.3c0-4.3,1.2-7.7,3.6-10.3c2.4-2.6,5.6-3.9,9.5-3.9c4,0,7.1,1.3,9.5,3.9c2.4,2.6,3.6,6,3.6,10.3
c0,4.3-1.2,7.7-3.6,10.3c-2.4,2.6-5.6,3.9-9.5,3.9s-7.1-1.3-9.5-3.9C476.6,458.9,475.4,455.5,475.4,451.3z M481.2,451.3
c0,3,0.6,5.4,1.9,7.2c1.3,1.8,3.1,2.7,5.4,2.7c2.3,0,4.1-0.9,5.4-2.6c1.3-1.8,1.9-4.2,1.9-7.2c0-3-0.6-5.4-1.9-7.2
c-1.3-1.8-3.1-2.7-5.4-2.7c-2.3,0-4.1,0.9-5.4,2.7C481.9,445.8,481.2,448.2,481.2,451.3z"/>
<path class="st23" d="M502.7,442v-4.3h22.7v4.3h-8.6v22.8h-5.6V442H502.7z"/>
<path class="st23" d="M538.3,464.8v-27.1h19.6v4.3h-13.9v7.1h12.5v4.2h-12.5v11.4H538.3z"/>
<path class="st23" d="M559.9,451.3c0-4.3,1.2-7.7,3.6-10.3c2.4-2.6,5.6-3.9,9.5-3.9c4,0,7.1,1.3,9.5,3.9c2.4,2.6,3.6,6,3.6,10.3
c0,4.3-1.2,7.7-3.6,10.3c-2.4,2.6-5.6,3.9-9.5,3.9c-4,0-7.1-1.3-9.5-3.9C561.1,458.9,559.9,455.5,559.9,451.3z M565.7,451.3
c0,3,0.6,5.4,1.9,7.2c1.3,1.8,3.1,2.7,5.4,2.7c2.3,0,4.1-0.9,5.4-2.6c1.3-1.8,1.9-4.2,1.9-7.2c0-3-0.6-5.4-1.9-7.2
c-1.3-1.8-3.1-2.7-5.4-2.7c-2.3,0-4.1,0.9-5.4,2.7C566.3,445.8,565.7,448.2,565.7,451.3z"/>
<path class="st23" d="M590.5,454.6v-16.9h5.6v16.9c0,4.4,1.9,6.5,5.7,6.5c3.8,0,5.7-2.2,5.7-6.5v-16.9h5.6v16.9
c0,3.5-0.9,6.2-2.8,8c-1.9,1.9-4.7,2.8-8.5,2.8c-3.6,0-6.4-0.9-8.4-2.7S590.5,458.2,590.5,454.6z"/>
<path class="st23" d="M619.3,464.8v-27.1h5.8l8.5,14.1l3.3,5.5h0.2c-0.4-2.6-0.6-5.2-0.6-7.9v-11.7h5.6v27.1h-5.8l-8.5-13.8
l-3.3-5.7h-0.2c0.4,2.5,0.6,5.1,0.6,7.9v11.6H619.3z"/>
<path class="st23" d="M648.3,464.8v-27.1h9.2c4.6,0,8.2,1.2,10.7,3.5s3.7,5.7,3.7,10c0,1.4-0.1,2.8-0.4,4c-0.3,1.3-0.8,2.5-1.4,3.7
c-0.7,1.2-1.6,2.2-2.6,3c-1.1,0.8-2.4,1.5-4.1,2c-1.7,0.5-3.6,0.8-5.8,0.8H648.3z M653.9,460.5h3c3,0,5.3-0.7,6.8-2.2
c1.5-1.5,2.3-3.8,2.3-7.1c0-3.3-0.8-5.7-2.4-7.2s-3.8-2.1-6.7-2.1h-3.1V460.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

37
docs/_static/diag_frame.diag vendored Normal file
View File

@@ -0,0 +1,37 @@
# Modbus float_abcd frame structure diagram
blockdiag mb_float_frame {
# global properties
span_width = 5;
span_height = 5;
node_height = 30;
default_fontsize = 15;
default_group_color = lightgrey;
class spacer [shape=none, width=10];
# tuning node properties and connections
0,1,2 [class=spacer];
0; note
1; header
2; response -- uid -- cmd -- len -- fl_abcd -- crc
group float_abcd_packet {
label = "PDU";
color = gray;
shape = line;
style = dashed;
group{uid,resp_uid};group{cmd,resp_cmd};group{len,resp_len};group{crc,resp_crc};
group float_abcd{
color = blue;
shape = line;
style = dashed;
fl_abcd;dt_abcd;
}
}
note[label="1: Unit Identificator, 2: Function code, 3: Data length, 4: Float data array, 5: Checksum",colwidth=6,color=lightyellow,shape=roundedbox]
header[label="FLOAT_ABCD = 0x4640e400 = 12345.0",colwidth=6,color=lightgreen]
response[label="RX:",color=yellow];
uid[label="UID",numbered=1];cmd[label="FC",numbered=2];
len[label="LENGTH",numbered=3];crc[label="CRC",numbered=5];
resp_uid[label="0x01"];resp_cmd[label="0x03"];resp_len[label="0x08"];resp_crc[label="0x9065"];
fl_abcd[label="FLOAT_ABCD",color=lightgreen,numbered=4];
dt_abcd[label="0xE4004640",shape=note];
}

BIN
docs/_static/modbus-data-mapping.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/_static/modbus-segment.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

22
docs/_static/modbus_docs_versions.js vendored Normal file
View File

@@ -0,0 +1,22 @@
var DOCUMENTATION_VERSIONS = {
DEFAULTS: { has_targets: true,
supported_targets: [ "esp32", "esp32s2", "esp32s3", "esp32c2","esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32s2", "esp32s3, esp32p4" ]
},
VERSIONS: [
{ name: "latest" },
{ name: "v1.0.1", old:true },
{ name: "v2.0.0", old:false }
],
IDF_TARGETS: [
{ text: "ESP32", value: "esp32"},
{ text: "ESP32-S2", value: "esp32s2"},
{ text: "ESP32-S3", value: "esp32s3"},
{ text: "ESP32-C2", value: "esp32c2"},
{ text: "ESP32-C3", value: "esp32c3"},
{ text: "ESP32-C6", value: "esp32c6"},
{ text: "ESP32-H2", value: "esp32h2"},
{ text: "ESP32-P4", value: "esp32p4"},
{ text: "ESP32-C5", value: "esp32c5"},
{ text: "ESP32-C61", value: "esp32c61"}
]
};

61
docs/_static/modbus_frame_examples.diag vendored Normal file
View File

@@ -0,0 +1,61 @@
# Modbus frame packaging examples
blockdiag mb_master_frames {
# global properties
span_width = 5;
span_height = 5;
node_height = 30;
default_group_color = lightgrey;
default_fontsize = 15;
# tuning node properties and connections
group 16bit_packets {
label = "16bit frame";
color = red;
shape = line;
style = dashed;
16bit_notes;
}
group 32bit_packets {
label = "32bit frame";
color = green;
shape = line;
style = dashed;
group{32bit_notes};
}
group 64bit_packets {
label = "64bit frame";
color = blue;
shape = line;
style = dashed;
64bit_notes;
}
16bit_notes[label="(UINT16, INT16) VALUE = 0x3039 = 12345", width=600, color=orange, shape = roundedbox];
req_u16_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=520, colwidth=2, shape = roundedbox ,group=16bit_packets];
req_u16_frm1[label="TX:| 01 | 03 | 00 04 | 00 02 | 85 CA |", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
rsp_u16_hd1[label= "RX:| UID | FC | LEN | UINT16_AB1 | UINT16_AB2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=16bit_packets];
rsp_u16_frm1[label="RX:| 01 | 03 | 04 | 30 39 | 30 39 | F1 2C |", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
rsp_u16_hd2[label= "RX:| UID | FC | LEN | UINT16_BA1 | UINT16_BA2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=16bit_packets];
rsp_u16_frm2[label="RX:| 01 | 03 | 04 | 39 30 | 39 30 | E4 E4 |\n", color=lightgrey, width=540, colwidth=2,group=16bit_packets];
32bit_notes[label="(UINT32, INT32, FLOAT32) VALUE = 0x4640e400 = 12345.0", width=600, color=lightgreen, shape = roundedbox];
req_fl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=32bit_packets];
req_fl_frm1[label="TX:| 01 | 03 | 00 XX | 00 04 | C5 CB |", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
rsp_fl_hd1[label= "RX:| UID | FC | LEN | FLOAT_ABCD1 | FLOAT_ABCD2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=32bit_packets];
rsp_fl_frm1[label="RX:| 01 | 03 | 08 | E4 00 46 40 | E4 00 46 40 | 90 65 |", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
rsp_fl_hd2[label= "RX:| UID | FC | LEN | FLOAT_CDAB1 | FLOAT_CDAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm2[label="RX:| 01 | 03 | 08 | 46 40 E4 00 | 46 40 E4 00 | 18 71 |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
rsp_fl_hd3[label= "RX:| UID | FC | LEN | FLOAT_BADC1 | FLOAT_BADC2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm3[label="RX:| 01 | 03 | 08 | 00 E4 40 46 | 00 E4 40 46 | 46 D3 |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
rsp_fl_hd4[label= "RX:| UID | FC | LEN | FLOAT_DCAB1 | FLOAT_DCAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=32bit_packets];
rsp_fl_frm4[label="RX:| 01 | 03 | 08 | 40 46 00 E4 | 40 46 00 E4 | 32 6B |\n", color=lightgrey, width=540, colwidth=2,group=32bit_packets];
64bit_notes[label="(UINT64, INT64, FLOAT64) VALUE = 0x40c81c8000000000 = 12345.0", width=600, color=lightblue, shape = roundedbox];
req_dbl_hd1[label= "TX:| UID | FC | REG_START | REG_LEN | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=64bit_packets];
req_dbl_frm1[label="TX:| 01 | 03 | 00 28 | 00 08 | C4 04 |", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
rsp_dbl_hd1[label= "RX:| UID | FC | LEN | DOUBLE_ABCDEFGH1 | DOUBLE_ABCDEFGH2 | CRC |", color=lightyellow, width=540, colwidth=2, shape = roundedbox ,group=64bit_packets];
rsp_dbl_frm1[label="RX:| 01 | 03 | 10 | 00 00 00 00 1C 80 40 C8 | 00 00 00 00 1C 80 40 C8 | 9F 4B |", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
rsp_dbl_hd2[label= "RX:| UID | FC | LEN | DOUBLE_HGFEDCBA1 | DOUBLE_HGFEDCBA2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm2[label="RX:| 01 | 03 | 10 | C8 40 80 1C 00 00 00 00 | C8 40 80 1C 00 00 00 00 | DF D3 |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
rsp_dbl_hd3[label= "RX:| UID | FC | LEN | DOUBLE_GHEFCDAB1 | DOUBLE_GHEFCDAB2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm3[label="RX:| 01 | 03 | 10 | 40 C8 1C 80 00 00 00 00 | 40 C8 1C 80 00 00 00 00 | B1 9C |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
rsp_dbl_hd4[label= "RX:| UID | FC | LEN | DOUBLE_BADCFEHG1 | DOUBLE_BADCFEHG2 | CRC |\n ", color=lightyellow, width=540, colwidth=2, shape = roundedbox, group=64bit_packets];
rsp_dbl_frm4[label="RX:| 01 | 03 | 10 | 00 00 00 00 80 1C C8 40 | 00 00 00 00 80 1C C8 40 | 86 94 |\n", color=lightgrey, width=540, colwidth=2,group=64bit_packets];
}

33
docs/conf_common.py Normal file
View File

@@ -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' ]
languages = ['en']

View File

@@ -0,0 +1,78 @@
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 demonstrate the 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 <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_slave>`__
.. _example_mb_master:
- `Modbus serial master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/serial/mb_serial_master>`__
.. _example_mb_tcp_master:
- `Modbus TCP master example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_slave>`__
.. _example_mb_tcp_slave:
- `Modbus TCP slave example <https://github.com/espressif/esp-modbus/tree/release/v2.0/examples/tcp/mb_tcp_master>`__
Please refer to the specific example README.md for details.
.. _modbus_organization:
Protocol References
-------------------
- `Modbus Organization with protocol specifications <https://modbus.org/specs.php>`__
API Reference
-------------
.. include-build-file:: inc/esp_modbus_common.inc
.. include-build-file:: inc/esp_modbus_master.inc
.. include-build-file:: inc/esp_modbus_slave.inc
.. _modbus_api_endianness_conversion:
Modbus Endianness Conversion API Reference
------------------------------------------
.. include-build-file:: inc/mb_endianness_utils.inc

27
docs/en/conf.py Normal file
View File

@@ -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'

16
docs/en/index.rst Normal file
View File

@@ -0,0 +1,16 @@
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 <overview_messaging_and_mapping>
Modbus Port Initialization <port_initialization>
Modbus Master API <master_api_overview>
Modbus Slave API <slave_api_overview>
Applications and References <applications_and_references>
.. note:: The ESP-Modbus library starting from version v2.0.0 supports creation of several instances of Modbus master and slave objects. The instance of each type shall be created using appropriate constructor API which uses the pointer to predefined object configuration options as first parameter and returns the pointer to created communication object as a second parameter. The returned pointer shall be saved and later be used as instance identifier in first parameter of each call of Modbus API functions.

View File

@@ -0,0 +1,415 @@
.. _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 (two bytes).
* - ``param_offset``
- Instance Offset
- Offset to instance of the characteristic in bytes. It is used to calculate the absolute address to the characteristic in the storage structure.
It is optional field and can be set to zero if the parameter is not used in the application.
* - ``param_type``
- Data Type
- Specifies type of the characteristic. Possible types are described in the section :ref:`modbus_mapping_complex_data_types`.
* - ``param_size``
- Data Size
- The storage size of the characteristic (in bytes) describes the size of data to keep into data instance during mapping. For the :ref:`modbus_mapping_complex_data_types` this allows to define the data container of the corresponded type.
* - ``param_opts``
- Parameter Options
- Limits, options of characteristic used during processing of alarm in user application (optional)
* - ``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.
Examples Of Mapping
@@@@@@@@@@@@@@@@@@@
Please refer to section :ref:`modbus_mapping_complex_data_types` for more information about used data types.
Example 1: Configure access to legacy parameter types is described below.
.. list-table:: Table 2 Example Register mapping table of Modbus slave
:widths: 5 5 2 10 5 5 68
:header-rows: 1
* - 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.
* - 3
- 40002
- 16
- 1..100 bytes
- ASCII or binary array
- Not defined
- Device name (16 bytes) ASCII string. The type of `PARAM_TYPE_ASCII` allows to read/write complex parameter (string or binary data) that corresponds to one CID.
.. code:: c
// Enumeration of modbus slave addresses accessed by master device
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_DEV_NAME1,
CID_TEMP_DATA_1,
CID_SER_NUM2,
CID_SW_VER2,
CID_DEV_NAME2,
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_DEV_NAME1, STR("Device name"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 8,
0, PARAM_TYPE_ASCII, 16, 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_DEV_NAME2, STR("Device name"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 8,
0, PARAM_TYPE_ASCII, 16, 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]));
Example 2: Configure access using extended parameter types for third-party devices.
.. list-table:: Table 3 Example Register mapping table of Modbus slave
:widths: 2 4 2 10 3 68
:header-rows: 1
* - CID
- Register
- Length
- Range
- Units
- Description
* - 0
- 40000
- 4
- 0 ... 255
- No units
- :cpp:enumerator:`PARAM_TYPE_U8_A` - unsigned integer 8-bit
* - 1
- 40002
- 4
- 0 ... 65535
- No Units
- :cpp:enumerator:`PARAM_TYPE_U16_AB` uinsigned integer 16-bit
* - 3
- 40004
- 8
- 0 ... Unsigned integer 32-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_U32_ABCD` - unsigned integer 32-bit in ABCD format
* - 4
- 40008
- 8
- 0 ... Unsigned integer 32-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB` - FLOAT 32-bit value in CDAB format
* - 5
- 400012
- 16
- 0 ... Unsigned integer 64-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH` - Unsigned integer 64-bit value in ABCDEFGH format
* - 6
- 400020
- 16
- 0 ... Unsigned integer 64-bit range
- No units
- :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA` - Double precision 64-bit value in HGFEDCBA format
.. code:: c
#include "limits.h"
#include "mbcontroller.h"
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
#define HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
#define HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
#pragma pack(push, 1)
// Example structure that contains parameter arrays of different types
// with different options of endianness.
typedef struct
{
uint16_t holding_u8_a[2];
uint16_t holding_u16_ab[2];
uint32_t holding_uint32_abcd[2];
float holding_float_cdab[2];
double holding_uint64_abcdefgh[2];
double holding_double_hgfedcba[2];
} holding_reg_params_t;
#pragma pack(pop)
// Enumeration of modbus slave addresses accessed by master device
enum {
MB_DEVICE_ADDR1 = 1, // Short address of Modbus slave device
MB_SLAVE_COUNT
};
// Enumeration of all supported CIDs for device (used in parameter definition table)
enum {
CID_HOLD_U8_A = 0,
CID_HOLD_U16_AB,
CID_HOLD_UINT32_ABCD,
CID_HOLD_FLOAT_CDAB,
CID_HOLD_UINT64_ABCDEFGH,
CID_HOLD_DOUBLE_HGFEDCBA,
CID_COUNT
};
// Example Data Dictionary for to address parameters from slaves with different options of endianness
mb_parameter_descriptor_t device_parameters[] = {
// CID, Name, Units, Modbus addr, register type, Modbus Reg Start Addr, Modbus Reg read length,
// Instance offset (NA), Instance type, Instance length (bytes), Options (NA), Permissions
{ CID_HOLD_U8_A, STR("U8_A"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_u8_a), HOLD_REG_SIZE(holding_u8_a),
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (HOLD_REG_SIZE(holding_u8_a) << 1),
OPTS( 0, UCHAR_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_AB, STR("U16_AB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_u16_ab), HOLD_REG_SIZE(holding_u16_ab),
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, USHRT_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_uint32_abcd), HOLD_REG_SIZE(holding_uint32_abcd),
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (HOLD_REG_SIZE(holding_uint32_abcd) << 1),
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_float_cdab), HOLD_REG_SIZE(holding_float_cdab),
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (HOLD_REG_SIZE(holding_float_cdab) << 1),
OPTS( 0, ULONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT64_ABCDEFGH, STR("UINT64_ABCDEFGH"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_uint64_abcdefgh), HOLD_REG_SIZE(holding_uint64_abcdefgh),
HOLD_OFFSET(holding_uint64_abcdefgh), PARAM_TYPE_UINT64_ABCDEFGH, (HOLD_REG_SIZE(holding_uint64_abcdefgh) << 1),
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("--"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
HOLD_REG_START(holding_double_hgfedcba), HOLD_REG_SIZE(holding_double_hgfedcba),
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
OPTS( 0, ULLONG_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
};
uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
The example above describes the definition of just several extended types. The types described in the :ref:`modbus_mapping_complex_data_types` allow to address the most useful value formats from devices of known third-party vendors.
Once the type of characteristic is defined in data dictionary the stack is responsible for conversion of values to/from the corresponding type option into the format recognizable by compiler.
.. note:: Please refer to your vendor device manual and its mapping table to select the types suitable for your device.
The Modbus stack contains also the :ref:`modbus_api_endianness_conversion` - endianness conversion API functions that allow to convert values from/to each extended type into compiler representation.
During initialization of the Modbus stack, a pointer to the Data Dictionary (called descriptor) must be provided as the parameter of the function below.
:cpp:func:`mbc_master_set_descriptor`:
Initialization of master descriptor. The descriptor represents an array of type :cpp:type:`mb_parameter_descriptor_t` and describes all the characteristics accessed by master.
.. code:: c
static void *master_handle = NULL; // Must exist in the module and be initialized prior to call
....
// Set master data dictionary for initialized master instance - master_handle
ESP_ERROR_CHECK(mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters));
The Data Dictionary can be initialized from SD card, MQTT or other source before start of stack. Once the initialization and setup is done, the Modbus controller allows the reading of complex parameters from any slave included in descriptor table using its CID.
Refer to :ref:`example TCP master <example_mb_tcp_master>`, :ref:`example Serial master <example_mb_master>` for more information.
.. _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
static void *master_handle = NULL; // Pointer to allocated interface structure
....
esp_err_t err = mbc_master_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "mb controller start fail, err = 0x%x.", (int)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.
.. note:: The function can be used to form the custom request with non-standard commands to resolve compatibility issues with the custom slaves. If it is not the case the regular API should be used: :cpp:func:`mbc_master_set_parameter`, :cpp:func:`mbc_master_get_parameter`.
: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.
:cpp:func:`mbc_master_get_parameter_with`
The function allows to read the data of a characteristic from any slave device addressed by `uid` parameter of the function instead of slave address defined in the data dictionary. In this case the ``mb_slave_addr`` field of the parameter descriptor :cpp:type:`mb_parameter_descriptor_t` shall be equal to ``MB_SLAVE_ADDR_PLACEHOLDER``. In case of TCP type of communication the connection phase should be completed prior call of this function.
Example:
.. code:: c
static void *master_handle = NULL;
....
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, &param_descriptor);
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
err = mbc_master_get_parameter(master_handle, param_descriptor->cid, (uint8_t*)temp_data, &type);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
param_descriptor->cid,
param_descriptor->param_key,
param_descriptor->param_units,
*(uint32_t*)temp_data);
} else {
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
param_descriptor->cid,
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 `cid` parameter in corresponded slave device. The additional data for parameter request is taken from master parameter description table.
:cpp:func:`mbc_master_set_parameter_with`
The function is similar to previous function but allows to set the data of a characteristic in any slave device addressed by `uid` parameter of the function instead of the slave address ``mb_slave_addr`` field defined in the data dictionary. The corresponded ``mb_slave_addr`` field for the characteristic in the object disctionary shall be defined as ``MB_SLAVE_ADDR_PLACEHOLDER``.
.. note:: When the TCP mode of communication is used the functions above additionally check the connection state of the slave being accessed and return error if the slave connection is not actual.
.. code:: c
static void *master_handle = NULL;
....
uint8_t type = 0; // Type of parameter
uint8_t temp_data[4] = {0}; // temporary buffer
// Read the characteristic from slave and save the data to temp_data instance
esp_err_t err = mbc_master_set_parameter(master_handle, CID_TEMP_DATA_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
// Pointer to allocated interface structure, must be intitialized by constructor
static void *master_handle = NULL;
...
ESP_ERROR_CHECK(mbc_master_destroy(master_handle));

View File

@@ -0,0 +1,283 @@
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`.
.. _modbus_supported_communication_options:
Modbus Supported Communication Options
--------------------------------------
The Modbus library supports the standard communication options as per Modbus specification stated below.
.. list-table:: Standard Modbus communication options
:widths: 10 90
:header-rows: 1
* - Modbus option
- Description of the option
* - RTU communication
- * 1 start bit
* 8 data bits, least significant bit sent first
* 1 bit for even / odd parity-no bit for no parity
* 1 stop bit if parity is used, 2 stop bits if no parity
* Cyclical Redundancy Check (CRC)
* - ASCII communication
- * 1 start bit
* 7-8 data bits, least significant bit sent first
* 1 bit for even / odd parity-no bit for no parity
* 1 stop bit if parity is used, 2 stop bits if no parity
* Longitudinal Redundancy Check (LRC)
* - TCP communication
- * Communications between client (master) - server (slave) over TCP/IP networks
* Connection uses the standard port 502
* The frames do not require checksum calculation (provided by lower layers)
Some vendors may use subset of communication options. In this case the detailed information is clarified in the device manual and it is possible to override the standard communication options for support of such devices.
Please refer to :ref:`modbus_api_slave_setup_communication_options`, :ref:`modbus_api_master_setup_communication_options` for more information.
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
.. _modbus_mapping_complex_data_types:
Mapping Of Complex Data Types
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
As per section 4.2 of Modbus specification, "MODBUS uses a ``big-Endian`` representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first". The biggest official structure defined by the Modbus specification is a 16-bit word register, which is 2 bytes. However, vendors sometimes group two or even four 16-bit registers together to be interpretted as 32-bit or 64-bit values, respectively. It is also possible when the Modbus vendors group many registers together for serial numbers, text strings, time/date, etc. Regardless of how the vendor intends the data to be interpreted, the Modbus protocol itself simply transfers 16-bit word registers. These values grouped from registers may use either little-endian or big-endian register order.
.. note:: Each individual 16-bit register, is encoded in big-endian order (assuming the Modbus device abides by the Modbus specification). However, the 32-bit and 64-bit types naming conventions like ABCD or ABCDEFGH, does not take into account the network format byte order of frame. For example: the ABCD prefix for 32-bit values means the common Modbus mapping format and corresponds to the CDAB on network format (order in the frame).
Common Data Types Supported By Modbus Vendors
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.. list-table:: Table 1 basic types used by Modbus vendors
:widths: 8 3 20
:header-rows: 1
* - Type
- Range
- Format description
* - U8, I8 - Unsigned/Signed 8-bit type
- (0 .. 255)/(-128 .. 127)
- Common unsigned 8-bit type that is stored usually in one Modbus register. The value can be stored in HI or LO byte of the register or packed with the next byte into one 16 - bit register.
* - U16 - Unsigned integer 16-bit type
- 0 - 65535
- Stored in one 16-bit register. The values can be stored with AB or BA endianness.
* - I16 - Signed integer 16-bit type
- -32768 to 32767 is allowed.
- Stored in one 16-bit register. The values can be stored with AB or BA forendiannessmat.
* - I32 - Signed long integer 32-bit type
- -2147483648 to 2147483647 is allowed.
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness (see below).
* - U32 - Unsigned long integer 32-bit type
- 0 to 4294967295 is allowed.
- Stored in two consecutive 16-bit register. The values can be stored with ABCD - DCBA endianness.
* - U64 Unsigned Long long integers (Unsigned integer 64)
- 0 to 18446744073709551615 is allowed.
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
* - I64 Signed Long long integers (Signed integer 64)
- -9223372036854775808 to 9223372036854775807 is allowed.
- Stored in four consecutive 16-bit register. The values can be stored with ABCDEFGH - BADCFEHG endianness.
* - Floating point single precision 32-bit
- 1.17549435E-38 to 3.40282347E+38 is allowed.
- Stored in two consecutive 16-bit register per IEEE754. The values can be stored with ABCD - DCBA endianness.
* - Floating point double precision 64-bit
- +/-5.0E-324 to +/-1.7E+308 is allowed.
- Stored in four consecutive 16-bit register per IEEE754. The values can be stored with ABCDEFGH - BADCFEHG endianness.
As showed in the table above the float and double types do not fit to the 16-bit register and reguire several consecutive registers be used to store the value. However, different manufacturers store the consecutive bytes in different order (not standardized). For example: The DCBA prefix means inversed Modbus format (BADC order on network format).
.. list-table:: Table 2 Modbus byte order for extended types
:widths: 3 28
:header-rows: 1
* - Postfix
- Format description
* - ABCD
- Big endian, high order byte first
* - CDAB
- Big endian, reversed register order (Little endian with byte swap)
* - BADC
- Little endian, reversed register order (Big endian with byte swap)
* - DCBA
- Little endian (Low order byte first)
The extended data types are used to define all possible combinations of groupped values are represented below and correspond to ``param_type`` field of the data dictionary as described in the table below:
.. list-table:: Table 3 Modbus extended data types of characteristics
:widths: 6 28 10
:header-rows: 1
* - Type
- Format type description (common format)
- Format type (network format)
* - :cpp:enumerator:`PARAM_TYPE_U8`
- compatibility type corresponds to :cpp:enumerator:`PARAM_TYPE_U8_A`
- Unsigned integer 8 bit type
* - :cpp:enumerator:`PARAM_TYPE_U16`
- Unsigned integer 16 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U16_AB`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32`
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT`
- Default unsigned integer 32 bit type, corresponds to :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_ASCII`
- Default ASCII string format
- Packed ASCII string data
* - :cpp:enumerator:`PARAM_TYPE_BIN`
- Binary data type
- Default type for binary packed data
* - :cpp:enumerator:`PARAM_TYPE_I8_A`
- I8 signed integer in low byte of register, high byte is zero
- I8 signed integer LO
* - :cpp:enumerator:`PARAM_TYPE_I8_B`
- I8 signed integer in high byte of register, low byte is zero
- I8 signed integer HI
* - :cpp:enumerator:`PARAM_TYPE_U8_A`
- U8 unsigned integer written to low byte of register, high byte is zero
- U8 unsigned integer LO
* - :cpp:enumerator:`PARAM_TYPE_U8_B`
- U8 unsigned integer written to hi byte of register, low byte is zero
- U8 unsigned integer HI
* - :cpp:enumerator:`PARAM_TYPE_I16_AB`
- I16 signed integer, big endian
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I16_BA`
- I16 signed integer, little endian
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U16_AB`
- U16 unsigned integer, big endian
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U16_BA`
- U16 unsigned integer, little endian
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_I32_ABCD`
- I32 ABCD signed integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I32_CDAB`
- I32 CDAB signed integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I32_BADC`
- I32 BADC signed integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_I32_DCBA`
- I32 DCBA signed integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32_ABCD`
- U32 ABCD unsigned integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U32_CDAB`
- U32 CDAB unsigned integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U32_BADC`
- U32 BADC unsigned integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U32_DCBA`
- U32 DCBA unsigned integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_ABCD`
- Float ABCD floating point, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_CDAB`
- Float CDAB floating point, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_BADC`
- Float BADC floating point, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_FLOAT_DCBA`
- Float DCBA floating point, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_ABCDEFGH`
- I64, ABCDEFGH signed integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_HGFEDCBA`
- I64, HGFEDCBA signed integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_I64_GHEFCDAB`
- I64, GHEFCDAB signed integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_I64_BADCFEHG`
- I64, BADCFEHG signed integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_U64_ABCDEFGH`
- U64, ABCDEFGH unsigned integer, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U64_HGFEDCBA`
- U64, HGFEDCBA unsigned integer, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_U64_GHEFCDAB`
- U64, GHEFCDAB unsigned integer, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_U64_BADCFEHG`
- U64, BADCFEHG unsigned integer, little endian, reversed register order
- Little endian
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_ABCDEFGH`
- Double ABCDEFGH floating point, big endian
- Little endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_HGFEDCBA`
- Double HGFEDCBA floating point, little endian
- Big endian byte swap
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_GHEFCDAB`
- Double GHEFCDAB floating point, big endian, reversed register order
- Big endian
* - :cpp:enumerator:`PARAM_TYPE_DOUBLE_BADCFEHG`
- Double BADCFEHG floating point, little endian, reversed register order
- Little endian
.. note:: The support for the extended data types should be enabled using the option ``CONFIG_FMB_EXT_TYPE_SUPPORT`` in kconfig menu.
The below diagrams show how the extended data types appear on network layer.
.. blockdiag:: /../_static/diag_frame.diag
:scale: 80%
:caption: Modbus master response with ABCD frame
:align: center
.. blockdiag:: /../_static/modbus_frame_examples.diag
:scale: 80%
:caption: Modbus frame packaging examples (16-bit, 32-bit, 64-bit data)
:align: center
The approach showed above can be used to pack the data into MBAP frames used by Modbus TCP as well as for other types with similar size.
The following sections give an overview of how to use the ESP_Modbus component found under `components/freemodbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly:
- :ref:`modbus_api_port_initialization`
- :ref:`modbus_api_slave_overview`
- :ref:`modbus_api_master_overview`

View File

@@ -0,0 +1,172 @@
.. _modbus_api_port_initialization:
Modbus Port Initialization
^^^^^^^^^^^^^^^^^^^^^^^^^^
The ESP_Modbus supports Modbus SERIAL and TCP communication objects and an object 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_create_serial`
- :cpp:func:`mbc_master_create_serial`
- :cpp:func:`mbc_master_create_tcp`
- :cpp:func:`mbc_slave_create_tcp`
Calling the constructor function allows to create communication object with the specific communication options be defined in the configuration structure. The pointer to communication object is returned by constructor API and is being used as a handle for each following API call.
.. code:: c
// Pointer to allocate interface structure
// is used later as a first parameter for each API call
static void *master_handle = NULL;
ESP_ERROR_CHECK(mbc_master_create_serial(&config, &master_handle));
...
.. code:: c
static void *master_handle = NULL;
ESP_ERROR_CHECK(mbc_master_create_tcp(&config, &master_handle));
...
.. code:: c
static void *slave_handle = NULL;
ESP_ERROR_CHECK(mbc_slave_create_tcp(&config, &slave_handle));
...
.. code:: c
static void *slave_handle = NULL;
ESP_ERROR_CHECK(mbc_slave_create_serial(&config, &slave_handle));
...
Refer to :ref:`modbus_api_master_setup_communication_options` and :ref:`modbus_api_slave_setup_communication_options` for more information on how to configure communication options for the master and slave object accordingly.
.. _modbus_api_master_setup_communication_options:
Master Communication Options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The configuration structure is used to recognize the type of object being initialized. An example of initialization for Modbus serial master in RTU is below. The configuration structure provided as a parameter and is different for serial and TCP communication mode.
.. code:: c
#define MB_PORT_NUM 2
#define MB_DEV_SPEED 115200
static void *master_handle = NULL;
....
// Initialize Modbus controller
mb_communication_info_t config = {
.ser_opts.port = MB_PORT_NUM, // master communication port number
.ser_opts.mode = MB_RTU, // mode of Modbus communication (MB_RTU, MB_ASCII)
.ser_opts.baudrate = MB_DEV_SPEED, // baud rate of the port
.ser_opts.parity = MB_PARITY_NONE, // parity option for the port
.ser_opts.uid = 0, // unused for master
.ser_opts.response_tout_ms = 1000, // slave response time for master (if = 0, taken from default config)
.ser_opts.data_bits = UART_DATA_8_BITS, // number of data bits for communication port
.ser_opts.stop_bits = UART_STOP_BITS_1 // number of stop bits for the communication port
};
esp_err_t err = mbc_master_create_serial(&config, &master_handle);
if (master_handler == NULL || err != ESP_OK) {
ESP_LOGE(TAG, "mb controller initialization fail.");
}
.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to the `UART communication section <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#uart-api-running-uart-communication>`__ in documentation.
An example of initialization for Modbus TCP master is below. The Modbus master TCP 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 Unit Identifier defined in the table below corresponds to UID (slave short address field) in the Data Dictionary.
The format of slave definition following the notation `UID;slave_host_ip_or_dns_name;port_number` and allows some variations as described in the example below.
.. code:: c
// This is public pointer for the module and used by master
// to resolve slave addresses and reconnect when connection is broken
static char *slave_ip_address_table[] = {
"01;mb_slave_tcp_01;502", // Define the slave using mdns host name ("mb_slave_tcp_01") with UID = 01 and communication port 502
"200;mb_slave_tcp_c8;1502", // Definition of slave with mdns name "mb_slave_tcp_C8" and UID = 200, port = 1502
"35;192.168.32.54;1502", // Definition of slave with the static IPV4 address and UID = 35, port = 502
"12:2001:0db8:85a3:0000:0000:8a2e:0370:7334:502", // Definition of the slave with static IPV6 address and UID = 12, port = 502
NULL // End of table condition (must be included)
};
.. code:: c
#define MB_TCP_PORT 502
static void *master_handle = NULL;
....
mb_communication_info_t tcp_master_config = {
.tcp_opts.port = MB_TCP_PORT, // Default TCP Port number
.tcp_opts.mode = MB_TCP, // TCP mode of communication
.tcp_opts.addr_type = MB_IPV4, // type of IP address (MB_IPV4, MB_IPV6)
.tcp_opts.ip_addr_table = (void *)slave_ip_address_table, // list of slaves for master (must be defined)
.tcp_opts.uid = 0, // the UID unused for master
.tcp_opts.start_disconnected = false, // false - manage connections to all slaves before start
.tcp_opts.response_tout_ms = 2000, // slave response time in milliseconds for master, 0 - use default konfig
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(), // the pointer to netif inteface
};
esp_err_t err = mbc_master_create_tcp(pcomm_info, &master_handle);
if (master_handler == NULL || err != ESP_OK) {
ESP_LOGE(TAG, "mb controller initialization fail.");
}
.. note:: Refer to `esp_netif component <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_netif.html>`__ for more information about network interface initialization.
The slave IP addresses of the slaves can be resolved automatically by the stack using mDNS service as described in the example. In this case each slave has to use the mDNS service support and define its host name appropriately.
Refer to :ref:`example TCP master <example_mb_tcp_master>`, :ref:`example TCP slave <example_mb_tcp_slave>` for more information.
.. note:: The Modbus Master TCP functionality is under testing and competition status will be announced later over official channels.
.. _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).
This example code to initialize Modbus serial slave:
.. code:: c
#define MB_PORT_NUM 2
#define MB_DEV_SPEED 115200
#define MB_SLAVE_ADDR 1
static void* slave_handle = NULL;
....
mb_communication_info_t config = {
.ser_opts.port = MB_PORT_NUM,
.ser_opts.mode = MB_ASCII, // ASCII communication mode
.ser_opts.baudrate = MB_DEV_SPEED,
.ser_opts.parity = MB_PARITY_NONE,
.ser_opts.uid = MB_SLAVE_ADDR, // Modbus slave UID - Unit Identifier (short address)
.ser_opts.data_bits = UART_DATA_8_BITS,
.ser_opts.stop_bits = UART_STOP_BITS_1
};
// Initialization and setup of Modbus serial slave in ASCII communication mode
esp_err_t err = mbc_slave_create_serial(&config, &slave_handle);
if (slave_handle == NULL || err != ESP_OK) {
ESP_LOGE(TAG, "mb controller initialization fail.");
}
.. note:: RS485 communication requires call to UART specific APIs to setup communication mode and pins. Refer to the `UART communication section <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#uart-api-running-uart-communication>`__ in documentation.
This example code to initialize Modbus TCP slave:
.. code:: c
#define MB_SLAVE_ADDR 1
#define MB_TCP_PORT_NUMBER 1502
static void* slave_handle = NULL;
....
mb_communication_info_t tcp_slave_config = {
.tcp_opts.port = MB_TCP_PORT_NUMBER, // communication port number for Modbus slave
.tcp_opts.mode = MB_TCP, // mode of communication for slave
.tcp_opts.addr_type = MB_IPV4, // type of addressing being used
.tcp_opts.ip_addr_table = NULL, // Bind to any address
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(),// the pointer to netif inteface
.tcp_opts.uid = MB_SLAVE_ADDR // Modbus slave Unit Identifier
};
esp_err_t err = mbc_slave_create_tcp(&tcp_slave_config, &slave_handle);
if (slave_handle == NULL || err != ESP_OK) {
ESP_LOGE(TAG, "mb controller initialization fail.");
}
.. note:: Refer to `esp_netif component <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_netif.html>`__ for more information about network interface initialization.
.. note:: The Modbus Slave TCP functionality is under testing and the competition status will be announced later over official channels.

View File

@@ -0,0 +1,213 @@
.. _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 using communication options.
2. :ref:`modbus_api_slave_configure_descriptor` - Configure data descriptors to access slave parameters.
3. :ref:`modbus_api_slave_setup_communication_options` - Allows to setup communication options for selected port.
4. :ref:`modbus_api_slave_communication` - Start stack and sending / receiving data. Filter events when master accesses the register areas.
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)
....
static void *slave_handle = NULL; // Pointer to interface structure allocated by constructor
....
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(slave_handle, 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(slave_handle, 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.
The stack supports the extended data types when enabled through the the option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` in kconfig menu.
In this case the mapped data values can be initialized to specific format using :ref:`modbus_api_endianness_conversion`.
Please refer to secton :ref:`modbus_mapping_complex_data_types` for more information about data types.
Example initialization of mapped values:
.. code:: c
#include "mbcontroller.h" // for mbcontroller defines and api
val_32_arr holding_float_abcd[2] = {0};
val_64_arr holding_double_ghefcdab[2] = {0};
...
// set the Modbus parameter to specific format
portENTER_CRITICAL(&param_lock); // critical section is required if the stack is active
mb_set_float_abcd(&holding_float_abcd[0], (float)12345.0);
mb_set_float_abcd(&holding_float_abcd[1], (float)12345.0);
mb_set_double_ghefcdab(&holding_double_ghefcdab[0], (double)12345.0);
portEXIT_CRITICAL(&param_lock);
...
// The actual abcd formatted value can be converted to actual float represenatation as below
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[0]));
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[1]));
ESP_LOGI("TEST", "Test value ghefcdab: %lf", mb_get_double_ghefcdab(&holding_double_ghefcdab[0]));
...
.. _modbus_api_slave_communication:
Slave Communication
^^^^^^^^^^^^^^^^^^^
The function below is used to start Modbus controller interface and allows communication.
:cpp:func:`mbc_slave_start`
.. code:: c
static void* slave_handle = NULL;
....
ESP_ERROR_CHECK(mbc_slave_start(slave_handle)); // The handle must be initialized prior to start call.
: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)
....
static void *slave_handle = NULL; // communication object handle
....
// Get the mask of the queued events, the function
// blocks while waiting for register access
(void)mbc_slave_check_event(mbc_slave_handle, MB_READ_WRITE_MASK);
// Obtain the parameter information from parameter queue regarding access from master
ESP_ERROR_CHECK(mbc_slave_get_param_info(mbc_slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(uint32_t)reg_info.time_stamp,
(uint32_t)reg_info.mb_offset,
(uint32_t)reg_info.type,
(uint32_t)reg_info.address,
(uint32_t)reg_info.size);
} else if (reg_info.type & (MB_EVENT_INPUT_REG_RD)) {
ESP_LOGI(TAG, "INPUT %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(uint32_t)reg_info.time_stamp,
(uint32_t)reg_info.mb_offset,
(uint32_t)reg_info.type,
(uint32_t)reg_info.address,
(uint32_t)reg_info.size);
}
:cpp:func:`mbc_slave_lock`
:cpp:func:`mbc_slave_unlock`
The direct access to slave register area from user application must be protected by critical section. The following functions can be used to protect access to the data from registered mapping area while the communication object is active.
.. code:: c
static void *slave_handle = NULL; // communication object handle
...
(void)mbc_slave_lock(slave_handle); // ignore the returned error if the object is not actual
holding_reg_area[1] += 10; // the data is part of initialized register area accessed by slave
(void)mbc_slave_unlock(slave_handle);
The access to registered area shared between several slave objects from user application must be protected by critical section base on spin lock:
.. code:: c
#include "freertos/FreeRTOS.h"
...
static portMUX_TYPE g_spinlock = portMUX_INITIALIZER_UNLOCKED;
...
portENTER_CRITICAL(&param_lock);
holding_reg_area[2] = 123;
portEXIT_CRITICAL(&param_lock);
.. _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_delete`
.. code:: c
ESP_ERROR_CHECK(mbc_slave_delete(slave_handle)); // delete the master communication object defined by its handle

39
docs/generate_docs Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
exit_if_error() {
local exit_code=$1
shift
[[ $exit_code ]] && # do nothing if no error code passed
((exit_code != 0)) && { # do nothing if error code is 0
printf 'ERROR: %s\n' "$@" >&2
exit "$exit_code"
}
}
rm -rf _build
build-docs --target esp32 --language en || exit_if_error $? "Documentation build fail."
# Modifes target field of html files
ELEMENT="<script type='text/javascript'>
window.onload =(function() {
var myAnchor = document.getElementById('target-select');
var mySpan = document.createElement('input');
mySpan.style.float = 'left';
mySpan.setAttribute('type', 'text');
mySpan.setAttribute('maxLength', '10');
mySpan.value = 'all targets';
mySpan.setAttribute('disabled', true);
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
})();
</script>"
FILES=$(find . -path "*/_build/en/esp32/html/*.html")
for FILE in ${FILES}
do
echo ${ELEMENT} >> "${FILE}"
done
exit_if_error $? "Documentation build fail."
echo "Documentation build ok."

2
docs/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
esp-docs>=1.9.1,<2.0
Pillow==9.5.0

18
docs/utils.sh Normal file
View File

@@ -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
}

View File

@@ -0,0 +1,27 @@
tcp/mb_tcp_master:
disable_test:
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
reason: only manual test is performed
disable:
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1
tcp/mb_tcp_slave:
disable_test:
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
reason: only manual test is performed
disable:
- if: CONFIG_NAME == "wifi" and SOC_WIFI_SUPPORTED != 1
serial/mb_serial_master:
disable_test:
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
reason: only manual test is performed
disable:
- if: CONFIG_NAME == "default" and SOC_WIFI_SUPPORTED != 1
serial/mb_serial_slave:
disable_test:
- if: IDF_TARGET != "esp32" or CONFIG_NAME == "dummy_config"
reason: only manual test is performed

9
examples/README.md Normal file
View File

@@ -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

325
examples/conftest.py Normal file
View File

@@ -0,0 +1,325 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
import logging
import os
import sys
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, Match, Optional, TextIO, Tuple
import pexpect
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
from pytest_embedded_idf.app import IdfApp
from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.serial import IdfSerial
class Stages(Enum):
STACK_DEFAULT = 1
STACK_IPV4 = 2
STACK_IPV6 = 3
STACK_INIT = 4
STACK_CONNECT = 5
STACK_START = 6
STACK_PAR_OK = 7
STACK_PAR_FAIL = 8
STACK_DESTROY = 9
DEFAULT_SDKCONFIG = 'default'
ALLOWED_PERCENT_OF_FAILS = 10
class ModbusTestDut(IdfDut):
TEST_IP_PROMPT = r'Waiting IP([0-9]{1,2}) from stdin:\r\r\n'
TEST_IP_SET_CONFIRM = r'.*IP\([0-9]+\) = \[([0-9a-zA-Z\.\:]+)\] set from stdin.*'
TEST_IP_ADDRESS_REGEXP = r'.*example_[a-z]+: .* IPv4 [a-z]+:.* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*'
TEST_APP_NAME = r'I \([0-9]+\) [a-z_]+: Project name:\s+([_a-z]*)'
TEST_EXPECT_STR_TIMEOUT = 120
TEST_ACK_TIMEOUT = 60
TEST_MAX_CIDS = 8
app: IdfApp
serial: IdfSerial
def __init__(self, *args, **kwargs) -> None: # type: ignore
super().__init__(*args, **kwargs)
self.logger = logging.getLogger()
self.test_output: Optional[TextIO] = None
self.ip_address: Optional[str] = None
self.app_name: Optional[str] = None
self.param_fail_count = 0
self.param_ok_count = 0
self.test_stage = Stages.STACK_DEFAULT
self.dictionary = None
self.test_finish = False
self.test_status = False
def close(self) -> None:
super().close()
def dut_get_ip(self) -> Optional[str]:
if self.ip_address is None:
expect_address = self.expect(self.TEST_IP_ADDRESS_REGEXP, timeout=self.TEST_EXPECT_STR_TIMEOUT)
if isinstance(expect_address, Match):
self.ip_address = expect_address.group(1).decode('ascii')
return self.ip_address
def dut_get_name(self) -> Optional[str]:
if self.app_name is None:
expect_name = self.expect(self.TEST_APP_NAME, timeout=self.TEST_EXPECT_STR_TIMEOUT)
if isinstance(expect_name, Match):
self.app_name = expect_name.group(1).decode('ascii')
return self.app_name
def dut_send_ip(self, slave_ip: Optional[str]) -> Optional[int]:
''' The function sends the slave IP address defined as a parameter to master
'''
addr_num = 0
self.expect(self.TEST_IP_PROMPT, timeout=self.TEST_ACK_TIMEOUT)
if isinstance(slave_ip, str):
for addr_num in range(0, self.TEST_MAX_CIDS):
message = r'IP{}={}'.format(addr_num, slave_ip)
self.logger.info('{} sent to master'.format(message))
self.write(message)
return addr_num
def get_expect_proc(self) -> Optional[object]:
expect_proc: object = None
try:
expect_proc = self.__getattribute__('pexpect_proc')
except:
expect_proc = self.__getattribute__('_p')
finally:
if (expect_proc and callable(getattr(expect_proc, 'expect'))):
return expect_proc
else :
return None
def expect_any(self, *expect_items: Tuple[str, Callable], timeout: Optional[int]) -> None:
"""
expect_any(*expect_items, timeout=DEFAULT_TIMEOUT)
expect any of the patterns.
will call callback (if provided) if pattern match succeed and then return.
will pass match result to the callback.
:raise ExpectTimeout: failed to match any one of the expect items before timeout
:raise UnsupportedExpectItem: pattern in expect_item is not string or compiled RegEx
:arg expect_items: one or more expect items.
string, compiled RegEx pattern or (string or RegEx(string pattern), callback)
:keyword timeout: timeout for expect
:return: matched item
"""
def process_expected_item(item_raw: Tuple[str, Callable[..., Any]]) -> Dict[str, Any]:
# convert item raw data to standard dict
item = {
'pattern': item_raw[0] if isinstance(item_raw, tuple) else item_raw,
'callback': item_raw[1] if isinstance(item_raw, tuple) else None,
'index': -1,
'ret': None,
}
return item
expect_items_list = [process_expected_item(item) for item in expect_items]
expect_patterns = [item['pattern'] for item in expect_items_list if item['pattern'] is not None]
match_item = None
# Workaround: We need to use the original expect method of pexpect process which returns
# index of matched pattern instead of Match object returned by dut.expect()
expect_proc: Optional[object] = self.get_expect_proc()
if expect_proc is not None:
match_index = expect_proc.expect(expect_patterns, timeout)
if isinstance(match_index, int):
match_item = expect_items_list[match_index] # type: ignore
match_item['index'] = match_index # type: ignore , keep match index
if isinstance(expect_proc.match, Match) and len(expect_proc.match.groups()) > 0:
match_item['ret'] = expect_proc.match.groups()
if match_item['callback']:
match_item['callback'](match_item['ret']) # execution of callback function
else:
self.logger.error('%s: failed to parse output. Please check component versions.', self.app_name)
raise RuntimeError from None
def dut_test_start(self, dictionary: Dict, timeout_value=TEST_EXPECT_STR_TIMEOUT) -> None: # type: ignore
""" The method to initialize and handle test stages
"""
def handle_get_ip4(data: Optional[Any]) -> None:
""" Handle get_ip v4
"""
self.logger.info('%s[STACK_IPV4]: %s', self.app_name, str(data))
self.test_stage = Stages.STACK_IPV4
def handle_get_ip6(data: Optional[Any]) -> None:
""" Handle get_ip v6
"""
self.logger.info('%s[STACK_IPV6]: %s', self.app_name, str(data))
self.test_stage = Stages.STACK_IPV6
def handle_init(data: Optional[Any]) -> None:
""" Handle init
"""
self.logger.info('%s[STACK_INIT]: %s', self.app_name, str(data))
self.test_stage = Stages.STACK_INIT
def handle_connect(data: Optional[Any]) -> None:
""" Handle connect
"""
self.logger.info('%s[STACK_CONNECT]: %s', self.app_name, str(data))
self.test_stage = Stages.STACK_CONNECT
def handle_test_start(data: Optional[Any]) -> None:
""" Handle connect
"""
self.logger.info('%s[STACK_START]: %s', self.app_name, str(data))
self.test_stage = Stages.STACK_START
def handle_par_ok(data: Optional[Any]) -> None:
""" Handle parameter ok
"""
self.logger.info('%s[READ_PAR_OK]: %s', self.app_name, str(data))
if self.test_stage.value >= Stages.STACK_START.value:
self.param_ok_count += 1
self.test_stage = Stages.STACK_PAR_OK
def handle_par_fail(data: Optional[Any]) -> None:
""" Handle parameter fail
"""
self.logger.info('%s[READ_PAR_FAIL]: %s', self.app_name, str(data))
self.param_fail_count += 1
self.test_stage = Stages.STACK_PAR_FAIL
def handle_destroy(data: Optional[Any]) -> None:
""" Handle destroy
"""
self.logger.info('%s[%s]: %s', self.app_name, Stages.STACK_DESTROY.name, str(data))
self.test_stage = Stages.STACK_DESTROY
self.test_finish = True
while not self.test_finish:
try:
self.expect_any((dictionary[Stages.STACK_IPV4], handle_get_ip4),
(dictionary[Stages.STACK_IPV6], handle_get_ip6),
(dictionary[Stages.STACK_INIT], handle_init),
(dictionary[Stages.STACK_CONNECT], handle_connect),
(dictionary[Stages.STACK_START], handle_test_start),
(dictionary[Stages.STACK_PAR_OK], handle_par_ok),
(dictionary[Stages.STACK_PAR_FAIL], handle_par_fail),
(dictionary[Stages.STACK_DESTROY], handle_destroy),
timeout=timeout_value)
except pexpect.TIMEOUT:
self.logger.info('%s, expect timeout on stage %s (%s seconds)', self.app_name, self.test_stage.name, timeout_value)
self.test_finish = True
def dut_check_errors(self) -> None:
''' Verify allowed percentage of errors for the dut
'''
allowed_ok_percentage = ((self.param_ok_count / (self.param_ok_count + self.param_fail_count + 1)) * 100)
if self.param_ok_count and (allowed_ok_percentage > (100 - ALLOWED_PERCENT_OF_FAILS)):
self.logger.info('%s: ok_count: %d, fail count: %d', self.app_name, self.param_ok_count, self.param_fail_count)
else :
self.logger.error('%s: ok_count: %d, number of failed readings %d exceeds %d percent', self.app_name, self.param_ok_count, self.param_fail_count, ALLOWED_PERCENT_OF_FAILS)
raise RuntimeError from None
############
# Fixtures #
############
@pytest.fixture(scope='session', autouse=True)
def session_tempdir() -> str:
_tmpdir = os.path.join(
os.path.dirname(__file__),
'pytest_embedded_log',
datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
)
os.makedirs(_tmpdir, exist_ok=True)
return _tmpdir
@pytest.fixture(autouse=True)
@multi_dut_fixture
def junit_properties(
test_case_name: str, record_xml_attribute: Callable[[str, object], None]
) -> None:
"""
This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name>
"""
record_xml_attribute('name', test_case_name)
@pytest.fixture(scope='module')
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
mp = MonkeyPatch()
request.addfinalizer(mp.undo)
return mp
@pytest.fixture(scope='module', autouse=True)
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', ModbusTestDut)
@pytest.fixture
@multi_dut_argument
def config(request: FixtureRequest) -> str:
return getattr(request, 'param', None) or DEFAULT_SDKCONFIG
@pytest.fixture
@multi_dut_fixture
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:
"""
Check local build dir with the following priority:
1. build_<target>_<config>
2. build_<target>
3. build_<config>
4. build
Args:
app_path: app path
target: target
config: config
Returns:
valid build directory
"""
check_dirs = []
if target is not None and config is not None:
check_dirs.append(f'build_{target}_{config}')
if target is not None:
check_dirs.append(f'build_{target}')
if config is not None:
check_dirs.append(f'build_{config}')
check_dirs.append('build')
for check_dir in check_dirs:
binary_path = os.path.join(app_path, check_dir)
if os.path.isdir(binary_path):
logging.info(f'find valid binary path: {binary_path}')
return check_dir
logging.warning(
'checking binary path: %s... missing... try another place', binary_path
)
if config is not None and 'dummy' in config:
logging.warning('no build dir valid for application: %s, config: %s. Skip test.', binary_path, config)
return None
recommend_place = check_dirs[0]
logging.error(
f'no build dir valid. Please build the binary via "idf.py -B {recommend_place} build" and run pytest again'
)
sys.exit(1)

View File

@@ -0,0 +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
cmake_minimum_required(VERSION 3.5)
idf_component_register(SRCS "modbus_params.c"
INCLUDE_DIRS "include")

View File

@@ -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)

View File

@@ -0,0 +1,5 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .

View File

@@ -0,0 +1,102 @@
/*
* 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
#include <stdint.h>
#include "sdkconfig.h"
// 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;
uint8_t discrete_input_port2;
} discrete_reg_params_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct
{
uint8_t coils_port0;
uint8_t coils_port1;
uint8_t coils_port2;
} 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
{
#if CONFIG_FMB_EXT_TYPE_SUPPORT
uint16_t holding_u8_a[2];
uint16_t holding_u8_b[2];
uint16_t holding_u16_ab[2];
uint16_t holding_u16_ba[2];
uint32_t holding_uint32_abcd[2];
uint32_t holding_uint32_cdab[2];
uint32_t holding_uint32_badc[2];
uint32_t holding_uint32_dcba[2];
float holding_float_abcd[2];
float holding_float_cdab[2];
float holding_float_badc[2];
float holding_float_dcba[2];
double holding_double_abcdefgh[2];
double holding_double_hgfedcba[2];
double holding_double_ghefcdab[2];
double holding_double_badcfehg[2];
uint32_t holding_area2_end;
#endif
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;
uint32_t holding_area1_end;
} 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)

View File

@@ -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 <stdint.h>
#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 };

82
examples/serial/README.md Normal file
View File

@@ -0,0 +1,82 @@
# Modbus Master-Slave Example
## Overview
These two projects illustrate the communication between Modbus master and slave device in the segment.
Master initializes Modbus interface driver and then reads parameters from slave device in the segment.
After several successful read attempts slave sets the alarm relay (end of test condition).
Once master reads the alarm it stops communication and destroy driver.
The examples:
* `examples/protocols/modbus/serial/mb_master` - Modbus serial master ASCII/RTU
* `examples/protocols/modbus/serial/mb_slave` - Modbus serial slave ASCII/RTU
See README.md for each individual project for more information.
## How to use example
### Hardware Required
This example can be run on any commonly available ESP32 development board.
The master and slave boards should be connected to each other through the RS485 interface line driver.
See the connection schematic in README.md files of each example.
### Configure the project
This example test requires communication mode setting for master and slave be the same and slave address set to 1.
Please refer to README.md files of each example project for more information.
## About common_component in this example
The folder "mb_example_common" includes definitions of parameter structures for master and slave device (both projects share the same parameters).
However, currently it is for example purpose only and can be modified for particular application.
## Example Output
Example of Slave output:
```
I (343) SLAVE_TEST: Modbus slave stack initialized.
I (343) SLAVE_TEST: Start modbus test...
I (81463) SLAVE_TEST: HOLDING READ (81150420 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
I (82463) SLAVE_TEST: HOLDING READ (82150720 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
I (83573) SLAVE_TEST: HOLDING READ (83260630 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
I (84603) SLAVE_TEST: HOLDING READ (84290530 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
I (85703) SLAVE_TEST: HOLDING READ (85396692 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2868, SIZE:6
```
Example of Modbus Master output:
```
I (399) MASTER_TEST: Modbus master stack initialized...
I (499) MASTER_TEST: Start modbus test...
I (549) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.230000 (0x3f9d70a4) read successful.
I (629) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 12.100000 (0x4141999a) read successful.
I (709) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 3.560000 (0x4063d70a) read successful.
I (769) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 23.400000 (0x41bb3333) read successful.
I (829) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 5.890000 (0x40bc7ae1) read successful.
I (889) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 34.500000 (0x420a0000) read successful.
E (949) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x108) (ESP_ERR_INVALID_RESPONSE).
E (949) MASTER_TEST: Characteristic #6 (RelayP1) read fail, err = 264 (ESP_ERR_INVALID_RESPONSE).
E (1029) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x108) (ESP_ERR_INVALID_RESPONSE).
E (1029) MASTER_TEST: Characteristic #7 (RelayP2) read fail, err = 264 (ESP_ERR_INVALID_RESPONSE).
```
## Troubleshooting
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:
```
E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT).
```
ESP_ERR_TIMEOUT (0x107) - Modbus slave device does not respond during configured timeout. Check the connection and ability for communication using uart_echo_rs485 example or increase
Kconfig value CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND (CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS).
ESP_ERR_NOT_SUPPORTED (0x106), ESP_ERR_INVALID_RESPONSE (0x108) - Modbus slave device does not support requested command or register and sent exeption response.
ESP_ERR_INVALID_STATE (0x103) - Modbus stack is not configured correctly or can't work correctly due to critical failure.

View File

@@ -0,0 +1,9 @@
# 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.16)
# Exclude old component feemodbus which exists in old versions
set(EXCLUDE_COMPONENTS freemodbus)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_serial_master)

View File

@@ -0,0 +1,156 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# 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 pins for | Default pins for | External RS485 Driver Pin |
| | | ESP32 (C6) | ESP32-S2 (S3, C3, C2, H2) | |
| ----------------------|--------------------|-----------------------|---------------------------|---------------------------|
| 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).

View File

@@ -0,0 +1,26 @@
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) \
)

View File

@@ -0,0 +1,4 @@
set(PROJECT_NAME "modbus_serial_master")
idf_component_register(SRCS "serial_master.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,99 @@
menu "Modbus Example Configuration"
config MB_UART_PORT_ONE
bool
default y
depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1)
config MB_UART_PORT_TWO
bool
default y
depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2)
config MB_UART_PORT_NUM
int "UART port number"
range 0 2 if MB_UART_PORT_TWO
default 2 if MB_UART_PORT_TWO
range 0 1 if MB_UART_PORT_ONE
default 1 if MB_UART_PORT_ONE
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
range 0 23 if IDF_TARGET_ESP32C6
range 0 56 if IDF_TARGET_ESP32P4
default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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
range 0 23 if IDF_TARGET_ESP32C6
range 0 56 if IDF_TARGET_ESP32P4
default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 9 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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
range 0 56 if IDF_TARGET_ESP32P4
range 0 23 if IDF_TARGET_ESP32C6
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6
default 20 if IDF_TARGET_ESP32P4
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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

View File

@@ -0,0 +1,7 @@
dependencies:
idf: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
mb_example_common:
path: "../../../mb_example_common"

View File

@@ -0,0 +1,499 @@
/*
* SPDX-FileCopyrightText: 2016-2023 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 ))
#define TEST_HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
#define TEST_HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
#define TEST_INPUT_REG_START(field) (INPUT_OFFSET(field) >> 1)
#define TEST_INPUT_REG_SIZE(field) (sizeof(((input_reg_params_t *)0)->field) >> 1)
#define TEST_VALUE (12345) // default test value
#define TEST_ASCII_BIN (0xAAAAAAAA)
#define TEST_ARR_REG_SZ (58)
#define TEST_HUMI_MIN (-40)
#define TEST_HUMI_MAX (50)
#define TEST_TEMP_MIN (0)
#define TEST_TEMP_MAX (100)
// 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 EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
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_DISCR_P1,
#if CONFIG_FMB_EXT_TYPE_SUPPORT
CID_HOLD_U8_A,
CID_HOLD_U8_B,
CID_HOLD_U16_AB,
CID_HOLD_U16_BA,
CID_HOLD_UINT32_ABCD,
CID_HOLD_UINT32_CDAB,
CID_HOLD_UINT32_BADC,
CID_HOLD_UINT32_DCBA,
CID_HOLD_FLOAT_ABCD,
CID_HOLD_FLOAT_CDAB,
CID_HOLD_FLOAT_BADC,
CID_HOLD_FLOAT_DCBA,
CID_HOLD_DOUBLE_ABCDEFGH,
CID_HOLD_DOUBLE_HGFEDCBA,
CID_HOLD_DOUBLE_GHEFCDAB,
CID_HOLD_DOUBLE_BADCFEHG,
#endif
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,
TEST_INPUT_REG_START(input_data0), TEST_INPUT_REG_SIZE(input_data0),
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data0), TEST_HOLD_REG_SIZE(holding_data0),
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data1), TEST_INPUT_REG_SIZE(input_data1),
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data1), TEST_HOLD_REG_SIZE(holding_data1),
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data2), TEST_INPUT_REG_SIZE(input_data2),
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data2), TEST_HOLD_REG_SIZE(holding_data2),
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(test_regs), TEST_ARR_REG_SZ,
HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, (TEST_ARR_REG_SZ * 2),
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, TEST_ASCII_BIN ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 2, 6,
COIL_OFFSET(coils_port0), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 10, 6,
COIL_OFFSET(coils_port1), PARAM_TYPE_U8, 1,
OPTS( 0x55, 0x2A, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_DISCR_P1, STR("DiscreteInpP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 2, 7,
DISCR_OFFSET(discrete_input_port1), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
#if CONFIG_FMB_EXT_TYPE_SUPPORT
{ CID_HOLD_U8_A, STR("U8_A"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_a), TEST_HOLD_REG_SIZE(holding_u8_a),
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (TEST_HOLD_REG_SIZE(holding_u8_a) << 1),
OPTS( CHAR_MIN, 0x0055, 0x0055 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U8_B, STR("U8_B"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_b), TEST_HOLD_REG_SIZE(holding_u8_b),
HOLD_OFFSET(holding_u8_b), PARAM_TYPE_U8_B, (TEST_HOLD_REG_SIZE(holding_u8_b) << 1),
OPTS( 0, 0x5500, 0x5500 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_AB, STR("U16_AB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ab), TEST_HOLD_REG_SIZE(holding_u16_ab),
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_BA, STR("U16_BA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ba), TEST_HOLD_REG_SIZE(holding_u16_ba),
HOLD_OFFSET(holding_u16_ba), PARAM_TYPE_U16_BA, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_abcd), TEST_HOLD_REG_SIZE(holding_uint32_abcd),
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (TEST_HOLD_REG_SIZE(holding_uint32_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_CDAB, STR("UINT32_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_cdab), TEST_HOLD_REG_SIZE(holding_uint32_cdab),
HOLD_OFFSET(holding_uint32_cdab), PARAM_TYPE_U32_CDAB, (TEST_HOLD_REG_SIZE(holding_uint32_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_BADC, STR("UINT32_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_badc), TEST_HOLD_REG_SIZE(holding_uint32_badc),
HOLD_OFFSET(holding_uint32_badc), PARAM_TYPE_U32_BADC, (TEST_HOLD_REG_SIZE(holding_uint32_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_DCBA, STR("UINT32_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_dcba), TEST_HOLD_REG_SIZE(holding_uint32_dcba),
HOLD_OFFSET(holding_uint32_dcba), PARAM_TYPE_U32_DCBA, (TEST_HOLD_REG_SIZE(holding_uint32_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_ABCD, STR("FLOAT_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_abcd), TEST_HOLD_REG_SIZE(holding_float_abcd),
HOLD_OFFSET(holding_float_abcd), PARAM_TYPE_FLOAT_ABCD, (TEST_HOLD_REG_SIZE(holding_float_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_cdab), TEST_HOLD_REG_SIZE(holding_float_cdab),
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (TEST_HOLD_REG_SIZE(holding_float_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_BADC, STR("FLOAT_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_badc), TEST_HOLD_REG_SIZE(holding_float_badc),
HOLD_OFFSET(holding_float_badc), PARAM_TYPE_FLOAT_BADC, (TEST_HOLD_REG_SIZE(holding_float_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_DCBA, STR("FLOAT_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_dcba), TEST_HOLD_REG_SIZE(holding_float_dcba),
HOLD_OFFSET(holding_float_dcba), PARAM_TYPE_FLOAT_DCBA, (TEST_HOLD_REG_SIZE(holding_float_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_ABCDEFGH, STR("DOUBLE_ABCDEFGH"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_abcdefgh), TEST_HOLD_REG_SIZE(holding_double_abcdefgh),
HOLD_OFFSET(holding_double_abcdefgh), PARAM_TYPE_DOUBLE_ABCDEFGH, (TEST_HOLD_REG_SIZE(holding_double_abcdefgh) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_hgfedcba), TEST_HOLD_REG_SIZE(holding_double_hgfedcba),
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (TEST_HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_GHEFCDAB, STR("DOUBLE_GHEFCDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_ghefcdab), TEST_HOLD_REG_SIZE(holding_double_ghefcdab),
HOLD_OFFSET(holding_double_ghefcdab), PARAM_TYPE_DOUBLE_GHEFCDAB, (TEST_HOLD_REG_SIZE(holding_double_ghefcdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_BADCFEHG, STR("DOUBLE_BADCFEHG"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_badcfehg), TEST_HOLD_REG_SIZE(holding_double_badcfehg),
HOLD_OFFSET(holding_double_badcfehg), PARAM_TYPE_DOUBLE_BADCFEHG, (TEST_HOLD_REG_SIZE(holding_double_badcfehg) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER }
#endif
};
// Calculate number of parameters in the table
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
static void *master_handle = NULL;
// 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 #%u", (unsigned)param_descriptor->cid);
assert(instance_ptr != NULL);
}
return instance_ptr;
}
#define TEST_VERIFY_VALUES(handle, pdescr, pinst) (__extension__( \
{ \
assert(pinst); \
assert(pdescr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, pdescr->cid, \
(uint8_t *)pinst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*pitem)) { \
if (*pitem != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*pitem = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
(int)pdescr->cid, \
(char *)pdescr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)pinst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(char *)pdescr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
))
// User operation function to read slave values and check alarm
static void master_operation_func(void *arg)
{
esp_err_t err = ESP_OK;
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(master_handle, cid, &param_descriptor);
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
void *temp_data_ptr = master_get_param_data(param_descriptor);
assert(temp_data_ptr);
if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
(param_descriptor->cid == CID_HOLD_TEST_REG)) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr);
}
#if CONFIG_FMB_EXT_TYPE_SUPPORT
} else if ((param_descriptor->cid >= CID_HOLD_U16_AB)
&& (param_descriptor->cid <= CID_HOLD_U16_BA)) {
// Check the uint16 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_U8_A)
&& (param_descriptor->cid <= CID_HOLD_U8_B)) {
// Check the uint8 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_UINT32_ABCD)
&& (param_descriptor->cid <= CID_HOLD_UINT32_DCBA)) {
// Check the uint32 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_FLOAT_ABCD)
&& (param_descriptor->cid <= CID_HOLD_FLOAT_DCBA)) {
// Check the float parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if (param_descriptor->cid >= CID_HOLD_DOUBLE_ABCDEFGH) {
// Check the double parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (double *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(double *)temp_data_ptr,
*(uint64_t *)temp_data_ptr);
}
#endif
} else if (cid <= CID_HOLD_DATA_2) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
float value = *(float *)temp_data_ptr;
if (((value > param_descriptor->param_opts.max) ||
(value < param_descriptor->param_opts.min))) {
alarm_state = true;
break;
}
} else if ((cid >= CID_RELAY_P1) && (cid <= CID_DISCR_P1)) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint8_t *)temp_data_ptr) == ESP_OK) {
uint8_t state = *(uint8_t *)temp_data_ptr;
const char *rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
} else {
ESP_LOGE(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
alarm_state = true;
break;
}
if (state & param_descriptor->param_opts.opt1) {
alarm_state = true;
break;
}
}
}
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
}
}
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
}
if (alarm_state) {
ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
} else {
ESP_LOGE(TAG, "Alarm is not triggered after %u retries.",
MASTER_MAX_RETRY);
}
ESP_LOGI(TAG, "Destroy master...");
ESP_ERROR_CHECK(mbc_master_delete(master_handle));
}
// Modbus master initialization
static esp_err_t master_init(void)
{
// Initialize Modbus controller
mb_communication_info_t comm = {
.ser_opts.port = MB_PORT_NUM,
#if CONFIG_MB_COMM_MODE_ASCII
.ser_opts.mode = MB_ASCII,
#elif CONFIG_MB_COMM_MODE_RTU
.ser_opts.mode = MB_RTU,
#endif
.ser_opts.baudrate = MB_DEV_SPEED,
.ser_opts.parity = MB_PARITY_NONE,
.ser_opts.uid = 0,
.ser_opts.response_tout_ms = 1000,
.ser_opts.data_bits = UART_DATA_8_BITS,
.ser_opts.stop_bits = UART_STOP_BITS_1
};
esp_err_t err = mbc_master_create_serial(&comm, &master_handle);
MB_RETURN_ON_FALSE((master_handle != 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).", (int)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);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err);
err = mbc_master_start(master_handle);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"mb controller start fail, returned (0x%x).", (int)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).", (int)err);
vTaskDelay(5);
err = mbc_master_set_descriptor(master_handle, &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).", (int)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);
}

View File

@@ -0,0 +1,7 @@
CONFIG_MB_COMM_MODE_ASCII=y
CONFIG_MB_COMM_MODE_RTU=n
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y

View File

@@ -0,0 +1,7 @@
CONFIG_MB_COMM_MODE_ASCII=n
CONFIG_MB_COMM_MODE_RTU=y
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y

View File

@@ -0,0 +1,10 @@
# 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.16)
# Exclude old component feemodbus which exists in old versions
set(EXCLUDE_COMPONENTS freemodbus)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_serial_slave)

View File

@@ -0,0 +1,97 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# Modbus Slave Example
This example demonstrates using the port of the esp-modbus 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 `modbus` folder and supports the ESP32 targets. 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 pins for | Default pins for | External RS485 Driver Pin |
| | | ESP32 (C6) | ESP32-S2 (S3, C3, C2, H2) | |
| ----------------------|--------------------|-----------------------|---------------------------|---------------------------|
| 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 esp-modbus 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.

View File

@@ -0,0 +1,4 @@
set(PROJECT_NAME "modbus_serial_slave")
idf_component_register(SRCS "serial_slave.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,108 @@
menu "Modbus Example Configuration"
config MB_UART_PORT_ONE
bool
default y
depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1)
config MB_UART_PORT_TWO
bool
default y
depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2)
config MB_UART_PORT_NUM
int "UART port number"
range 0 2 if MB_UART_PORT_TWO
default 2 if MB_UART_PORT_TWO
range 0 1 if MB_UART_PORT_ONE
default 1 if MB_UART_PORT_ONE
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
range 0 23 if IDF_TARGET_ESP32C6
range 0 56 if IDF_TARGET_ESP32P4
default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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
range 0 56 if IDF_TARGET_ESP32P4
range 0 23 if IDF_TARGET_ESP32C6
default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 9 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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
range 0 23 if IDF_TARGET_ESP32C6
range 0 56 if IDF_TARGET_ESP32P4
default 20 if IDF_TARGET_ESP32P4
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6
range 0 46 if IDF_TARGET_ESP32S2
range 0 47 if IDF_TARGET_ESP32S3
range 0 19 if IDF_TARGET_ESP32C3
range 0 20 if IDF_TARGET_ESP32C2
range 0 27 if IDF_TARGET_ESP32H2
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2
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 247
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

View File

@@ -0,0 +1,7 @@
dependencies:
idf: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
mb_example_common:
path: "../../../mb_example_common"

View File

@@ -0,0 +1,283 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.h>
#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_AREA0_SIZE ((size_t)((HOLD_OFFSET(holding_data4) - HOLD_OFFSET(holding_data0)) << 1))
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
#define MB_REG_HOLDING_START_AREA1_SIZE ((size_t)((HOLD_OFFSET(holding_area1_end) - HOLD_OFFSET(holding_data4)) << 1))
#define MB_REG_HOLDING_START_AREA2 (HOLD_OFFSET(holding_u8_a))
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET(holding_area2_end) - HOLD_OFFSET(holding_u8_a)) << 1))
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
#define MB_CHAN_DATA_MAX_VAL (6)
#define MB_CHAN_DATA_OFFSET (1.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)
#define MB_TEST_VALUE 12345.0
static const char *TAG = "SLAVE_TEST";
static void *mbc_slave_handle = NULL;
// 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;
#if CONFIG_FMB_EXT_TYPE_SUPPORT
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[0], (uint8_t)0x55);
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[1], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[0], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[1], (uint8_t)0x55);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[1], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[1], (uint16_t)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[0], (float)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[1], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[0], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[1], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[0], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[1], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[0], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[1], (float)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[1], (uint32_t)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[0], (double)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[1], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[0], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[1], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[0], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[1], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[0], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[1], (double)MB_TEST_VALUE);
#endif
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 esp-modbus 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_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
// Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
// Initialize Modbus controller
mb_communication_info_t comm_config = {
.ser_opts.port = MB_PORT_NUM,
#if CONFIG_MB_COMM_MODE_ASCII
.ser_opts.mode = MB_ASCII,
#elif CONFIG_MB_COMM_MODE_RTU
.ser_opts.mode = MB_RTU,
#endif
.ser_opts.baudrate = MB_DEV_SPEED,
.ser_opts.parity = MB_PARITY_NONE,
.ser_opts.uid = MB_SLAVE_ADDR,
.ser_opts.data_bits = UART_DATA_8_BITS,
.ser_opts.stop_bits = UART_STOP_BITS_1
};
ESP_ERROR_CHECK(mbc_slave_create_serial(&comm_config, &mbc_slave_handle)); // Initialization of Modbus controller
// 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 in bytes
reg_area.size = MB_REG_HOLDING_START_AREA0_SIZE;
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_handle, reg_area));
// The second register area
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA1;
reg_area.address = (void*)&holding_reg_params.holding_data4;
reg_area.size = MB_REG_HOLDING_START_AREA1_SIZE;
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_handle, reg_area));
#if CONFIG_FMB_EXT_TYPE_SUPPORT
// The extended parameters register area
reg_area.type = MB_PARAM_HOLDING;
reg_area.start_offset = MB_REG_HOLDING_START_AREA2;
reg_area.address = (void*)&holding_reg_params.holding_u8_a;
reg_area.size = MB_REG_HOLDING_START_AREA2_SIZE;
ESP_ERROR_CHECK(mbc_slave_set_descriptor(mbc_slave_handle, reg_area));
#endif
// 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(mbc_slave_handle, 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(mbc_slave_handle, 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(mbc_slave_handle, 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(mbc_slave_handle, reg_area));
setup_reg_data(); // Set values into known state
// Starts of modbus controller and stack
ESP_ERROR_CHECK(mbc_slave_start(mbc_slave_handle));
// 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
(void)mbc_slave_check_event(mbc_slave_handle, MB_READ_WRITE_MASK);
// Get parameter information from parameter queue
ESP_ERROR_CHECK(mbc_slave_get_param_info(mbc_slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
// Filter events and process them accordingly
if(reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
ESP_LOGI(TAG, "HOLDING %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
{
(void)mbc_slave_lock(mbc_slave_handle);
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;
}
(void)mbc_slave_unlock(mbc_slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "DISCRETE READ (%" PRIu32 " us): ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)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_delete(mbc_slave_handle));
}

View File

@@ -0,0 +1,6 @@
CONFIG_MB_COMM_MODE_ASCII=y
CONFIG_MB_COMM_MODE_RTU=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y

View File

@@ -0,0 +1,6 @@
CONFIG_MB_COMM_MODE_ASCII=n
CONFIG_MB_COMM_MODE_RTU=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y

View File

@@ -0,0 +1,8 @@
#
# Modbus configuration
#
CONFIG_MB_COMM_MODE_ASCII=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y

View File

@@ -0,0 +1,71 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
# main thread in Multi DUT case.
import logging
import os
from typing import Tuple
import pytest
from conftest import ModbusTestDut, Stages
pattern_dict_slave = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_connect: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: '
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s'
r'ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'),
Stages.STACK_PAR_FAIL: (r'E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet'),
Stages.STACK_DESTROY: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed).')}
pattern_dict_master = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_connect: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized)'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling'),
Stages.STACK_START: (r'I \(([0-9]+)\) MASTER_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)'
r'\s\([a-zA-Z\_\%\/]+\) value =[a-zA-Z0-9\.\s]* \((0x[a-zA-Z0-9]+)\)[,\sa-z]+ successful'),
Stages.STACK_PAR_FAIL: (r'.*E \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+\s\(([a-zA-Z0-9_]+)\)\s'
r'read fail, err = [0-9]+ \([_A-Z]+\)'),
Stages.STACK_DESTROY: (r'I \(([0-9]+)\) MASTER_TEST: (Destroy master)...')}
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = 'modbus_test'
logger = logging.getLogger(LOGGER_NAME)
test_configs = [
'rtu',
'ascii'
]
@pytest.mark.esp32
@pytest.mark.multi_dut_modbus_serial
@pytest.mark.parametrize('config', test_configs, indirect=True)
@pytest.mark.parametrize(
'count, app_path', [
(2, f'{os.path.join(os.path.dirname(__file__), "mb_serial_master")}|{os.path.join(os.path.dirname(__file__), "mb_serial_slave")}')
],
indirect=True
)
def test_modbus_serial_communication(config: str, dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
dut_slave = dut[1]
dut_master = dut[0]
logger.info('DUT: %s start.', dut_master.dut_get_name())
logger.info('DUT: %s start.', dut_slave.dut_get_name())
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()
@pytest.mark.multi_dut_modbus_generic
@pytest.mark.parametrize('config', ['dummy_config'])
def test_modbus_serial_generic() -> None:
print('The generic serial example tests are not provided yet.')

58
examples/tcp/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Modbus TCP Master-Slave Example
## Overview
These two projects illustrate the communication between Modbus master and slave device in the segment.
Master initializes Modbus interface driver and then reads parameters from slave device in the segment.
After several successful read attempts slave sets the alarm relay (end of test condition).
Once master reads the alarm it stops communication and destroy driver.
The examples:
* `examples/protocols/modbus/tcp/mb_tcp_master` - Modbus TCP master
* `examples/protocols/modbus/tcp/mb_tcp_slave` - Modbus TCP slave
See README.md for each individual project for more information.
## How to use example
### Hardware Required
This example can be run on any commonly available ESP32(-S2) development board.
The master and slave boards should be connected to the same network (see the README.md file in example folder) and slave address `CONFIG_MB_SLAVE_ADDR` be defined for slave board(s).
See the connection schematic in README.md files of each example.
### Configure the project
This example test requires communication mode setting for master and slave be the same and slave address set to 1.
Please refer to README.md files of each example project for more information. This example uses the default option `CONFIG_MB_SLAVE_IP_FROM_STDIN` to resolve slave IP address and supports IPv4 address type for communication in this case.
## About common_component in this example
The folder "mb_example_common" one level above includes definitions of parameter structures for master and slave device (both projects share the same parameters).
However, currently it is for example purpose only and can be modified for particular application.
## Example Output
Refer to README.md file in the appropriate example folder for more information about master and slave log output.
## Troubleshooting
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:
```
E (1692332) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT).
```
ESP_ERR_TIMEOUT (0x107) - Modbus slave device does not respond during configured timeout.
Check ability for communication pinging each slave configured in the master parameter description table or use command on your host machine to find modbus slave using mDNS (requires `CONFIG_MB_MDNS_IP_RESOLVER` option be enabled):
```>dns-sd -L mb_slave_tcp_XX _modbus._tcp .```
where XX is the short slave address (index) of the slave configured in the Kconfig of slave example.
Also it is possible to increase Kconfig value `CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND` to compensate network communication delays between master and slaves.
ESP_ERR_NOT_SUPPORTED (0x106), ESP_ERR_INVALID_RESPONSE (0x108) - Modbus slave device does not support requested command or register and sent exeption response.
ESP_ERR_INVALID_STATE (0x103) - Modbus stack is not configured correctly or can't work correctly due to critical failure.

View File

@@ -0,0 +1,8 @@
# 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.16)
set(EXCLUDE_COMPONENTS freemodbus)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_tcp_master)

View File

@@ -0,0 +1,146 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# 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).

View File

@@ -0,0 +1,4 @@
set(PROJECT_NAME "modbus_tcp_master")
idf_component_register(SRCS "tcp_master.c"
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -0,0 +1,12 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
espressif/mdns:
version: "^1.0.0"
mb_example_common:
path: "../../../mb_example_common"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@@ -0,0 +1,726 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// esp-modbus Master Example ESP32
#include <string.h>
#include <sys/queue.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 "esp_netif.h"
#include "esp_mac.h"
#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 (10)
// 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 ))
#define TEST_HOLD_REG_START(field) (HOLD_OFFSET(field) >> 1)
#define TEST_HOLD_REG_SIZE(field) (sizeof(((holding_reg_params_t *)0)->field) >> 1)
#define TEST_INPUT_REG_START(field) (INPUT_OFFSET(field) >> 1)
#define TEST_INPUT_REG_SIZE(field) (sizeof(((input_reg_params_t *)0)->field) >> 1)
#define TEST_VALUE (12345) // default test value
#define TEST_ASCII_BIN (0xAAAAAAAA)
#define TEST_ARR_REG_SZ (58)
#define TEST_HUMI_MIN (-40)
#define TEST_HUMI_MAX (50)
#define TEST_TEMP_MIN (0)
#define TEST_TEMP_MAX (100)
// 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"
#define EACH_ITEM(array, length) \
(typeof(*(array)) *pitem = (array); (pitem < &((array)[length])); pitem++)
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 UID = 1
MB_DEVICE_ADDR2,
MB_DEVICE_ADDR3,
MB_DEVICE_COUNT = 3
};
// 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_DISCR_P1,
#if CONFIG_FMB_EXT_TYPE_SUPPORT
CID_HOLD_U8_A,
CID_HOLD_U8_B,
CID_HOLD_U16_AB,
CID_HOLD_U16_BA,
CID_HOLD_UINT32_ABCD,
CID_HOLD_UINT32_CDAB,
CID_HOLD_UINT32_BADC,
CID_HOLD_UINT32_DCBA,
CID_HOLD_FLOAT_ABCD,
CID_HOLD_FLOAT_CDAB,
CID_HOLD_FLOAT_BADC,
CID_HOLD_FLOAT_DCBA,
CID_HOLD_DOUBLE_ABCDEFGH,
CID_HOLD_DOUBLE_HGFEDCBA,
CID_HOLD_DOUBLE_GHEFCDAB,
CID_HOLD_DOUBLE_BADCFEHG,
#endif
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,
TEST_INPUT_REG_START(input_data0), TEST_INPUT_REG_SIZE(input_data0),
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data0), TEST_HOLD_REG_SIZE(holding_data0),
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data1), TEST_INPUT_REG_SIZE(input_data1),
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data1), TEST_HOLD_REG_SIZE(holding_data1),
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR2, MB_PARAM_INPUT,
TEST_INPUT_REG_START(input_data2), TEST_INPUT_REG_SIZE(input_data2),
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR3, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_data2), TEST_HOLD_REG_SIZE(holding_data2),
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4,
OPTS( TEST_HUMI_MIN, TEST_HUMI_MAX, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(test_regs), TEST_ARR_REG_SZ,
HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, (TEST_ARR_REG_SZ * 2),
OPTS( TEST_TEMP_MIN, TEST_TEMP_MAX, TEST_ASCII_BIN ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 2, 6,
COIL_OFFSET(coils_port0), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 10, 6,
COIL_OFFSET(coils_port1), PARAM_TYPE_U8, 1,
OPTS( 0x55, 0x2A, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_DISCR_P1, STR("DiscreteInpP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_DISCRETE, 2, 7,
DISCR_OFFSET(discrete_input_port1), PARAM_TYPE_U8, 1,
OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
#if CONFIG_FMB_EXT_TYPE_SUPPORT
{ CID_HOLD_U8_A, STR("U8_A"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_a), TEST_HOLD_REG_SIZE(holding_u8_a),
HOLD_OFFSET(holding_u8_a), PARAM_TYPE_U8_A, (TEST_HOLD_REG_SIZE(holding_u8_a) << 1),
OPTS( CHAR_MIN, 0x0055, 0x0055 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U8_B, STR("U8_B"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u8_b), TEST_HOLD_REG_SIZE(holding_u8_b),
HOLD_OFFSET(holding_u8_b), PARAM_TYPE_U8_B, (TEST_HOLD_REG_SIZE(holding_u8_b) << 1),
OPTS( 0, 0x5500, 0x5500 ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_AB, STR("U16_AB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ab), TEST_HOLD_REG_SIZE(holding_u16_ab),
HOLD_OFFSET(holding_u16_ab), PARAM_TYPE_U16_AB, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_U16_BA, STR("U16_BA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_u16_ba), TEST_HOLD_REG_SIZE(holding_u16_ba),
HOLD_OFFSET(holding_u16_ba), PARAM_TYPE_U16_BA, (TEST_HOLD_REG_SIZE(holding_u16_ab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_ABCD, STR("UINT32_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_abcd), TEST_HOLD_REG_SIZE(holding_uint32_abcd),
HOLD_OFFSET(holding_uint32_abcd), PARAM_TYPE_U32_ABCD, (TEST_HOLD_REG_SIZE(holding_uint32_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_CDAB, STR("UINT32_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_cdab), TEST_HOLD_REG_SIZE(holding_uint32_cdab),
HOLD_OFFSET(holding_uint32_cdab), PARAM_TYPE_U32_CDAB, (TEST_HOLD_REG_SIZE(holding_uint32_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_BADC, STR("UINT32_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_badc), TEST_HOLD_REG_SIZE(holding_uint32_badc),
HOLD_OFFSET(holding_uint32_badc), PARAM_TYPE_U32_BADC, (TEST_HOLD_REG_SIZE(holding_uint32_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_UINT32_DCBA, STR("UINT32_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_uint32_dcba), TEST_HOLD_REG_SIZE(holding_uint32_dcba),
HOLD_OFFSET(holding_uint32_dcba), PARAM_TYPE_U32_DCBA, (TEST_HOLD_REG_SIZE(holding_uint32_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_ABCD, STR("FLOAT_ABCD"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_abcd), TEST_HOLD_REG_SIZE(holding_float_abcd),
HOLD_OFFSET(holding_float_abcd), PARAM_TYPE_FLOAT_ABCD, (TEST_HOLD_REG_SIZE(holding_float_abcd) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_CDAB, STR("FLOAT_CDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_cdab), TEST_HOLD_REG_SIZE(holding_float_cdab),
HOLD_OFFSET(holding_float_cdab), PARAM_TYPE_FLOAT_CDAB, (TEST_HOLD_REG_SIZE(holding_float_cdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_BADC, STR("FLOAT_BADC"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_badc), TEST_HOLD_REG_SIZE(holding_float_badc),
HOLD_OFFSET(holding_float_badc), PARAM_TYPE_FLOAT_BADC, (TEST_HOLD_REG_SIZE(holding_float_badc) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_FLOAT_DCBA, STR("FLOAT_DCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_float_dcba), TEST_HOLD_REG_SIZE(holding_float_dcba),
HOLD_OFFSET(holding_float_dcba), PARAM_TYPE_FLOAT_DCBA, (TEST_HOLD_REG_SIZE(holding_float_dcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_ABCDEFGH, STR("DOUBLE_ABCDEFGH"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_abcdefgh), TEST_HOLD_REG_SIZE(holding_double_abcdefgh),
HOLD_OFFSET(holding_double_abcdefgh), PARAM_TYPE_DOUBLE_ABCDEFGH, (TEST_HOLD_REG_SIZE(holding_double_abcdefgh) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_HGFEDCBA, STR("DOUBLE_HGFEDCBA"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_hgfedcba), TEST_HOLD_REG_SIZE(holding_double_hgfedcba),
HOLD_OFFSET(holding_double_hgfedcba), PARAM_TYPE_DOUBLE_HGFEDCBA, (TEST_HOLD_REG_SIZE(holding_double_hgfedcba) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_GHEFCDAB, STR("DOUBLE_GHEFCDAB"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_ghefcdab), TEST_HOLD_REG_SIZE(holding_double_ghefcdab),
HOLD_OFFSET(holding_double_ghefcdab), PARAM_TYPE_DOUBLE_GHEFCDAB, (TEST_HOLD_REG_SIZE(holding_double_ghefcdab) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER },
{ CID_HOLD_DOUBLE_BADCFEHG, STR("DOUBLE_BADCFEHG"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING,
TEST_HOLD_REG_START(holding_double_badcfehg), TEST_HOLD_REG_SIZE(holding_double_badcfehg),
HOLD_OFFSET(holding_double_badcfehg), PARAM_TYPE_DOUBLE_BADCFEHG, (TEST_HOLD_REG_SIZE(holding_double_badcfehg) << 1),
OPTS( 0, TEST_VALUE, TEST_VALUE ), PAR_PERMS_READ_WRITE_TRIGGER }
#endif
};
// Calculate number of parameters in the table
const uint16_t num_device_parameters = (sizeof(device_parameters) / sizeof(device_parameters[0]));
static void* master_handle = NULL;
const size_t ip_table_sz;
// 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[MB_DEVICE_COUNT + 1] = {
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR2 and set to predefined value by user
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR3 and set to predefined value by user
NULL // End of table condition (must be included)
#elif CONFIG_MB_MDNS_IP_RESOLVER
// This is workaround for the test to use the same slave for all CIDs and ignore UID setting in the slave
"01;mb_slave_tcp_01;1502",
"02;mb_slave_tcp_01;1502",
"03;mb_slave_tcp_01;1502",
NULL // End of table condition (must be included)
#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;
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, "%02x;" IPSTR, (int)(*index + 1), 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, "%02x;" IPV6STR, (int)(*index + 1), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
abort();
}
}
#endif
printf("IP string: %s", ip_str);
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", (int)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.", (int)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.", (int)ip_cnt, addr_table[ip_cnt]);
ip_cnt++;
} else {
ESP_LOGI(TAG, "IP(%d) is not set in the table.", (int)ip_cnt);
break;
}
}
}
return ip_cnt;
}
#endif
static void master_destroy_slave_list(char **table, size_t ip_table_size)
{
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 #%u", param_descriptor->cid);
assert(instance_ptr != NULL);
}
return instance_ptr;
}
#define TEST_VERIFY_VALUES(handle, pdescr, pinst) (__extension__( \
{ \
assert(pinst); \
assert(pdescr); \
uint8_t type = 0; \
esp_err_t err = ESP_FAIL; \
err = mbc_master_get_parameter(handle, pdescr->cid, (uint8_t *)pinst, &type); \
if (err == ESP_OK) { \
bool is_correct = true; \
if (pdescr->param_opts.opt3) { \
for EACH_ITEM(pinst, pdescr->param_size / sizeof(*pitem)) { \
if (*pitem != (typeof(*(pinst)))pdescr->param_opts.opt3) { \
*pitem = (typeof(*(pinst)))pdescr->param_opts.opt3; \
ESP_LOGD(TAG, "Characteristic #%d (%s), initialize to 0x%" PRIx16 ".", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(uint16_t)pdescr->param_opts.opt3); \
is_correct = false; \
} \
} \
} \
if (!is_correct) { \
ESP_LOGE(TAG, "Characteristic #%d (%s), initialize.", \
(int)pdescr->cid, \
(char *)pdescr->param_key); \
err = mbc_master_set_parameter(handle, cid, (uint8_t *)pinst, &type); \
if (err != ESP_OK) { \
ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} else { \
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (..) write successful.", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(char *)pdescr->param_units); \
} \
} \
} else { \
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).", \
(int)pdescr->cid, \
(char *)pdescr->param_key, \
(int)err, \
(char *)esp_err_to_name(err)); \
} \
(err); \
} \
))
// User operation function to read slave values and check alarm
static void master_operation_func(void *arg)
{
esp_err_t err = ESP_OK;
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(master_handle, cid, &param_descriptor);
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
void *temp_data_ptr = master_get_param_data(param_descriptor);
assert(temp_data_ptr);
if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
(param_descriptor->cid == CID_HOLD_TEST_REG)) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr);
}
#if CONFIG_FMB_EXT_TYPE_SUPPORT
} else if ((param_descriptor->cid >= CID_HOLD_U16_AB)
&& (param_descriptor->cid <= CID_HOLD_U16_BA)) {
// Check the uint16 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_U8_A)
&& (param_descriptor->cid <= CID_HOLD_U8_B)) {
// Check the uint8 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint16_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%" PRIx16 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint16_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_UINT32_ABCD)
&& (param_descriptor->cid <= CID_HOLD_UINT32_DCBA)) {
// Check the uint32 parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint32_t *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %" PRIu32 " (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(uint32_t *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if ((param_descriptor->cid >= CID_HOLD_FLOAT_ABCD)
&& (param_descriptor->cid <= CID_HOLD_FLOAT_DCBA)) {
// Check the float parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
} else if (param_descriptor->cid >= CID_HOLD_DOUBLE_ABCDEFGH) {
// Check the double parameters
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (double *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %lf (0x%" PRIx64 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(double *)temp_data_ptr,
*(uint64_t *)temp_data_ptr);
}
#endif
} else if (cid <= CID_HOLD_DATA_2) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (float *)temp_data_ptr) == ESP_OK) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%" PRIx32 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
*(float *)temp_data_ptr,
*(uint32_t *)temp_data_ptr);
}
float value = *(float *)temp_data_ptr;
if (((value > param_descriptor->param_opts.max) ||
(value < param_descriptor->param_opts.min))) {
alarm_state = true;
break;
}
} else if ((cid >= CID_RELAY_P1) && (cid <= CID_DISCR_P1)) {
if (TEST_VERIFY_VALUES(master_handle, param_descriptor, (uint8_t *)temp_data_ptr) == ESP_OK) {
uint8_t state = *(uint8_t *)temp_data_ptr;
const char *rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) {
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 ") read successful.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
} else {
ESP_LOGE(TAG, "Characteristic #%d %s (%s) value = %s (0x%" PRIx8 "), unexpected value.",
(int)param_descriptor->cid,
(char *)param_descriptor->param_key,
(char *)param_descriptor->param_units,
(const char *)rw_str,
*(uint8_t *)temp_data_ptr);
alarm_state = true;
break;
}
if (state & param_descriptor->param_opts.opt1) {
alarm_state = true;
break;
}
}
}
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
}
}
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
}
if (alarm_state) {
ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid);
} else {
ESP_LOGE(TAG, "Alarm is not triggered after %u retries.",
MASTER_MAX_RETRY);
}
ESP_LOGI(TAG, "Destroy master...");
vTaskDelay(1);
}
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).",
(int)result);
result = esp_netif_init();
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_init fail, returns(0x%x).",
(int)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).",
(int)result);
// 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).",
(int)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).",
(int)result);
#endif
#if 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).",
(int)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).",
(int)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).",
(int)err);
err = nvs_flash_deinit();
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_deinit fail, returns(0x%x).",
(int)err);
return err;
}
// Modbus master initialization
static esp_err_t master_init(mb_communication_info_t *pcomm_info)
{
esp_err_t err = mbc_master_create_tcp(pcomm_info, &master_handle);
MB_RETURN_ON_FALSE((master_handle != 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).",
(int)err);
err = mbc_master_set_descriptor(master_handle, &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).",
(int)err);
ESP_LOGI(TAG, "Modbus master stack initialized...");
err = mbc_master_start(master_handle);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mb controller start fail, returns(0x%x).",
(int)err);
vTaskDelay(5);
return err;
}
static esp_err_t master_destroy(void)
{
esp_err_t err = mbc_master_delete(master_handle);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_master_destroy fail, returns(0x%x).",
(int)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 tcp_master_config = {
.tcp_opts.port = MB_TCP_PORT,
.tcp_opts.mode = MB_TCP,
.tcp_opts.addr_type = ip_addr_type,
.tcp_opts.ip_addr_table = (void *)slave_ip_address_table,
.tcp_opts.uid = 0,
.tcp_opts.start_disconnected = false,
.tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND,
.tcp_opts.ip_netif_ptr = (void*)get_example_netif()
};
ESP_ERROR_CHECK(master_init(&tcp_master_config));
master_operation_func(NULL);
ESP_ERROR_CHECK(master_destroy());
ESP_ERROR_CHECK(destroy_services());
}

View File

@@ -0,0 +1,33 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE=4096
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
CONFIG_ETH_PHY_INTERFACE_RMII=y
CONFIG_ETH_USE_SPI_ETHERNET=n

View File

@@ -0,0 +1,20 @@
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"

View File

@@ -0,0 +1,22 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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_USE_ISR_DISPATCH_METHOD=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"

View File

@@ -0,0 +1,9 @@
# 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)
# Exclude old component feemodbus which exists in old versions
set(EXCLUDE_COMPONENTS freemodbus)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_tcp_slave)

View File

@@ -0,0 +1,85 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# Modbus Slave Example
The stack located in `modbus` folder and includes support for ESP32 target chips. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information). 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 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 esp-modbus 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.

View File

@@ -0,0 +1,4 @@
set(PROJECT_NAME "modbus_tcp_slave")
idf_component_register(SRCS "tcp_slave.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,12 @@
menu "Modbus Example Configuration"
config MB_SLAVE_ADDR
int "Modbus slave address"
range 1 247 if !FMB_TCP_UID_ENABLED
range 0 247 if FMB_TCP_UID_ENABLED
default 1
help
This is the Modbus slave address in the network.
The address is used as an index to resolve slave ip address.
endmenu

View File

@@ -0,0 +1,11 @@
dependencies:
idf: ">=5.0"
espressif/esp-modbus:
version: "^2.0.0"
override_path: "../../../../"
espressif/mdns:
version: "^1.0.0"
mb_example_common:
path: "../../../mb_example_common"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@@ -0,0 +1,415 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// FreeModbus Slave Example ESP32
#include <stdio.h>
#include "esp_err.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.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)
// 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_AREA0_SIZE ((size_t)((HOLD_OFFSET(holding_data4) - HOLD_OFFSET(holding_data0)) << 1))
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
#define MB_REG_HOLDING_START_AREA1_SIZE ((size_t)((HOLD_OFFSET(holding_area1_end) - HOLD_OFFSET(holding_data4)) << 1))
#define MB_REG_HOLDING_START_AREA2 (HOLD_OFFSET(holding_u8_a))
#define MB_REG_HOLDING_START_AREA2_SIZE ((size_t)((HOLD_OFFSET(holding_area2_end) - HOLD_OFFSET(holding_u8_a)) << 1))
#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)
#define MB_TEST_VALUE (12345.0)
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
static const char *TAG = "SLAVE_TEST";
static void *slave_handle = NULL;
// 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;
#if CONFIG_FMB_EXT_TYPE_SUPPORT
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[0], (uint8_t)0x55);
mb_set_uint8_a((val_16_arr *)&holding_reg_params.holding_u8_a[1], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[0], (uint8_t)0x55);
mb_set_uint8_b((val_16_arr *)&holding_reg_params.holding_u8_b[1], (uint8_t)0x55);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[1], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ab((val_16_arr *)&holding_reg_params.holding_u16_ab[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[0], (uint16_t)MB_TEST_VALUE);
mb_set_uint16_ba((val_16_arr *)&holding_reg_params.holding_u16_ba[1], (uint16_t)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[0], (float)MB_TEST_VALUE);
mb_set_float_abcd((val_32_arr *)&holding_reg_params.holding_float_abcd[1], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[0], (float)MB_TEST_VALUE);
mb_set_float_cdab((val_32_arr *)&holding_reg_params.holding_float_cdab[1], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[0], (float)MB_TEST_VALUE);
mb_set_float_badc((val_32_arr *)&holding_reg_params.holding_float_badc[1], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[0], (float)MB_TEST_VALUE);
mb_set_float_dcba((val_32_arr *)&holding_reg_params.holding_float_dcba[1], (float)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_abcd((val_32_arr *)&holding_reg_params.holding_uint32_abcd[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_cdab((val_32_arr *)&holding_reg_params.holding_uint32_cdab[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_badc((val_32_arr *)&holding_reg_params.holding_uint32_badc[1], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[0], (uint32_t)MB_TEST_VALUE);
mb_set_uint32_dcba((val_32_arr *)&holding_reg_params.holding_uint32_dcba[1], (uint32_t)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[0], (double)MB_TEST_VALUE);
mb_set_double_abcdefgh((val_64_arr *)&holding_reg_params.holding_double_abcdefgh[1], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[0], (double)MB_TEST_VALUE);
mb_set_double_hgfedcba((val_64_arr *)&holding_reg_params.holding_double_hgfedcba[1], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[0], (double)MB_TEST_VALUE);
mb_set_double_ghefcdab((val_64_arr *)&holding_reg_params.holding_double_ghefcdab[1], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[0], (double)MB_TEST_VALUE);
mb_set_double_badcfehg((val_64_arr *)&holding_reg_params.holding_double_badcfehg[1], (double)MB_TEST_VALUE);
#endif
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
(void)mbc_slave_check_event(slave_handle, MB_READ_WRITE_MASK);
ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
// Filter events and process them accordingly
if(reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
// Get parameter information from parameter queue
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
rw_str,
(unsigned)reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(int)reg_info.address,
(unsigned)reg_info.size);
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
{
(void)mbc_slave_unlock(slave_handle);
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;
ESP_LOGI(TAG, "Riched maximum value");
}
(void)mbc_slave_unlock(slave_handle);
}
} else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
ESP_LOGI(TAG, "DISCRETE READ (%" PRIu32 " us): ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
} else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
ESP_LOGI(TAG, "COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
rw_str,
reg_info.time_stamp,
(unsigned)reg_info.mb_offset,
(unsigned)reg_info.type,
(uint32_t)reg_info.address,
(unsigned)reg_info.size);
if (coil_reg_params.coils_port1 == 0xFF) {
ESP_LOGI(TAG, "Stop polling.");
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).",
(int)result);
result = esp_netif_init();
MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"esp_netif_init fail, returns(0x%x).",
(int)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).",
(int)result);
// 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).",
(int)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).",
(int)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).",
(int)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).",
(int)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).",
(int)err);
err = nvs_flash_deinit();
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"nvs_flash_deinit fail, returns(0x%x).",
(int)err);
return err;
}
// Modbus slave initialization
static esp_err_t slave_init(mb_communication_info_t *pcomm_info)
{
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
// Initialization of Modbus controller
esp_err_t err = mbc_slave_create_tcp(pcomm_info, &slave_handle);
MB_RETURN_ON_FALSE((err == ESP_OK && slave_handle != NULL), ESP_ERR_INVALID_STATE,
TAG,
"mb controller create fail.");
// 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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)err);
#if CONFIG_FMB_EXT_TYPE_SUPPORT
// The extended parameters register area
reg_area.type = MB_PARAM_HOLDING;
reg_area.start_offset = MB_REG_HOLDING_START_AREA2;
reg_area.address = (void*)&holding_reg_params.holding_u8_a;
reg_area.size = MB_REG_HOLDING_START_AREA2_SIZE;
err = mbc_slave_set_descriptor(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)err);
#endif
// 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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)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(slave_handle, reg_area);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_set_descriptor fail, returns(0x%x).",
(int)err);
// Set values into known state
setup_reg_data();
// Starts of modbus controller and stack
err = mbc_slave_start(slave_handle);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_start fail, returns(0x%x).",
(int)err);
vTaskDelay(5);
return err;
}
static esp_err_t slave_destroy(void)
{
esp_err_t err = mbc_slave_delete(slave_handle);
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
TAG,
"mbc_slave_destroy fail, returns(0x%x).",
(int)err);
return err;
}
// An example application of Modbus slave. It is based on esp-modbus 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 tcp_slave_config = {
.tcp_opts.port = MB_TCP_PORT_NUMBER,
.tcp_opts.mode = MB_TCP,
#if !CONFIG_EXAMPLE_CONNECT_IPV6
.tcp_opts.addr_type = MB_IPV4,
#else
.tcp_opts.addr_type = MB_IPV6,
#endif
.tcp_opts.ip_addr_table = NULL, // Bind to any address
.tcp_opts.ip_netif_ptr = (void*)get_example_netif(),
.tcp_opts.uid = MB_SLAVE_ADDR
};
ESP_ERROR_CHECK(slave_init(&tcp_slave_config));
// 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());
}

View File

@@ -0,0 +1,32 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_FMB_EXT_TYPE_SUPPORT=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE=4096
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
CONFIG_ETH_PHY_INTERFACE_RMII=y
CONFIG_ETH_USE_SPI_ETHERNET=n

View File

@@ -0,0 +1,19 @@
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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_EXT_TYPE_SUPPORT=y
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=3000
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_FMB_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"

View File

@@ -0,0 +1,21 @@
#
# Modbus configuration
#
CONFIG_FMB_COMM_MODE_TCP_EN=y
CONFIG_FMB_TCP_PORT_DEFAULT=1502
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_TCP_UID_ENABLED=n
CONFIG_MB_SLAVE_ADDR=1
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_EXAMPLE_CONNECT_IPV6=n
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
CONFIG_EXAMPLE_WIFI_SSID="${CI_WIFI_SSID}"
CONFIG_EXAMPLE_WIFI_PASSWORD="${CI_WIFI_PASSW}"
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CONNECT_WIFI=y

View File

@@ -0,0 +1,77 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
# main thread in Multi DUT case.
import logging
import os,sys
import subprocess
# pytest required libraries
import pytest
from conftest import ModbusTestDut, Stages
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
TEST_ROBOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tools/robot'))
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = 'modbus_test'
logger = logging.getLogger(LOGGER_NAME)
if os.name == 'nt':
CLOSE_FDS = False
else:
CLOSE_FDS = True
pattern_dict_slave = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: '
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s'
r'ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'),
Stages.STACK_PAR_FAIL: (r'E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet'),
Stages.STACK_DESTROY: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed).')}
@pytest.mark.esp32
@pytest.mark.multi_dut_modbus_tcp
@pytest.mark.parametrize('config', ['ethernet'], indirect=True)
@pytest.mark.parametrize(
'count, app_path', [
(1, f'{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}')
],
indirect=True
)
def test_modbus_tcp_host_to_slave_communication(app_path, dut: ModbusTestDut) -> None:
logger.info('DUT: %s start.', dut.dut_get_name())
dut_slave_ip_address = dut.dut_get_ip()
assert dut_slave_ip_address is not None, "The DUT could not get IP address. Abort."
dut_slave_ip_port = dut.app.sdkconfig.get('FMB_TCP_PORT_DEFAULT')
assert dut_slave_ip_port is not None, f"DUT port is not correct: {dut_slave_ip_port}"
logger.info(f'Start test for the slave: {app_path}, {dut_slave_ip_address}:{dut_slave_ip_port}')
try:
cmd = 'robot ' + \
f'--variable MODBUS_DEF_SERVER_IP:{dut_slave_ip_address} ' + \
f'--variable MODBUS_DEF_SERVER_PORT:{dut_slave_ip_port} ' + \
f'{TEST_ROBOT_DIR}/ModbusTestSuite.robot'
p = subprocess.Popen(cmd,
stdin=None, stdout=None, stderr=None,
shell=True,
close_fds=CLOSE_FDS
)
dut.dut_test_start(dictionary=pattern_dict_slave)
p.wait()
logger.info(f'Test for the node: {dut_slave_ip_address} is completed.')
dut.dut_check_errors()
except subprocess.CalledProcessError as e:
logging.error('robot framework fail with error: %d', e.returncode)
logging.debug("Command ran: '%s'", e.cmd)
logging.debug('Command out:')
logging.debug(e.output)
logging.error('Check the correctneess of the suite script.')
raise e

View File

@@ -0,0 +1,77 @@
# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This is the script to reproduce the issue when the expect() is called from
# main thread in Multi DUT case.
import logging
import os
from typing import Tuple
import pytest
from conftest import ModbusTestDut, Stages
pattern_dict_slave = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_SLAVE_PORT: Socket \(#[0-9]+\), accept client connection from address: '
r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_START: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I\s\(([0-9]+)\) SLAVE_TEST: ([A-Z]+ [A-Z]+) \([a-zA-Z0-9_]+ us\),\s'
r'ADDR:([0-9]+), TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'),
Stages.STACK_PAR_FAIL: (r'E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet'),
Stages.STACK_DESTROY: (r'I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed).')}
pattern_dict_master = {Stages.STACK_IPV4: (r'I \([0-9]+\) example_[a-z]+: - IPv4 address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})'),
Stages.STACK_IPV6: (r'I \([0-9]+\) example_[a-z]+: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})'),
Stages.STACK_INIT: (r'I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized)'),
Stages.STACK_CONNECT: (r'I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling'),
Stages.STACK_START: (r'I \(([0-9]+)\) MASTER_TEST: (Start modbus test)'),
Stages.STACK_PAR_OK: (r'I \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)'
r'\s\([a-zA-Z\_\%\/]+\) value =[a-zA-Z0-9\.\s]* \((0x[a-zA-Z0-9]+)\)[,\sa-z]+ successful'),
Stages.STACK_PAR_FAIL: (r'.*E \(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+\s\(([a-zA-Z0-9_]+)\)\s'
r'read fail, err = [x0-9]+ \([_A-Z]+\)'),
Stages.STACK_DESTROY: (r'I \(([0-9]+)\) MASTER_TEST: (Destroy master)...')}
LOG_LEVEL = logging.DEBUG
LOGGER_NAME = 'modbus_test'
CONFORMANCE_TEST_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../tools/robot'))
logger = logging.getLogger(LOGGER_NAME)
test_configs = [
'wifi',
'ethernet'
]
@pytest.mark.esp32
@pytest.mark.multi_dut_modbus_tcp
@pytest.mark.parametrize('config', test_configs, indirect=True)
@pytest.mark.parametrize(
'count, app_path', [
(2, f'{os.path.join(os.path.dirname(__file__), "mb_tcp_master")}|{os.path.join(os.path.dirname(__file__), "mb_tcp_slave")}')
],
indirect=True
)
def test_modbus_tcp_communication(dut: Tuple[ModbusTestDut, ModbusTestDut]) -> None:
dut_slave = dut[1]
dut_master = dut[0]
logger.info('DUT: %s start.', dut_master.dut_get_name())
logger.info('DUT: %s start.', dut_slave.dut_get_name())
dut_slave_ip_address = dut_slave.dut_get_ip()
dut_master.dut_send_ip(dut_slave_ip_address)
dut_slave.dut_test_start(dictionary=pattern_dict_slave)
dut_master.dut_test_start(dictionary=pattern_dict_master)
dut_slave.dut_check_errors()
dut_master.dut_check_errors()
@pytest.mark.multi_dut_modbus_generic
@pytest.mark.parametrize('config', ['dummy_config'])
def test_modbus_tcp_generic(config) -> None:
logger.info('The generic tcp example tests are not provided yet.')

16
idf_component.yml Normal file
View File

@@ -0,0 +1,16 @@
version: "2.0.1"
description: ESP-MODBUS is the official Modbus library for Espressif SoCs.
url: https://github.com/espressif/esp-modbus
dependencies:
idf: ">=5.0"
files:
exclude:
- "docs/**/*"
- "docs"
- "test/**/*"
- "test"
- "pytest_embedded_log/**/*"
- "build*/**/*"
- "**/*.zip"
- "__pycache__/**/*"

23
linker.lf Normal file
View File

@@ -0,0 +1,23 @@
[mapping:esp_modbus]
archive: libesp-modbus.a
entries:
* (default)
port_event: mb_port_event_set_err_type (noflash_text)
port_event: mb_port_event_post (noflash_text)
port_other: lock_obj (noflash_text)
port_other: unlock_obj (noflash_text)
if FMB_TIMER_USE_ISR_DISPATCH_METHOD = y:
# tcp_master: mbm_tcp_transp_timer_expired (noflash_text)
tcp_slave: mbs_tcp_transp_timer_expired (noflash_text)
# port_tcp_slave: mbs_port_timer_expired (noflash_text)
port_tcp_master: mbm_port_timer_expired (noflash_text)
port_timer: timer_alarm_cb (noflash_text)
port_timer: mb_port_set_cur_timer_mode (noflash_text)
port_timer: mb_port_get_cur_timer_mode (noflash_text)
port_timer: mb_port_timer_disable (noflash_text)
ascii_master: mbm_ascii_transp_timer_expired (noflash_text)
ascii_slave: mbs_ascii_transp_timer_expired (noflash_text)
rtu_master: mbm_rtu_transp_timer_expired (noflash_text)
rtu_slave: mbs_rtu_transp_timer_expired (noflash_text)

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// Stack callback functions prototypes
#pragma once
#include "mb_common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef mb_err_enum_t (*reg_input_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t);
typedef mb_err_enum_t (*reg_holding_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t, mb_reg_mode_enum_t);
typedef mb_err_enum_t (*reg_coils_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t, mb_reg_mode_enum_t);
typedef mb_err_enum_t (*reg_discrete_cb)(mb_base_t*, uint8_t *, uint16_t, uint16_t);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,716 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h" // for esp_err_t
#include "mbc_master.h" // for master interface define
#include "esp_modbus_master.h" // for public interface defines
static const char TAG[] __attribute__((unused)) = "MB_CONTROLLER_MASTER";
// This file implements public API for Modbus master controller.
/**
* Modbus controller delete function
*/
esp_err_t mbc_master_delete(void *ctx)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbm_controller->delete, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
error = mbm_controller->delete (ctx);
MB_RETURN_ON_FALSE((error == ESP_OK), error,
TAG, "Master delete failure, error=(0x%x).", (uint16_t)error);
return error;
}
/**
* Critical section lock function
*/
esp_err_t mbc_master_lock(void *ctx)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mbm_controller->mb_base;
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
CRITICAL_SECTION_LOCK(pmb_obj->lock);
return ESP_OK;
}
/**
* Critical section unlock function
*/
esp_err_t mbc_master_unlock(void *ctx)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mbm_controller->mb_base;
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
CRITICAL_SECTION_UNLOCK(pmb_obj->lock);
return ESP_OK;
}
esp_err_t mbc_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t **param_info)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->get_cid_info && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->get_cid_info(ctx, cid, param_info);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master get cid info failure, error=(0x%x).", (uint16_t)error);
return error;
}
/**
* Set parameter value for characteristic selected by name and cid
*/
esp_err_t mbc_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->set_parameter && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
error = mbm_controller->set_parameter(ctx, cid, value, type);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master set parameter failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/**
* Set parameter value for characteristic selected by name and cid
*/
esp_err_t mbc_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->set_parameter_with && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
error = mbm_controller->set_parameter_with(ctx, cid, uid, value, type);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master set parameter failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/**
* Get parameter data for corresponding characteristic
*/
esp_err_t mbc_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->get_parameter && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->get_parameter(ctx, cid, value, type);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master get parameter failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return error;
}
/**
* Get parameter data for corresponding characteristic
*/
esp_err_t mbc_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->get_parameter_with && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->get_parameter_with(ctx, cid, uid, value, type);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master get parameter failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return error;
}
/**
* Send custom Modbus request defined as mb_param_request_t structure
*/
esp_err_t mbc_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbm_controller->send_request && mbm_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->send_request(ctx, request, data_ptr);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master send request failure error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/**
* Set Modbus parameter description table
*/
esp_err_t mbc_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor,
const uint16_t num_elements)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbm_controller->set_descriptor,
ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly configured.");
error = mbm_controller->set_descriptor(ctx, descriptor, num_elements);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master set descriptor failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/**
* Modbus controller stack start function
*/
esp_err_t mbc_master_start(void *ctx)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbm_controller->start, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
error = mbm_controller->start(ctx);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master start failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/**
* Modbus controller stack stop function
*/
esp_err_t mbc_master_stop(void *ctx)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
mbm_controller_iface_t *mbm_controller = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbm_controller->stop, ESP_ERR_INVALID_STATE, TAG,
"Master interface is not correctly initialized.");
error = mbm_controller->stop(ctx);
MB_RETURN_ON_FALSE((error == ESP_OK), error, TAG,
"Master stop failure, error=(0x%x) (%s).",
(uint16_t)error, esp_err_to_name(error));
return ESP_OK;
}
/* ----------------------- Callback functions for Modbus stack ---------------------------------*/
// These are executed by modbus stack to read appropriate type of registers.
/**
* Modbus master input register callback function.
*
* @param ctx interface context pointer
* @param reg_buffer input register buffer
* @param reg_addr input register address
* @param num_regs input register number
*
* @return result
*/
// Callback function for reading of MB Input Registers
// mbm_reg_input_cb_serial
mb_err_enum_t mbc_reg_input_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr, uint16_t num_regs)
{
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG,
"Master stack processing error.");
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
// Number of input registers to be transferred
uint16_t num_input_regs = (uint16_t)mbm_opts->reg_buffer_size;
uint8_t *input_reg_buf = (uint8_t *)mbm_opts->reg_buffer_ptr; // Get instance address
uint16_t regs_cnt = num_regs;
mb_err_enum_t status = MB_ENOERR;
// If input or configuration parameters are incorrect then return an error to stack layer
if ((input_reg_buf) && (num_regs >= 1) && (num_input_regs == regs_cnt))
{
CRITICAL_SECTION(inst->lock)
{
while (regs_cnt > 0)
{
_XFER_2_RD(input_reg_buf, reg_buffer);
regs_cnt -= 1;
}
}
}
else
{
status = MB_ENOREG;
}
return status;
}
/**
* Modbus master holding register callback function.
*
* @param ctx interface context pointer
* @param reg_buffer holding register buffer
* @param reg_addr holding register address
* @param num_regs holding register number
* @param mode read or write
*
* @return result
*/
// Callback function for reading of MB Holding Registers
// Executed by stack when request to read/write holding registers is received
// mbm_reg_holding_cb_serial
mb_err_enum_t mbc_reg_holding_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
uint16_t num_regs, mb_reg_mode_enum_t mode)
{
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
uint16_t num_hold_regs = (uint16_t)mbm_opts->reg_buffer_size;
uint8_t *holding_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
mb_err_enum_t status = MB_ENOERR;
uint16_t regs_cnt = num_regs;
// Check input and configuration parameters for correctness
if ((holding_buf) && (num_hold_regs == num_regs) && (num_regs >= 1))
{
switch (mode)
{
case MB_REG_WRITE:
CRITICAL_SECTION(inst->lock)
{
while (regs_cnt > 0)
{
_XFER_2_RD(reg_buffer, holding_buf);
regs_cnt -= 1;
}
}
break;
case MB_REG_READ:
CRITICAL_SECTION(inst->lock)
{
while (regs_cnt > 0)
{
_XFER_2_WR(holding_buf, reg_buffer);
holding_buf += 2;
regs_cnt -= 1;
}
}
break;
}
}
else
{
status = MB_ENOREG;
}
return status;
}
/**
* Modbus master coils callback function.
*
* @param ctx interface context pointer
* @param reg_buffer coils buffer
* @param reg_addr coils address
* @param ncoils coils number
* @param mode read or write
*
* @return result
*/
// Callback function for reading of MB Coils Registers
// mbm_reg_coils_cb_serial
mb_err_enum_t mbc_reg_coils_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
uint16_t ncoils, mb_reg_mode_enum_t mode)
{
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
uint16_t num_coil_regs = (uint16_t)mbm_opts->reg_buffer_size;
uint8_t *coils_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
mb_err_enum_t status = MB_ENOERR;
uint16_t reg_index;
uint16_t coils_cnt = ncoils;
reg_addr--; // The address is already + 1
if ((num_coil_regs >= 1) && (coils_buf) && (ncoils == num_coil_regs))
{
reg_index = (reg_addr % 8);
switch (mode)
{
case MB_REG_WRITE:
CRITICAL_SECTION(inst->lock)
{
while (coils_cnt > 0)
{
uint8_t result = mb_util_get_bits((uint8_t *)coils_buf, reg_index - (reg_addr % 8), 1);
mb_util_set_bits(reg_buffer, reg_index - (reg_addr % 8), 1, result);
reg_index++;
coils_cnt--;
}
}
break;
case MB_REG_READ:
CRITICAL_SECTION(inst->lock)
{
while (coils_cnt > 0)
{
uint8_t result = mb_util_get_bits(reg_buffer, reg_index - (reg_addr % 8), 1);
mb_util_set_bits((uint8_t *)coils_buf, reg_index - (reg_addr % 8), 1, result);
reg_index++;
coils_cnt--;
}
}
break;
} // switch ( mode )
}
else
{
// If the configuration or input parameters are incorrect then return error to stack
status = MB_ENOREG;
}
return status;
}
/**
* Modbus master discrete callback function.
*
* @param ctx - pointer to interface structure
* @param reg_buffer discrete buffer
* @param reg_addr discrete address
* @param n_discrete discrete number
*
* @return result
*/
// Callback function for reading of MB Discrete Input Registers
// mbm_reg_discrete_cb_serial
mb_err_enum_t mbc_reg_discrete_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t reg_addr,
uint16_t n_discrete)
{
MB_RETURN_ON_FALSE((reg_buffer), MB_EINVAL, TAG, "Master stack processing error.");
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(MB_MASTER_GET_IFACE_FROM_BASE(inst));
uint16_t num_discr_regs = (uint16_t)mbm_opts->reg_buffer_size;
uint8_t *discr_buf = (uint8_t *)mbm_opts->reg_buffer_ptr;
mb_err_enum_t status = MB_ENOERR;
uint16_t bit_index, num_reg;
uint8_t *temp_discr_buf;
num_reg = n_discrete;
temp_discr_buf = (uint8_t *)discr_buf;
// It is already plus one in Modbus function method.
reg_addr--;
if ((num_discr_regs >= 1) && (discr_buf) && (n_discrete >= 1) && (n_discrete == num_discr_regs))
{
bit_index = (uint16_t)(reg_addr) % 8; // Get bit index
CRITICAL_SECTION(inst->lock)
{
while (num_reg > 0)
{
uint8_t result = mb_util_get_bits(reg_buffer, bit_index - (reg_addr % 8), 1);
mb_util_set_bits(temp_discr_buf, bit_index - (reg_addr % 8), 1, result);
bit_index++;
num_reg--;
}
}
}
else
{
status = MB_ENOREG;
}
return status;
}
// Helper function to set parameter buffer according to its type
esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param_type, size_t param_size)
{
esp_err_t err = ESP_OK;
MB_RETURN_ON_FALSE((src), ESP_ERR_INVALID_STATE, TAG,"incorrect data pointer.");
MB_RETURN_ON_FALSE((dest), ESP_ERR_INVALID_STATE, TAG,"incorrect data pointer.");
void *pdest = dest;
void *psrc = src;
// Transfer parameter data into value of characteristic
switch(param_type)
{
case PARAM_TYPE_U8:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8) {
*((uint8_t *)pdest) = *((uint8_t*)psrc);
}
break;
case PARAM_TYPE_U16:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
*((uint16_t *)pdest) = *((uint16_t*)psrc);
}
break;
case PARAM_TYPE_U32:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
*((uint32_t *)pdest) = *((uint32_t*)psrc);
}
break;
case PARAM_TYPE_FLOAT:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
*((float *)pdest) = *(float*)psrc;
}
break;
case PARAM_TYPE_ASCII:
case PARAM_TYPE_BIN:
memcpy((void *)dest, (void*)src, (size_t)param_size);
break;
#if CONFIG_FMB_EXT_TYPE_SUPPORT
case PARAM_TYPE_I8_A:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
mb_set_int8_a((val_16_arr *)pdest, (*(int8_t*)psrc));
ESP_LOGV(TAG, "Convert uint8 B[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_I8_B:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
mb_set_int8_b((val_16_arr *)pdest, (int8_t)((*(uint16_t*)psrc) >> 8));
ESP_LOGV(TAG, "Convert int8 A[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_U8_A:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
mb_set_uint8_a((val_16_arr *)pdest, (*(uint8_t*)psrc));
ESP_LOGV(TAG, "Convert uint8 A[%d] 0x%02" PRIx16 " = %02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_U8_B:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U8_REG) {
uint8_t data = (uint8_t)((*(uint16_t*)psrc) >> 8);
mb_set_uint8_b((val_16_arr *)pdest, data);
ESP_LOGV(TAG, "Convert uint8 B[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_I16_AB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I16) {
mb_set_int16_ab((val_16_arr *)pdest, *(int16_t*)psrc);
ESP_LOGV(TAG, "Convert int16 AB[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_I16_BA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I16) {
mb_set_int16_ba((val_16_arr *)pdest, *(int16_t*)psrc);
ESP_LOGV(TAG, "Convert int16 BA[%d] 0x%04" PRIx16 " = 0x%04" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_U16_AB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
mb_set_uint16_ab((val_16_arr *)pdest, *(uint16_t*)psrc);
ESP_LOGV(TAG, "Convert uint16 AB[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_U16_BA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U16) {
mb_set_uint16_ba((val_16_arr *)pdest, *(uint16_t*)psrc);
ESP_LOGV(TAG, "Convert uint16 BA[%d] 0x%02" PRIx16 " = 0x%02" PRIx16, i, *(uint16_t *)psrc, *(uint16_t *)pdest);
}
break;
case PARAM_TYPE_I32_ABCD:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
mb_set_int32_abcd((val_32_arr *)pdest, *(int32_t *)psrc);
ESP_LOGV(TAG, "Convert int32 ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_U32_ABCD:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
mb_set_uint32_abcd((val_32_arr *)pdest, *(uint32_t *)psrc);
ESP_LOGV(TAG, "Convert uint32 ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_FLOAT_ABCD:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
mb_set_float_abcd((val_32_arr *)pdest, *(float *)psrc);
ESP_LOGV(TAG, "Convert float ABCD[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_I32_CDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
mb_set_int32_cdab((val_32_arr *)pdest, *(int32_t *)psrc);
ESP_LOGV(TAG, "Convert int32 CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_U32_CDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
mb_set_uint32_cdab((val_32_arr *)pdest, *(uint32_t *)psrc);
ESP_LOGV(TAG, "Convert uint32 CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_FLOAT_CDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
mb_set_float_cdab((val_32_arr *)pdest, *(float *)psrc);
ESP_LOGV(TAG, "Convert float CDAB[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_I32_BADC:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
mb_set_int32_badc((val_32_arr *)pdest, *(int32_t *)psrc);
ESP_LOGV(TAG, "Convert int32 BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_U32_BADC:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
mb_set_uint32_badc((val_32_arr *)pdest, *(uint32_t *)psrc);
ESP_LOGV(TAG, "Convert uint32 BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_FLOAT_BADC:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
mb_set_float_badc((val_32_arr *)pdest, *(float *)psrc);
ESP_LOGV(TAG, "Convert float BADC[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_I32_DCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I32) {
mb_set_int32_dcba((val_32_arr *)pdest, *(int32_t *)psrc);
ESP_LOGV(TAG, "Convert int32 DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_U32_DCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U32) {
mb_set_uint32_dcba((val_32_arr *)pdest, *(uint32_t *)psrc);
ESP_LOGV(TAG, "Convert uint32 DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_FLOAT_DCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_FLOAT) {
mb_set_float_dcba((val_32_arr *)pdest, *(float *)psrc);
ESP_LOGV(TAG, "Convert float DCBA[%d] 0x%04" PRIx32 " = 0x%04" PRIx32, i, *(uint32_t *)psrc, *(uint32_t *)pdest);
}
break;
case PARAM_TYPE_I64_ABCDEFGH:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
mb_set_int64_abcdefgh((val_64_arr *)pdest, *(int64_t *)psrc);
ESP_LOGV(TAG, "Convert int64 ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_U64_ABCDEFGH:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
mb_set_uint64_abcdefgh((val_64_arr *)pdest, *(uint64_t *)psrc);
ESP_LOGV(TAG, "Convert double ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_DOUBLE_ABCDEFGH:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
mb_set_double_abcdefgh((val_64_arr *)pdest, *(double *)psrc);
ESP_LOGV(TAG, "Convert double ABCDEFGH[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_I64_HGFEDCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
mb_set_int64_hgfedcba((val_64_arr *)pdest, *(int64_t *)psrc);
ESP_LOGV(TAG, "Convert int64 HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_U64_HGFEDCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
mb_set_uint64_hgfedcba((val_64_arr *)pdest, *(uint64_t *)psrc);
ESP_LOGV(TAG, "Convert double HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_DOUBLE_HGFEDCBA:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
mb_set_double_hgfedcba((val_64_arr *)pdest, *(double *)psrc);
ESP_LOGV(TAG, "Convert double HGFEDCBA[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_I64_GHEFCDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
mb_set_int64_ghefcdab((val_64_arr *)pdest, *(int64_t *)psrc);
ESP_LOGV(TAG, "Convert int64 GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_U64_GHEFCDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
mb_set_uint64_ghefcdab((val_64_arr *)pdest, *(uint64_t *)psrc);
ESP_LOGV(TAG, "Convert uint64 GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_DOUBLE_GHEFCDAB:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
mb_set_double_ghefcdab((val_64_arr *)pdest, *(double *)psrc);
ESP_LOGV(TAG, "Convert double GHEFCDAB[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_I64_BADCFEHG:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_I64) {
mb_set_int64_badcfehg((val_64_arr *)pdest, *(int64_t *)psrc);
ESP_LOGV(TAG, "Convert int64 BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_U64_BADCFEHG:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_U64) {
mb_set_uint64_badcfehg((val_64_arr *)pdest, *(uint64_t *)psrc);
ESP_LOGV(TAG, "Convert uint64 BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
case PARAM_TYPE_DOUBLE_BADCFEHG:
for MB_EACH_ELEM(psrc, pdest, param_size, PARAM_SIZE_DOUBLE) {
mb_set_double_badcfehg((val_64_arr *)pdest, *(double *)psrc);
ESP_LOGV(TAG, "Convert double BADCFEHG[%d] 0x%" PRIx64 " = 0x%" PRIx64, i, *(uint64_t *)psrc, *(uint64_t *)pdest);
}
break;
#endif
default:
ESP_LOGE(TAG, "%s: Incorrect param type (%u).",
__FUNCTION__, (unsigned)param_type);
err = ESP_ERR_NOT_SUPPORTED;
break;
}
return err;
}

View File

@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h" // for esp_err_t
#include "mbc_master.h" // for master interface define
#include "esp_modbus_master.h" // for public slave defines
#include "mbc_serial_master.h" // for public interface defines
#include "sdkconfig.h" // for KConfig defines
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
/**
* Initialization of Modbus master serial
*/
esp_err_t mbc_master_create_serial(mb_communication_info_t *config, void **handler)
{
void *ctx = NULL;
esp_err_t error = ESP_ERR_NOT_SUPPORTED;
switch(config->mode) {
case MB_RTU:
case MB_ASCII:
error = mbc_serial_master_create(config, &ctx);
break;
default:
return ESP_ERR_NOT_SUPPORTED;
}
if ((ctx) && (error == ESP_OK)) {
//mbc_master_init_iface(ctx);
*handler = ctx;
}
return error;
}
#endif

View File

@@ -0,0 +1,28 @@
/*
* 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
#include "sdkconfig.h"
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
/**
* Initialization of Modbus TCP Master controller interface
*/
esp_err_t mbc_master_create_tcp(mb_communication_info_t *config, void **handler)
{
void *ctx = NULL;
esp_err_t error = mbc_tcp_master_create(config, &ctx);
if ((ctx) && (error == ESP_OK)) {
*handler = ctx;
}
return error;
}
#endif

View File

@@ -0,0 +1,517 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h" // for esp_err_t
#include "esp_timer.h" // for esp_timer_get_time()
#include "sdkconfig.h" // for KConfig defines
#include "mbc_slave.h" // for slave private type definitions
#include "esp_modbus_common.h" // for common defines
#include "esp_modbus_slave.h" // for public slave defines
#include "mb_utils.h" // for stack bit setting utilities
#ifdef CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
#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_CONTROLLER_SLAVE_ID (CONFIG_FMB_CONTROLLER_SLAVE_ID)
#define MB_SLAVE_ID_SHORT (MB_ID_BYTE3(MB_CONTROLLER_SLAVE_ID))
// Slave ID constant
static uint8_t mb_slave_id[] = { MB_ID_BYTE0(MB_CONTROLLER_SLAVE_ID),
MB_ID_BYTE1(MB_CONTROLLER_SLAVE_ID),
MB_ID_BYTE2(MB_CONTROLLER_SLAVE_ID) };
#endif
static const char TAG[] __attribute__((unused)) = "MB_CONTROLLER_SLAVE";
// Searches the register in the area specified by type, returns descriptor if found, else NULL
static mb_descr_entry_t *mbc_slave_find_reg_descriptor(void *ctx, mb_param_type_t type, uint16_t addr, size_t regs)
{
mb_descr_entry_t *it;
uint16_t reg_size = 0;
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
if (LIST_EMPTY(&mbs_opts->area_descriptors[type])) {
return NULL;
}
// search for the register in each area
for (it = LIST_FIRST(&mbs_opts->area_descriptors[type]); it != NULL; it = LIST_NEXT(it, entries)) {
reg_size = REG_SIZE(type, it->size);
if ((addr >= it->start_offset)
&& (it->p_data)
&& (regs >= 1)
&& ((addr + regs) <= (it->start_offset + reg_size))
&& (reg_size >= 1)) {
return it;
}
}
return NULL;
}
static void mbc_slave_free_descriptors(void *ctx)
{
mb_descr_entry_t *it;
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
for (int descr_type = 0; descr_type < MB_PARAM_COUNT; descr_type++) {
while ((it = LIST_FIRST(&mbs_opts->area_descriptors[descr_type]))) {
LIST_REMOVE(it, entries);
free(it);
}
}
}
void mbc_slave_init_iface(void *ctx)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
// Initialize list head for register areas
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_INPUT]);
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_HOLDING]);
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_COIL]);
LIST_INIT(&mbs_opts->area_descriptors[MB_PARAM_DISCRETE]);
}
/**
* Modbus controller delete function
*/
esp_err_t mbc_slave_delete(void *ctx)
{
esp_err_t error = ESP_OK;
// Is initialization done?
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
// Check if interface has been initialized
MB_RETURN_ON_FALSE(mbs_controller->delete,
ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly configured.");
// Call the slave controller destroy function
error = mbs_controller->delete(ctx);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Slave delete failure error=(0x%x).", (uint16_t)error);
}
// Destroy all opened descriptors
mbc_slave_free_descriptors(ctx);
free(mbs_controller);
mbs_controller = NULL;
return error;
}
/**
* Critical section lock function
*/
esp_err_t mbc_slave_lock(void *ctx)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mbs_controller->mb_base;
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
CRITICAL_SECTION_LOCK(pmb_obj->lock);
return ESP_OK;
}
/**
* Critical section unlock function
*/
esp_err_t mbc_slave_unlock(void *ctx)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
mb_base_t *pmb_obj = (mb_base_t *)mbs_controller->mb_base;
MB_RETURN_ON_FALSE((pmb_obj && pmb_obj->lock), ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
CRITICAL_SECTION_UNLOCK(pmb_obj->lock);
return ESP_OK;
}
/**
* Start Modbus controller start function
*/
esp_err_t mbc_slave_start(void *ctx)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbs_controller->start, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly configured.");
#ifdef CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
// Set the slave ID if the KConfig option is selected
mb_err_enum_t status = mb_set_slv_id(mbs_controller->mb_base, MB_SLAVE_ID_SHORT, true, (uint8_t *)mb_slave_id, sizeof(mb_slave_id));
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG, "mb stack set slave ID failure.");
#endif
error = mbs_controller->start(ctx);
MB_RETURN_ON_FALSE((error == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"Slave start failure error=(0x%x).", (uint16_t)error);
mbs_controller->is_active = true;
return error;
}
/**
* Start Modbus controller stop function
*/
esp_err_t mbc_slave_stop(void *ctx)
{
esp_err_t error = ESP_OK;
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
MB_RETURN_ON_FALSE(mbs_controller->stop,
ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly configured.");
error = mbs_controller->stop(ctx);
MB_RETURN_ON_FALSE((error == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
"Slave stop failure error=(0x%x).", (uint16_t)error);
mbs_controller->is_active = false;
return error;
}
/**
* Blocking function to get event on parameter group change for application task
*/
mb_event_group_t mbc_slave_check_event(void *ctx, mb_event_group_t group)
{
MB_RETURN_ON_FALSE(ctx, MB_EVENT_NO_EVENTS, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbs_controller->check_event && mbs_controller->is_active),
MB_EVENT_NO_EVENTS, TAG,
"Slave interface is not correctly configured.");
mb_event_group_t event = mbs_controller->check_event(ctx, group);
return event;
}
/**
* Function to get notification about parameter change from application task
*/
esp_err_t mbc_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((mbs_controller->get_param_info && mbs_controller->is_active),
ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly configured.");
return mbs_controller->get_param_info(ctx, reg_info, timeout);
}
/**
* Function to set area descriptors for modbus parameters
*/
esp_err_t mbc_slave_set_descriptor(void *ctx, mb_register_area_descriptor_t descr_data)
{
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
esp_err_t error = ESP_OK;
mbs_controller_iface_t *mbs_controller = MB_SLAVE_GET_IFACE(ctx);
if (mbs_controller->set_descriptor) {
error = mbs_controller->set_descriptor(ctx, descr_data);
MB_RETURN_ON_FALSE((error == ESP_OK),
ESP_ERR_INVALID_STATE, TAG,
"Slave set descriptor failure error=(0x%x).",
(uint16_t)error);
} else {
mb_slave_options_t *mbs_opts = &mbs_controller->opts;
MB_RETURN_ON_FALSE((descr_data.size < MB_INST_MAX_SIZE) && (descr_data.size >= MB_INST_MIN_SIZE),
ESP_ERR_INVALID_ARG, TAG, "mb area size is incorrect.");
uint16_t reg_size = REG_SIZE(descr_data.type, descr_data.size);
// Check if the address is already in the descriptor list
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, descr_data.type, descr_data.start_offset, reg_size);
if (!it) {
// Start register exists in any area?
it = mbc_slave_find_reg_descriptor(ctx, descr_data.type, descr_data.start_offset, 1);
}
MB_RETURN_ON_FALSE((it == NULL), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor or already defined.");
mb_descr_entry_t *new_descr = (mb_descr_entry_t*) heap_caps_malloc(sizeof(mb_descr_entry_t),
MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
MB_RETURN_ON_FALSE(new_descr, ESP_ERR_NO_MEM, TAG, "mb can not allocate memory for descriptor.");
new_descr->start_offset = descr_data.start_offset;
new_descr->type = descr_data.type;
new_descr->p_data = descr_data.address;
new_descr->size = descr_data.size;
new_descr->access = descr_data.access;
LIST_INSERT_HEAD(&mbs_opts->area_descriptors[descr_data.type], new_descr, entries);
error = ESP_OK;
}
return error;
}
// The helper function to get time stamp in microseconds
static uint64_t mbc_slave_get_time_stamp(void)
{
uint64_t time_stamp = esp_timer_get_time();
return time_stamp;
}
// Helper function to send parameter information to application task
static esp_err_t mbc_slave_send_param_info(void *ctx, mb_event_group_t par_type, uint16_t mb_offset,
uint8_t *par_address, uint16_t par_size)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
esp_err_t error = ESP_FAIL;
mb_param_info_t par_info;
// Check if queue is not full the send parameter information
par_info.type = par_type;
par_info.size = par_size;
par_info.address = par_address;
par_info.time_stamp = mbc_slave_get_time_stamp();
par_info.mb_offset = mb_offset;
BaseType_t status = xQueueSend(mbs_opts->notification_queue_handle, &par_info, MB_PAR_INFO_TOUT);
if (pdTRUE == status) {
ESP_LOGD(TAG, "Queue send parameter info (type, address, size): %d, 0x%" PRIx32 ", %d",
(int)par_type, (uint32_t)par_address, (int)par_size);
error = ESP_OK;
} else if (errQUEUE_FULL == status) {
ESP_LOGD(TAG, "Parameter queue is overflowed.");
}
return error;
}
// Helper function to send notification
static esp_err_t mbc_slave_send_param_access_notification(void *ctx, mb_event_group_t event)
{
MB_RETURN_ON_FALSE(ctx, ESP_ERR_INVALID_STATE, TAG,
"Slave interface is not correctly initialized.");
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
esp_err_t err = ESP_FAIL;
mb_event_group_t bits = (mb_event_group_t)xEventGroupSetBits(mbs_opts->event_group_handle, (EventBits_t)event);
if (bits & event) {
ESP_LOGD(TAG, "The MB_REG_CHANGE_EVENT = 0x%.2x is set.", (int)event);
err = ESP_OK;
}
return err;
}
/*
* Below are the common slave read/write register callback functions
* The concrete slave port can override them using interface function pointers
*/
// Callback function for reading of MB Input Registers
mb_err_enum_t mbc_reg_input_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs)
{
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
mb_err_enum_t status = MB_ENOERR;
address--; // address of register is already +1
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_INPUT, address, n_regs);
if (it) {
uint16_t input_reg_start = (uint16_t)it->start_offset; // Get Modbus start address
uint8_t *input_buffer = (uint8_t *)it->p_data; // Get instance address
uint16_t regs = n_regs;
uint16_t reg_index;
// If input or configuration parameters are incorrect then return an error to stack layer
reg_index = (uint16_t)(address - input_reg_start);
reg_index <<= 1; // register Address to byte address
input_buffer += reg_index;
uint8_t *buffer_start = input_buffer;
CRITICAL_SECTION(inst->lock)
{
while (regs > 0) {
_XFER_2_RD(reg_buffer, input_buffer);
reg_index += 2;
regs -= 1;
}
}
// Send access notification
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_INPUT_REG_RD);
// Send parameter info to application task
(void)mbc_slave_send_param_info(ctx, MB_EVENT_INPUT_REG_RD, address,
(uint8_t *)buffer_start, n_regs);
} else {
status = MB_ENOREG;
}
return status;
}
// Callback function for reading of MB Holding Registers
// Executed by stack when request to read/write holding registers is received
mb_err_enum_t mbc_reg_holding_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode)
{
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
mb_err_enum_t status = MB_ENOERR;
uint16_t reg_index;
address--; // address of register is already +1
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_HOLDING, address, n_regs);
if (it) {
uint16_t reg_holding_start = (uint16_t)it->start_offset; // Get Modbus start address
uint8_t *holding_buffer = (uint8_t *)it->p_data; // Get instance address
uint16_t regs = n_regs;
reg_index = (uint16_t) (address - reg_holding_start);
reg_index <<= 1; // register Address to byte address
holding_buffer += reg_index;
uint8_t *buffer_start = holding_buffer;
switch (mode) {
case MB_REG_READ:
if (it->access != MB_ACCESS_WO) {
CRITICAL_SECTION(inst->lock)
{
while (regs > 0) {
_XFER_2_RD(reg_buffer, holding_buffer);
reg_index += 2;
regs -= 1;
};
}
// Send access notification
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_HOLDING_REG_RD);
// Send parameter info
(void)mbc_slave_send_param_info(ctx, MB_EVENT_HOLDING_REG_RD, address,
(uint8_t *)buffer_start, n_regs);
} else {
status = MB_EINVAL;
}
break;
case MB_REG_WRITE:
if (it->access != MB_ACCESS_RO) {
CRITICAL_SECTION(inst->lock)
{
while (regs > 0) {
_XFER_2_WR(holding_buffer, reg_buffer);
holding_buffer += 2;
reg_index += 2;
regs -= 1;
};
}
// Send access notification
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_HOLDING_REG_WR);
// Send parameter info
(void)mbc_slave_send_param_info(ctx, MB_EVENT_HOLDING_REG_WR, (uint16_t)address,
(uint8_t *)buffer_start, (uint16_t)n_regs);
} else {
status = MB_EINVAL;
}
break;
}
} else {
status = MB_ENOREG;
}
return status;
}
// Callback function for reading of MB Coils Registers
mb_err_enum_t mbc_reg_coils_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode)
{
void *ctx =(void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
MB_RETURN_ON_FALSE(ctx, MB_EILLSTATE, TAG, "Slave stack uninitialized.");
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
mb_err_enum_t status = MB_ENOERR;
uint16_t reg_index;
uint16_t coils = n_coils;
address--; // The address is already +1
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_COIL, address, n_coils);
if (it) {
uint16_t reg_coils_start = (uint16_t)it->start_offset; // MB offset of coils
uint8_t *reg_coils_buf = (uint8_t *)it->p_data;
reg_index = (uint16_t) (address - it->start_offset);
char *coils_data_buf = (char *)(reg_coils_buf + (reg_index >> 3));
switch (mode) {
case MB_REG_READ:
if (it->access != MB_ACCESS_WO) {
CRITICAL_SECTION(inst->lock)
{
while (coils > 0) {
uint8_t result = mb_util_get_bits((uint8_t *)reg_coils_buf, reg_index, 1);
mb_util_set_bits(reg_buffer, reg_index - (address - reg_coils_start), 1, result);
reg_index++;
coils--;
}
}
// Send an event to notify application task about event
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_COILS_RD);
(void)mbc_slave_send_param_info(ctx, MB_EVENT_COILS_RD, address,
(uint8_t *)(coils_data_buf), n_coils);
} else {
status = MB_EINVAL;
}
break;
case MB_REG_WRITE:
if (it->access != MB_ACCESS_RO) {
CRITICAL_SECTION(inst->lock)
{
while (coils > 0) {
uint8_t result = mb_util_get_bits(reg_buffer,
reg_index - (address - reg_coils_start), 1);
mb_util_set_bits((uint8_t *)reg_coils_buf, reg_index, 1, result);
reg_index++;
coils--;
}
}
// Send an event to notify application task about event
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_COILS_WR);
(void)mbc_slave_send_param_info(ctx, MB_EVENT_COILS_WR, address,
(uint8_t *)coils_data_buf, n_coils);
} else {
status = MB_EINVAL;
}
break;
} // switch ( mode )
} else {
// If the configuration or input parameters are incorrect then return error to stack
status = MB_ENOREG;
}
return status;
}
// Callback function for reading of MB Discrete Input Registers
mb_err_enum_t mbc_reg_discrete_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete)
{
void *ctx = (void *)MB_SLAVE_GET_IFACE_FROM_BASE(inst);
MB_RETURN_ON_FALSE(reg_buffer, MB_EINVAL, TAG, "Slave stack call failed.");
mb_err_enum_t status = MB_ENOERR;
uint16_t reg_index;
uint16_t reg_bit_index;
uint16_t n_reg;
uint8_t *discrete_input_buf;
// It already plus one in modbus function method.
address--;
mb_descr_entry_t *it = mbc_slave_find_reg_descriptor(ctx, MB_PARAM_DISCRETE, address, n_discrete);
if (it) {
uint16_t reg_discrete_start = (uint16_t)it->start_offset; // MB offset of registers
n_reg = (n_discrete >> 3) + 1;
discrete_input_buf = (uint8_t *)it->p_data; // the storage address
reg_index = (uint16_t) (address - reg_discrete_start) / 8; // Get register index in the buffer for bit number
reg_bit_index = (uint16_t)(address - reg_discrete_start) % 8; // Get bit index
uint8_t *temp_buf = &discrete_input_buf[reg_index];
CRITICAL_SECTION(inst->lock)
{
while (n_reg > 0) {
*reg_buffer++ = mb_util_get_bits(&discrete_input_buf[reg_index++], reg_bit_index, 8);
n_reg--;
}
}
reg_buffer--;
// Last discrete
n_discrete = n_discrete % 8;
// Filling zero to high bit
*reg_buffer = *reg_buffer << (8 - n_discrete);
*reg_buffer = *reg_buffer >> (8 - n_discrete);
// Send an event to notify application task about event
(void)mbc_slave_send_param_access_notification(ctx, MB_EVENT_DISCRETE_RD);
(void)mbc_slave_send_param_info(ctx, MB_EVENT_DISCRETE_RD, address,
(uint8_t *)temp_buf, n_discrete);
} else {
status = MB_ENOREG;
}
return status;
}

View File

@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h" // for esp_err_t
#include "sdkconfig.h" // for KConfig defines
#include "mbc_slave.h" // for slave interface define
#include "esp_modbus_slave.h" // for public slave defines
#include "mbc_serial_slave.h" // for public interface defines
#include "mb_port_types.h"
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
/**
* Initialization of Modbus Serial slave controller
*/
esp_err_t mbc_slave_create_serial(mb_communication_info_t *config, void **handler)
{
void *ctx = NULL;
esp_err_t error = ESP_ERR_NOT_SUPPORTED;
switch(config->mode)
{
case MB_RTU:
case MB_ASCII:
// Call constructor function of actual port implementation
error = mbc_serial_slave_create(config, &ctx);
break;
default:
return ESP_ERR_NOT_SUPPORTED;
}
if ((ctx) && (error == ESP_OK)) {
mbc_slave_init_iface(ctx);
*handler = ctx;
}
return error;
}
#endif

View File

@@ -0,0 +1,30 @@
/*
* 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
#include "sdkconfig.h"
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
/**
* Initialization of Modbus TCP Slave controller
*/
esp_err_t mbc_slave_create_tcp(mb_communication_info_t *config, void **handler)
{
void *ctx = NULL;
esp_err_t error = mbc_tcp_slave_create(config, &ctx);
if ((ctx) && (error == ESP_OK)) {
mbc_slave_init_iface(ctx);
*handler = ctx;
}
return error;
}
#endif

View File

@@ -0,0 +1,171 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/uart.h" // for UART types
#include "sdkconfig.h"
#if CONFIG_FMB_EXT_TYPE_SUPPORT
#include "mb_endianness_utils.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include "port_common.h"
#if __has_include("esp_check.h")
#include "esp_check.h"
#include "esp_log.h"
#include <inttypes.h>
#include "mb_port_types.h"
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) ESP_RETURN_ON_FALSE(a, err_code, tag, format __VA_OPT__(,) __VA_ARGS__)
#else
// if cannot include esp_check then use custom check macro
#define MB_RETURN_ON_FALSE(a, err_code, tag, format, ...) do { \
if (!(a)) { \
ESP_LOGE(tag, "%s(%d): " format, __FUNCTION__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \
return err_code; \
} \
} while(0)
#endif
#define MB_SLAVE_ADDR_PLACEHOLDER (0xFF)
#define MB_CONTROLLER_STACK_SIZE (CONFIG_FMB_CONTROLLER_STACK_SIZE) // Stack size for Modbus controller
#define MB_CONTROLLER_PRIORITY (CONFIG_FMB_PORT_TASK_PRIO - 1) // priority of MB controller task
#define MB_PORT_TASK_AFFINITY (CONFIG_FMB_PORT_TASK_AFFINITY)
// Default port defines
#define MB_PAR_INFO_TOUT (10) // Timeout for get parameter info
#define MB_PARITY_NONE (UART_PARITY_DISABLE)
#define MB_SECTION(lock) CRITICAL_SECTION(lock) {}
// The Macros below handle the endianness while transfer N byte data into buffer
#define _XFER_4_RD(dst, src) { \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 1); \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 0); \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 3); \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 2); \
(src) += 4; \
}
#define _XFER_2_RD(dst, src) { \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 1); \
*(uint8_t *)(dst)++ = *(uint8_t *)(src + 0); \
(src) += 2; \
}
#define _XFER_4_WR(dst, src) { \
*(uint8_t *)(dst + 1) = *(uint8_t *)(src)++; \
*(uint8_t *)(dst + 0) = *(uint8_t *)(src)++; \
*(uint8_t *)(dst + 3) = *(uint8_t *)(src)++; \
*(uint8_t *)(dst + 2) = *(uint8_t *)(src)++ ; \
}
#define _XFER_2_WR(dst, src) { \
*(uint8_t *)(dst + 1) = *(uint8_t *)(src)++; \
*(uint8_t *)(dst + 0) = *(uint8_t *)(src)++; \
}
/**
* @brief Types of actual Modbus implementation
*/
typedef enum
{
MB_PORT_SERIAL_MASTER = 0x00, /*!< Modbus port type serial master. */
MB_PORT_SERIAL_SLAVE, /*!< Modbus port type serial slave. */
MB_PORT_TCP_MASTER, /*!< Modbus port type TCP master. */
MB_PORT_TCP_SLAVE, /*!< Modbus port type TCP slave. */
MB_PORT_COUNT, /*!< Modbus port count. */
MB_PORT_INACTIVE = 0xFF
} mb_port_type_t;
/**
* @brief Event group for parameters notification
*/
typedef enum
{
MB_EVENT_NO_EVENTS = 0x00,
MB_EVENT_HOLDING_REG_WR = BIT0, /*!< Modbus Event Write Holding registers. */
MB_EVENT_HOLDING_REG_RD = BIT1, /*!< Modbus Event Read Holding registers. */
MB_EVENT_INPUT_REG_RD = BIT3, /*!< Modbus Event Read Input registers. */
MB_EVENT_COILS_WR = BIT4, /*!< Modbus Event Write Coils. */
MB_EVENT_COILS_RD = BIT5, /*!< Modbus Event Read Coils. */
MB_EVENT_DISCRETE_RD = BIT6, /*!< Modbus Event Read Discrete bits. */
MB_EVENT_STACK_STARTED = BIT7, /*!< Modbus Event Stack started */
MB_EVENT_STACK_CONNECTED = BIT8 /*!< Modbus Event Stack started */
} mb_event_group_t;
/**
* @brief Type of Modbus parameter
*/
typedef enum {
MB_PARAM_HOLDING = 0x00, /*!< Modbus Holding register. */
MB_PARAM_INPUT, /*!< Modbus Input register. */
MB_PARAM_COIL, /*!< Modbus Coils. */
MB_PARAM_DISCRETE, /*!< Modbus Discrete bits. */
MB_PARAM_COUNT,
MB_PARAM_UNKNOWN = 0xFF
} mb_param_type_t;
typedef enum _mb_comm_mode mb_mode_type_t;
typedef struct mb_base_t mb_base_t;
/*!
* \brief Modbus TCP type of address for communication.
*/
typedef enum _addr_type_enum mb_tcp_addr_type_t;
/*!
* \brief Modbus TCP communication options structure.
*/
typedef struct _port_tcp_opts mb_tcp_opts_t;
/*!
* \brief Modbus serial communication options structure.
*/
typedef struct _port_serial_opts mb_serial_opts_t;
/*!
* \brief Modbus common communication options structure.
*/
typedef struct _port_common_opts mb_common_opts_t;
/**
* @brief Device communication structure to setup Modbus controller
*/
typedef union
{
mb_comm_mode_t mode; /*!< mode option to check the communication object type*/
mb_common_opts_t common_opts; /*!< Common options for communication object. */
#if (CONFIG_FMB_COMM_MODE_TCP_EN)
mb_tcp_opts_t tcp_opts; /*!< tcp options for communication object */
#endif
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
mb_serial_opts_t ser_opts; /*!< serial options for communication object */
#endif
} mb_communication_info_t;
/**
* common interface method types
*/
typedef esp_err_t (*iface_create_fp)(mb_communication_info_t*, void **); /*!< Interface method create */
typedef esp_err_t (*iface_method_default_fp)(void *ctx); /*!< Interface method default prototype */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,472 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "soc/soc.h" // for BITN definitions
#include "esp_modbus_common.h" // for common types
#ifdef __cplusplus
extern "C" {
#endif
#define MB_MASTER_ASSERT(con) do { \
if (!(con)) { ESP_LOGE(TAG, "assert errno:%d, errno_str: !(%s)", errno, strerror(errno)); assert(0 && #con); } \
} while (0)
#define MB_MASTER_GET_IFACE(pctx) (__extension__( \
{ \
MB_MASTER_ASSERT((pctx)); \
((mbm_controller_iface_t*)pctx); \
} \
))
#define MB_MASTER_GET_OPTS(pctx) (&MB_MASTER_GET_IFACE(pctx)->opts)
#define MB_MASTER_IS_ACTIVE(pctx) ((bool)(MB_MASTER_GET_IFACE(pctx)->is_active))
#define MB_MASTER_GET_IFACE_FROM_BASE(pinst) (__extension__( \
{ \
MB_MASTER_ASSERT(pinst); \
mb_base_t *pbase = (mb_base_t *)pinst; \
MB_RETURN_ON_FALSE(pbase->descr.parent, MB_EILLSTATE, TAG, "Master interface is not correctly initialized."); \
((mbm_controller_iface_t*)pbase->descr.parent); \
} \
))
/*!
* \brief The macro to access arrays of elements for type conversion.
*/
#define MB_EACH_ELEM(psrc, pdest, arr_size, elem_size) \
(int i = 0; (i < (arr_size / elem_size)); i++, pdest += elem_size, psrc += elem_size)
/*!
* \brief Modbus descriptor table parameter type defines.
*/
typedef enum {
PARAM_TYPE_U8 = 0x00, /*!< Unsigned 8 */
PARAM_TYPE_U16 = 0x01, /*!< Unsigned 16 */
PARAM_TYPE_U32 = 0x02, /*!< Unsigned 32 */
PARAM_TYPE_FLOAT = 0x03, /*!< Float type */
PARAM_TYPE_ASCII = 0x04, /*!< ASCII type */
PARAM_TYPE_BIN = 0x07, /*!< BIN type */
PARAM_TYPE_I8_A = 0x0A, /*!< I8 signed integer in high byte of register */
PARAM_TYPE_I8_B = 0x0B, /*!< I8 signed integer in low byte of register */
PARAM_TYPE_U8_A = 0x0C, /*!< U8 unsigned integer written to hi byte of register */
PARAM_TYPE_U8_B = 0x0D, /*!< U8 unsigned integer written to low byte of register */
PARAM_TYPE_I16_AB = 0x0E, /*!< I16 signed integer, big endian */
PARAM_TYPE_I16_BA = 0x0F, /*!< I16 signed integer, little endian */
PARAM_TYPE_U16_AB = 0x10, /*!< U16 unsigned integer, big endian*/
PARAM_TYPE_U16_BA = 0x11, /*!< U16 unsigned integer, little endian */
PARAM_TYPE_I32_ABCD = 0x12, /*!< I32 ABCD signed integer, big endian */
PARAM_TYPE_I32_CDAB = 0x13, /*!< I32 CDAB signed integer, big endian, reversed register order */
PARAM_TYPE_I32_BADC = 0x14, /*!< I32 BADC signed integer, little endian, reversed register order */
PARAM_TYPE_I32_DCBA = 0x15, /*!< I32 DCBA signed integer, little endian */
PARAM_TYPE_U32_ABCD = 0x16, /*!< U32 ABCD unsigned integer, big endian */
PARAM_TYPE_U32_CDAB = 0x17, /*!< U32 CDAB unsigned integer, big endian, reversed register order */
PARAM_TYPE_U32_BADC = 0x18, /*!< U32 BADC unsigned integer, little endian, reversed register order */
PARAM_TYPE_U32_DCBA = 0x19, /*!< U32 DCBA unsigned integer, little endian */
PARAM_TYPE_FLOAT_ABCD = 0x1A, /*!< Float ABCD floating point, big endian */
PARAM_TYPE_FLOAT_CDAB = 0x1B, /*!< Float CDAB floating point big endian, reversed register order */
PARAM_TYPE_FLOAT_BADC = 0x1C, /*!< Float BADC floating point, little endian, reversed register order */
PARAM_TYPE_FLOAT_DCBA = 0x1D, /*!< Float DCBA floating point, little endian */
PARAM_TYPE_I64_ABCDEFGH = 0x1E, /*!< I64, ABCDEFGH signed integer, big endian */
PARAM_TYPE_I64_HGFEDCBA = 0x1F, /*!< I64, HGFEDCBA signed integer, little endian */
PARAM_TYPE_I64_GHEFCDAB = 0x20, /*!< I64, GHEFCDAB signed integer, big endian, reversed register order */
PARAM_TYPE_I64_BADCFEHG = 0x21, /*!< I64, BADCFEHG signed integer, little endian, reversed register order */
PARAM_TYPE_U64_ABCDEFGH = 0x22, /*!< U64, ABCDEFGH unsigned integer, big endian */
PARAM_TYPE_U64_HGFEDCBA = 0x23, /*!< U64, HGFEDCBA unsigned integer, little endian */
PARAM_TYPE_U64_GHEFCDAB = 0x24, /*!< U64, GHEFCDAB unsigned integer, big endian, reversed register order */
PARAM_TYPE_U64_BADCFEHG = 0x25, /*!< U64, BADCFEHG unsigned integer, little endian, reversed register order */
PARAM_TYPE_DOUBLE_ABCDEFGH = 0x26, /*!< Double ABCDEFGH floating point, big endian*/
PARAM_TYPE_DOUBLE_HGFEDCBA = 0x27, /*!< Double HGFEDCBA floating point, little endian*/
PARAM_TYPE_DOUBLE_GHEFCDAB = 0x28, /*!< Double GHEFCDAB floating point, big endian, reversed register order */
PARAM_TYPE_DOUBLE_BADCFEHG = 0x29 /*!< Double BADCFEHG floating point, little endian, reversed register order */
} mb_descr_type_t;
/*!
* \brief Modbus descriptor table parameter size in bytes.
*/
typedef enum {
PARAM_SIZE_U8 = 0x01, /*!< Unsigned 8 */
PARAM_SIZE_U8_REG = 0x02, /*!< Unsigned 8, register value */
PARAM_SIZE_I8_REG = 0x02, /*!< Signed 8, register value */
PARAM_SIZE_I16 = 0x02, /*!< Unsigned 16 */
PARAM_SIZE_U16 = 0x02, /*!< Unsigned 16 */
PARAM_SIZE_I32 = 0x04, /*!< Signed 32 */
PARAM_SIZE_U32 = 0x04, /*!< Unsigned 32 */
PARAM_SIZE_FLOAT = 0x04, /*!< Float 32 size */
PARAM_SIZE_ASCII = 0x08, /*!< ASCII size default*/
PARAM_SIZE_ASCII24 = 0x18, /*!< ASCII24 size */
PARAM_SIZE_I64 = 0x08, /*!< Signed integer 64 size */
PARAM_SIZE_U64 = 0x08, /*!< Unsigned integer 64 size */
PARAM_SIZE_DOUBLE = 0x08, /*!< Double 64 size */
PARAM_MAX_SIZE
} mb_descr_size_t;
/*!
* \brief Modbus parameter options for description table (associated with the characteristic).
* and can be used in user application to process data.
*/
typedef union {
struct {
int opt1; /*!< Parameter option1 */
int opt2; /*!< Parameter option2 */
int opt3; /*!< Parameter option3 */
}; /*!< Parameter options version 1 */
struct {
int min; /*!< Parameter minimum value */
int max; /*!< Parameter maximum value */
int step; /*!< Step of parameter change tracking */
}; /*!< Parameter options version 2 */
} mb_parameter_opt_t;
/**
* @brief Permissions for the characteristics
*/
typedef enum {
PAR_PERMS_READ = 1 << BIT0, /**< the characteristic of the device are readable */
PAR_PERMS_WRITE = 1 << BIT1, /**< the characteristic of the device are writable*/
PAR_PERMS_TRIGGER = 1 << BIT2, /**< the characteristic of the device are triggerable */
PAR_PERMS_READ_WRITE = PAR_PERMS_READ | PAR_PERMS_WRITE, /**< the characteristic of the device are readable & writable */
PAR_PERMS_READ_TRIGGER = PAR_PERMS_READ | PAR_PERMS_TRIGGER, /**< the characteristic of the device are readable & triggerable */
PAR_PERMS_WRITE_TRIGGER = PAR_PERMS_WRITE | PAR_PERMS_TRIGGER, /**< the characteristic of the device are writable & triggerable */
PAR_PERMS_READ_WRITE_TRIGGER = PAR_PERMS_READ_WRITE | PAR_PERMS_TRIGGER, /**< the characteristic of the device are readable & writable & triggerable */
} mb_param_perms_t;
/**
* @brief Characteristics descriptor type is used to describe characteristic and
* link it with Modbus parameters that reflect its data.
*/
typedef struct {
uint16_t cid; /*!< Characteristic cid */
const char * param_key; /*!< The key (name) of the parameter */
const char * param_units; /*!< The physical units of the parameter */
uint8_t mb_slave_addr; /*!< Slave address of device in the Modbus segment */
mb_param_type_t mb_param_type; /*!< Type of modbus parameter */
uint16_t mb_reg_start; /*!< This is the Modbus register address. This is the 0 based value. */
uint16_t mb_size; /*!< Size of mb parameter in registers */
uint32_t param_offset; /*!< Parameter name (OFFSET in the parameter structure or address of instance) */
mb_descr_type_t param_type; /*!< Float, U8, U16, U32, ASCII, etc. */
mb_descr_size_t param_size; /*!< Number of bytes in the parameter. */
mb_parameter_opt_t param_opts; /*!< Parameter options used to check limits and etc. */
mb_param_perms_t access; /*!< Access permissions based on mode */
} mb_parameter_descriptor_t;
/**
* @brief Modbus register request type structure
*/
typedef struct {
uint8_t slave_addr; /*!< Modbus slave address */
uint8_t command; /*!< Modbus command to send */
uint16_t reg_start; /*!< Modbus start register */
uint16_t reg_size; /*!< Modbus number of registers */
} mb_param_request_t;
/**
* @brief Initialize Modbus controller and stack for TCP port
*
* @param[out] ctx pointer to master interface structure
* @param[in] config - the pointer to stack configuration structure
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
* - ESP_ERR_NOT_SUPPORTED Port type not supported
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_master_create_tcp(mb_communication_info_t *config, void ** ctx);
/**
* @brief Initialize Modbus Master controller and stack for Serial port
*
* @param[out] ctx pointer to master interface structure
* @param[in] config the pointer to configuration structure
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
* - ESP_ERR_NOT_SUPPORTED Port type not supported
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_master_create_serial(mb_communication_info_t *config, void ** ctx);
/**
* @brief Deletes Modbus controller and stack engine
*
* @param[in] ctx context pointer of the initialized modbus interface
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Parameter error
*/
esp_err_t mbc_master_delete(void *ctx);
/**
* @brief Critical section lock function for parameter access
*
* @param[in] ctx pointer to master interface structure
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_master_lock(void *ctx);
/**
* @brief Critical section unlock function for parameter access
*
* @param[in] ctx pointer to master interface structure
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_master_unlock(void *ctx);
/**
* @brief Starts Modbus communication stack
*
* @param[in] ctx context pointer of the initialized modbus master interface structure
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Modbus stack start error
*/
esp_err_t mbc_master_start(void *ctx);
/**
* @brief Stops Modbus communication stack
*
* @param[in] ctx context pointer of the initialized modbus interface
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Modbus stack stop error
*/
esp_err_t mbc_master_stop(void *ctx);
/***************************** Specific interface functions ********************************************
* Interface functions below provide basic methods to read/write access to slave devices in Modbus
* segment as well as API to read specific supported characteristics linked to Modbus parameters
* of devices in Modbus network.
*******************************************************************************************************/
/**
* @brief Assign parameter description table for Modbus controller interface.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] descriptor pointer to parameter description table
* @param num_elements number of elements in the table
*
* @return
* - esp_err_t ESP_OK - set descriptor successfully
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument in function call
*/
esp_err_t mbc_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements);
/**
* @brief Send data request as defined in parameter request, waits response
* from slave and returns status of command execution. This function provides standard way
* for read/write access to Modbus devices in the network.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] request pointer to request structure of type mb_param_request_t
* @param[in] data_ptr pointer to data buffer to send or received data (dependent of command field in request)
*
* @return
* - esp_err_t ESP_OK - request was successful
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
* - esp_err_t ESP_ERR_TIMEOUT - operation timeout or no response from slave
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
*/
esp_err_t mbc_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr);
/**
* @brief Get information about supported characteristic defined as cid. Uses parameter description table to get
* this information. The function will check if characteristic defined as a cid parameter is supported
* and returns its description in param_info. Returns ESP_ERR_NOT_FOUND if characteristic is not supported.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] cid characteristic id
* @param param_info pointer to pointer of characteristic data.
*
* @return
* - esp_err_t ESP_OK - request was successful and buffer contains the supported characteristic name
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function
* - esp_err_t ESP_ERR_NOT_FOUND - the characteristic (cid) not found
* - esp_err_t ESP_FAIL - unknown error during lookup table processing
*/
esp_err_t mbc_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t** param_info);
/**
* @brief Read parameter from modbus slave device whose name is defined by name and has cid.
* The additional data for request is taken from parameter description (lookup) table.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] cid id of the characteristic for parameter
* @param[out] value pointer to data buffer of parameter
* @param[out] type parameter type associated with the name returned from parameter description table.
*
* @return
* - esp_err_t ESP_OK - request was successful and value buffer contains
* representation of actual parameter data from slave
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
* - esp_err_t ESP_ERR_NOT_FOUND - the parameter is not found in the parameter description table
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
*/
esp_err_t mbc_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type);
/**
* @brief Read parameter from modbus slave device whose name is defined by name and has cid.
* The additional data for request is taken from parameter description (lookup) table.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] cid id of the characteristic for parameter
* @param[in] uid unit identificator of the slave to set parameter
* @param[out] value pointer to data buffer of parameter
* @param[out] type parameter type associated with the name returned from parameter description table.
*
* @return
* - esp_err_t ESP_OK - request was successful and value buffer contains
* representation of actual parameter data from slave
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
* - esp_err_t ESP_ERR_NOT_FOUND - the parameter is not found in the parameter description table
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
*/
esp_err_t mbc_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type);
/**
* @brief Set characteristic's value defined as a name and cid parameter.
* The additional data for cid parameter request is taken from master parameter lookup table.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] cid id of the characteristic for parameter
* @param[out] value pointer to data buffer of parameter (actual representation of json value field in binary form)
* @param[out] type pointer to parameter type associated with the name returned from parameter lookup table.
*
* @return
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave during processing of parameter
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
*/
esp_err_t mbc_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type);
/**
* @brief Set characteristic's value defined as a name and cid parameter.
* The additional data for cid parameter request is taken from master parameter lookup table.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param[in] cid id of the characteristic for parameter
* @param[in] uid unit identificator of the slave to set parameter
* @param[out] value pointer to data buffer of parameter (actual representation of json value field in binary form)
* @param[out] type pointer to parameter type associated with the name returned from parameter lookup table.
*
* @return
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_INVALID_RESPONSE - an invalid response from slave during processing of parameter
* - esp_err_t ESP_ERR_INVALID_STATE - invalid state during data processing or allocation failure
* - esp_err_t ESP_ERR_NOT_FOUND - the requested slave is not found (not connected or not configured)
* - esp_err_t ESP_ERR_TIMEOUT - operation timed out and no response from slave
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
* - esp_err_t ESP_FAIL - slave returned an exception or other failure
*/
esp_err_t mbc_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type);
/**
* @brief Holding register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
* @param[in] n_regs - number of registers
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_holding_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode) __attribute__ ((weak));
/**
* @brief Input register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_regs - number of registers
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_input_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs) __attribute__ ((weak));
/**
* @brief Discrete register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_discrete - number of discrete registers
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_discrete_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete) __attribute__ ((weak));
/**
* @brief Coil register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_coils - number of coil registers
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_coils_master_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode) __attribute__ ((weak));
/**
* @brief The helper function to set data of parameters according to its type
*
* @param[in] dest the destination address of the parameter
* @param[in] src the source address of the parameter
* @param[out] param_type type of parameter from data dictionary
* @param[out] param_size the storage size of the characteristic (in bytes).
* Describes the size of data to keep into data instance during mapping.
*
* @return
* - esp_err_t ESP_OK - request was successful and value was saved in the slave device registers
* - esp_err_t ESP_ERR_INVALID_ARG - invalid argument of function or parameter descriptor
* - esp_err_t ESP_ERR_NOT_SUPPORTED - the request command is not supported by slave
*/
esp_err_t mbc_master_set_param_data(void* dest, void* src, mb_descr_type_t param_type, size_t param_size);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
// Public interface header for slave
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "soc/soc.h" // for BITN definitions
#include "freertos/FreeRTOS.h" // for task creation and queues access
#include "freertos/event_groups.h" // for event groups
#include "esp_modbus_common.h" // for common types
#ifdef __cplusplus
extern "C" {
#endif
#define REG_SIZE(type, nregs) ((type == MB_PARAM_INPUT) || (type == MB_PARAM_HOLDING)) ? (nregs >> 1) : (nregs << 3)
#define MB_SLAVE_ASSERT(con) do { \
if (!(con)) { ESP_LOGE(TAG, "assert errno:%d, errno_str: !(%s)", errno, strerror(errno)); assert(0 && #con); } \
} while (0)
#define MB_SLAVE_GET_IFACE(pctx) (__extension__( \
{ \
MB_SLAVE_ASSERT((pctx)); \
((mbs_controller_iface_t*)pctx); \
} \
))
#define MB_SLAVE_GET_OPTS(pctx) (&MB_SLAVE_GET_IFACE(pctx)->opts)
#define MB_SLAVE_IS_ACTIVE(pctx) ((bool)(MB_SLAVE_GET_IFACE(pctx)->is_active))
#define MB_SLAVE_GET_IFACE_FROM_BASE(pinst) (__extension__( \
{ \
MB_SLAVE_ASSERT(pinst); \
mb_base_t *pbase = (mb_base_t*)pinst; \
MB_RETURN_ON_FALSE(pbase->descr.parent, MB_EILLSTATE, TAG, "Slave interface is not correctly initialized."); \
((mbs_controller_iface_t*)pbase->descr.parent); \
} \
))
/**
* @brief Parameter access event information type
*/
typedef struct {
uint32_t time_stamp; /*!< Timestamp of Modbus Event (uS)*/
uint16_t mb_offset; /*!< Modbus register offset */
mb_event_group_t type; /*!< Modbus event type */
uint8_t *address; /*!< Modbus data storage address */
size_t size; /*!< Modbus event register size (number of registers)*/
} mb_param_info_t;
/**
* @brief Area access type modificator
*/
typedef enum {
MB_ACCESS_RW = 0x0000,
MB_ACCESS_RO = 0x0001,
MB_ACCESS_WO = 0x0002
} mb_param_access_t;
/**
* @brief Parameter storage area descriptor
*/
typedef struct {
uint16_t start_offset; /*!< Modbus start address for area descriptor */
mb_param_type_t type; /*!< Type of storage area descriptor */
mb_param_access_t access; /*!< Area access type */
void *address; /*!< Instance address for storage area descriptor */
size_t size; /*!< Instance size for area descriptor (bytes) */
} mb_register_area_descriptor_t;
/**
* @brief Initialize Modbus Slave controller and stack for TCP port
*
* @param[out] ctx context pointer of the initialized modbus interface
* @param[in] config - pointer to configuration structure for the slave
*
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
* - ESP_ERR_NOT_SUPPORTED Port type not supported
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_slave_create_tcp(mb_communication_info_t *config, void **ctx);
/**
* @brief Initialize Modbus Slave controller and stack for Serial port
*
* @param[out] ctx context pointer of the initialized modbus interface
* @param[in] config - pointer to configuration structure for the slave
*
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
* - ESP_ERR_NOT_SUPPORTED Port type not supported
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_slave_create_serial(mb_communication_info_t *config, void **ctx);
/**
* @brief Initialize Modbus Slave controller interface handle
*
* @param[in] ctx - pointer to slave interface data structure
*/
void mbc_slave_init_iface(void *ctx);
/**
* @brief Deletes Modbus controller and stack engine
*
* @param[in] ctx context pointer of the initialized modbus interface
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Parameter error
*/
esp_err_t mbc_slave_delete(void *ctx);
/**
* @brief Critical section lock function for parameter access
*
* @param[in] ctx pointer to slave handle (modbus interface)
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_slave_lock(void *ctx);
/**
* @brief Critical section unlock for parameter access
*
* @param[in] ctx pointer to slave handle (modbus interface)
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE Initialization failure
*/
esp_err_t mbc_slave_unlock(void *ctx);
/**
* @brief Start of Modbus communication stack
*
* @param[in] ctx context pointer of the initialized modbus interface
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Modbus stack start error
*/
esp_err_t mbc_slave_start(void *ctx);
/**
* @brief Stop of Modbus communication stack
*
* @param[in] ctx context pointer of the initialized modbus interface
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Modbus stack stop error
*/
esp_err_t mbc_slave_stop(void *ctx);
/**
* @brief Wait for specific event on parameter change.
*
* @param[in] ctx context pointer of the initialized modbus interface
* @param group Group event bit mask to wait for change
*
* @return
* - mb_event_group_t event bits triggered
*/
mb_event_group_t mbc_slave_check_event(void *ctx, mb_event_group_t group);
/**
* @brief Get parameter information
*
* @param[in] ctx context pointer of the initialized modbus interface *
* @param[out] reg_info parameter info structure
* @param[in] timeout Timeout in milliseconds to read information from
* parameter queue
*
* @return
* - ESP_OK Success
* - ESP_ERR_TIMEOUT Can not get data from parameter queue
* or queue overflow
*/
esp_err_t mbc_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout);
/**
* @brief Set Modbus area descriptor
*
* @param[in] ctx context pointer of the initialized modbus interface *
* @param descr_data Modbus registers area descriptor structure
*
* @return
* - ESP_OK: The appropriate descriptor is set
* - ESP_ERR_INVALID_ARG: The argument is incorrect
*/
esp_err_t mbc_slave_set_descriptor(void *ctx, mb_register_area_descriptor_t descr_data);
/**
* @brief Holding register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_regs - number of registers
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_holding_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs, mb_reg_mode_enum_t mode) __attribute__ ((weak));
/**
* @brief Input register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_regs - number of registers
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_input_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_regs) __attribute__ ((weak));
/**
* @brief Discrete register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_discrete - number of discrete registers
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_discrete_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_discrete) __attribute__ ((weak));
/**
* @brief Coil register read/write callback function
*
* @param[in] inst the pointer of the initialized modbus base
* @param[in] reg_buffer input buffer of registers
* @param[in] address - start address of register
* @param[in] n_coils - number of discrete registers
* @param[in] mode - parameter access mode (MB_REG_READ, MB_REG_WRITE)
*
* @return
* - MB_ENOERR: Read write is successful
* - MB_ENOREG: The argument is incorrect
*/
mb_err_enum_t mbc_reg_coils_slave_cb(mb_base_t *inst, uint8_t *reg_buffer, uint16_t address, uint16_t n_coils, mb_reg_mode_enum_t mode) __attribute__ ((weak));
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,556 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
/**
* @brief Defines the constant values based on native compiler byte ordering.
*/
#define MB_BO16_0 0
#define MB_BO16_1 1
#define MB_BO32_0 0
#define MB_BO32_1 1
#define MB_BO32_2 2
#define MB_BO32_3 3
#define MB_BO64_0 0
#define MB_BO64_1 1
#define MB_BO64_2 2
#define MB_BO64_3 3
#define MB_BO64_4 4
#define MB_BO64_5 5
#define MB_BO64_6 6
#define MB_BO64_7 7
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The sized array types used for mapping of extended values
*/
typedef uint8_t val_16_arr[2];
typedef uint8_t val_32_arr[4];
typedef uint8_t val_64_arr[8];
/**
* @brief Get int8_t (low byte) value represenatation from register
*
* @return
* - int8_t value of converted from register value
*/
int8_t mb_get_int8_a(val_16_arr *pi16);
/**
* @brief Set i8 value to the register value pointed by pi16
*
* @return
* - uint16_t value which represents the actual hex value of the register
*/
uint16_t mb_set_int8_a(val_16_arr *pi16, int8_t i8);
/**
* @brief Get int8_t (high byte) value from the register value pointed by pi16
*
* @return
* - uint16_t value which represents the actual hex value of the register
*/
int8_t mb_get_int8_b(val_16_arr *pi16);
/**
* @brief Set i8 (high byte) value from the register value pointed by pi16
*
* @return
* - uint16_t value which represents the actual hex value of the register
*/
uint16_t mb_set_int8_b(val_16_arr *pi16, int8_t i8);
/**
* @brief Get uint8_t (low byte) value represenatation from register poined by pu16
*
* @return
* - uint8_t the value of converted from register value
*/
uint8_t mb_get_uint8_a(val_16_arr *pu16);
/**
* @brief Set u8 (low byte) value into the register value pointed by pu16
*
* @return
* - uint16_t the value which represents the actual hex value of the register
*/
uint16_t mb_set_uint8_a(val_16_arr *pu16, uint8_t u8);
/**
* @brief Get uint8_t (high byte) value from the register value pointed by pu16
*
* @return
* - uint16_t the value which represents the actual hex value of the register
*/
uint8_t mb_get_uint8_b(val_16_arr *pu16);
/**
* @brief Set u8 (high byte) value into the register value pointed by pu16
*
* @return
* - uint16_t the value which represents the actual hex value of the register
*/
uint16_t mb_set_uint8_b(val_16_arr *pu16, uint8_t u8);
/**
* @brief Get int16_t value from the register value pointed by pu16 with ab endianness
*
* @return
* - int16_t the value which represents the converted value from register
*/
int16_t mb_get_int16_ab(val_16_arr *pi16);
/**
* @brief Set i16 value to the register pointed by pi16 with ab endianness
*
* @return
* - int16_t the value which represents the converted value from register
*/
uint16_t mb_set_int16_ab(val_16_arr *pi16, int16_t i16);
/**
* @brief Get uint16_t value from the register value pointed by pu16 with ab endianness
*
* @return
* - uint16_t value which represents the converted register value
*/
uint16_t mb_get_uint16_ab(val_16_arr *pu16);
/**
* @brief Set u16 value to the register pointed by pu16 with ab endianness
*
* @return
* - uint16_t value which represents the converted value from register
*/
uint16_t mb_set_uint16_ab(val_16_arr *pu16, uint16_t u16);
/**
* @brief Get int16_t value from the register value pointed by pu16 with ba endianness
*
* @return
* - int16_t value which represents the converted register value
*/
int16_t mb_get_int16_ba(val_16_arr *pi16);
/**
* @brief Set i16 value to the register pointed by pi16 with ba endianness
*
* @return
* - uint16_t value which represents the converted value from register
*/
uint16_t mb_set_int16_ba(val_16_arr *pi16, int16_t i16);
/**
* @brief Get uint16_t value from the register value pointed by pu16 with ba endianness
*
* @return
* - uint16_t value which represents the converted register value
*/
uint16_t mb_get_uint16_ba(val_16_arr *pu16);
/**
* @brief Set u16 value to the register pointed by pu16 with ba endianness
*
* @return
* - uint16_t value which represents the converted value from register
*/
uint16_t mb_set_uint16_ba(val_16_arr *pu16, uint16_t u16);
/**
* @brief Get int32_t value from the register value pointed by pi32 with abcd endianness
*
* @return
* - int32_t value which represents the converted register value
*/
int32_t mb_get_int32_abcd(val_32_arr *pi32);
/**
* @brief Set i32 value to the register pointed by pi32 with abcd endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_int32_abcd(val_32_arr *pi32, int32_t i32);
/**
* @brief Get uint32_t value from the register value pointed by pu32 with abcd endianness
*
* @return
* - uint32_t value which represents the converted register value
*/
uint32_t mb_get_uint32_abcd(val_32_arr *pu32);
/**
* @brief Set u32 value to the register pointed by pu32 with abcd endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_uint32_abcd(val_32_arr *pu32, uint32_t u32);
/**
* @brief Get int32_t value from the register value pointed by pi32 with badc endianness
*
* @return
* - int32_t value which represents the converted register value
*/
int32_t mb_get_int32_badc(val_32_arr *pi32);
/**
* @brief Set i32 value to the register pointed by pi32 with badc endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_int32_badc(val_32_arr *pi32, int32_t i32);
/**
* @brief Get uint32_t value from the register value pointed by pu32 with badc endianness
*
* @return
* - unt32_t value which represents the converted register value
*/
uint32_t mb_get_uint32_badc(val_32_arr *pu32);
/**
* @brief Set u32 value to the register pointed by pu32 with badc endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_uint32_badc(val_32_arr *pu32, uint32_t u32);
/**
* @brief Get int32_t value from the register value pointed by pi32 with cdab endianness
*
* @return
* - int32_t value which represents the converted register value
*/
int32_t mb_get_int32_cdab(val_32_arr *pi32);
/**
* @brief Set i32 value to the register pointed by pi32 with cdab endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_int32_cdab(val_32_arr *pi32, int32_t i32);
/**
* @brief Get uint32_t value from the register value pointed by pu32 with cdab endianness
*
* @return
* - int32_t value which represents the converted register value
*/
uint32_t mb_get_uint32_cdab(val_32_arr *pu32);
/**
* @brief Set u32 value to the register pointed by pu32 with cdab endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_uint32_cdab(val_32_arr *pu32, uint32_t u32);
/**
* @brief Get int32_t value from the register value pointed by pi32 with dcba endianness
*
* @return
* - int32_t value which represents the converted register value
*/
int32_t mb_get_int32_dcba(val_32_arr *pi32);
/**
* @brief Set i32 value to the register pointed by pi32 with dcba endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_int32_dcba(val_32_arr *pi32, int32_t i32);
/**
* @brief Get uint32_t value from the register value pointed by pu32 with dcba endianness
*
* @return
* - uint32_t value which represents the converted register value
*/
uint32_t mb_get_uint32_dcba(val_32_arr *pu32);
/**
* @brief Set u32 value to the register pointed by pu32 with dcba endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_uint32_dcba(val_32_arr *pu32, uint32_t u32);
/**
* @brief Get float value from the register pointed by pf with abcd endianness
*
* @return
* - float value which represents the converted register value
*/
float mb_get_float_abcd(val_32_arr *pf);
/**
* @brief Set f value to the register pointed by pf with abcd endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_float_abcd(val_32_arr *pf, float f);
/**
* @brief Get float value from the register pointed by pf with badc endianness
*
* @return
* - float value which represents the converted register value
*/
float mb_get_float_badc(val_32_arr *pf);
/**
* @brief Set f value to the register pointed by pf with badc endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_float_badc(val_32_arr *pf, float f);
/**
* @brief Get float value from the register pointed by pf with cdab endianness
*
* @return
* - float value which represents the converted register value
*/
float mb_get_float_cdab(val_32_arr *pf);
/**
* @brief Set f value to the register pointed by pf with cdab endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_float_cdab(val_32_arr *pf, float f);
/**
* @brief Get float value from the register pointed by pf with dcba endianness
*
* @return
* - float value which represents the converted register value
*/
float mb_get_float_dcba(val_32_arr *pf);
/**
* @brief Set f value to the register pointed by pf with dcba endianness
*
* @return
* - uint32_t value which represents the converted value from register
*/
uint32_t mb_set_float_dcba(val_32_arr *pf, float f);
/**
* @brief Get double value from the register pointed by pd with abcdefgh endianness
*
* @return
* - double value which represents the converted register value
*/
double mb_get_double_abcdefgh(val_64_arr *pd);
/**
* @brief Set d value to the register pointed by pd with abcdefgh endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_double_abcdefgh(val_64_arr *pd, double d);
/**
* @brief Get double value from the register pointed by pd with hgfedcba endianness
*
* @return
* - double value which represents the converted register value
*/
double mb_get_double_hgfedcba(val_64_arr *pd);
/**
* @brief Set d value to the register pointed by pd with hgfedcba endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_double_hgfedcba(val_64_arr *pd, double d);
/**
* @brief Get double value from the register pointed by pd with ghefcdab endianness
*
* @return
* - double value which represents the converted register value
*/
double mb_get_double_ghefcdab(val_64_arr *pd);
/**
* @brief Set d value to the register pointed by pd with ghefcdab endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_double_ghefcdab(val_64_arr *pd, double d);
/**
* @brief Get double value from the register pointed by pd with badcfehg endianness
*
* @return
* - double value which represents the converted register value
*/
double mb_get_double_badcfehg(val_64_arr *pd);
/**
* @brief Set d value to the register pointed by pd with badcfehg endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_double_badcfehg(val_64_arr *pd, double d);
/**
* @brief Get int64_t value from the register pointed by pi64 with abcdefgh endianness
*
* @return
* - int64_t value which represents the converted register value
*/
int64_t mb_get_int64_abcdefgh(val_64_arr *pi64);
/**
* @brief Set i value to the register pointed by pi with abcdefgh endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_int64_abcdefgh(val_64_arr *pi, int64_t i);
/**
* @brief Get int64_t value from the register pointed by pi64 with ghefcdab endianness
*
* @return
* - int64_t value which represents the converted register value
*/
int64_t mb_get_int64_ghefcdab(val_64_arr *pi64);
/**
* @brief Set i value to the register pointed by pi with ghefcdab endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_int64_ghefcdab(val_64_arr *pi, int64_t i);
/**
* @brief Get int64_t value from the register pointed by pi64 with hgfedcba endianness
*
* @return
* - int64_t value which represents the converted register value
*/
int64_t mb_get_int64_hgfedcba(val_64_arr *pi64);
/**
* @brief Set i value to the register pointed by pi with hgfedcba endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_int64_hgfedcba(val_64_arr *pi, int64_t i);
/**
* @brief Get int64_t value from the register pointed by pi64 with badcfehg endianness
*
* @return
* - int64_t value which represents the converted register value
*/
int64_t mb_get_int64_badcfehg(val_64_arr *pi64);
/**
* @brief Set i value to the register pointed by pi with badcfehg endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_int64_badcfehg(val_64_arr *pi, int64_t i);
/**
* @brief Get uint64_t value from the register pointed by pui with abcdefgh endianness
*
* @return
* - uint64_t value which represents the converted register value
*/
uint64_t mb_get_uint64_abcdefgh(val_64_arr *pui);
/**
* @brief Set ui value to the register pointed by pi with abcdefgh endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_uint64_abcdefgh(val_64_arr *pui, uint64_t ui);
/**
* @brief Get uint64_t value from the register pointed by pui with hgfedcba endianness
*
* @return
* - uint64_t value which represents the converted register value
*/
uint64_t mb_get_uint64_hgfedcba(val_64_arr *pui);
/**
* @brief Set ui value to the register pointed by pui with hgfedcba endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_uint64_hgfedcba(val_64_arr *pui, uint64_t ui);
/**
* @brief Get uint64_t value from the register pointed by pui with ghefcdab endianness
*
* @return
* - uint64_t value which represents the converted register value
*/
uint64_t mb_get_uint64_ghefcdab(val_64_arr *pui);
/**
* @brief Set ui value to the register pointed by pui with ghefcdab endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_uint64_ghefcdab(val_64_arr *pui, uint64_t ui);
/**
* @brief Get uint64_t value from the register pointed by pui with badcfehg endianness
*
* @return
* - uint64_t value which represents the converted register value
*/
uint64_t mb_get_uint64_badcfehg(val_64_arr *pui);
/**
* @brief Set ui value to the register pointed by pui with badcfehg endianness
*
* @return
* - uint64_t value which represents the converted value from register
*/
uint64_t mb_set_uint64_badcfehg(val_64_arr *pui, uint64_t ui);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "string.h" // for strerror()
#include "errno.h" // for errno
#include "esp_err.h" // for error handling
#include "driver/uart.h" // for uart port number defines
#include "sdkconfig.h" // for KConfig options
#include "esp_modbus_master.h"
#include "esp_modbus_slave.h"

View File

@@ -0,0 +1,683 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include "mb_endianness_utils.h"
#define INLINE inline __attribute__((always_inline))
static INLINE int16_t mb_get_int16_generic(int n0, int n1, val_16_arr *psrc)
{
val_16_arr *pv = psrc;
union {
val_16_arr arr;
int16_t value;
} bov;
bov.arr[n0] = (*pv)[MB_BO16_0];
bov.arr[n1] = (*pv)[MB_BO16_1];
return (bov.value);
}
static INLINE uint16_t mb_get_uint16_generic(int n0, int n1, val_16_arr *psrc)
{
val_16_arr *pv = psrc;
union {
val_16_arr arr;
uint16_t value;
} bov;
bov.arr[n0] = (*pv)[MB_BO16_0];
bov.arr[n1] = (*pv)[MB_BO16_1];
return (bov.value);
}
static INLINE uint16_t mb_set_uint16_generic(int n0, int n1, val_16_arr *pdest, uint16_t val)
{
val_16_arr *pv = pdest;
union {
val_16_arr arr;
uint16_t value;
} bov;
bov.value = val;
(*pv)[MB_BO16_0] = bov.arr[n0];
(*pv)[MB_BO16_1] = bov.arr[n1];
return (*((uint16_t *)pv));
}
static INLINE int16_t mb_set_int16_generic(int n0, int n1, val_16_arr *pdest, int16_t val)
{
val_16_arr *pv = pdest;
union {
val_16_arr arr;
int16_t value;
} bov;
bov.value = val;
(*pv)[MB_BO16_0] = bov.arr[n0];
(*pv)[MB_BO16_1] = bov.arr[n1];
return (*((uint16_t *)pv));
}
static INLINE uint32_t mb_get_uint32_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
{
val_32_arr *pv = psrc;
union {
val_32_arr arr;
uint32_t value;
} bov;
bov.arr[n0] = (*pv)[MB_BO32_0];
bov.arr[n1] = (*pv)[MB_BO32_1];
bov.arr[n2] = (*pv)[MB_BO32_2];
bov.arr[n3] = (*pv)[MB_BO32_3];
return (bov.value);
}
static INLINE int32_t mb_get_int32_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
{
val_32_arr *pv = psrc;
union {
val_32_arr arr;
int32_t value;
} bov;
bov.arr[n0] = (*pv)[MB_BO32_0];
bov.arr[n1] = (*pv)[MB_BO32_1];
bov.arr[n2] = (*pv)[MB_BO32_2];
bov.arr[n3] = (*pv)[MB_BO32_3];
return (bov.value);
}
static INLINE float mb_get_float_generic(int n0, int n1, int n2, int n3, val_32_arr *psrc)
{
val_32_arr *pv = psrc;
union {
val_32_arr arr;
float value;
} bov;
bov.arr[n0] = (*pv)[MB_BO32_0];
bov.arr[n1] = (*pv)[MB_BO32_1];
bov.arr[n2] = (*pv)[MB_BO32_2];
bov.arr[n3] = (*pv)[MB_BO32_3];
return (bov.value);
}
static INLINE uint32_t mb_set_int32_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, int32_t val)
{
val_32_arr *pv = pdest;
union {
val_32_arr arr;
int32_t value;
} bov;
bov.value = val;
(*pv)[MB_BO32_0] = bov.arr[n0];
(*pv)[MB_BO32_1] = bov.arr[n1];
(*pv)[MB_BO32_2] = bov.arr[n2];
(*pv)[MB_BO32_3] = bov.arr[n3];
return (*((uint32_t *)pv));
}
static INLINE uint32_t mb_set_uint32_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, uint32_t val)
{
val_32_arr *pv = pdest;
union {
val_32_arr arr;
uint32_t value;
} bov;
bov.value = val;
(*pv)[MB_BO32_0] = bov.arr[n0];
(*pv)[MB_BO32_1] = bov.arr[n1];
(*pv)[MB_BO32_2] = bov.arr[n2];
(*pv)[MB_BO32_3] = bov.arr[n3];
return (*((uint32_t *)pv));
}
static INLINE uint32_t mb_set_float_generic(int n0, int n1, int n2, int n3, val_32_arr *pdest, float val)
{
val_32_arr *pv = pdest;
union {
val_32_arr arr;
float value;
} bov;
bov.value = val;
(*pv)[MB_BO32_0] = bov.arr[n0];
(*pv)[MB_BO32_1] = bov.arr[n1];
(*pv)[MB_BO32_2] = bov.arr[n2];
(*pv)[MB_BO32_3] = bov.arr[n3];
return (*((uint32_t *)pv));
}
static INLINE int64_t mb_get_int64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
{
val_64_arr *pv64 = psrc;
union {
val_64_arr arr;
int64_t value;
} bo64;
bo64.arr[n0] = (*pv64)[MB_BO64_0];
bo64.arr[n1] = (*pv64)[MB_BO64_1];
bo64.arr[n2] = (*pv64)[MB_BO64_2];
bo64.arr[n3] = (*pv64)[MB_BO64_3];
bo64.arr[n4] = (*pv64)[MB_BO64_4];
bo64.arr[n5] = (*pv64)[MB_BO64_5];
bo64.arr[n6] = (*pv64)[MB_BO64_6];
bo64.arr[n7] = (*pv64)[MB_BO64_7];
return (bo64.value);
}
static INLINE uint64_t mb_get_uint64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
{
val_64_arr *pv64 = psrc;
union {
val_64_arr arr;
uint64_t value;
} bo64;
bo64.arr[n0] = (*pv64)[MB_BO64_0];
bo64.arr[n1] = (*pv64)[MB_BO64_1];
bo64.arr[n2] = (*pv64)[MB_BO64_2];
bo64.arr[n3] = (*pv64)[MB_BO64_3];
bo64.arr[n4] = (*pv64)[MB_BO64_4];
bo64.arr[n5] = (*pv64)[MB_BO64_5];
bo64.arr[n6] = (*pv64)[MB_BO64_6];
bo64.arr[n7] = (*pv64)[MB_BO64_7];
return (bo64.value);
}
static INLINE double mb_get_double_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *psrc)
{
val_64_arr *pv64 = psrc;
union {
val_64_arr arr;
double value;
} bo64;
bo64.arr[n0] = (*pv64)[MB_BO64_0];
bo64.arr[n1] = (*pv64)[MB_BO64_1];
bo64.arr[n2] = (*pv64)[MB_BO64_2];
bo64.arr[n3] = (*pv64)[MB_BO64_3];
bo64.arr[n4] = (*pv64)[MB_BO64_4];
bo64.arr[n5] = (*pv64)[MB_BO64_5];
bo64.arr[n6] = (*pv64)[MB_BO64_6];
bo64.arr[n7] = (*pv64)[MB_BO64_7];
return (bo64.value);
}
static INLINE uint64_t mb_set_int64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, int64_t val)
{
val_64_arr *pv = pdest;
union {
val_64_arr arr;
int64_t value;
} bo64;
bo64.value = val;
(*pv)[MB_BO64_0] = bo64.arr[n0];
(*pv)[MB_BO64_1] = bo64.arr[n1];
(*pv)[MB_BO64_2] = bo64.arr[n2];
(*pv)[MB_BO64_3] = bo64.arr[n3];
(*pv)[MB_BO64_4] = bo64.arr[n4];
(*pv)[MB_BO64_5] = bo64.arr[n5];
(*pv)[MB_BO64_6] = bo64.arr[n6];
(*pv)[MB_BO64_7] = bo64.arr[n7];
return (*((uint64_t *)pv));
}
static INLINE uint64_t mb_set_uint64_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, uint64_t val)
{
val_64_arr *pv = pdest;
union {
val_64_arr arr;
uint64_t value;
} bo64;
bo64.value = val;
(*pv)[MB_BO64_0] = bo64.arr[n0];
(*pv)[MB_BO64_1] = bo64.arr[n1];
(*pv)[MB_BO64_2] = bo64.arr[n2];
(*pv)[MB_BO64_3] = bo64.arr[n3];
(*pv)[MB_BO64_4] = bo64.arr[n4];
(*pv)[MB_BO64_5] = bo64.arr[n5];
(*pv)[MB_BO64_6] = bo64.arr[n6];
(*pv)[MB_BO64_7] = bo64.arr[n7];
return (*((uint64_t *)pv));
}
static INLINE uint64_t mb_set_double_generic(int n0, int n1, int n2, int n3, int n4, int n5, int n6, int n7, val_64_arr *pdest, double val)
{
val_64_arr *pv = pdest;
union {
val_64_arr arr;
double value;
} bo64;
bo64.value = val;
(*pv)[MB_BO64_0] = bo64.arr[n0];
(*pv)[MB_BO64_1] = bo64.arr[n1];
(*pv)[MB_BO64_2] = bo64.arr[n2];
(*pv)[MB_BO64_3] = bo64.arr[n3];
(*pv)[MB_BO64_4] = bo64.arr[n4];
(*pv)[MB_BO64_5] = bo64.arr[n5];
(*pv)[MB_BO64_6] = bo64.arr[n6];
(*pv)[MB_BO64_7] = bo64.arr[n7];
return (*((uint64_t *)pv));
}
int8_t mb_get_int8_a(pi16)
val_16_arr *pi16;
{
return((int8_t)(*pi16)[MB_BO16_0]);
}
uint16_t mb_set_int8_a(pi16, i8)
val_16_arr *pi16;
int8_t i8;
{
(*pi16)[MB_BO16_0] = (char)i8;
(*pi16)[MB_BO16_1] = 0;
return (*((uint16_t *)pi16));
}
int8_t mb_get_int8_b(pi16)
val_16_arr *pi16;
{
return((int8_t)(*pi16)[MB_BO16_1]);
}
uint16_t mb_set_int8_b(pi16, i8)
val_16_arr *pi16;
int8_t i8;
{
(*pi16)[MB_BO16_0] = 0;
(*pi16)[MB_BO16_1] = (char)i8;
return (*((uint16_t *)pi16));
}
uint8_t mb_get_uint8_a(pu16)
val_16_arr *pu16;
{
return((uint8_t)(*pu16)[MB_BO16_0]);
}
uint16_t mb_set_uint8_a(pu16, u8)
val_16_arr *pu16;
uint8_t u8;
{
(*pu16)[MB_BO16_0] = (char)u8;
(*pu16)[MB_BO16_1] = 0;
return (*((uint16_t *)pu16));
}
uint8_t mb_get_uint8_b(pu16)
val_16_arr *pu16;
{
return((uint8_t)(*pu16)[MB_BO16_1]);
}
uint16_t mb_set_uint8_b(pu16, u8)
val_16_arr *pu16;
uint8_t u8;
{
(*pu16)[MB_BO16_0] = 0;
(*pu16)[MB_BO16_1] = (char)u8;
return (*((uint16_t *)pu16));
}
int16_t mb_get_int16_ab(pi16)
val_16_arr *pi16;
{
return mb_get_int16_generic(0, 1, pi16);
}
uint16_t mb_set_int16_ab(pi16, i16)
val_16_arr *pi16;
int16_t i16;
{
return mb_set_int16_generic(0, 1, pi16, i16);
}
uint16_t mb_get_uint16_ab(pu16)
val_16_arr *pu16;
{
return mb_get_uint16_generic(0, 1, pu16);
}
uint16_t mb_set_uint16_ab(pu16, u16)
val_16_arr *pu16;
uint16_t u16;
{
return mb_set_uint16_generic(0, 1, pu16, u16);
}
int16_t mb_get_int16_ba(pi16)
val_16_arr *pi16;
{
return mb_get_int16_generic(1, 0, pi16);
}
uint16_t mb_set_int16_ba(pi16, i16)
val_16_arr *pi16;
int16_t i16;
{
return mb_set_int16_generic(1, 0, pi16, i16);
}
uint16_t mb_get_uint16_ba(pu16)
val_16_arr *pu16;
{
return mb_get_int16_generic(1, 0, pu16);
}
uint16_t mb_set_uint16_ba(pu16, u16)
val_16_arr *pu16;
uint16_t u16;
{
return mb_set_int16_generic(1, 0, pu16, u16);
}
int32_t mb_get_int32_abcd(pi32)
val_32_arr *pi32;
{
return mb_get_int32_generic(0, 1, 2, 3, pi32);
}
uint32_t mb_set_int32_abcd(pi32, i32)
val_32_arr *pi32;
int32_t i32;
{
return mb_set_int32_generic(0, 1, 2, 3, pi32, i32);
}
uint32_t mb_get_uint32_abcd(pu32)
val_32_arr *pu32;
{
return mb_get_uint32_generic(0, 1, 2, 3, pu32);
}
uint32_t mb_set_uint32_abcd(pu32, u32)
val_32_arr *pu32;
uint32_t u32;
{
return mb_set_uint32_generic(0, 1, 2, 3, pu32, u32);
}
int32_t mb_get_int32_badc(pi32)
val_32_arr *pi32;
{
return mb_get_int32_generic(1, 0, 3, 2, pi32);
}
uint32_t mb_set_int32_badc(pi32, i32)
val_32_arr *pi32;
int32_t i32;
{
return mb_set_int32_generic(1, 0, 3, 2, pi32, i32);
}
uint32_t mb_get_uint32_badc(pu32)
val_32_arr *pu32;
{
return mb_get_uint32_generic(1, 0, 3, 2, pu32);
}
uint32_t mb_set_uint32_badc(pu32, u32)
val_32_arr *pu32;
uint32_t u32;
{
return mb_set_uint32_generic(1, 0, 3, 2, pu32, u32);
}
int32_t mb_get_int32_cdab(pi32)
val_32_arr *pi32;
{
return mb_get_int32_generic(2, 3, 0, 1, pi32);
}
uint32_t mb_set_int32_cdab(pi32, i32)
val_32_arr *pi32;
int32_t i32;
{
return mb_set_int32_generic(2, 3, 0, 1, pi32, i32);
}
uint32_t mb_get_uint32_cdab(pu32)
val_32_arr *pu32;
{
return mb_get_uint32_generic(2, 3, 0, 1, pu32);
}
uint32_t mb_set_uint32_cdab(pu32, u32)
val_32_arr *pu32;
uint32_t u32;
{
return mb_set_uint32_generic(2, 3, 0, 1, pu32, u32);
}
int32_t mb_get_int32_dcba(pi32)
val_32_arr *pi32;
{
return mb_get_int32_generic(3, 2, 1, 0, pi32);
}
uint32_t mb_set_int32_dcba(pi32, i32)
val_32_arr *pi32;
int32_t i32;
{
return mb_set_int32_generic(3, 2, 1, 0, pi32, i32);
}
uint32_t mb_get_uint32_dcba(pu32)
val_32_arr *pu32;
{
return mb_get_uint32_generic(3, 2, 1, 0, pu32);
}
uint32_t mb_set_uint32_dcba(pu32, u32)
val_32_arr *pu32;
uint32_t u32;
{
return mb_set_uint32_generic(3, 2, 1, 0, pu32, u32);
}
float mb_get_float_abcd(pf)
val_32_arr *pf;
{
return mb_get_float_generic(0, 1, 2, 3, pf);
}
uint32_t mb_set_float_abcd(pf, f)
val_32_arr *pf;
float f;
{
return mb_set_float_generic(0, 1, 2, 3, pf, f);
}
float mb_get_float_badc(pf)
val_32_arr *pf;
{
return mb_get_float_generic(1, 0, 3, 2, pf);
}
uint32_t mb_set_float_badc(pf, f)
val_32_arr *pf;
float f;
{
return mb_set_float_generic(1, 0, 3, 2, pf, f);
}
float mb_get_float_cdab(pf)
val_32_arr *pf;
{
return mb_get_float_generic(2, 3, 0, 1, pf);
}
uint32_t mb_set_float_cdab(pf, f)
val_32_arr *pf;
float f;
{
return mb_set_float_generic(2, 3, 0, 1, pf, f);
}
float mb_get_float_dcba(pf)
val_32_arr *pf;
{
return mb_get_float_generic(3, 2, 1, 0, pf);
}
uint32_t mb_set_float_dcba(pf, f)
val_32_arr *pf;
float f;
{
return mb_set_float_generic(3, 2, 1, 0, pf, f);
}
double mb_get_double_abcdefgh(pd)
val_64_arr *pd;
{
return mb_get_double_generic(0, 1, 2, 3, 4, 5, 6, 7, pd);
}
uint64_t mb_set_double_abcdefgh(pd, d)
val_64_arr *pd;
double d;
{
return mb_set_double_generic(0, 1, 2, 3, 4, 5, 6, 7, pd, d);
}
double mb_get_double_hgfedcba(pd)
val_64_arr *pd;
{
return mb_get_double_generic(7, 6, 5, 4, 3, 2, 1, 0, pd);
}
uint64_t mb_set_double_hgfedcba(pd, d)
val_64_arr *pd;
double d;
{
return mb_set_double_generic(7, 6, 5, 4, 3, 2, 1, 0, pd, d);
}
double mb_get_double_ghefcdab(pd)
val_64_arr *pd;
{
return mb_get_double_generic(6, 7, 4, 5, 2, 3, 0, 1, pd);
}
uint64_t mb_set_double_ghefcdab(pd, d)
val_64_arr *pd;
double d;
{
return mb_set_double_generic(6, 7, 4, 5, 2, 3, 0, 1, pd, d);
}
double mb_get_double_badcfehg(pd)
val_64_arr *pd;
{
return mb_get_double_generic(1, 0, 3, 2, 5, 4, 7, 6, pd);
}
uint64_t mb_set_double_badcfehg(pd, d)
val_64_arr *pd;
double d;
{
return mb_set_double_generic(1, 0, 3, 2, 5, 4, 7, 6, pd, d);
}
int64_t mb_get_int64_abcdefgh(pi64)
val_64_arr *pi64;
{
return mb_get_int64_generic(0, 1, 2, 3, 4, 5, 6, 7, pi64);
}
uint64_t mb_set_int64_abcdefgh(pi, i)
val_64_arr *pi;
int64_t i;
{
return mb_set_int64_generic(0, 1, 2, 3, 4, 5, 6, 7, pi, i);
}
int64_t mb_get_int64_hgfedcba(pi64)
val_64_arr *pi64;
{
return mb_get_int64_generic(7, 6, 5, 4, 3, 2, 1, 0, pi64);
}
uint64_t mb_set_int64_hgfedcba(pi, i)
val_64_arr *pi;
int64_t i;
{
return mb_set_int64_generic(7, 6, 5, 4, 3, 2, 1, 0, pi, i);
}
int64_t mb_get_int64_ghefcdab(pi64)
val_64_arr *pi64;
{
return mb_get_int64_generic(6, 7, 4, 5, 2, 3, 0, 1, pi64);
}
uint64_t mb_set_int64_ghefcdab(pi, i)
val_64_arr *pi;
int64_t i;
{
return mb_set_int64_generic(6, 7, 4, 5, 2, 3, 0, 1, pi, i);
}
int64_t mb_get_int64_badcfehg(pi64)
val_64_arr *pi64;
{
return mb_get_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pi64);
}
uint64_t mb_set_int64_badcfehg(pi, i)
val_64_arr *pi;
int64_t i;
{
return mb_set_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pi, i);
}
uint64_t mb_get_uint64_abcdefgh(pui)
val_64_arr *pui;
{
return mb_get_uint64_generic(0, 1, 2, 3, 4, 5, 6, 7, pui);
}
uint64_t mb_set_uint64_abcdefgh(pui, ui)
val_64_arr *pui;
uint64_t ui;
{
return mb_set_uint64_generic(0, 1, 2, 3, 4, 5, 6, 7, pui, ui);
}
uint64_t mb_get_uint64_hgfedcba(pui)
val_64_arr *pui;
{
return mb_get_uint64_generic(7, 6, 5, 4, 3, 2, 1, 0, pui);
}
uint64_t mb_set_uint64_hgfedcba(pui, ui)
val_64_arr *pui;
uint64_t ui;
{
return mb_set_uint64_generic(7, 6, 5, 4, 3, 2, 1, 0, pui, ui);
}
uint64_t mb_get_uint64_ghefcdab(pui)
val_64_arr *pui;
{
return mb_get_uint64_generic(6, 7, 4, 5, 2, 3, 0, 1, pui);
}
uint64_t mb_set_uint64_ghefcdab(pui, ui)
val_64_arr *pui;
uint64_t ui;
{
return mb_set_uint64_generic(6, 7, 4, 5, 2, 3, 0, 1, pui, ui);
}
uint64_t mb_get_uint64_badcfehg(pui)
val_64_arr *pui;
{
return mb_get_int64_generic(1, 0, 3, 2, 5, 4, 7, 6, pui);
}
uint64_t mb_set_uint64_badcfehg(pui, ui)
val_64_arr *pui;
uint64_t ui;
{
return mb_set_uint64_generic(1, 0, 3, 2, 5, 4, 7, 6, pui, ui);
}

View File

@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <sys/queue.h> // for list
#include "freertos/FreeRTOS.h" // for task creation and queue access
#include "freertos/task.h" // for task api access
#include "freertos/event_groups.h" // for event groups
#include "driver/uart.h" // for UART types
#include "errno.h" // for errno
#include "esp_log.h" // for log write
#include "string.h" // for strerror()
#include "esp_modbus_common.h" // for common types
#include "esp_modbus_master.h" // for public master types
#include "mb_common.h" // for mb_base_t
#include "mb_utils.h"
#include "mb_master.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ----------------------- Defines ------------------------------------------*/
// Set the maximum resource waiting time, the actual time of resouce release
// will be dependent on response time set by timer + convertion time if the command is received
#define MB_MAX_RESP_DELAY_MS (3000)
/**
* @brief Request mode for parameter to use in data dictionary
*/
typedef enum {
MB_PARAM_READ, /*!< Read parameter values. */
MB_PARAM_WRITE /*!< Write parameter values. */
} mb_param_mode_t;
/**
* @brief Modbus controller handler structure
*/
typedef struct {
mb_port_type_t port_type; /*!< Modbus port type */
mb_communication_info_t comm_opts; /*!< Modbus communication info */
uint8_t *reg_buffer_ptr; /*!< Modbus data buffer pointer */
uint16_t reg_buffer_size; /*!< Modbus data buffer size */
TaskHandle_t task_handle; /*!< Modbus task handle */
EventGroupHandle_t event_group_handle; /*!< Modbus controller event group */
const mb_parameter_descriptor_t *param_descriptor_table; /*!< Modbus controller parameter description table */
size_t mbm_param_descriptor_size; /*!< Modbus controller parameter description table size */
} mb_master_options_t;
typedef esp_err_t (*iface_get_cid_info_fp)(void *, uint16_t, const mb_parameter_descriptor_t **); /*!< Interface get_cid_info method */
typedef esp_err_t (*iface_get_parameter_fp)(void *, uint16_t, uint8_t *, uint8_t *); /*!< Interface get_parameter method */
typedef esp_err_t (*iface_get_parameter_with_fp)(void *, uint16_t, uint8_t, uint8_t *, uint8_t *); /*!< Interface get_parameter_with method */
typedef esp_err_t (*iface_send_request_fp)(void *, mb_param_request_t*, void *); /*!< Interface send_request method */
typedef esp_err_t (*iface_mbm_set_descriptor_fp)(void *, const mb_parameter_descriptor_t*, const uint16_t); /*!< Interface set_descriptor method */
typedef esp_err_t (*iface_set_parameter_fp)(void *, uint16_t, uint8_t *, uint8_t *); /*!< Interface set_parameter method */
typedef esp_err_t (*iface_set_parameter_with_fp)(void *, uint16_t, uint8_t, uint8_t *, uint8_t *); /*!< Interface set_parameter_with method */
/**
* @brief Modbus controller interface structure
*/
typedef struct {
mb_base_t *mb_base;
// Master object interface options
mb_master_options_t opts;
bool is_active; /*!< Interface is active */
// Public interface methods
iface_create_fp create; /*!< Interface constructor */
iface_method_default_fp delete; /*!< Interface method delete */
iface_method_default_fp start; /*!< Interface method start */
iface_method_default_fp stop; /*!< Interface method stop */
iface_get_cid_info_fp get_cid_info; /*!< Interface get_cid_info method */
iface_get_parameter_fp get_parameter; /*!< Interface get_parameter method */
iface_get_parameter_with_fp get_parameter_with; /*!< Interface get_parameter_with method */
iface_send_request_fp send_request; /*!< Interface send_request method */
iface_mbm_set_descriptor_fp set_descriptor; /*!< Interface set_descriptor method */
iface_set_parameter_fp set_parameter; /*!< Interface set_parameter method */
iface_set_parameter_with_fp set_parameter_with; /*!< Interface set_parameter_with method */
} mbm_controller_iface_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/uart.h" // for uart defines
#include "errno.h" // for errno
#include "sys/queue.h" // for list
#include "esp_log.h" // for log write
#include "string.h" // for strerror()
#ifdef __cplusplus
extern "C" {
#endif
#include "mb_common.h" // for mb_base_t
#include "esp_modbus_slave.h" // for public type defines
/* ----------------------- Defines ------------------------------------------*/
#define MB_INST_MIN_SIZE (1) // The minimal size of Modbus registers area in bytes
#define MB_INST_MAX_SIZE (65535 * 2) // The maximum size of Modbus area in bytes
#define MB_CONTROLLER_NOTIFY_QUEUE_SIZE (CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE) // Number of messages in parameter notification queue
#define MB_CONTROLLER_NOTIFY_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT)) // notification timeout
/**
* @brief Modbus area descriptor list item
*/
typedef struct mb_descr_entry_s {
uint16_t start_offset; /*!< Modbus start address for area descriptor */
mb_param_type_t type; /*!< Type of storage area descriptor */
mb_param_access_t access; /*!< Area access type */
void *p_data; /*!< Instance address for storage area descriptor */
size_t size; /*!< Instance size for area descriptor (bytes) */
LIST_ENTRY(mb_descr_entry_s) entries; /*!< The Modbus area descriptor entry */
} mb_descr_entry_t;
/**
* @brief Modbus controller handler structure
*/
typedef struct {
mb_port_type_t port_type; /*!< port type */
mb_communication_info_t comm_opts; /*!< communication info */
TaskHandle_t task_handle; /*!< task handle */
EventGroupHandle_t event_group_handle; /*!< controller event group */
QueueHandle_t notification_queue_handle; /*!< controller notification queue */
LIST_HEAD(mbs_area_descriptors_, mb_descr_entry_s) area_descriptors[MB_PARAM_COUNT]; /*!< register area descriptors */
} mb_slave_options_t;
typedef mb_event_group_t (*iface_check_event_fp)(void *, mb_event_group_t); /*!< Interface method check_event */
typedef esp_err_t (*iface_get_param_info_fp)(void *, mb_param_info_t*, uint32_t); /*!< Interface method get_param_info */
typedef esp_err_t (*iface_mbs_set_descriptor_fp)(void *, mb_register_area_descriptor_t); /*!< Interface method set_descriptor */
/**
* @brief Request mode for parameter to use in data dictionary
*/
typedef struct
{
mb_base_t *mb_base;
mb_slave_options_t opts; /*!< Modbus slave options */
bool is_active; /*!< Modbus controller interface is active */
// Functional pointers to internal static functions of the implementation (public interface methods)
iface_create_fp create; /*!< Interface factory method */
iface_method_default_fp delete; /*!< Interface method delete */
iface_method_default_fp start; /*!< Interface method start */
iface_method_default_fp stop; /*!< Interface method start */
iface_check_event_fp check_event; /*!< Interface method check_event */
iface_get_param_info_fp get_param_info; /*!< Interface method get_param_info */
iface_mbs_set_descriptor_fp set_descriptor; /*!< Interface method set_descriptor */
} mbs_controller_iface_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,732 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_serial_master.c
// Serial master implementation of the Modbus controller
#include <sys/time.h> // for calculation of time stamp in milliseconds
#include "esp_log.h" // for log_write
#include <string.h> // for memcpy
#include "freertos/FreeRTOS.h" // for task creation
#include "freertos/task.h" // for task api access
#include "freertos/event_groups.h" // for event groups
#include "sdkconfig.h" // for KConfig values
#include "esp_modbus_common.h" // for common types
#include "esp_modbus_master.h" // for public master types
#include "mbc_master.h" // for private master types
#include "mbc_serial_master.h" // for serial master create function and types
#include "mb_common.h" // for mb types definition
#include "mb_config.h"
#include "mb_proto.h"
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
/*-----------------------Master mode use these variables----------------------*/
static const char *TAG = "mbc_serial.master";
// Modbus event processing task
static void mbc_ser_master_task(void *param)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(param);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(param);
// Main Modbus stack processing cycle
for (;;)
{
// Wait for poll events
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
portMAX_DELAY);
// Check if stack started then poll for data
if (status & MB_EVENT_STACK_STARTED)
{
(void)mbm_iface->mb_base->poll(mbm_iface->mb_base);
}
}
}
// Modbus controller stack start function
static esp_err_t mbc_serial_master_start(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t status = MB_EIO;
mbm_iface->mb_base->descr.parent = ctx;
MB_RETURN_ON_FALSE((mbm_opts->mbm_param_descriptor_size >= 1),
ESP_ERR_INVALID_ARG, TAG, "mb descriptor table size is incorrect.");
status = mbm_iface->mb_base->enable(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack start fail, returned (0x%x).", (int)status);
// Set the mbcontroller start flag
EventBits_t flag = xEventGroupSetBits(mbm_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
mbm_iface->is_active = true;
return ESP_OK;
}
// Modbus controller stack stop function
static esp_err_t mbc_serial_master_stop(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t status = MB_EIO;
mbm_iface->mb_base->descr.parent = ctx;
// Set the mbcontroller start flag
EventBits_t flag = xEventGroupClearBits(mbm_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
status = mbm_iface->mb_base->disable(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack disable fail, returned (0x%x).", (int)status);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop event set error.");
mbm_iface->is_active = false;
return ESP_OK;
}
// Modbus controller destroy function
static esp_err_t mbc_serial_master_delete(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t mb_error = MB_ENOERR;
// Check the stack started bit
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
pdMS_TO_TICKS(MB_MASTER_TIMEOUT_MS_RESPOND));
if (mbm_iface->is_active || (status & MB_EVENT_STACK_STARTED))
{
ESP_LOGV(TAG, "mb stack is active, try to disable.");
MB_RETURN_ON_FALSE((mbc_serial_master_stop(ctx) == ESP_OK),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
}
mbm_iface->is_active = false;
vTaskDelete(mbm_opts->task_handle);
mbm_opts->task_handle = NULL;
vEventGroupDelete(mbm_opts->event_group_handle);
mbm_opts->event_group_handle = NULL;
mb_error = mbm_iface->mb_base->delete(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack delete failure, returned (0x%x).", (int)mb_error);
mbm_iface->mb_base = NULL;
free(mbm_iface); // free the memory allocated
return ESP_OK;
}
// Set Modbus parameter description table
static esp_err_t mbc_serial_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements)
{
MB_RETURN_ON_FALSE((descriptor), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor.");
MB_RETURN_ON_FALSE((num_elements >= 1), ESP_ERR_INVALID_ARG, TAG, "mb table size is incorrect.");
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
const mb_parameter_descriptor_t *reg_ptr = descriptor;
// Go through all items in the table to check all Modbus registers
for (uint16_t counter = 0; counter < (num_elements); counter++, reg_ptr++)
{
// Below is the code to check consistency of the table format and required fields.
MB_RETURN_ON_FALSE((reg_ptr->cid == counter),
ESP_ERR_INVALID_ARG, TAG, "mb descriptor cid field is incorrect.");
MB_RETURN_ON_FALSE((reg_ptr->param_key),
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param key is incorrect.");
MB_RETURN_ON_FALSE((reg_ptr->mb_size > 0),
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param size is incorrect.");
}
mbm_opts->param_descriptor_table = descriptor;
mbm_opts->mbm_param_descriptor_size = num_elements;
return ESP_OK;
}
// Send custom Modbus request defined as mb_param_request_t structure
static esp_err_t mbc_serial_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb request structure.");
MB_RETURN_ON_FALSE((data_ptr), ESP_ERR_INVALID_ARG, TAG, "mb incorrect data pointer.");
mb_err_enum_t mb_error = MB_EBUSY;
esp_err_t error = ESP_FAIL;
if (mb_port_event_res_take(mbm_controller_iface->mb_base->port_obj, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS)))
{
uint8_t mb_slave_addr = request->slave_addr;
uint8_t mb_command = request->command;
uint16_t mb_offset = request->reg_start;
uint16_t mb_size = request->reg_size;
// Set the buffer for callback function processing of received data
mbm_opts->reg_buffer_ptr = (uint8_t *)data_ptr;
mbm_opts->reg_buffer_size = mb_size;
mb_port_event_res_release(mbm_controller_iface->mb_base->port_obj);
// Calls appropriate request function to send request and waits response
switch (mb_command)
{
#if MB_FUNC_READ_COILS_ENABLED
case MB_FUNC_READ_COILS:
mb_error = mbm_rq_read_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
case MB_FUNC_WRITE_SINGLE_COIL:
mb_error = mbm_rq_write_coil(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
*(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
case MB_FUNC_WRITE_MULTIPLE_COILS:
mb_error = mbm_rq_write_multi_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size, (uint8_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
case MB_FUNC_READ_DISCRETE_INPUTS:
mb_error = mbm_rq_read_discrete_inputs(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
case MB_FUNC_READ_HOLDING_REGISTER:
mb_error = mbm_rq_read_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
case MB_FUNC_WRITE_REGISTER:
mb_error = mbm_rq_write_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
*(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
mb_error = mbm_rq_write_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr,
(uint16_t)mb_offset, (uint16_t)mb_size,
(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
case MB_FUNC_READWRITE_MULTIPLE_REGISTERS:
mb_error = mbm_rq_rw_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size, (uint16_t *)data_ptr,
(uint16_t)mb_offset, (uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_INPUT_ENABLED
case MB_FUNC_READ_INPUT_REGISTER:
mb_error = mbm_rq_read_inp_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
default:
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
__FUNCTION__, mb_command);
mb_error = MB_ENOREG;
break;
}
}
// Propagate the Modbus errors to higher level
switch (mb_error)
{
case MB_ENOERR:
error = ESP_OK;
break;
case MB_ENOREG:
error = ESP_ERR_NOT_SUPPORTED; // Invalid register request
break;
case MB_ETIMEDOUT:
error = ESP_ERR_TIMEOUT; // Slave did not send response
break;
case MB_EILLFUNC:
case MB_ERECVDATA:
error = ESP_ERR_INVALID_RESPONSE; // Invalid response from slave
break;
case MB_EBUSY:
error = ESP_ERR_INVALID_STATE; // Master is busy (previous request is pending)
break;
default:
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (uint16_t)mb_error);
error = ESP_FAIL;
break;
}
return error;
}
static esp_err_t mbc_serial_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t **param_buffer)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
MB_RETURN_ON_FALSE((param_buffer),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect data buffer pointer.");
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor table or not set.");
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size),
ESP_ERR_NOT_FOUND, TAG, "mb incorrect cid of characteristic.");
// It is assumed that characteristics cid increased in the table
const mb_parameter_descriptor_t *reg_info = &mbm_opts->param_descriptor_table[cid];
MB_RETURN_ON_FALSE((reg_info->param_key),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect characteristic key.");
*param_buffer = reg_info;
return ESP_OK;
}
// Helper function to get modbus command for each type of Modbus register area
static uint8_t mbc_serial_master_get_command(mb_param_type_t param_type, mb_param_mode_t mode)
{
uint8_t command = 0;
switch (param_type)
{
case MB_PARAM_HOLDING:
command = (mode == MB_PARAM_WRITE) ? MB_FUNC_WRITE_MULTIPLE_REGISTERS : MB_FUNC_READ_HOLDING_REGISTER;
break;
case MB_PARAM_INPUT:
command = MB_FUNC_READ_INPUT_REGISTER;
break;
case MB_PARAM_COIL:
command = (mode == MB_PARAM_WRITE) ? MB_FUNC_WRITE_MULTIPLE_COILS : MB_FUNC_READ_COILS;
break;
case MB_PARAM_DISCRETE:
if (mode != MB_PARAM_WRITE)
{
command = MB_FUNC_READ_DISCRETE_INPUTS;
}
else
{
ESP_LOGE(TAG, "%s: Incorrect mode (%u)",
__FUNCTION__, (unsigned)mode);
}
break;
default:
ESP_LOGE(TAG, "%s: Incorrect param type (%u)",
__FUNCTION__, (unsigned)param_type);
break;
}
return command;
}
// Helper to search parameter by name in the parameter description table
// and fills Modbus request fields accordingly
static esp_err_t mbc_serial_master_set_request(void *ctx, uint16_t cid, mb_param_mode_t mode,
mb_param_request_t *request,
mb_parameter_descriptor_t *reg_data)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
esp_err_t error = ESP_ERR_NOT_FOUND;
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb incorrect request parameter.");
MB_RETURN_ON_FALSE((mode <= MB_PARAM_WRITE), ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode.");
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size), ESP_ERR_INVALID_ARG, TAG, "mb incorrect cid parameter.");
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table), ESP_ERR_INVALID_ARG, TAG, "mb data dictionary is incorrect.");
const mb_parameter_descriptor_t *reg_ptr = mbm_opts->param_descriptor_table;
reg_ptr += cid;
if (reg_ptr->cid == cid)
{
request->slave_addr = reg_ptr->mb_slave_addr;
request->reg_start = reg_ptr->mb_reg_start;
request->reg_size = reg_ptr->mb_size;
request->command = mbc_serial_master_get_command(reg_ptr->mb_param_type, mode);
MB_RETURN_ON_FALSE((request->command > 0), ESP_ERR_INVALID_ARG, TAG, "mb incorrect command or parameter type.");
if (reg_data)
{
*reg_data = *reg_ptr; // Set the cid registered parameter data
}
error = ESP_OK;
}
return error;
}
// Get parameter data for corresponding characteristic
static esp_err_t mbc_serial_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
// alloc buffer to store parameter data
pdata = calloc(1, (reg_info.mb_size << 1));
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
error = mbc_serial_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
if (value) {
error = mbc_master_set_param_data((void *)value, (void *)pdata,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
} else {
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Get parameter data for corresponding characteristic
static esp_err_t mbc_serial_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
uint8_t *value_ptr, uint8_t *type)
{
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request;
mb_parameter_descriptor_t reg_info = {0};
uint8_t *pdata = NULL;
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid))
{
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)
{
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
}
request.slave_addr = uid; // override the UID
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
// alloc buffer to store parameter data
pdata = calloc(1, (reg_info.mb_size << 1));
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
// Send request to read characteristic data
error = mbc_serial_master_send_request(ctx, &request, pdata);
if (error == ESP_OK)
{
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
if (value_ptr) {
error = mbc_master_set_param_data((void *)value_ptr, (void *)pdata,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
}
else
{
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
}
else
{
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Set parameter value for characteristic selected by cid
static esp_err_t mbc_serial_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
// Transfer value of characteristic into parameter buffer
error = mbc_master_set_param_data((void *)pdata, (void *)value,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
free(pdata);
return ESP_ERR_INVALID_STATE;
}
// Send request to write characteristic data
error = mbc_serial_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
} else {
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Set parameter value for characteristic selected by name and cid
static esp_err_t mbc_serial_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
uint8_t *value_ptr, uint8_t *type)
{
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request;
mb_parameter_descriptor_t reg_info = {0};
uint8_t *pdata = NULL;
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid))
{
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)
{
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
}
request.slave_addr = uid; // override the UID
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
// Transfer value of characteristic into parameter buffer
error = mbc_master_set_param_data((void *)pdata, (void *)value_ptr,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
free(pdata);
return ESP_ERR_INVALID_STATE;
}
// Send request to write characteristic data
error = mbc_serial_master_send_request(ctx, &request, value_ptr);
if (error == ESP_OK)
{
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
else
{
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
}
else
{
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
static void mbc_serial_master_iface_free(void *ctx)
{
mbm_controller_iface_t *mbm_iface = (mbm_controller_iface_t *)(ctx);
if (mbm_iface)
{
if (mbm_iface->opts.task_handle)
{
vTaskDelete(mbm_iface->opts.task_handle);
mbm_iface->opts.task_handle = NULL;
}
if (mbm_iface->opts.event_group_handle)
{
vEventGroupDelete(mbm_iface->opts.event_group_handle);
mbm_iface->opts.event_group_handle = NULL;
}
free(mbm_iface); // free the memory allocated for interface
}
}
static esp_err_t mbc_serial_master_controller_create(void **ctx)
{
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack init interface fail.");
mbm_controller_iface_t *mbm_controller_iface = NULL;
esp_err_t ret = ESP_ERR_INVALID_STATE;
BaseType_t status = 0;
// Allocate space for controller
mbm_controller_iface = malloc(sizeof(mbm_controller_iface_t));
MB_GOTO_ON_FALSE((mbm_controller_iface), ESP_ERR_INVALID_STATE, error,
TAG, "mb stack memory allocation fail.");
// Initialize interface properties
mb_master_options_t *mbm_opts = &mbm_controller_iface->opts;
// Initialization of active context of the modbus controller
mbm_opts->event_group_handle = xEventGroupCreate();
MB_GOTO_ON_FALSE((mbm_opts->event_group_handle), ESP_ERR_INVALID_STATE, error, TAG, "mb event group error.");
// Create modbus controller task
status = xTaskCreatePinnedToCore((void *)&mbc_ser_master_task,
"mbc_ser_master",
MB_CONTROLLER_STACK_SIZE,
mbm_controller_iface,
MB_CONTROLLER_PRIORITY,
&mbm_opts->task_handle,
MB_PORT_TASK_AFFINITY);
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
"mb controller task creation error");
MB_MASTER_ASSERT(mbm_opts->task_handle); // The task is created but handle is incorrect
// Initialize public interface methods of the interface
mbm_controller_iface->create = mbc_serial_master_create;
mbm_controller_iface->delete = mbc_serial_master_delete;
mbm_controller_iface->start = mbc_serial_master_start;
mbm_controller_iface->stop = mbc_serial_master_stop;
mbm_controller_iface->get_cid_info = mbc_serial_master_get_cid_info;
mbm_controller_iface->get_parameter = mbc_serial_master_get_parameter;
mbm_controller_iface->get_parameter_with = mbc_serial_master_get_parameter_with;
mbm_controller_iface->send_request = mbc_serial_master_send_request;
mbm_controller_iface->set_descriptor = mbc_serial_master_set_descriptor;
mbm_controller_iface->set_parameter = mbc_serial_master_set_parameter;
mbm_controller_iface->set_parameter_with = mbc_serial_master_set_parameter_with;
mbm_controller_iface->mb_base = NULL;
*ctx = mbm_controller_iface;
return ESP_OK;
error:
mbc_serial_master_iface_free((void *)mbm_controller_iface);
return ret;
}
// Initialization of resources for Modbus serial master controller
esp_err_t mbc_serial_master_create(mb_communication_info_t *config, void **ctx)
{
mbm_controller_iface_t *mbm_controller_iface = NULL;
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
MB_RETURN_ON_FALSE((!*ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack is not destroyed?");
mb_serial_opts_t *pcomm_info = &config->ser_opts;
// Check communication options
MB_RETURN_ON_FALSE(((pcomm_info->mode == MB_RTU) || (pcomm_info->mode == MB_ASCII)),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode = (%u).", (unsigned)pcomm_info->mode);
MB_RETURN_ON_FALSE((pcomm_info->port <= UART_NUM_MAX), ESP_ERR_INVALID_ARG, TAG,
"mb wrong port to set = (%u).", (unsigned)pcomm_info->port);
MB_RETURN_ON_FALSE((pcomm_info->parity <= UART_PARITY_ODD), ESP_ERR_INVALID_ARG, TAG,
"mb wrong parity option = (%u).", (unsigned)pcomm_info->parity);
esp_err_t ret = mbc_serial_master_controller_create((void *)&mbm_controller_iface);
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG, "mbc create returns (0x%x).", (int)ret);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
mbm_opts->comm_opts = *config;
mbm_opts->port_type = MB_PORT_SERIAL_MASTER;
// Keep the response time setting
if (!pcomm_info->response_tout_ms)
{
mbm_opts->comm_opts.ser_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
}
// Initialize Modbus stack using mbcontroller parameters
mb_err_enum_t err = MB_EILLSTATE;
void *pinst = (void *)mbm_controller_iface;
if (pcomm_info->mode == MB_RTU)
{
err = mbm_rtu_create(pcomm_info, &pinst);
}
else if (pcomm_info->mode == MB_ASCII)
{
err = mbm_ascii_create(pcomm_info, &pinst);
}
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
"mb object create returns (0x%x).", (int)err);
mbm_controller_iface->mb_base = (mb_base_t *)pinst;
const mb_rw_callbacks_t rw_cbs = {
.reg_input_cb = mbc_reg_input_master_cb,
.reg_holding_cb = mbc_reg_holding_master_cb,
.reg_coils_cb = mbc_reg_coils_master_cb,
.reg_discrete_cb = mbc_reg_discrete_master_cb
};
mbm_controller_iface->mb_base->rw_cbs = rw_cbs;
mbm_controller_iface->is_active = false;
*ctx = mbm_controller_iface;
return ESP_OK;
error:
if (mbm_controller_iface)
{
if (mbm_controller_iface->mb_base)
{
mbm_controller_iface->mb_base->delete(mbm_controller_iface->mb_base);
mbm_controller_iface->mb_base = NULL;
}
mbc_serial_master_iface_free((void *)mbm_controller_iface);
*ctx = NULL;
}
return ret;
}
#endif

View File

@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_serial_master.h Modbus controller serial master implementation header file
#pragma once
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "soc/soc.h" // for BITN definitions
#include "esp_err.h" // for esp_err_t
#include "esp_modbus_common.h" // for common defines
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
/**
* @brief Initialize Modbus controller and stack
*
* @param[out] ctx - pointer to pointer of interface structure
* @param[in] config - pointer to configuration structure
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
*/
esp_err_t mbc_serial_master_create(mb_communication_info_t *config, void **ctx);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,293 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_serial_slave.c
// Implementation of the Modbus controller serial slave
#include <sys/time.h> // for calculation of time stamp in milliseconds
#include "esp_log.h" // for log_write
#include "esp_modbus_common.h" // for common defines
#include "esp_modbus_slave.h" // for public slave interface types
#include "mbc_slave.h" // for private slave interface types
#include "mbc_serial_slave.h" // for serial slave implementation definitions
#include "mb_common.h" // for mb object types definition
#include "sdkconfig.h" // for KConfig values
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
static const char *TAG = "mbc_serial.slave";
// Modbus task function
static void mbc_ser_slave_task(void *param)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(param);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(param);
// Main Modbus stack processing cycle
for (;;)
{
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE, // do not clear bits
pdFALSE,
portMAX_DELAY);
// Check if stack started then poll for data
if (status & MB_EVENT_STACK_STARTED)
{
(void)mbs_iface->mb_base->poll(mbs_iface->mb_base);
}
// esp_task_wdt_reset();
}
}
// Start Modbus controller start function
static esp_err_t mbc_serial_slave_start(void *ctx)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_err_enum_t status = MB_EIO;
status = mbs_iface->mb_base->enable(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack enable fail, returned (0x%x).", (int)status);
// Set the mbcontroller start flag
EventBits_t flag = xEventGroupSetBits(mbs_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
mbs_iface->mb_base->descr.parent = ctx;
mbs_iface->is_active = true;
return ESP_OK;
}
// Start Modbus controller stop function
static esp_err_t mbc_serial_slave_stop(void *ctx)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_err_enum_t status = MB_EIO;
// Clear the mbcontroller start flag
EventBits_t flag = xEventGroupClearBits(mbs_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
status = mbs_iface->mb_base->disable(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack disable fail, returned (0x%x).", (int)status);
mbs_iface->mb_base->descr.parent = NULL;
mbs_iface->is_active = false;
return ESP_OK;
}
// Blocking function to get event on parameter group change for application task
static mb_event_group_t mbc_serial_slave_check_event(void *ctx, mb_event_group_t group)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
MB_SLAVE_ASSERT(mbs_opts->event_group_handle);
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle, (BaseType_t)group,
pdTRUE, pdFALSE, portMAX_DELAY);
return (mb_event_group_t)status;
}
// Function to get notification about parameter change from application task
static esp_err_t mbc_serial_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
esp_err_t err = ESP_ERR_TIMEOUT;
MB_RETURN_ON_FALSE((mbs_opts->notification_queue_handle),
ESP_ERR_INVALID_ARG, TAG, "mb queue handle is invalid.");
MB_RETURN_ON_FALSE((reg_info), ESP_ERR_INVALID_ARG, TAG, "mb register information is invalid.");
BaseType_t status = xQueueReceive(mbs_opts->notification_queue_handle,
reg_info, pdMS_TO_TICKS(timeout));
if (status == pdTRUE)
{
err = ESP_OK;
}
return err;
}
// Modbus controller delete function
static esp_err_t mbc_serial_slave_delete(void *ctx)
{
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mb_err_enum_t mb_error = MB_ENOERR;
// Check the stack started bit
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
MB_CONTROLLER_NOTIFY_TIMEOUT);
if (mbs_iface->is_active || (status & MB_EVENT_STACK_STARTED))
{
ESP_LOGV(TAG, "mb stack is active, try to disable.");
if (mbc_serial_slave_stop(ctx) != ESP_OK) {
ESP_LOGE(TAG, "mb stack stop failure.");
}
}
mbs_iface->is_active = false;
vTaskDelete(mbs_opts->task_handle);
vEventGroupDelete(mbs_opts->event_group_handle);
vQueueDelete(mbs_opts->notification_queue_handle);
mbs_opts->notification_queue_handle = NULL;
mbs_opts->event_group_handle = NULL;
mbs_opts->task_handle = NULL;
mb_error = mbs_iface->mb_base->delete(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack close failure returned (0x%x).", (int)mb_error);
// free the controller will be performed in common slave object
return ESP_OK;
}
static void mbc_serial_slave_iface_free(void *ctx)
{
mbs_controller_iface_t *mbs_iface = (mbs_controller_iface_t *)(ctx);
if (mbs_iface)
{
if (mbs_iface->opts.task_handle)
{
vTaskDelete(mbs_iface->opts.task_handle);
mbs_iface->opts.task_handle = NULL;
}
if (mbs_iface->opts.event_group_handle)
{
vEventGroupDelete(mbs_iface->opts.event_group_handle);
mbs_iface->opts.event_group_handle = NULL;
}
if (mbs_iface->opts.notification_queue_handle)
{
vQueueDelete(mbs_iface->opts.notification_queue_handle);
}
free(mbs_iface); // free the memory allocated for interface
}
}
static esp_err_t mbc_serial_slave_controller_create(void **ctx)
{
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
esp_err_t ret = ESP_ERR_INVALID_STATE;
mbs_controller_iface_t *mbs_controller_iface = malloc(sizeof(mbs_controller_iface_t));
MB_GOTO_ON_FALSE((mbs_controller_iface), ESP_ERR_NO_MEM, error,
TAG, "mb stack memory allocation fail.");
mb_slave_options_t *mbs_opts = &mbs_controller_iface->opts;
mbs_opts->port_type = MB_PORT_SERIAL_SLAVE; // set interface port type
// Initialization of active context of the Modbus controller
BaseType_t status = 0;
// Parameter change notification queue
mbs_opts->event_group_handle = xEventGroupCreate();
MB_GOTO_ON_FALSE((mbs_opts->event_group_handle), ESP_ERR_NO_MEM, error,
TAG, "mb event group error.");
// Parameter change notification queue
mbs_opts->notification_queue_handle = xQueueCreate(MB_CONTROLLER_NOTIFY_QUEUE_SIZE, sizeof(mb_param_info_t));
MB_GOTO_ON_FALSE((mbs_opts->notification_queue_handle), ESP_ERR_NO_MEM, error,
TAG, "mb notify queue creation error.");
// Create Modbus controller task
status = xTaskCreatePinnedToCore((void *)&mbc_ser_slave_task,
"mbc_ser_slave",
MB_CONTROLLER_STACK_SIZE,
mbs_controller_iface,
MB_CONTROLLER_PRIORITY,
&mbs_opts->task_handle,
MB_PORT_TASK_AFFINITY);
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
"mb controller task creation error");
MB_SLAVE_ASSERT(mbs_opts->task_handle); // The task is created but handle is incorrect
// Initialize interface function pointers
mbs_controller_iface->create = mbc_serial_slave_create;
mbs_controller_iface->delete = mbc_serial_slave_delete;
mbs_controller_iface->check_event = mbc_serial_slave_check_event;
mbs_controller_iface->get_param_info = mbc_serial_slave_get_param_info;
mbs_controller_iface->set_descriptor = NULL; // Use common set descriptor function
mbs_controller_iface->start = mbc_serial_slave_start;
mbs_controller_iface->stop = mbc_serial_slave_stop;
mbs_controller_iface->mb_base = NULL;
*ctx = mbs_controller_iface;
return ESP_OK;
error:
mbc_serial_slave_iface_free((void *)mbs_controller_iface);
return ret;
}
// Initialization of Modbus controller
esp_err_t mbc_serial_slave_create(mb_communication_info_t *config, void **ctx)
{
mbs_controller_iface_t *mbs_controller_iface = NULL;
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
MB_RETURN_ON_FALSE((!*ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack is not destroyed?");
mb_serial_opts_t *pcomm_info = &config->ser_opts;
// Check communication options
MB_RETURN_ON_FALSE(((pcomm_info->mode == MB_RTU) || (pcomm_info->mode == MB_ASCII)),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode = (%u).",
(unsigned)pcomm_info->mode);
MB_RETURN_ON_FALSE((pcomm_info->port <= UART_NUM_MAX), ESP_ERR_INVALID_ARG, TAG,
"mb wrong port to set = (%u).", (unsigned)pcomm_info->port);
MB_RETURN_ON_FALSE((pcomm_info->parity <= UART_PARITY_ODD), ESP_ERR_INVALID_ARG, TAG,
"mb wrong parity option = (%u).", (unsigned)pcomm_info->parity);
MB_RETURN_ON_FALSE((pcomm_info->uid <= MB_ADDRESS_MAX),
ESP_ERR_INVALID_ARG, TAG, "mb wrong slave address = (0x%u).",
(unsigned)pcomm_info->uid);
esp_err_t ret = mbc_serial_slave_controller_create((void *)&mbs_controller_iface);
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
"mbc create returns (0x%x).", (int)ret);
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(mbs_controller_iface);
mbs_opts->port_type = MB_PORT_SERIAL_SLAVE;
mbs_opts->comm_opts = *config;
mb_err_enum_t err = MB_ENOERR;
void *pinst = (void *)mbs_controller_iface;
// Initialize Modbus stack using mbcontroller parameters
if (pcomm_info->mode == MB_RTU)
{
err = mbs_rtu_create(pcomm_info, &pinst);
}
else if (pcomm_info->mode == MB_ASCII)
{
err = mbs_ascii_create(pcomm_info, &pinst);
}
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
"mbs create returns (0x%x).", (int)err);
mbs_controller_iface->mb_base = (mb_base_t *)pinst;
// Configure Modbus read/write callbacks for the base modbus object
const mb_rw_callbacks_t rw_cbs = {
.reg_input_cb = mbc_reg_input_slave_cb,
.reg_holding_cb = mbc_reg_holding_slave_cb,
.reg_coils_cb = mbc_reg_coils_slave_cb,
.reg_discrete_cb = mbc_reg_discrete_slave_cb
};
mbs_controller_iface->mb_base->rw_cbs = rw_cbs;
mbs_controller_iface->is_active = false;
*ctx = (void *)mbs_controller_iface;
return ESP_OK;
error:
if (mbs_controller_iface) {
if (mbs_controller_iface->mb_base) {
mbs_controller_iface->mb_base->delete (mbs_controller_iface->mb_base);
mbs_controller_iface->mb_base = NULL;
}
mbc_serial_slave_iface_free((void *)mbs_controller_iface);
*ctx = NULL;
}
return ret;
}
#endif

View File

@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_serial_slave.h Modbus controller serial slave implementation header file
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "esp_modbus_common.h" // for common defines
#include "sdkconfig.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_CONTROLLER_NOTIFY_QUEUE_SIZE (CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE) // Number of messages in parameter notification queue
#define MB_CONTROLLER_NOTIFY_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT)) // notification timeout
#if (CONFIG_FMB_COMM_MODE_RTU_EN || CONFIG_FMB_COMM_MODE_ASCII_EN)
/*
* @brief Initialize Modbus controller and stack
*
* @param[out] ctx - pointer to pointer of interface structure
* @param[in] config - pointer to configuration structure
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
*/
esp_err_t mbc_serial_slave_create(mb_communication_info_t *config, void **ctx);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,768 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_tcp_master.c
// TCP master implementation of the Modbus controller
#include <sys/time.h> // for calculation of time stamp in milliseconds
#include "esp_log.h" // for log_write
#include <string.h> // for memcpy
#include <sys/queue.h> // for list
#include "freertos/FreeRTOS.h" // for task creation and queue access
#include "freertos/task.h" // for task api access
#include "freertos/event_groups.h" // for event groups
#include "freertos/queue.h" // for queue api access
#include "sdkconfig.h" // for KConfig values
#include "esp_modbus_common.h" // for common types
#include "esp_modbus_master.h" // for public master types
#include "mbc_master.h" // for private master types
#include "mbc_tcp_master.h" // for tcp master create function and types
#include "port_tcp_common.h"
#include "port_tcp_master.h"
#include "mb_common.h" // for mb types definition
#include "mb_config.h"
#include "mb_proto.h"
#include "mb_port_types.h"
#if MB_MASTER_TCP_ENABLED
/*-----------------------Master mode use these variables----------------------*/
static const char *TAG = "mbc_tcp.master";
#define MB_TCP_CONNECTION_TOUT pdMS_TO_TICKS(CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000)
//typedef enum _mb_sock_state mb_sock_state_t;
// Modbus event processing task
static void modbus_tcp_master_task(void *param)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(param);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(param);
// Main Modbus stack processing cycle
for (;;) {
// Wait for poll events
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
portMAX_DELAY);
// Check if stack started then poll for data
if (status & MB_EVENT_STACK_STARTED) {
(void)mbm_iface->mb_base->poll(mbm_iface->mb_base);
}
}
}
static void mbc_tcp_master_conn_done_cb(void *ctx)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
ESP_LOGI(TAG, "mb controller connection done.");
EventBits_t flag = xEventGroupSetBits(mbm_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_CONNECTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_CONNECTED),
;, TAG, "mb stack connected event set error.");
}
// Modbus controller stack start function
static esp_err_t mbc_tcp_master_start(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t status = MB_EIO;
mbm_iface->mb_base->descr.parent = ctx;
MB_RETURN_ON_FALSE((mbm_opts->mbm_param_descriptor_size >= 1),
ESP_ERR_INVALID_ARG, TAG,"mb descriptor table size is incorrect.");
status = mbm_iface->mb_base->enable(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack start fail, returned (0x%x).", (uint16_t)status);
// Wait the connection esteblished before start polling according to the option
if (!mbm_opts->comm_opts.tcp_opts.start_disconnected) {
BaseType_t start = xEventGroupWaitBits(mbm_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_CONNECTED),
pdFALSE,
pdFALSE,
MB_TCP_CONNECTION_TOUT);
MB_RETURN_ON_FALSE((start), ESP_ERR_INVALID_STATE, TAG,
"mb stack could not connect to slaves for %u seconds.",
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC);
}
mbm_iface->is_active = true;
xEventGroupSetBits(mbm_opts->event_group_handle, (EventBits_t)MB_EVENT_STACK_STARTED);
return ESP_OK;
}
// Modbus controller stack stop function
static esp_err_t mbc_tcp_master_stop(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t status = MB_EIO;
mbm_iface->mb_base->descr.parent = ctx;
// Set the mbcontroller start flag
EventBits_t flag = xEventGroupClearBits(mbm_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop event set error.");
status = mbm_iface->mb_base->disable(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack disable fail, returned (0x%x).", (uint16_t)status);
mbm_iface->is_active = false;
return ESP_OK;
}
// Set Modbus parameter description table
static esp_err_t mbc_tcp_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements)
{
MB_RETURN_ON_FALSE((descriptor), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor.");
MB_RETURN_ON_FALSE((num_elements >= 1), ESP_ERR_INVALID_ARG, TAG, "mb table size is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
const char **comm_ip_table = (const char **)mbm_opts->comm_opts.tcp_opts.ip_addr_table;
MB_RETURN_ON_FALSE((comm_ip_table), ESP_ERR_INVALID_ARG, TAG, "mb ip table address is incorrect.");
const mb_parameter_descriptor_t *reg_ptr = descriptor;
mb_uid_info_t *paddr_info = NULL;
// Go through all items in the table to check all Modbus registers
for (int idx = 0; idx < (num_elements); idx++, reg_ptr++) {
// Check consistency of the table format and required fields.
MB_RETURN_ON_FALSE((reg_ptr->cid == idx), ESP_ERR_INVALID_ARG, TAG, "mb descriptor cid field is incorrect.");
MB_RETURN_ON_FALSE((reg_ptr->param_key), ESP_ERR_INVALID_ARG, TAG, "mb descriptor param key is incorrect.");
MB_RETURN_ON_FALSE((reg_ptr->mb_size > 0), ESP_ERR_INVALID_ARG, TAG, "mb descriptor param size is incorrect.");
if (reg_ptr->mb_slave_addr == MB_SLAVE_ADDR_PLACEHOLDER) {
continue; // skip not defined uid in the data dictionary
}
// Is the slave with the UID already in the list?
paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj, reg_ptr->mb_slave_addr, MB_SOCK_STATE_OPENED);
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_INVALID_ARG, TAG,
"mb missing IP address configuration for cid #%u, uid=%d.", (unsigned)reg_ptr->cid, (int)reg_ptr->mb_slave_addr);
ESP_LOGI(TAG, "mb found config for cid #%d, uid=%d.", (int)reg_ptr->cid, (int)reg_ptr->mb_slave_addr);
}
mbm_opts->param_descriptor_table = descriptor;
mbm_opts->mbm_param_descriptor_size = num_elements;
return ESP_OK;
}
// Send custom Modbus request defined as mb_param_request_t structure
static esp_err_t mbc_tcp_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb request structure.");
MB_RETURN_ON_FALSE((data_ptr), ESP_ERR_INVALID_ARG, TAG, "mb incorrect data pointer.");
mb_err_enum_t mb_error = MB_EBUSY;
esp_err_t error = ESP_FAIL;
if (mb_port_event_res_take(mbm_controller_iface->mb_base->port_obj, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS))) {
uint8_t mb_slave_addr = request->slave_addr;
uint8_t mb_command = request->command;
uint16_t mb_offset = request->reg_start;
uint16_t mb_size = request->reg_size;
// Set the buffer for callback function processing of received data
mbm_opts->reg_buffer_ptr = (uint8_t *)data_ptr;
mbm_opts->reg_buffer_size = mb_size;
mb_port_event_res_release(mbm_controller_iface->mb_base->port_obj);
// Calls appropriate request function to send request and waits response
switch(mb_command) {
#if MB_FUNC_READ_COILS_ENABLED
case MB_FUNC_READ_COILS:
mb_error = mbm_rq_read_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size ,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_COIL_ENABLED
case MB_FUNC_WRITE_SINGLE_COIL:
mb_error = mbm_rq_write_coil(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
*(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
case MB_FUNC_WRITE_MULTIPLE_COILS:
mb_error = mbm_rq_write_multi_coils(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size, (uint8_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
case MB_FUNC_READ_DISCRETE_INPUTS:
mb_error = mbm_rq_read_discrete_inputs(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_HOLDING_ENABLED
case MB_FUNC_READ_HOLDING_REGISTER:
mb_error = mbm_rq_read_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED
case MB_FUNC_WRITE_REGISTER:
mb_error = mbm_rq_write_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
*(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
mb_error = mbm_rq_write_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr,
(uint16_t)mb_offset, (uint16_t)mb_size,
(uint16_t *)data_ptr,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED
case MB_FUNC_READWRITE_MULTIPLE_REGISTERS:
mb_error = mbm_rq_rw_multi_holding_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size, (uint16_t *)data_ptr,
(uint16_t)mb_offset, (uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
#if MB_FUNC_READ_INPUT_ENABLED
case MB_FUNC_READ_INPUT_REGISTER:
mb_error = mbm_rq_read_inp_reg(mbm_controller_iface->mb_base, (uint8_t)mb_slave_addr, (uint16_t)mb_offset,
(uint16_t)mb_size,
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
break;
#endif
default:
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u) ",
__FUNCTION__, (unsigned)mb_command);
mb_error = MB_ENOREG;
break;
}
}
// Propagate the Modbus errors to higher level
switch(mb_error) {
case MB_ENOERR:
error = ESP_OK;
break;
case MB_ENOREG:
error = ESP_ERR_NOT_SUPPORTED; // Invalid register request
break;
case MB_ETIMEDOUT:
error = ESP_ERR_TIMEOUT; // Slave did not send response
break;
case MB_EILLFUNC:
case MB_ERECVDATA:
error = ESP_ERR_INVALID_RESPONSE; // Invalid response from slave
break;
case MB_EBUSY:
error = ESP_ERR_INVALID_STATE; // Master is busy (previous request is pending)
break;
default:
ESP_LOGE(TAG, "%s: Incorrect return code (%x) ", __FUNCTION__, (int)mb_error);
error = ESP_FAIL;
break;
}
return error;
}
static esp_err_t mbc_tcp_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t** param_buffer)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
MB_RETURN_ON_FALSE((param_buffer),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect data buffer pointer.");
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor table or not set.");
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size),
ESP_ERR_NOT_FOUND, TAG, "mb incorrect cid of characteristic.");
// It is assumed that characteristics cid increased in the table
const mb_parameter_descriptor_t *reg_info = &mbm_opts->param_descriptor_table[cid];
MB_RETURN_ON_FALSE((reg_info->param_key),
ESP_ERR_INVALID_ARG, TAG, "mb incorrect characteristic key.");
*param_buffer = reg_info;
return ESP_OK;
}
// Helper function to get modbus command for each type of Modbus register area
static uint8_t mbc_tcp_master_get_command(mb_param_type_t param_type, mb_param_mode_t mode)
{
uint8_t command = 0;
switch(param_type){ // Check commands
case MB_PARAM_HOLDING:
command = (mode == MB_PARAM_WRITE) ?
MB_FUNC_WRITE_MULTIPLE_REGISTERS :
MB_FUNC_READ_HOLDING_REGISTER;
break;
case MB_PARAM_INPUT:
command = MB_FUNC_READ_INPUT_REGISTER;
break;
case MB_PARAM_COIL:
command = (mode == MB_PARAM_WRITE) ?
MB_FUNC_WRITE_MULTIPLE_COILS :
MB_FUNC_READ_COILS;
break;
case MB_PARAM_DISCRETE:
if (mode != MB_PARAM_WRITE) {
command = MB_FUNC_READ_DISCRETE_INPUTS;
} else {
ESP_LOGE(TAG, "%s: Incorrect mode (%u)",
__FUNCTION__, (unsigned)mode);
}
break;
default:
ESP_LOGE(TAG, "%s: Incorrect param type (%u)",
__FUNCTION__, (unsigned)param_type);
break;
}
return command;
}
// Helper to search parameter in the parameter description table and fills Modbus request fields accordingly
static esp_err_t mbc_tcp_master_set_request(void *ctx, uint16_t cid, mb_param_mode_t mode, mb_param_request_t *request,
mb_parameter_descriptor_t *reg_data)
{
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
esp_err_t error = ESP_ERR_NOT_FOUND;
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb incorrect request parameter.");
MB_RETURN_ON_FALSE((mode <= MB_PARAM_WRITE), ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode.");
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size), ESP_ERR_INVALID_ARG, TAG, "mb incorrect cid parameter.");
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table), ESP_ERR_INVALID_ARG, TAG, "mb data dictionary is incorrect.");
const mb_parameter_descriptor_t *reg_ptr = mbm_opts->param_descriptor_table;
reg_ptr += cid;
if (reg_ptr->cid == cid) {
request->slave_addr = reg_ptr->mb_slave_addr;
request->reg_start = reg_ptr->mb_reg_start;
request->reg_size = reg_ptr->mb_size;
request->command = mbc_tcp_master_get_command(reg_ptr->mb_param_type, mode);
MB_RETURN_ON_FALSE((request->command > 0), ESP_ERR_INVALID_ARG, TAG, "mb incorrect command or parameter type.");
if (reg_data) {
*reg_data = *reg_ptr; // Set the cid registered parameter data
}
error = ESP_OK;
}
return error;
}
// Get parameter data for corresponding characteristic
static esp_err_t mbc_tcp_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
request.slave_addr, MB_SOCK_STATE_CONNECTED);
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
"mb can not send request for cid #%u with uid = %d.",
(unsigned)reg_info.cid, (int)request.slave_addr);
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
// alloc buffer to store parameter data
pdata = calloc(1, (reg_info.mb_size << 1));
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
error = mbc_tcp_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
if (value) {
error = mbc_master_set_param_data((void *)value, (void *)pdata,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
} else {
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Get parameter data for corresponding characteristic
static esp_err_t mbc_tcp_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_READ, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid)) {
// check that the requested uid is connected (call to port iface)
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
uid, MB_SOCK_STATE_CONNECTED);
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
"mb can not send request for cid #%u with uid=%d.", (unsigned)reg_info.cid, (int)uid);
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
}
request.slave_addr = uid; // override the UID
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
// alloc buffer to store parameter data
pdata = calloc(1, (reg_info.mb_size << 1));
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
error = mbc_tcp_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
if (value) {
error = mbc_master_set_param_data((void *)value, (void *)pdata,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
error = ESP_ERR_INVALID_STATE;
} else {
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
}
} else {
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The cid(%u) address information is not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Set parameter value for characteristic selected by name and cid
static esp_err_t mbc_tcp_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
request.slave_addr, MB_SOCK_STATE_CONNECTED);
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
"mb can not send request for cid #%u with uid=%d.",
(unsigned)reg_info.cid, (int)request.slave_addr);
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
// Transfer value of characteristic into parameter buffer
error = mbc_master_set_param_data((void *)pdata, (void *)value,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
free(pdata);
return ESP_ERR_INVALID_STATE;
}
// Send request to write characteristic data
error = mbc_tcp_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
} else {
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Set parameter value for characteristic selected by name and cid
static esp_err_t mbc_tcp_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid, uint8_t *value, uint8_t *type)
{
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
mb_param_request_t request ;
mb_parameter_descriptor_t reg_info = { 0 };
uint8_t *pdata = NULL;
error = mbc_tcp_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, &reg_info);
if ((error == ESP_OK) && (cid == reg_info.cid)) {
// check that the requested uid is connected (call to port iface)
mb_uid_info_t *paddr_info = mbm_port_tcp_get_slave_info(mbm_controller_iface->mb_base->port_obj,
uid, MB_SOCK_STATE_CONNECTED);
MB_RETURN_ON_FALSE((paddr_info), ESP_ERR_NOT_FOUND, TAG,
"mb can not send request for cid #%d with uid=%d.",
(unsigned)reg_info.cid, (int)uid);
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
}
request.slave_addr = uid; // override the UID
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
pdata = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
if (!pdata) {
return ESP_ERR_INVALID_STATE;
}
// Transfer value of characteristic into parameter buffer
error = mbc_master_set_param_data((void *)pdata, (void *)value,
reg_info.param_type, reg_info.param_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "fail to set parameter data.");
free(pdata);
return ESP_ERR_INVALID_STATE;
}
// Send request to write characteristic data
error = mbc_tcp_master_send_request(ctx, &request, pdata);
if (error == ESP_OK) {
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
} else {
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
}
free(pdata);
// Set the type of parameter found in the table
*type = reg_info.param_type;
} else {
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
__FUNCTION__, (unsigned)reg_info.cid);
error = ESP_ERR_INVALID_ARG;
}
return error;
}
// Modbus controller delete function
static esp_err_t mbc_tcp_master_delete(void *ctx)
{
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
mb_err_enum_t mb_error = MB_ENOERR;
// Check the stack started bit
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
pdMS_TO_TICKS(mbm_opts->comm_opts.tcp_opts.response_tout_ms));
if (mbm_iface->is_active || (status & MB_EVENT_STACK_STARTED)) {
ESP_LOGD(TAG, "mb stack is active, try to disable.");
MB_RETURN_ON_FALSE((mbc_tcp_master_stop(ctx) == ESP_OK),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
}
mbm_iface->is_active = false;
vTaskDelete(mbm_opts->task_handle);
mbm_opts->task_handle = NULL;
vEventGroupDelete(mbm_opts->event_group_handle);
mbm_opts->event_group_handle = NULL;
mb_error = mbm_iface->mb_base->delete(mbm_iface->mb_base);
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack delete failure, returned (0x%x).", (unsigned)mb_error);
free(mbm_iface); // free the memory allocated
ctx = NULL;
return ESP_OK;
}
// Initialization of resources for Modbus TCP master controller
esp_err_t mbc_tcp_master_controller_create(void ** ctx)
{
mbm_controller_iface_t *mbm_controller_iface = (mbm_controller_iface_t *)*ctx;
MB_RETURN_ON_FALSE((mbm_controller_iface == NULL), ESP_ERR_INVALID_STATE, TAG,
"mb stack is not destroyed.");
esp_err_t ret = ESP_ERR_INVALID_STATE;
mbm_controller_iface = malloc(sizeof(mbm_controller_iface_t));
MB_GOTO_ON_FALSE((mbm_controller_iface), ESP_ERR_INVALID_STATE, error,
TAG, "mb stack memory allocation fail.");
// Initialize interface properties
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
// Initialization of active context of the modbus controller
BaseType_t status = 0;
// Parameter change notification queue
mbm_opts->event_group_handle = xEventGroupCreate();
MB_GOTO_ON_FALSE((mbm_opts->event_group_handle), ESP_ERR_INVALID_STATE, error, TAG, "mb event group error.");
// Create modbus controller task
status = xTaskCreatePinnedToCore((void *)&modbus_tcp_master_task,
"mbm_ctrl_tcp_task",
MB_CONTROLLER_STACK_SIZE,
mbm_controller_iface,
MB_CONTROLLER_PRIORITY,
&mbm_opts->task_handle,
MB_PORT_TASK_AFFINITY);
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
"mb controller task creation error, xTaskCreate() returns (0x%x).", (unsigned)status);
MB_MASTER_ASSERT(mbm_opts->task_handle); // The task is created but handle is incorrect
// Initialize public interface methods of the interface
mbm_controller_iface->create = mbc_tcp_master_create;
mbm_controller_iface->delete = mbc_tcp_master_delete;
mbm_controller_iface->start = mbc_tcp_master_start;
mbm_controller_iface->stop = mbc_tcp_master_stop;
mbm_controller_iface->get_cid_info = mbc_tcp_master_get_cid_info;
mbm_controller_iface->get_parameter = mbc_tcp_master_get_parameter;
mbm_controller_iface->get_parameter_with = mbc_tcp_master_get_parameter_with;
mbm_controller_iface->send_request = mbc_tcp_master_send_request;
mbm_controller_iface->set_descriptor = mbc_tcp_master_set_descriptor;
mbm_controller_iface->set_parameter = mbc_tcp_master_set_parameter;
mbm_controller_iface->set_parameter_with = mbc_tcp_master_set_parameter_with;
*ctx = mbm_controller_iface;
return ESP_OK;
error:
if (mbm_controller_iface) {
if (mbm_controller_iface->opts.task_handle) {
vTaskDelete(mbm_controller_iface->opts.task_handle);
mbm_controller_iface->opts.task_handle = NULL;
}
if (mbm_controller_iface->opts.event_group_handle) {
vEventGroupDelete(mbm_controller_iface->opts.event_group_handle);
mbm_controller_iface->opts.event_group_handle = NULL;
}
}
free(mbm_controller_iface); // free the memory allocated
ctx = NULL;
return ret;
}
// Initialization of resources for Modbus serial master controller
esp_err_t mbc_tcp_master_create(mb_communication_info_t *config, void **ctx)
{
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
mbm_controller_iface_t *mbm_controller_iface = (mbm_controller_iface_t *)*ctx;
MB_RETURN_ON_FALSE((!mbm_controller_iface), ESP_ERR_INVALID_STATE, TAG,
"mb stack is not destroyed.");
// Check communication options
mb_tcp_opts_t tcp_opts = (mb_tcp_opts_t)config->tcp_opts;
MB_RETURN_ON_FALSE((tcp_opts.ip_addr_table), ESP_ERR_INVALID_ARG, TAG, "mb ip table address is incorrect.");
MB_RETURN_ON_FALSE((tcp_opts.mode == MB_TCP),
ESP_ERR_INVALID_ARG, TAG, "mb transport protocol is incorrect.");
MB_RETURN_ON_FALSE(((tcp_opts.addr_type == MB_IPV6) || (tcp_opts.addr_type == MB_IPV4)),
ESP_ERR_INVALID_ARG, TAG, "mb ip address type is incorrect.");
MB_RETURN_ON_FALSE((tcp_opts.port), ESP_ERR_INVALID_ARG, TAG, "mb port is not defined.");
esp_err_t ret = mbc_tcp_master_controller_create((void *)&mbm_controller_iface);
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
"mbc create returns (0x%x).", (int)ret);
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
// keep the communication options to be able to restart port driver
mbm_opts->comm_opts = *config;
mbm_opts->port_type = MB_PORT_TCP_MASTER;
tcp_opts.mode = MB_TCP; // Override mode, UDP mode is not supported
mbm_opts->comm_opts.tcp_opts = tcp_opts;
// Keep the response time setting
if (!tcp_opts.response_tout_ms) {
mbm_opts->comm_opts.tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
}
mb_err_enum_t err = MB_EILLSTATE;
void *pinst = (void *)mbm_controller_iface; // set as descr.parent object
// Initialize Modbus stack using mbcontroller parameters
if (tcp_opts.mode == MB_TCP) {
err = mbm_tcp_create(&tcp_opts, &pinst);
}
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
"mbm create returns (0x%x).", (int)ret);
mbm_controller_iface->mb_base = (mb_base_t *)pinst;
const mb_rw_callbacks_t rw_cbs = {
.reg_input_cb = mbc_reg_input_master_cb,
.reg_holding_cb = mbc_reg_holding_master_cb,
.reg_coils_cb = mbc_reg_coils_master_cb,
.reg_discrete_cb = mbc_reg_discrete_master_cb
};
mbm_controller_iface->mb_base->rw_cbs = rw_cbs;
if (!mbm_opts->comm_opts.tcp_opts.start_disconnected) {
mbm_port_tcp_set_conn_cb(mbm_controller_iface->mb_base->port_obj,
&mbc_tcp_master_conn_done_cb,
(void *)mbm_controller_iface);
}
mbm_controller_iface->is_active = false;
*ctx = mbm_controller_iface;
return ESP_OK;
error:
if (mbm_controller_iface->mb_base) {
mbm_controller_iface->mb_base->delete(mbm_controller_iface->mb_base);
mbm_controller_iface->mb_base = NULL;
}
return ret;
}
#endif

View File

@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_tcp_master.h Modbus controller TCP master implementation header file
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> // for standard int types definition
#include <stddef.h> // for NULL and std defines
#include "esp_modbus_common.h" // for common defines
/* ----------------------- Defines ------------------------------------------*/
/**
* @brief Create Modbus Master controller and stack for TCP port
*
* @param[out] ctx - pointer to pointer of interface structure
* @param[in] config - pointer to configuration structure
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Parameter error
*/
esp_err_t mbc_tcp_master_create(mb_communication_info_t *config, void **ctx);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,274 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// mbc_tcp_slave.c
// Implementation of the Modbus controller TCP slave
#include <sys/time.h> // for calculation of time stamp in milliseconds
#include "esp_log.h" // for log_write
#include "sdkconfig.h" // for KConfig values
#include "esp_modbus_common.h" // for common defines
#include "esp_modbus_slave.h" // for public slave interface types
#include "mbc_slave.h" // for private slave interface types
#include "mbc_tcp_slave.h" // for tcp slave mb controller defines
#include "port_tcp_common.h"
#include "mb_common.h" // for mb types definition
#if MB_TCP_ENABLED
static const char *TAG = "mbc_tcp.slave";
// Modbus task function
static void modbus_tcp_slave_task(void *param)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(param);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(param);
// Main Modbus stack processing cycle
for (;;) {
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE, // do not clear bits
pdFALSE,
portMAX_DELAY);
// Check if stack started then poll for data
if (status & MB_EVENT_STACK_STARTED) {
(void)mbs_iface->mb_base->poll(mbs_iface->mb_base);
}
}
}
// Start Modbus controller start function
static esp_err_t mbc_tcp_slave_start(void *ctx)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_err_enum_t status = MB_EIO;
status = mbs_iface->mb_base->enable(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack enable fail, returned (0x%x).", (uint16_t)status);
// Set the mbcontroller start flag
EventBits_t flag = xEventGroupSetBits(mbs_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
mbs_iface->mb_base->descr.parent = ctx;
mbs_iface->is_active = true;
return ESP_OK;
}
// Start Modbus controller stop function
static esp_err_t mbc_tcp_slave_stop(void *ctx)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_err_enum_t status = MB_EIO;
status = mbs_iface->mb_base->disable(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack disable fail, returned (0x%x).", (uint16_t)status);
// Clear the mbcontroller start flag
EventBits_t flag = xEventGroupClearBits(mbs_opts->event_group_handle,
(EventBits_t)MB_EVENT_STACK_STARTED);
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
mbs_iface->mb_base->descr.parent = NULL;
mbs_iface->is_active = false;
return ESP_OK;
}
// Blocking function to get event on parameter group change for application task
static mb_event_group_t mbc_tcp_slave_check_event(void *ctx, mb_event_group_t group)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
MB_SLAVE_ASSERT(mbs_opts->event_group_handle);
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle, (BaseType_t)group,
pdTRUE , pdFALSE, portMAX_DELAY);
return (mb_event_group_t)status;
}
// Function to get notification about parameter change from application task
static esp_err_t mbc_tcp_slave_get_param_info(void *ctx, mb_param_info_t *reg_info, uint32_t timeout)
{
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
esp_err_t err = ESP_ERR_TIMEOUT;
MB_RETURN_ON_FALSE((mbs_opts->notification_queue_handle),
ESP_ERR_INVALID_ARG, TAG, "mb queue handle is invalid.");
MB_RETURN_ON_FALSE(reg_info, ESP_ERR_INVALID_ARG, TAG, "mb register information is invalid.");
BaseType_t status = xQueueReceive(mbs_opts->notification_queue_handle,
reg_info, pdMS_TO_TICKS(timeout));
if (status == pdTRUE) {
err = ESP_OK;
}
return err;
}
// Modbus controller delete function
static esp_err_t mbc_tcp_slave_delete(void *ctx)
{
mbs_controller_iface_t *mbs_iface = MB_SLAVE_GET_IFACE(ctx);
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(ctx);
mb_err_enum_t mb_error = MB_ENOERR;
// Check the stack started bit
BaseType_t status = xEventGroupWaitBits(mbs_opts->event_group_handle,
(BaseType_t)(MB_EVENT_STACK_STARTED),
pdFALSE,
pdFALSE,
MB_CONTROLLER_NOTIFY_TIMEOUT);
if (mbs_iface->is_active || (status & MB_EVENT_STACK_STARTED)) {
ESP_LOGV(TAG, "mb stack is active, try to disable.");
MB_RETURN_ON_FALSE((mbc_tcp_slave_stop(ctx) == ESP_OK),
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
}
mbs_iface->is_active = false;
vTaskDelete(mbs_opts->task_handle);
vEventGroupDelete(mbs_opts->event_group_handle);
vQueueDelete(mbs_opts->notification_queue_handle);
mb_error = mbs_iface->mb_base->delete(mbs_iface->mb_base);
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
"mb stack close failure returned (0x%x).", (int)mb_error);
// free the controller will be performed in common object
return ESP_OK;
}
esp_err_t mbc_tcp_slave_controller_create(void ** ctx)
{
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
mbs_controller_iface_t *mbs_controller_iface = *ctx;
esp_err_t ret = ESP_ERR_INVALID_STATE;
MB_RETURN_ON_FALSE((mbs_controller_iface == NULL), ESP_ERR_INVALID_STATE, TAG,
"mb stack is not destroyed.");
mbs_controller_iface = malloc(sizeof(mbs_controller_iface_t));
MB_GOTO_ON_FALSE((mbs_controller_iface), ESP_ERR_NO_MEM, error,
TAG, "mb stack memory allocation fail.");
mb_slave_options_t *mbs_opts = &mbs_controller_iface->opts;
mbs_opts->port_type = MB_PORT_TCP_SLAVE; // set interface port type
// Initialization of active context of the Modbus controller
BaseType_t status = 0;
// Parameter change notification queue
mbs_opts->event_group_handle = xEventGroupCreate();
MB_GOTO_ON_FALSE((mbs_opts->event_group_handle), ESP_ERR_NO_MEM, error,
TAG, "mb event group error.");
// Parameter change notification queue
mbs_opts->notification_queue_handle = xQueueCreate(
MB_CONTROLLER_NOTIFY_QUEUE_SIZE,
sizeof(mb_param_info_t));
MB_GOTO_ON_FALSE((mbs_opts->notification_queue_handle), ESP_ERR_NO_MEM, error,
TAG, "mb notify queue creation error.");
// Create Modbus controller task
status = xTaskCreatePinnedToCore((void *)&modbus_tcp_slave_task,
"mbc_tcp_slave",
MB_CONTROLLER_STACK_SIZE,
mbs_controller_iface,
MB_CONTROLLER_PRIORITY,
&mbs_opts->task_handle,
MB_PORT_TASK_AFFINITY);
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
"mb controller task creation error, xTaskCreate() returns (0x%x).", (uint16_t)status);
// The task is created but handle is incorrect
MB_SLAVE_ASSERT(mbs_opts->task_handle);
// Initialization of interface pointers
mbs_controller_iface->create = mbc_tcp_slave_create;
mbs_controller_iface->delete = mbc_tcp_slave_delete;
mbs_controller_iface->start = mbc_tcp_slave_start;
mbs_controller_iface->check_event = mbc_tcp_slave_check_event;
mbs_controller_iface->get_param_info = mbc_tcp_slave_get_param_info;
mbs_controller_iface->set_descriptor = NULL; // Use common descriptor setter
*ctx = mbs_controller_iface;
return ESP_OK;
error:
if (mbs_controller_iface) {
if (mbs_controller_iface->opts.task_handle) {
vTaskDelete(mbs_controller_iface->opts.task_handle);
mbs_controller_iface->opts.task_handle = NULL;
}
if (mbs_controller_iface->opts.event_group_handle) {
vEventGroupDelete(mbs_controller_iface->opts.event_group_handle);
mbs_controller_iface->opts.event_group_handle = NULL;
}
}
free(mbs_controller_iface); // free the memory allocated
ctx = NULL;
return ret;
}
// Initialization of Modbus controller
esp_err_t mbc_tcp_slave_create(mb_communication_info_t *config, void **ctx)
{
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
"mb stack init interface fail.");
mbs_controller_iface_t *mbs_controller_iface = (mbs_controller_iface_t *)*ctx;
MB_RETURN_ON_FALSE((!mbs_controller_iface), ESP_ERR_INVALID_STATE, TAG,
"mb stack is not destroyed.");
// Check communication options
mb_tcp_opts_t tcp_opts = (mb_tcp_opts_t)config->tcp_opts;
MB_RETURN_ON_FALSE(((tcp_opts.mode == MB_TCP) || (tcp_opts.mode == MB_UDP)),
ESP_ERR_INVALID_ARG, TAG, "mb transport protocol is incorrect.");
MB_RETURN_ON_FALSE(((tcp_opts.addr_type == MB_IPV6) || (tcp_opts.addr_type == MB_IPV4)),
ESP_ERR_INVALID_ARG, TAG, "mb ip address type is incorrect.");
MB_RETURN_ON_FALSE((tcp_opts.port), ESP_ERR_INVALID_ARG, TAG, "mb port is not defined.");
esp_err_t ret = mbc_tcp_slave_controller_create((void *)&mbs_controller_iface);
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG,
"mbc create returns (0x%x).", (uint16_t)ret);
mb_slave_options_t *mbs_opts = MB_SLAVE_GET_OPTS(mbs_controller_iface);
// keep the communication options to be able to restart port driver
mbs_opts->comm_opts = *config;
mbs_opts->port_type = MB_PORT_TCP_SLAVE;
tcp_opts.mode = MB_TCP; // Override mode, UDP mode is not supported
// Keep the response time setting
if (!tcp_opts.response_tout_ms) {
tcp_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
}
// Set default values of communication options
if (!tcp_opts.port) {
mbs_opts->comm_opts.tcp_opts.port = MB_TCP_DEFAULT_PORT;
}
mbs_opts->comm_opts.tcp_opts = tcp_opts;
mb_err_enum_t err = MB_ENOERR;
void *pinst = (void *)mbs_controller_iface;
// Initialize Modbus stack using mbcontroller parameters
err = mbs_tcp_create(&tcp_opts, &pinst);
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
"mbscreate returns (0x%x).", (uint16_t)err);
mbs_controller_iface->mb_base = (mb_base_t *)pinst;
mbs_controller_iface->mb_base->descr.is_master = false;
// Configure Modbus read/write callbacks for the base modbus object
const mb_rw_callbacks_t rw_cbs = {
.reg_input_cb = mbc_reg_input_slave_cb,
.reg_holding_cb = mbc_reg_holding_slave_cb,
.reg_coils_cb = mbc_reg_coils_slave_cb,
.reg_discrete_cb = mbc_reg_discrete_slave_cb
};
mbs_controller_iface->mb_base->rw_cbs = rw_cbs;
mbs_controller_iface->is_active = false;
*ctx = (void *)mbs_controller_iface;
return ESP_OK;
error:
if (mbs_controller_iface->mb_base) {
mbs_controller_iface->mb_base->delete(mbs_controller_iface->mb_base);
mbs_controller_iface->mb_base = NULL;
}
return ret;
}
#endif //#if MB_TCP_ENABLED

Some files were not shown because too many files have changed in this diff Show More