mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-22 01:27:11 +08:00
feat(modbus): Bring freemodbus component and example from IDF
Commit ID: d0b9829e
This commit is contained in:
@ -0,0 +1,6 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
idf_component_register(SRCS "modbus_params.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES freemodbus)
|
10
examples/protocols/modbus/mb_example_common/README.md
Normal file
10
examples/protocols/modbus/mb_example_common/README.md
Normal 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)
|
5
examples/protocols/modbus/mb_example_common/component.mk
Normal file
5
examples/protocols/modbus/mb_example_common/component.mk
Normal file
@ -0,0 +1,5 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := .
|
@ -0,0 +1,62 @@
|
||||
/*=====================================================================================
|
||||
* Description:
|
||||
* The Modbus parameter structures used to define Modbus instances that
|
||||
* can be addressed by Modbus protocol. Define these structures per your needs in
|
||||
* your application. Below is just an example of possible parameters.
|
||||
*====================================================================================*/
|
||||
#ifndef _DEVICE_PARAMS
|
||||
#define _DEVICE_PARAMS
|
||||
|
||||
// This file defines structure of modbus parameters which reflect correspond modbus address space
|
||||
// for each modbus register type (coils, discreet inputs, holding registers, input registers)
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
uint8_t discrete_input0:1;
|
||||
uint8_t discrete_input1:1;
|
||||
uint8_t discrete_input2:1;
|
||||
uint8_t discrete_input3:1;
|
||||
uint8_t discrete_input4:1;
|
||||
uint8_t discrete_input5:1;
|
||||
uint8_t discrete_input6:1;
|
||||
uint8_t discrete_input7:1;
|
||||
uint8_t discrete_input_port1:8;
|
||||
} discrete_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
uint8_t coils_port0;
|
||||
uint8_t coils_port1;
|
||||
} coil_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
float input_data0;
|
||||
float input_data1;
|
||||
float input_data2;
|
||||
float input_data3;
|
||||
uint16_t data[150];
|
||||
} input_reg_params_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
float holding_data0;
|
||||
float holding_data1;
|
||||
float holding_data2;
|
||||
float holding_data3;
|
||||
uint16_t test_regs[150];
|
||||
} 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)
|
17
examples/protocols/modbus/mb_example_common/modbus_params.c
Normal file
17
examples/protocols/modbus/mb_example_common/modbus_params.c
Normal file
@ -0,0 +1,17 @@
|
||||
/*=====================================================================================
|
||||
* 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/protocols/modbus/serial/README.md
Normal file
82
examples/protocols/modbus/serial/README.md
Normal 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.
|
||||
|
||||
|
288
examples/protocols/modbus/serial/example_test.py
Normal file
288
examples/protocols/modbus/serial/example_test.py
Normal file
@ -0,0 +1,288 @@
|
||||
# Need Python 3 string formatting functions
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from threading import Thread
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = "modbus_test"
|
||||
|
||||
# Allowed parameter reads
|
||||
TEST_READ_MIN_COUNT = 10 # Minimum number of correct readings
|
||||
TEST_READ_MAX_ERR_COUNT = 2 # Maximum allowed read errors during initialization
|
||||
|
||||
TEST_THREAD_EXPECT_TIMEOUT = 120 # Test theread expect timeout in seconds
|
||||
TEST_THREAD_JOIN_TIMEOUT = 180 # Test theread join timeout in seconds
|
||||
|
||||
# Test definitions
|
||||
TEST_MASTER_RTU = 'master_rtu'
|
||||
TEST_SLAVE_RTU = 'slave_rtu'
|
||||
|
||||
TEST_MASTER_ASCII = 'master_ascii'
|
||||
TEST_SLAVE_ASCII = 'slave_ascii'
|
||||
|
||||
# Define tuple of strings to expect for each DUT.
|
||||
#
|
||||
master_expect = ("MASTER_TEST: Modbus master stack initialized...", "MASTER_TEST: Start modbus test...", "MASTER_TEST: Destroy master...")
|
||||
slave_expect = ("SLAVE_TEST: Modbus slave stack initialized.", "SLAVE_TEST: Start modbus test...", "SLAVE_TEST: Modbus controller destroyed.")
|
||||
|
||||
# The dictionary for expected values in listing
|
||||
expect_dict_master_ok = {"START": (),
|
||||
"READ_PAR_OK": (),
|
||||
"ALARM_MSG": (u'7',)}
|
||||
|
||||
expect_dict_master_err = {"READ_PAR_ERR": (u'263', u'ESP_ERR_TIMEOUT'),
|
||||
"READ_STK_ERR": (u'107', u'ESP_ERR_TIMEOUT')}
|
||||
|
||||
# The dictionary for regular expression patterns to check in listing
|
||||
pattern_dict_master_ok = {"START": (r'.*I \([0-9]+\) MASTER_TEST: Start modbus test...'),
|
||||
"READ_PAR_OK": (r'.*I\s\([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]+\) read successful.'),
|
||||
"ALARM_MSG": (r'.*I \([0-9]*\) MASTER_TEST: Alarm triggered by cid #([0-9]+).')}
|
||||
|
||||
pattern_dict_master_err = {"READ_PAR_ERR_TOUT": (r'.*E \([0-9]+\) MASTER_TEST: Characteristic #[0-9]+'
|
||||
r'\s\([a-zA-Z0-9_]+\) read fail, err = [0-9]+ \([_A-Z]+\).'),
|
||||
"READ_STK_ERR_TOUT": (r'.*E \([0-9]+\) MB_CONTROLLER_MASTER: [a-zA-Z0-9_]+\([0-9]+\):\s'
|
||||
r'SERIAL master get parameter failure error=\(0x([a-zA-Z0-9]+)\) \(([_A-Z]+)\).')}
|
||||
|
||||
# The dictionary for expected values in listing
|
||||
expect_dict_slave_ok = {"START": (),
|
||||
"READ_PAR_OK": (),
|
||||
"DESTROY": ()}
|
||||
|
||||
# The dictionary for regular expression patterns to check in listing
|
||||
pattern_dict_slave_ok = {"START": (r'.*I \([0-9]+\) SLAVE_TEST: Start modbus test...'),
|
||||
"READ_PAR_OK": (r'.*I\s\([0-9]+\) SLAVE_TEST: [A-Z]+ READ \([a-zA-Z0-9_]+ us\),\s'
|
||||
r'ADDR:[0-9]+, TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'),
|
||||
"DESTROY": (r'.*I\s\([0-9]+\) SLAVE_TEST: Modbus controller destroyed.')}
|
||||
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
|
||||
class DutTestThread(Thread):
|
||||
def __init__(self, dut=None, name=None, expect=None):
|
||||
""" Initialize the thread parameters
|
||||
"""
|
||||
self.tname = name
|
||||
self.dut = dut
|
||||
self.expected = expect
|
||||
self.result = False
|
||||
self.data = None
|
||||
super(DutTestThread, self).__init__()
|
||||
|
||||
def run(self):
|
||||
""" The function implements thread functionality
|
||||
"""
|
||||
# Must reset again as flashing during start_app will reset multiple times, causing unexpected results
|
||||
self.dut.reset()
|
||||
|
||||
# Capture output from the DUT
|
||||
self.dut.start_capture_raw_data()
|
||||
|
||||
# Check expected strings in the listing
|
||||
for string in self.expected:
|
||||
self.dut.expect(string, TEST_THREAD_EXPECT_TIMEOUT)
|
||||
|
||||
# Check DUT exceptions
|
||||
dut_exceptions = self.dut.get_exceptions()
|
||||
if "Guru Meditation Error:" in dut_exceptions:
|
||||
raise Exception("%s generated an exception: %s\n" % (str(self.dut), dut_exceptions))
|
||||
|
||||
# Mark thread has run to completion without any exceptions
|
||||
self.data = self.dut.stop_capture_raw_data()
|
||||
self.result = True
|
||||
|
||||
|
||||
def test_filter_output(data=None, start_pattern=None, end_pattern=None):
|
||||
"""Use patters to filter output
|
||||
"""
|
||||
start_index = str(data).find(start_pattern)
|
||||
end_index = str(data).find(end_pattern)
|
||||
logger.debug("Listing start index= %d, end=%d" % (start_index, end_index))
|
||||
if start_index == -1 or end_index == -1:
|
||||
return data
|
||||
return data[start_index:end_index + len(end_pattern)]
|
||||
|
||||
|
||||
def test_expect_re(data, pattern):
|
||||
"""
|
||||
Check if re pattern is matched in data cache
|
||||
:param data: data to process
|
||||
:param pattern: compiled RegEx pattern
|
||||
:return: match groups if match succeed otherwise None
|
||||
"""
|
||||
ret = None
|
||||
if isinstance(pattern, type(u'')):
|
||||
pattern = pattern.encode('utf-8')
|
||||
regex = re.compile(pattern)
|
||||
if isinstance(data, type(u'')):
|
||||
data = data.encode('utf-8')
|
||||
match = regex.search(data)
|
||||
if match:
|
||||
ret = tuple(None if x is None else x.decode() for x in match.groups())
|
||||
index = match.end()
|
||||
else:
|
||||
index = None
|
||||
return ret, index
|
||||
|
||||
|
||||
def test_check_output(data=None, check_dict=None, expect_dict=None):
|
||||
""" Check output for the test
|
||||
Check log using regular expressions:
|
||||
"""
|
||||
global logger
|
||||
match_count = 0
|
||||
index = 0
|
||||
data_lines = data.splitlines()
|
||||
for key, pattern in check_dict.items():
|
||||
if key not in expect_dict:
|
||||
break
|
||||
# Check pattern in the each line
|
||||
for line in data_lines:
|
||||
group, index = test_expect_re(line, pattern)
|
||||
if index is not None:
|
||||
logger.debug("Found key{%s}=%s, line: \n%s" % (key, group, line))
|
||||
if expect_dict[key] == group:
|
||||
logger.debug("The result is correct for the key:%s, expected:%s == returned:%s" % (key, str(expect_dict[key]), str(group)))
|
||||
match_count += 1
|
||||
return match_count
|
||||
|
||||
|
||||
def test_check_mode(dut=None, mode_str=None, value=None):
|
||||
""" Check communication mode for dut
|
||||
"""
|
||||
global logger
|
||||
try:
|
||||
opt = dut.app.get_sdkconfig()[mode_str]
|
||||
logger.info("%s {%s} = %s.\n" % (str(dut), mode_str, opt))
|
||||
return value == opt
|
||||
except Exception:
|
||||
logger.info('ENV_TEST_FAILURE: %s: Cannot find option %s in sdkconfig.' % (str(dut), mode_str))
|
||||
return False
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='UT_T2_RS485', ignore=True)
|
||||
def test_modbus_communication(env, comm_mode):
|
||||
global logger
|
||||
|
||||
# Get device under test. "dut1 - master", "dut2 - slave" must be properly connected through RS485 interface driver
|
||||
dut_master = env.get_dut("modbus_master", "examples/protocols/modbus/serial/mb_master", dut_class=ttfw_idf.ESP32DUT)
|
||||
dut_slave = env.get_dut("modbus_slave", "examples/protocols/modbus/serial/mb_slave", dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
try:
|
||||
logger.debug("Environment vars: %s\r\n" % os.environ)
|
||||
logger.debug("DUT slave sdkconfig: %s\r\n" % dut_slave.app.get_sdkconfig())
|
||||
logger.debug("DUT master sdkconfig: %s\r\n" % dut_master.app.get_sdkconfig())
|
||||
|
||||
# Check Kconfig configuration options for each built example
|
||||
if test_check_mode(dut_master, "CONFIG_MB_COMM_MODE_ASCII", "y") and test_check_mode(dut_slave, "CONFIG_MB_COMM_MODE_ASCII", "y"):
|
||||
logger.info("ENV_TEST_INFO: Modbus ASCII test mode selected in the configuration. \n")
|
||||
slave_name = TEST_SLAVE_ASCII
|
||||
master_name = TEST_MASTER_ASCII
|
||||
elif test_check_mode(dut_master, "CONFIG_MB_COMM_MODE_RTU", "y") and test_check_mode(dut_slave, "CONFIG_MB_COMM_MODE_RTU", "y"):
|
||||
logger.info("ENV_TEST_INFO: Modbus RTU test mode selected in the configuration. \n")
|
||||
slave_name = TEST_SLAVE_RTU
|
||||
master_name = TEST_MASTER_RTU
|
||||
else:
|
||||
logger.error("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n")
|
||||
raise Exception("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n")
|
||||
# Check if slave address for example application is default one to be able to communicate
|
||||
if not test_check_mode(dut_slave, "CONFIG_MB_SLAVE_ADDR", "1"):
|
||||
logger.error("ENV_TEST_FAILURE: Slave address option is incorrect.\n")
|
||||
raise Exception("ENV_TEST_FAILURE: Slave address option is incorrect.\n")
|
||||
|
||||
# Flash app onto each DUT
|
||||
dut_master.start_app()
|
||||
dut_slave.start_app()
|
||||
|
||||
# Create thread for each dut
|
||||
dut_master_thread = DutTestThread(dut=dut_master, name=master_name, expect=master_expect)
|
||||
dut_slave_thread = DutTestThread(dut=dut_slave, name=slave_name, expect=slave_expect)
|
||||
|
||||
# Start each thread
|
||||
dut_slave_thread.start()
|
||||
dut_master_thread.start()
|
||||
|
||||
# Wait for threads to complete
|
||||
dut_slave_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
dut_master_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
|
||||
if dut_slave_thread.isAlive():
|
||||
logger.error("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
if dut_master_thread.isAlive():
|
||||
logger.error("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
finally:
|
||||
dut_master.close()
|
||||
dut_slave.close()
|
||||
|
||||
# Check if test threads completed successfully and captured data
|
||||
if not dut_slave_thread.result or dut_slave_thread.data is None:
|
||||
logger.error("The thread %s was not run successfully." % dut_slave_thread.tname)
|
||||
raise Exception("The thread %s was not run successfully." % dut_slave_thread.tname)
|
||||
|
||||
if not dut_master_thread.result or dut_master_thread.data is None:
|
||||
logger.error("The thread %s was not run successfully." % dut_slave_thread.tname)
|
||||
raise Exception("The thread %s was not run successfully." % dut_master_thread.tname)
|
||||
|
||||
# Filter output to get test messages
|
||||
master_output = test_filter_output(dut_master_thread.data, master_expect[0], master_expect[len(master_expect) - 1])
|
||||
if master_output is not None:
|
||||
logger.info("The data for master thread is captured.")
|
||||
logger.debug(master_output)
|
||||
|
||||
slave_output = test_filter_output(dut_slave_thread.data, slave_expect[0], slave_expect[len(slave_expect) - 1])
|
||||
if slave_output is not None:
|
||||
logger.info("The data for slave thread is captured.")
|
||||
logger.debug(slave_output)
|
||||
|
||||
# Check if parameters are read correctly by master
|
||||
match_count = test_check_output(master_output, pattern_dict_master_ok, expect_dict_master_ok)
|
||||
if match_count < TEST_READ_MIN_COUNT:
|
||||
logger.error("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count))
|
||||
raise Exception("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count))
|
||||
logger.info("OK pattern test for %s, match_count=%d." % (dut_master_thread.tname, match_count))
|
||||
|
||||
# If the test completed successfully (alarm triggered) but there are some errors during reading of parameters
|
||||
match_count = test_check_output(master_output, pattern_dict_master_err, expect_dict_master_err)
|
||||
if match_count > TEST_READ_MAX_ERR_COUNT:
|
||||
logger.error("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count))
|
||||
raise Exception("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count))
|
||||
logger.info("ERROR pattern test for %s, match_count=%d." % (dut_master_thread.tname, match_count))
|
||||
|
||||
match_count = test_check_output(slave_output, pattern_dict_slave_ok, expect_dict_slave_ok)
|
||||
if match_count < TEST_READ_MIN_COUNT:
|
||||
logger.error("There are errors reading parameters from %s, %d" % (dut_slave_thread.tname, match_count))
|
||||
raise Exception("There are errors reading parameters from %s, %d" % (dut_slave_thread.tname, match_count))
|
||||
logger.info("OK pattern test for %s, match_count=%d." % (dut_slave_thread.tname, match_count))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
# create file handler which logs even debug messages
|
||||
fh = logging.FileHandler('modbus_test.log')
|
||||
fh.setLevel(logging.DEBUG)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# create console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
logger.addHandler(ch)
|
||||
logger.info("Start script %s." % os.path.basename(__file__))
|
||||
print("Logging file name: %s" % logger.handlers[0].baseFilename)
|
||||
test_modbus_communication()
|
||||
logging.shutdown()
|
@ -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.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_master)
|
11
examples/protocols/modbus/serial/mb_master/Makefile
Normal file
11
examples/protocols/modbus/serial/mb_master/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_master
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/mb_example_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
155
examples/protocols/modbus/serial/mb_master/README.md
Normal file
155
examples/protocols/modbus/serial/mb_master/README.md
Normal file
@ -0,0 +1,155 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# 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 WROVER-KIT board.
|
||||
|
||||
Option 2:
|
||||
Several ESP32 WROVER-KIT board 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 WROVER-KIT 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 WROVER KIT 1 | | RS-485 side | | External PC (emulator) with USB to serial or
|
||||
RTS --+--->| DE | / \ | DE|---+ ESP32 WROVER KIT 2 (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).
|
||||
```
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
| ESP32 Interface | #define | Default ESP32 Pin | Default ESP32-S2 Pins | External RS485 Driver Pin |
|
||||
| ----------------------|--------------------|-----------------------|-----------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO20 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO19 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO18 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: The GPIO22 - GPIO25 can not be used with ESP32-S2 chip because they are used for flash chip connection. Please refer to UART documentation for selected target.
|
||||
|
||||
Connect USB to RS485 adapter to computer and connect its D+, D- output lines with the D+, D- lines of RS485 line driver connected to ESP32 (See picture 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 WROVER KIT 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).
|
||||
|
@ -0,0 +1,5 @@
|
||||
set(PROJECT_NAME "modbus_master")
|
||||
|
||||
idf_component_register(SRCS "master.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
@ -0,0 +1,66 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if IDF_TARGET_ESP32
|
||||
default 2 if IDF_TARGET_ESP32
|
||||
range 0 1 if IDF_TARGET_ESP32S2
|
||||
default 1 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 22 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
default 19 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
default 20 if IDF_TARGET_ESP32S2
|
||||
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 46 if IDF_TARGET_ESP32S2
|
||||
default 18
|
||||
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
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
320
examples/protocols/modbus/serial/mb_master/main/master.c
Normal file
320
examples/protocols/modbus/serial/mb_master/main/master.c
Normal file
@ -0,0 +1,320 @@
|
||||
// Copyright 2016-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "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_RATE_MS)
|
||||
|
||||
// Timeout between polls
|
||||
#define POLL_TIMEOUT_MS (1)
|
||||
#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_RATE_MS)
|
||||
|
||||
#define MASTER_TAG "MASTER_TEST"
|
||||
|
||||
#define MASTER_CHECK(a, ret_val, str, ...) \
|
||||
if (!(a)) { \
|
||||
ESP_LOGE(MASTER_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
return (ret_val); \
|
||||
}
|
||||
|
||||
// The macro to get offset for parameter in the appropriate structure
|
||||
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
|
||||
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
|
||||
#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
|
||||
// Discrete offset macro
|
||||
#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
|
||||
|
||||
#define STR(fieldname) ((const char*)( fieldname ))
|
||||
// Options can be used as bit masks or parameter limits
|
||||
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
|
||||
|
||||
// Enumeration of modbus device addresses accessed by master device
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here)
|
||||
};
|
||||
|
||||
// Enumeration of all supported CIDs for device (used in parameter definition table)
|
||||
enum {
|
||||
CID_INP_DATA_0 = 0,
|
||||
CID_HOLD_DATA_0,
|
||||
CID_INP_DATA_1,
|
||||
CID_HOLD_DATA_1,
|
||||
CID_INP_DATA_2,
|
||||
CID_HOLD_DATA_2,
|
||||
CID_HOLD_TEST_REG,
|
||||
CID_RELAY_P1,
|
||||
CID_RELAY_P2,
|
||||
CID_COUNT
|
||||
};
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters:
|
||||
// The CID field in the table must be unique.
|
||||
// Modbus Slave Addr field defines slave address of the device with correspond parameter.
|
||||
// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
|
||||
// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
|
||||
// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
|
||||
// Data Type, Data Size specify type of the characteristic and its data size.
|
||||
// Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
|
||||
// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
|
||||
const mb_parameter_descriptor_t device_parameters[] = {
|
||||
// { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
|
||||
{ CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
|
||||
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
|
||||
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
|
||||
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2,
|
||||
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2,
|
||||
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2,
|
||||
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58,
|
||||
HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
|
||||
COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
|
||||
COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
|
||||
|
||||
// The function to get pointer to parameter storage (instance) according to parameter description table
|
||||
static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
|
||||
{
|
||||
assert(param_descriptor != NULL);
|
||||
void* instance_ptr = NULL;
|
||||
if (param_descriptor->param_offset != 0) {
|
||||
switch(param_descriptor->mb_param_type)
|
||||
{
|
||||
case MB_PARAM_HOLDING:
|
||||
instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_INPUT:
|
||||
instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_DISCRETE:
|
||||
instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
default:
|
||||
instance_ptr = NULL;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
|
||||
assert(instance_ptr != NULL);
|
||||
}
|
||||
return instance_ptr;
|
||||
}
|
||||
|
||||
// User operation function to read slave values and check alarm
|
||||
static void master_operation_func(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
float value = 0;
|
||||
bool alarm_state = false;
|
||||
const mb_parameter_descriptor_t* param_descriptor = NULL;
|
||||
|
||||
ESP_LOGI(MASTER_TAG, "Start modbus test...");
|
||||
|
||||
for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
|
||||
// Read all found characteristics from slave(s)
|
||||
for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
|
||||
{
|
||||
// Get data from parameters description table
|
||||
// and use this information to fill the characteristics description table
|
||||
// and having all required fields in just one table
|
||||
err = mbc_master_get_cid_info(cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
|
||||
void* temp_data_ptr = master_get_param_data(param_descriptor);
|
||||
assert(temp_data_ptr);
|
||||
uint8_t type = 0;
|
||||
if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
|
||||
(param_descriptor->cid == CID_HOLD_TEST_REG)) {
|
||||
// Check for long array of registers of type PARAM_TYPE_ASCII
|
||||
err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)temp_data_ptr, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
// Initialize data of test array and write to slave
|
||||
if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) {
|
||||
memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size);
|
||||
*(uint32_t*)temp_data_ptr = 0xAAAAAAAA;
|
||||
err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)temp_data_ptr, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%08x), write successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)&value, &type);
|
||||
if (err == ESP_OK) {
|
||||
*(float*)temp_data_ptr = value;
|
||||
if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
|
||||
(param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
value,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
if (((value > param_descriptor->param_opts.max) ||
|
||||
(value < param_descriptor->param_opts.min))) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uint16_t state = *(uint16_t*)temp_data_ptr;
|
||||
const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
(const char*)rw_str,
|
||||
*(uint16_t*)temp_data_ptr);
|
||||
if (state & param_descriptor->param_opts.opt1) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
|
||||
}
|
||||
}
|
||||
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); //
|
||||
}
|
||||
|
||||
if (alarm_state) {
|
||||
ESP_LOGI(MASTER_TAG, "Alarm triggered by cid #%d.",
|
||||
param_descriptor->cid);
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Alarm is not triggered after %d retries.",
|
||||
MASTER_MAX_RETRY);
|
||||
}
|
||||
ESP_LOGI(MASTER_TAG, "Destroy master...");
|
||||
ESP_ERROR_CHECK(mbc_master_destroy());
|
||||
}
|
||||
|
||||
// Modbus master initialization
|
||||
static esp_err_t master_init(void)
|
||||
{
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t comm = {
|
||||
.port = MB_PORT_NUM,
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
.mode = MB_MODE_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
.mode = MB_MODE_RTU,
|
||||
#endif
|
||||
.baudrate = MB_DEV_SPEED,
|
||||
.parity = MB_PARITY_NONE
|
||||
};
|
||||
void* master_handler = NULL;
|
||||
|
||||
esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler);
|
||||
MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_STATE,
|
||||
"mb controller initialization fail.");
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller initialization fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
err = mbc_master_setup((void*)&comm);
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller setup fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
// Set UART pin numbers
|
||||
err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
|
||||
CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);
|
||||
|
||||
err = mbc_master_start();
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller start fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err);
|
||||
// Set driver mode to Half Duplex
|
||||
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err);
|
||||
|
||||
vTaskDelay(5);
|
||||
err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller set descriptor fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
ESP_LOGI(MASTER_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);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=y
|
||||
CONFIG_FMB_TIMER_GROUP=0
|
||||
CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150
|
9
examples/protocols/modbus/serial/mb_slave/CMakeLists.txt
Normal file
9
examples/protocols/modbus/serial/mb_slave/CMakeLists.txt
Normal 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(modbus_slave)
|
11
examples/protocols/modbus/serial/mb_slave/Makefile
Normal file
11
examples/protocols/modbus/serial/mb_slave/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_slave
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/mb_example_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
96
examples/protocols/modbus/serial/mb_slave/README.md
Normal file
96
examples/protocols/modbus/serial/mb_slave/README.md
Normal file
@ -0,0 +1,96 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Modbus Slave Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32. The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in `mb_example_common/modbus_params.h\c` files and can be updated by user.
|
||||
These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
The FreeModbus stack located in `components/freemodbus` folder and contains the `/port` folder inside with FreeModbus stack port for ESP32. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information).
|
||||
|
||||
The slave example uses shared parameter structures defined in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 WROVER-KIT 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 WROVER-KIT 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 WROVER KIT 1 | | 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 command and table below.
|
||||
```
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
| ESP32 Interface | #define | Default ESP32 Pin | Default ESP32-S2 Pins | External RS485 Driver Pin |
|
||||
| ----------------------|--------------------|-----------------------|-----------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO20 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO19 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO18 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: The GPIO22 - GPIO25 can not be used with ESP32-S2 chip because they are used for flash chip connection. Please refer to UART documentation for selected target.
|
||||
|
||||
Define the ```Modbus communiction mode``` for slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave application).
|
||||
Set ```Modbus slave address``` for the example application (by default for example script is set to 1).
|
||||
The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help strings of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
Option 2:
|
||||
Setup ESP32 WROVER-KIT 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.
|
||||
|
@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_slave")
|
||||
|
||||
idf_component_register(SRCS "slave.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,75 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if IDF_TARGET_ESP32
|
||||
default 2 if IDF_TARGET_ESP32
|
||||
range 0 1 if IDF_TARGET_ESP32S2
|
||||
default 1 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 22 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
default 19 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
default 20 if IDF_TARGET_ESP32S2
|
||||
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 46 if IDF_TARGET_ESP32S2
|
||||
default 18
|
||||
help
|
||||
GPIO number for UART RTS pin. This pin is connected to
|
||||
~RE/DE pin of RS485 transceiver to switch direction.
|
||||
See UART documentation for more information about available pin
|
||||
numbers for UART.
|
||||
|
||||
choice MB_COMM_MODE
|
||||
prompt "Modbus communication mode"
|
||||
default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN
|
||||
help
|
||||
Selection of Modbus communication mode option for Modbus.
|
||||
|
||||
config MB_COMM_MODE_RTU
|
||||
bool "RTU mode"
|
||||
depends on FMB_COMM_MODE_RTU_EN
|
||||
|
||||
config MB_COMM_MODE_ASCII
|
||||
bool "ASCII mode"
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
|
||||
endchoice
|
||||
|
||||
config MB_SLAVE_ADDR
|
||||
int "Modbus slave address"
|
||||
range 1 127
|
||||
default 1
|
||||
help
|
||||
This is the Modbus slave address in the network.
|
||||
It is used to organize Modbus network with several slaves connected into the same segment.
|
||||
|
||||
|
||||
endmenu
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
202
examples/protocols/modbus/serial/mb_slave/main/slave.c
Normal file
202
examples/protocols/modbus/serial/mb_slave/main/slave.c
Normal file
@ -0,0 +1,202 @@
|
||||
/* FreeModbus Slave Example ESP32
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#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 MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_INPUT_START (0x0000)
|
||||
#define MB_REG_HOLDING_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
|
||||
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
|
||||
#define MB_CHAN_DATA_MAX_VAL (6)
|
||||
#define MB_CHAN_DATA_OFFSET (0.2f)
|
||||
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \
|
||||
| MB_EVENT_HOLDING_REG_RD \
|
||||
| MB_EVENT_DISCRETE_RD \
|
||||
| MB_EVENT_COILS_RD)
|
||||
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \
|
||||
| MB_EVENT_COILS_WR)
|
||||
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
|
||||
|
||||
static const char *SLAVE_TAG = "SLAVE_TEST";
|
||||
|
||||
static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
// Set register values into known state
|
||||
static void setup_reg_data(void)
|
||||
{
|
||||
// Define initial state of parameters
|
||||
discrete_reg_params.discrete_input1 = 1;
|
||||
discrete_reg_params.discrete_input3 = 1;
|
||||
discrete_reg_params.discrete_input5 = 1;
|
||||
discrete_reg_params.discrete_input7 = 1;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on freemodbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main(void)
|
||||
{
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
mb_communication_info_t comm_info; // Modbus communication parameters
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(SLAVE_TAG, ESP_LOG_INFO);
|
||||
void* mbc_slave_handler = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(mbc_slave_init(MB_PORT_SERIAL_SLAVE, &mbc_slave_handler)); // Initialization of Modbus controller
|
||||
|
||||
// Setup communication parameters and start stack
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
comm_info.mode = MB_MODE_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
comm_info.mode = MB_MODE_RTU,
|
||||
#endif
|
||||
comm_info.slave_addr = MB_SLAVE_ADDR;
|
||||
comm_info.port = MB_PORT_NUM;
|
||||
comm_info.baudrate = MB_DEV_SPEED;
|
||||
comm_info.parity = MB_PARITY_NONE;
|
||||
ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info));
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbc_slave_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register area.
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START; // Offset of register area in Modbus protocol
|
||||
reg_area.address = (void*)&holding_reg_params; // Set pointer to storage instance
|
||||
reg_area.size = sizeof(holding_reg_params); // Set the size of register storage instance
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Input Registers area
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START;
|
||||
reg_area.address = (void*)&input_reg_params;
|
||||
reg_area.size = sizeof(input_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Coils register area
|
||||
reg_area.type = MB_PARAM_COIL;
|
||||
reg_area.start_offset = MB_REG_COILS_START;
|
||||
reg_area.address = (void*)&coil_reg_params;
|
||||
reg_area.size = sizeof(coil_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Discrete Inputs register area
|
||||
reg_area.type = MB_PARAM_DISCRETE;
|
||||
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
|
||||
reg_area.address = (void*)&discrete_reg_params;
|
||||
reg_area.size = sizeof(discrete_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
setup_reg_data(); // Set values into known state
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
ESP_ERROR_CHECK(mbc_slave_start());
|
||||
|
||||
// Set UART pin numbers
|
||||
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD,
|
||||
CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS,
|
||||
UART_PIN_NO_CHANGE));
|
||||
|
||||
// Set UART driver mode to Half Duplex
|
||||
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
|
||||
|
||||
ESP_LOGI(SLAVE_TAG, "Modbus slave stack initialized.");
|
||||
ESP_LOGI(SLAVE_TAG, "Start modbus test...");
|
||||
|
||||
// The cycle below will be terminated when parameter holdingRegParams.dataChan0
|
||||
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
|
||||
for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
|
||||
// Check for read/write events of Modbus master for certain events
|
||||
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
|
||||
const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
|
||||
// Filter events and process them accordingly
|
||||
if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
|
||||
// Get parameter information from parameter queue
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
portENTER_CRITICAL(¶m_lock);
|
||||
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
|
||||
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
|
||||
coil_reg_params.coils_port1 = 0xFF;
|
||||
}
|
||||
portEXIT_CRITICAL(¶m_lock);
|
||||
}
|
||||
} else if (event & MB_EVENT_INPUT_REG_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & MB_EVENT_DISCRETE_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (coil_reg_params.coils_port1 == 0xFF) break;
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller on alarm
|
||||
ESP_LOGI(SLAVE_TAG,"Modbus controller destroyed.");
|
||||
vTaskDelay(100);
|
||||
ESP_ERROR_CHECK(mbc_slave_destroy());
|
||||
}
|
10
examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults
Normal file
10
examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=y
|
||||
CONFIG_FMB_TIMER_GROUP=0
|
||||
CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
58
examples/protocols/modbus/tcp/README.md
Normal file
58
examples/protocols/modbus/tcp/README.md
Normal 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.
|
||||
|
||||
|
303
examples/protocols/modbus/tcp/example_test.py
Normal file
303
examples/protocols/modbus/tcp/example_test.py
Normal file
@ -0,0 +1,303 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from threading import Thread
|
||||
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import DUT
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
LOGGER_NAME = "modbus_test"
|
||||
|
||||
# Allowed options for the test
|
||||
TEST_READ_MAX_ERR_COUNT = 3 # Maximum allowed read errors during initialization
|
||||
TEST_THREAD_JOIN_TIMEOUT = 60 # Test theread join timeout in seconds
|
||||
TEST_EXPECT_STR_TIMEOUT = 30 # Test expect timeout in seconds
|
||||
TEST_MASTER_TCP = 'mb_tcp_master'
|
||||
TEST_SLAVE_TCP = 'mb_tcp_slave'
|
||||
|
||||
STACK_DEFAULT = 0
|
||||
STACK_IPV4 = 1
|
||||
STACK_IPV6 = 2
|
||||
STACK_INIT = 3
|
||||
STACK_CONNECT = 4
|
||||
STACK_START = 5
|
||||
STACK_PAR_OK = 6
|
||||
STACK_PAR_FAIL = 7
|
||||
STACK_DESTROY = 8
|
||||
|
||||
pattern_dict_slave = {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}).*'),
|
||||
STACK_IPV6: (r'.*I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}).*'),
|
||||
STACK_INIT: (r'.*I \(([0-9]+)\) MB_TCP_SLAVE_PORT: (Protocol stack initialized).'),
|
||||
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}).*'),
|
||||
STACK_START: (r'.*I\s\(([0-9]+)\) SLAVE_TEST: (Start modbus test).*'),
|
||||
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]+'),
|
||||
STACK_PAR_FAIL: (r'.*E \(([0-9]+)\) SLAVE_TEST: Response time exceeds configured [0-9]+ [ms], ignore packet.*'),
|
||||
STACK_DESTROY: (r'.*I\s\(([0-9]+)\) SLAVE_TEST: (Modbus controller destroyed).')}
|
||||
|
||||
pattern_dict_master = {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}).*'),
|
||||
STACK_IPV6: (r'.*I \([0-9]+\) example_connect: - IPv6 address: (([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}).*'),
|
||||
STACK_INIT: (r'.*I \(([0-9]+)\) MASTER_TEST: (Modbus master stack initialized).*'),
|
||||
STACK_CONNECT: (r'.*.*I\s\(([0-9]+)\) MB_TCP_MASTER_PORT: (Connected [0-9]+ slaves), start polling.*'),
|
||||
STACK_START: (r'.*I \(([0-9]+)\) MASTER_TEST: (Start modbus test).*'),
|
||||
STACK_PAR_OK: (r'.*I\s\(([0-9]+)\) MASTER_TEST: Characteristic #[0-9]+ ([a-zA-Z0-9_]+)'
|
||||
r'\s\([a-zA-Z\%\/]+\) value = [a-zA-Z0-9\.]+ \(0x[a-zA-Z0-9]+\) read successful.*'),
|
||||
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]+\).*'),
|
||||
STACK_DESTROY: (r'.*I\s\(([0-9]+)\) MASTER_TEST: (Destroy master).*')}
|
||||
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
|
||||
|
||||
class DutTestThread(Thread):
|
||||
""" Test thread class
|
||||
"""
|
||||
def __init__(self, dut=None, name=None, ip_addr=None, expect=None):
|
||||
""" Initialize the thread parameters
|
||||
"""
|
||||
self.tname = name
|
||||
self.dut = dut
|
||||
self.expected = expect
|
||||
self.data = None
|
||||
self.ip_addr = ip_addr
|
||||
self.test_finish = False
|
||||
self.param_fail_count = 0
|
||||
self.param_ok_count = 0
|
||||
self.test_stage = STACK_DEFAULT
|
||||
super(DutTestThread, self).__init__()
|
||||
|
||||
def __enter__(self):
|
||||
logger.debug("Restart %s." % self.tname)
|
||||
# Reset DUT first
|
||||
self.dut.reset()
|
||||
# Capture output from the DUT
|
||||
self.dut.start_capture_raw_data(capture_id=self.dut.name)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
""" The exit method of context manager
|
||||
"""
|
||||
if exc_type is not None or exc_value is not None:
|
||||
logger.info("Thread %s rised an exception type: %s, value: %s" % (self.tname, str(exc_type), str(exc_value)))
|
||||
|
||||
def run(self):
|
||||
""" The function implements thread functionality
|
||||
"""
|
||||
# Initialize slave IP for master board
|
||||
if (self.ip_addr is not None):
|
||||
self.set_ip(0)
|
||||
|
||||
# Check expected strings in the listing
|
||||
self.test_start(TEST_EXPECT_STR_TIMEOUT)
|
||||
|
||||
# Check DUT exceptions
|
||||
dut_exceptions = self.dut.get_exceptions()
|
||||
if "Guru Meditation Error:" in dut_exceptions:
|
||||
raise Exception("%s generated an exception: %s\n" % (str(self.dut), dut_exceptions))
|
||||
|
||||
# Mark thread has run to completion without any exceptions
|
||||
self.data = self.dut.stop_capture_raw_data(capture_id=self.dut.name)
|
||||
|
||||
def set_ip(self, index=0):
|
||||
""" The method to send slave IP to master application
|
||||
"""
|
||||
message = r'.*Waiting IP([0-9]{1,2}) from stdin.*'
|
||||
# Read all data from previous restart to get prompt correctly
|
||||
self.dut.read()
|
||||
result = self.dut.expect(re.compile(message), TEST_EXPECT_STR_TIMEOUT)
|
||||
if int(result[0]) != index:
|
||||
raise Exception("Incorrect index of IP=%d for %s\n" % (int(result[0]), str(self.dut)))
|
||||
message = "IP%s=%s" % (result[0], self.ip_addr)
|
||||
self.dut.write(message, "\r\n", False)
|
||||
logger.debug("Sent message for %s: %s" % (self.tname, message))
|
||||
message = r'.*IP\([0-9]+\) = \[([0-9a-zA-Z\.\:]+)\] set from stdin.*'
|
||||
result = self.dut.expect(re.compile(message), TEST_EXPECT_STR_TIMEOUT)
|
||||
logger.debug("Thread %s initialized with slave IP (%s)." % (self.tname, result[0]))
|
||||
|
||||
def test_start(self, timeout_value):
|
||||
""" The method to initialize and handle test stages
|
||||
"""
|
||||
def handle_get_ip4(data):
|
||||
""" Handle get_ip v4
|
||||
"""
|
||||
logger.debug("%s[STACK_IPV4]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_IPV4
|
||||
|
||||
def handle_get_ip6(data):
|
||||
""" Handle get_ip v6
|
||||
"""
|
||||
logger.debug("%s[STACK_IPV6]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_IPV6
|
||||
|
||||
def handle_init(data):
|
||||
""" Handle init
|
||||
"""
|
||||
logger.debug("%s[STACK_INIT]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_INIT
|
||||
|
||||
def handle_connect(data):
|
||||
""" Handle connect
|
||||
"""
|
||||
logger.debug("%s[STACK_CONNECT]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_CONNECT
|
||||
|
||||
def handle_test_start(data):
|
||||
""" Handle connect
|
||||
"""
|
||||
logger.debug("%s[STACK_START]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_START
|
||||
|
||||
def handle_par_ok(data):
|
||||
""" Handle parameter ok
|
||||
"""
|
||||
logger.debug("%s[READ_PAR_OK]: %s" % (self.tname, str(data)))
|
||||
if self.test_stage >= STACK_START:
|
||||
self.param_ok_count += 1
|
||||
self.test_stage = STACK_PAR_OK
|
||||
|
||||
def handle_par_fail(data):
|
||||
""" Handle parameter fail
|
||||
"""
|
||||
logger.debug("%s[READ_PAR_FAIL]: %s" % (self.tname, str(data)))
|
||||
self.param_fail_count += 1
|
||||
self.test_stage = STACK_PAR_FAIL
|
||||
|
||||
def handle_destroy(data):
|
||||
""" Handle destroy
|
||||
"""
|
||||
logger.debug("%s[DESTROY]: %s" % (self.tname, str(data)))
|
||||
self.test_stage = STACK_DESTROY
|
||||
self.test_finish = True
|
||||
|
||||
while not self.test_finish:
|
||||
try:
|
||||
self.dut.expect_any((re.compile(self.expected[STACK_IPV4]), handle_get_ip4),
|
||||
(re.compile(self.expected[STACK_IPV6]), handle_get_ip6),
|
||||
(re.compile(self.expected[STACK_INIT]), handle_init),
|
||||
(re.compile(self.expected[STACK_CONNECT]), handle_connect),
|
||||
(re.compile(self.expected[STACK_START]), handle_test_start),
|
||||
(re.compile(self.expected[STACK_PAR_OK]), handle_par_ok),
|
||||
(re.compile(self.expected[STACK_PAR_FAIL]), handle_par_fail),
|
||||
(re.compile(self.expected[STACK_DESTROY]), handle_destroy),
|
||||
timeout=timeout_value)
|
||||
except DUT.ExpectTimeout:
|
||||
logger.debug("%s, expect timeout on stage #%d (%s seconds)" % (self.tname, self.test_stage, timeout_value))
|
||||
self.test_finish = True
|
||||
|
||||
|
||||
def test_check_mode(dut=None, mode_str=None, value=None):
|
||||
""" Check communication mode for dut
|
||||
"""
|
||||
global logger
|
||||
try:
|
||||
opt = dut.app.get_sdkconfig()[mode_str]
|
||||
logger.debug("%s {%s} = %s.\n" % (str(dut), mode_str, opt))
|
||||
return value == opt
|
||||
except Exception:
|
||||
logger.error('ENV_TEST_FAILURE: %s: Cannot find option %s in sdkconfig.' % (str(dut), mode_str))
|
||||
return False
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_Modbus_TCP')
|
||||
def test_modbus_communication(env, comm_mode):
|
||||
global logger
|
||||
|
||||
rel_project_path = os.path.join('examples', 'protocols', 'modbus', 'tcp')
|
||||
# Get device under test. Both duts must be able to be connected to WiFi router
|
||||
dut_master = env.get_dut('modbus_tcp_master', os.path.join(rel_project_path, TEST_MASTER_TCP))
|
||||
dut_slave = env.get_dut('modbus_tcp_slave', os.path.join(rel_project_path, TEST_SLAVE_TCP))
|
||||
log_file = os.path.join(env.log_path, "modbus_tcp_test.log")
|
||||
print("Logging file name: %s" % log_file)
|
||||
|
||||
try:
|
||||
# create file handler which logs even debug messages
|
||||
logger.setLevel(logging.DEBUG)
|
||||
fh = logging.FileHandler(log_file)
|
||||
fh.setLevel(logging.DEBUG)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
# create console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
# set format of output for both handlers
|
||||
formatter = logging.Formatter('%(levelname)s:%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
# Check Kconfig configuration options for each built example
|
||||
if (test_check_mode(dut_master, "CONFIG_FMB_COMM_MODE_TCP_EN", "y") and
|
||||
test_check_mode(dut_slave, "CONFIG_FMB_COMM_MODE_TCP_EN", "y")):
|
||||
slave_name = TEST_SLAVE_TCP
|
||||
master_name = TEST_MASTER_TCP
|
||||
else:
|
||||
logger.error("ENV_TEST_FAILURE: IP resolver mode do not match in the master and slave implementation.\n")
|
||||
raise Exception("ENV_TEST_FAILURE: IP resolver mode do not match in the master and slave implementation.\n")
|
||||
address = None
|
||||
if test_check_mode(dut_master, "CONFIG_MB_SLAVE_IP_FROM_STDIN", "y"):
|
||||
logger.info("ENV_TEST_INFO: Set slave IP address through STDIN.\n")
|
||||
# Flash app onto DUT (Todo: Debug case when the slave flashed before master then expect does not work correctly for no reason
|
||||
dut_slave.start_app()
|
||||
dut_master.start_app()
|
||||
if test_check_mode(dut_master, "CONFIG_EXAMPLE_CONNECT_IPV6", "y"):
|
||||
address = dut_slave.expect(re.compile(pattern_dict_slave[STACK_IPV6]), TEST_EXPECT_STR_TIMEOUT)
|
||||
else:
|
||||
address = dut_slave.expect(re.compile(pattern_dict_slave[STACK_IPV4]), TEST_EXPECT_STR_TIMEOUT)
|
||||
if address is not None:
|
||||
print("Found IP slave address: %s" % address[0])
|
||||
else:
|
||||
raise Exception("ENV_TEST_FAILURE: Slave IP address is not found in the output. Check network settings.\n")
|
||||
else:
|
||||
raise Exception("ENV_TEST_FAILURE: Slave IP resolver is not configured correctly.\n")
|
||||
|
||||
# Create thread for each dut
|
||||
with DutTestThread(dut=dut_master, name=master_name, ip_addr=address[0], expect=pattern_dict_master) as dut_master_thread:
|
||||
with DutTestThread(dut=dut_slave, name=slave_name, ip_addr=None, expect=pattern_dict_slave) as dut_slave_thread:
|
||||
|
||||
# Start each thread
|
||||
dut_slave_thread.start()
|
||||
dut_master_thread.start()
|
||||
|
||||
# Wait for threads to complete
|
||||
dut_slave_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
dut_master_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT)
|
||||
|
||||
if dut_slave_thread.isAlive():
|
||||
logger.error("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
if dut_master_thread.isAlive():
|
||||
logger.error("TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
raise Exception("TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" %
|
||||
(dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT))
|
||||
|
||||
logger.info("TEST_INFO: %s error count = %d, %s error count = %d.\n" %
|
||||
(dut_master_thread.tname, dut_master_thread.param_fail_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_fail_count))
|
||||
logger.info("TEST_INFO: %s ok count = %d, %s ok count = %d.\n" %
|
||||
(dut_master_thread.tname, dut_master_thread.param_ok_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_ok_count))
|
||||
|
||||
if ((dut_master_thread.param_fail_count > TEST_READ_MAX_ERR_COUNT) or
|
||||
(dut_slave_thread.param_fail_count > TEST_READ_MAX_ERR_COUNT) or
|
||||
(dut_slave_thread.param_ok_count == 0) or
|
||||
(dut_master_thread.param_ok_count == 0)):
|
||||
raise Exception("TEST_FAILURE: %s parameter read error(ok) count = %d(%d), %s parameter read error(ok) count = %d(%d).\n" %
|
||||
(dut_master_thread.tname, dut_master_thread.param_fail_count, dut_master_thread.param_ok_count,
|
||||
dut_slave_thread.tname, dut_slave_thread.param_fail_count, dut_slave_thread.param_ok_count))
|
||||
logger.info("TEST_SUCCESS: The Modbus parameter test is completed successfully.\n")
|
||||
|
||||
finally:
|
||||
dut_master.close()
|
||||
dut_slave.close()
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_modbus_communication()
|
11
examples/protocols/modbus/tcp/mb_tcp_master/CMakeLists.txt
Normal file
11
examples/protocols/modbus/tcp/mb_tcp_master/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_tcp_master)
|
11
examples/protocols/modbus/tcp/mb_tcp_master/Makefile
Normal file
11
examples/protocols/modbus/tcp/mb_tcp_master/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_tcp_master
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/mb_example_common
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
143
examples/protocols/modbus/tcp/mb_tcp_master/README.md
Normal file
143
examples/protocols/modbus/tcp/mb_tcp_master/README.md
Normal file
@ -0,0 +1,143 @@
|
||||
# Modbus TCP Master Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32 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(-S2) development board with modbus_tcp_slave example.
|
||||
|
||||
Option 2:
|
||||
Several ESP32(-S2) 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(-S2) 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 WROVER KIT 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).
|
||||
|
@ -0,0 +1,5 @@
|
||||
set(PROJECT_NAME "modbus_tcp_master")
|
||||
|
||||
idf_component_register(SRCS "tcp_master.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
600
examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c
Normal file
600
examples/protocols/modbus/tcp/mb_tcp_master/main/tcp_master.c
Normal file
@ -0,0 +1,600 @@
|
||||
// Copyright 2016-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "string.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 "mdns.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "mbcontroller.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define MB_TCP_PORT (CONFIG_FMB_TCP_PORT_DEFAULT) // TCP port used by example
|
||||
|
||||
// The number of parameters that intended to be used in the particular control process
|
||||
#define MASTER_MAX_CIDS num_device_parameters
|
||||
|
||||
// Number of reading of parameters from slave
|
||||
#define MASTER_MAX_RETRY (30)
|
||||
|
||||
// Timeout to update cid over Modbus
|
||||
#define UPDATE_CIDS_TIMEOUT_MS (500)
|
||||
#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_RATE_MS)
|
||||
|
||||
// Timeout between polls
|
||||
#define POLL_TIMEOUT_MS (1)
|
||||
#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_RATE_MS)
|
||||
#define MB_MDNS_PORT (502)
|
||||
|
||||
#define MASTER_TAG "MASTER_TEST"
|
||||
|
||||
#define MASTER_CHECK(a, ret_val, str, ...) \
|
||||
if (!(a)) { \
|
||||
ESP_LOGE(MASTER_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
return (ret_val); \
|
||||
}
|
||||
|
||||
// The macro to get offset for parameter in the appropriate structure
|
||||
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
|
||||
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
|
||||
#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
|
||||
#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
|
||||
#define STR(fieldname) ((const char*)( fieldname ))
|
||||
|
||||
// Options can be used as bit masks or parameter limits
|
||||
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
|
||||
|
||||
#define MB_ID_BYTE0(id) ((uint8_t)(id))
|
||||
#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
|
||||
#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
|
||||
#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
|
||||
|
||||
#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
|
||||
|
||||
#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
|
||||
#else
|
||||
#define MB_DEVICE_ID (uint32_t)0x00112233
|
||||
#endif
|
||||
|
||||
#define MB_MDNS_INSTANCE(pref) pref"mb_master_tcp"
|
||||
|
||||
// Enumeration of modbus device addresses accessed by master device
|
||||
// Each address in the table is a index of TCP slave ip address in mb_communication_info_t::tcp_ip_addr table
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1, // Slave address 1
|
||||
MB_DEVICE_COUNT
|
||||
};
|
||||
|
||||
// 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_RELAY_P1,
|
||||
CID_RELAY_P2,
|
||||
CID_COUNT
|
||||
};
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters:
|
||||
// The CID field in the table must be unique.
|
||||
// Modbus Slave Addr field defines slave address of the device with correspond parameter.
|
||||
// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
|
||||
// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
|
||||
// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
|
||||
// Data Type, Data Size specify type of the characteristic and its data size.
|
||||
// Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
|
||||
// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
|
||||
const mb_parameter_descriptor_t device_parameters[] = {
|
||||
// { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
|
||||
{ CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
|
||||
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
|
||||
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
|
||||
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2,
|
||||
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2,
|
||||
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2,
|
||||
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
|
||||
COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
|
||||
COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
|
||||
|
||||
// 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] = {
|
||||
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
"FROM_STDIN", // Address corresponds to MB_DEVICE_ADDR1 and set to predefined value by user
|
||||
NULL
|
||||
#elif CONFIG_MB_MDNS_IP_RESOLVER
|
||||
NULL,
|
||||
NULL
|
||||
#endif
|
||||
};
|
||||
|
||||
#if CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
|
||||
// Scan IP address according to IPV settings
|
||||
char* master_scan_addr(int* index, char* buffer)
|
||||
{
|
||||
char* ip_str = NULL;
|
||||
unsigned int a[8] = {0};
|
||||
int buf_cnt = 0;
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
buf_cnt = sscanf(buffer, "IP%d="IPSTR, index, &a[0], &a[1], &a[2], &a[3]);
|
||||
if (buf_cnt == 5) {
|
||||
if (-1 == asprintf(&ip_str, IPSTR, a[0], a[1], a[2], a[3])) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#else
|
||||
buf_cnt = sscanf(buffer, "IP%d="IPV6STR, index, &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6], &a[7]);
|
||||
if (buf_cnt == 9) {
|
||||
if (-1 == asprintf(&ip_str, IPV6STR, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return ip_str;
|
||||
}
|
||||
|
||||
static int master_get_slave_ip_stdin(char** addr_table)
|
||||
{
|
||||
char buf[128];
|
||||
int index;
|
||||
char* ip_str = NULL;
|
||||
int buf_cnt = 0;
|
||||
int ip_cnt = 0;
|
||||
|
||||
if (!addr_table) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(example_configure_stdin_stdout());
|
||||
while(1) {
|
||||
if (addr_table[ip_cnt] && strcmp(addr_table[ip_cnt], "FROM_STDIN") == 0) {
|
||||
printf("Waiting IP%d from stdin:\r\n", ip_cnt);
|
||||
while (fgets(buf, sizeof(buf), stdin) == NULL) {
|
||||
fputs(buf, stdout);
|
||||
}
|
||||
buf_cnt = strlen(buf);
|
||||
buf[buf_cnt - 1] = '\0';
|
||||
fputc('\n', stdout);
|
||||
ip_str = master_scan_addr(&index, buf);
|
||||
if (ip_str != NULL) {
|
||||
ESP_LOGI(MASTER_TAG, "IP(%d) = [%s] set from stdin.", ip_cnt, ip_str);
|
||||
if ((ip_cnt >= MB_DEVICE_COUNT) || (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(MASTER_TAG, "Leave IP(%d) = [%s] set manually.", ip_cnt, addr_table[ip_cnt]);
|
||||
ip_cnt++;
|
||||
} else {
|
||||
ESP_LOGI(MASTER_TAG, "IP(%d) is not set in the table.", ip_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ip_cnt;
|
||||
}
|
||||
|
||||
#elif CONFIG_MB_MDNS_IP_RESOLVER
|
||||
|
||||
// convert MAC from binary format to string
|
||||
static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
|
||||
{
|
||||
sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
|
||||
return mac_str;
|
||||
}
|
||||
|
||||
static inline char* gen_id_str(char* service_name, char* slave_id_str)
|
||||
{
|
||||
sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
|
||||
return slave_id_str;
|
||||
}
|
||||
|
||||
static void master_start_mdns_service()
|
||||
{
|
||||
char temp_str[32] = {0};
|
||||
uint8_t sta_mac[6] = {0};
|
||||
ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
|
||||
char* hostname = gen_mac_str(sta_mac, MB_MDNS_INSTANCE("")"_", temp_str);
|
||||
// initialize mDNS
|
||||
ESP_ERROR_CHECK(mdns_init());
|
||||
// set mDNS hostname (required if you want to advertise services)
|
||||
ESP_ERROR_CHECK(mdns_hostname_set(hostname));
|
||||
ESP_LOGI(MASTER_TAG, "mdns hostname set to: [%s]", hostname);
|
||||
|
||||
// set default mDNS instance name
|
||||
ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
|
||||
|
||||
// structure with TXT records
|
||||
mdns_txt_item_t serviceTxtData[] = {
|
||||
{"board","esp32"}
|
||||
};
|
||||
|
||||
// initialize service
|
||||
ESP_ERROR_CHECK(mdns_service_add(MB_MDNS_INSTANCE(""), "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
|
||||
// add mac key string text item
|
||||
ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
|
||||
// add slave id key txt item
|
||||
ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
|
||||
}
|
||||
|
||||
static char* master_get_slave_ip_str(mdns_ip_addr_t* address, mb_tcp_addr_type_t addr_type)
|
||||
{
|
||||
mdns_ip_addr_t* a = address;
|
||||
char* slave_ip_str = NULL;
|
||||
|
||||
while (a) {
|
||||
if ((a->addr.type == ESP_IPADDR_TYPE_V6) && (addr_type == MB_IPV6)) {
|
||||
if (-1 == asprintf(&slave_ip_str, IPV6STR, IPV62STR(a->addr.u_addr.ip6))) {
|
||||
abort();
|
||||
}
|
||||
} else if ((a->addr.type == ESP_IPADDR_TYPE_V4) && (addr_type == MB_IPV4)) {
|
||||
if (-1 == asprintf(&slave_ip_str, IPSTR, IP2STR(&(a->addr.u_addr.ip4)))) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (slave_ip_str) {
|
||||
break;
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
return slave_ip_str;
|
||||
}
|
||||
|
||||
static esp_err_t master_resolve_slave(const char* name, mdns_result_t* result, char** resolved_ip,
|
||||
mb_tcp_addr_type_t addr_type)
|
||||
{
|
||||
if (!name || !result) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
mdns_result_t* r = result;
|
||||
int t;
|
||||
char* slave_ip = NULL;
|
||||
for (; r ; r = r->next) {
|
||||
if ((r->ip_protocol == MDNS_IP_PROTOCOL_V4) && (addr_type == MB_IPV6)) {
|
||||
continue;
|
||||
} else if ((r->ip_protocol == MDNS_IP_PROTOCOL_V6) && (addr_type == MB_IPV4)) {
|
||||
continue;
|
||||
}
|
||||
// Check host name for Modbus short address and
|
||||
// append it into slave ip address table
|
||||
if ((strcmp(r->instance_name, name) == 0) && (r->port == CONFIG_FMB_TCP_PORT_DEFAULT)) {
|
||||
printf(" PTR : %s\n", r->instance_name);
|
||||
if (r->txt_count) {
|
||||
printf(" TXT : [%u] ", r->txt_count);
|
||||
for ( t = 0; t < r->txt_count; t++) {
|
||||
printf("%s=%s; ", r->txt[t].key, r->txt[t].value?r->txt[t].value:"NULL");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
slave_ip = master_get_slave_ip_str(r->addr, addr_type);
|
||||
if (slave_ip) {
|
||||
ESP_LOGI(MASTER_TAG, "Resolved slave %s[%s]:%u", r->hostname, slave_ip, r->port);
|
||||
*resolved_ip = slave_ip;
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
*resolved_ip = NULL;
|
||||
ESP_LOGD(MASTER_TAG, "Fail to resolve slave: %s", name);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
static int master_create_slave_list(mdns_result_t* results, char** addr_table,
|
||||
mb_tcp_addr_type_t addr_type)
|
||||
{
|
||||
if (!results) {
|
||||
return -1;
|
||||
}
|
||||
int i, addr, resolved = 0;
|
||||
const mb_parameter_descriptor_t* pdescr = &device_parameters[0];
|
||||
char** ip_table = addr_table;
|
||||
char slave_name[22] = {0};
|
||||
char* slave_ip = NULL;
|
||||
|
||||
for (i = 0; (i < num_device_parameters && pdescr); i++, pdescr++) {
|
||||
addr = pdescr->mb_slave_addr;
|
||||
if (-1 == sprintf(slave_name, "mb_slave_tcp_%02X", addr)) {
|
||||
ESP_LOGI(MASTER_TAG, "Fail to create instance name for index: %d", addr);
|
||||
abort();
|
||||
}
|
||||
if (!ip_table[addr - 1]) {
|
||||
esp_err_t err = master_resolve_slave(slave_name, results, &slave_ip, addr_type);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(MASTER_TAG, "Index: %d, sl_addr: %d, name:%s, failed to resolve!",
|
||||
i, addr, slave_name);
|
||||
// Set correspond index to NULL indicate host not resolved
|
||||
ip_table[addr - 1] = NULL;
|
||||
continue;
|
||||
}
|
||||
ip_table[addr - 1] = slave_ip; //slave_name;
|
||||
ESP_LOGI(MASTER_TAG, "Index: %d, sl_addr: %d, name:%s, resolve to IP: [%s]",
|
||||
i, addr, slave_name, slave_ip);
|
||||
resolved++;
|
||||
} else {
|
||||
ESP_LOGI(MASTER_TAG, "Index: %d, sl_addr: %d, name:%s, set to IP: [%s]",
|
||||
i, addr, slave_name, ip_table[addr - 1]);
|
||||
resolved++;
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
static void master_destroy_slave_list(char** table)
|
||||
{
|
||||
for (int i = 0; ((i < MB_DEVICE_COUNT) && table[i] != NULL); i++) {
|
||||
if (table[i]) {
|
||||
free(table[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int master_query_slave_service(const char * service_name, const char * proto,
|
||||
mb_tcp_addr_type_t addr_type)
|
||||
{
|
||||
ESP_LOGI(MASTER_TAG, "Query PTR: %s.%s.local", service_name, proto);
|
||||
|
||||
mdns_result_t* results = NULL;
|
||||
int count = 0;
|
||||
|
||||
esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
|
||||
if(err){
|
||||
ESP_LOGE(MASTER_TAG, "Query Failed: %s", esp_err_to_name(err));
|
||||
return count;
|
||||
}
|
||||
if(!results){
|
||||
ESP_LOGW(MASTER_TAG, "No results found!");
|
||||
return count;
|
||||
}
|
||||
|
||||
count = master_create_slave_list(results, slave_ip_address_table, addr_type);
|
||||
|
||||
mdns_query_results_free(results);
|
||||
return count;
|
||||
}
|
||||
#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(MASTER_TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
|
||||
assert(instance_ptr != NULL);
|
||||
}
|
||||
return instance_ptr;
|
||||
}
|
||||
|
||||
// User operation function to read slave values and check alarm
|
||||
static void master_operation_func(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
float value = 0;
|
||||
bool alarm_state = false;
|
||||
const mb_parameter_descriptor_t* param_descriptor = NULL;
|
||||
|
||||
ESP_LOGI(MASTER_TAG, "Start modbus test...");
|
||||
|
||||
for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
|
||||
// Read all found characteristics from slave(s)
|
||||
for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
|
||||
{
|
||||
// Get data from parameters description table
|
||||
// and use this information to fill the characteristics description table
|
||||
// and having all required fields in just one table
|
||||
err = mbc_master_get_cid_info(cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
|
||||
void* temp_data_ptr = master_get_param_data(param_descriptor);
|
||||
assert(temp_data_ptr);
|
||||
uint8_t type = 0;
|
||||
err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)&value, &type);
|
||||
if (err == ESP_OK) {
|
||||
*(float*)temp_data_ptr = value;
|
||||
if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
|
||||
(param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
value,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
if (((value > param_descriptor->param_opts.max) ||
|
||||
(value < param_descriptor->param_opts.min))) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uint16_t state = *(uint16_t*)temp_data_ptr;
|
||||
const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
|
||||
ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
(const char*)rw_str,
|
||||
*(uint16_t*)temp_data_ptr);
|
||||
if (state & param_descriptor->param_opts.opt1) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) read fail, err = %d (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
|
||||
}
|
||||
}
|
||||
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS);
|
||||
}
|
||||
|
||||
if (alarm_state) {
|
||||
ESP_LOGI(MASTER_TAG, "Alarm triggered by cid #%d.",
|
||||
param_descriptor->cid);
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Alarm is not triggered after %d retries.",
|
||||
MASTER_MAX_RETRY);
|
||||
}
|
||||
ESP_LOGI(MASTER_TAG, "Destroy master...");
|
||||
vTaskDelay(100);
|
||||
ESP_ERROR_CHECK(mbc_master_destroy());
|
||||
}
|
||||
|
||||
// Modbus master initialization
|
||||
static esp_err_t master_init(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();
|
||||
}
|
||||
ESP_ERROR_CHECK(result);
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
// Start mdns service and register device
|
||||
master_start_mdns_service();
|
||||
#endif
|
||||
|
||||
// This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
// Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
// examples/protocols/README.md for more information about this function.
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
|
||||
mb_communication_info_t comm_info = { 0 };
|
||||
comm_info.ip_port = MB_TCP_PORT;
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
comm_info.ip_addr_type = MB_IPV4;
|
||||
#else
|
||||
comm_info.ip_addr_type = MB_IPV6;
|
||||
#endif
|
||||
comm_info.ip_mode = MB_MODE_TCP;
|
||||
comm_info.ip_addr = (void*)slave_ip_address_table;
|
||||
comm_info.ip_netif_ptr = (void*)get_example_netif();
|
||||
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
int res = 0;
|
||||
for (int retry = 0; (res < num_device_parameters) && (retry < 10); retry++) {
|
||||
res = master_query_slave_service("_modbus", "_tcp", comm_info.ip_addr_type);
|
||||
}
|
||||
if (res < num_device_parameters) {
|
||||
ESP_LOGE(MASTER_TAG, "Could not resolve one or more slave IP addresses, resolved: %d out of %d.", res, num_device_parameters );
|
||||
ESP_LOGE(MASTER_TAG, "Make sure you configured all slaves according to device parameter table and they alive in the network.");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
mdns_free();
|
||||
#elif CONFIG_MB_SLAVE_IP_FROM_STDIN
|
||||
int ip_cnt = master_get_slave_ip_stdin(slave_ip_address_table);
|
||||
if (ip_cnt) {
|
||||
ESP_LOGI(MASTER_TAG, "Configured %d IP addresse(s).", ip_cnt);
|
||||
} else {
|
||||
ESP_LOGE(MASTER_TAG, "Fail to get IP address from stdin. Continue.");
|
||||
}
|
||||
#endif
|
||||
|
||||
void* master_handler = NULL;
|
||||
|
||||
esp_err_t err = mbc_master_init_tcp(&master_handler);
|
||||
MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_STATE,
|
||||
"mb controller initialization fail.");
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller initialization fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
err = mbc_master_setup((void*)&comm_info);
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller setup fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller set descriptor fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
ESP_LOGI(MASTER_TAG, "Modbus master stack initialized...");
|
||||
|
||||
err = mbc_master_start();
|
||||
MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE,
|
||||
"mb controller start fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
vTaskDelay(5);
|
||||
return err;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialization of device peripheral and objects
|
||||
ESP_ERROR_CHECK(master_init());
|
||||
vTaskDelay(10);
|
||||
|
||||
master_operation_func(NULL);
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
master_destroy_slave_list(slave_ip_address_table);
|
||||
#endif
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=y
|
||||
CONFIG_FMB_TCP_PORT_DEFAULT=502
|
||||
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
|
||||
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
|
||||
CONFIG_FMB_PORT_TASK_PRIO=10
|
||||
CONFIG_FMB_COMM_MODE_RTU_EN=n
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=n
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=2000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=y
|
||||
CONFIG_FMB_TIMER_GROUP=0
|
||||
CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_MB_MDNS_IP_RESOLVER=n
|
||||
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=n
|
13
examples/protocols/modbus/tcp/mb_tcp_slave/CMakeLists.txt
Normal file
13
examples/protocols/modbus/tcp/mb_tcp_slave/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# This component includes modbus example common definitions
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/mb_example_common)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_tcp_slave)
|
11
examples/protocols/modbus/tcp/mb_tcp_slave/Makefile
Normal file
11
examples/protocols/modbus/tcp/mb_tcp_slave/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
PROJECT_NAME := modbus_tcp_slave
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/mb_example_common
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
83
examples/protocols/modbus/tcp/mb_tcp_slave/README.md
Normal file
83
examples/protocols/modbus/tcp/mb_tcp_slave/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Modbus Slave Example
|
||||
|
||||
This example demonstrates using of FreeModbus TCP slave stack port implementation for ESP32(-S2). The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in `mb_example_common/modbus_params.h\c` files and can be updated by user.
|
||||
These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
The FreeModbus stack located in `components/freemodbus` folder and contain `/port` folder inside which contains FreeModbus stack port for ESP32. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information).
|
||||
|
||||
The slave example uses shared parameter structures defined in ```examples/protocols/modbus/mb_example_common``` folder.
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
The ESP32(-S2) 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(-S2) board.
|
||||
Note: The ```Example Data (Object) Dictionary``` in the modbus_tcp_master example can be edited to address parameters from other slaves connected into Modbus segment.
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to show the configuration menu:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
To configure the example to use Wi-Fi or Ethernet connection, open the project configuration menu and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" in the "Connect using" choice.
|
||||
Follow the instructions in `examples/common_components/protocol_examples_common` for further configuration.
|
||||
|
||||
The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help strings of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
Option 2:
|
||||
Setup ESP32(-S2) 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.
|
||||
|
@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_tcp_slave")
|
||||
|
||||
idf_component_register(SRCS "tcp_slave.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,18 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_SLAVE_ADDR
|
||||
int "Modbus slave address"
|
||||
range 1 127
|
||||
default 1
|
||||
help
|
||||
This is the Modbus slave address in the network.
|
||||
The address is used as an index to resolve slave ip address.
|
||||
|
||||
config MB_MDNS_IP_RESOLVER
|
||||
bool "Resolve slave addresses using mDNS service"
|
||||
default y
|
||||
help
|
||||
This option allows to use mDNS service to resolve IP addresses of the Modbus slaves.
|
||||
If the option is disabled the ip addresses of slaves are defined in static table.
|
||||
|
||||
endmenu
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
289
examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c
Normal file
289
examples/protocols/modbus/tcp/mb_tcp_slave/main/tcp_slave.c
Normal file
@ -0,0 +1,289 @@
|
||||
/* FreeModbus Slave Example ESP32
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#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 "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "mdns.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
|
||||
#define MB_TCP_PORT_NUMBER (CONFIG_FMB_TCP_PORT_DEFAULT)
|
||||
#define MB_MDNS_PORT (502)
|
||||
|
||||
// Defines below are used to define register start address for each type of Modbus registers
|
||||
#define MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_INPUT_START (0x0000)
|
||||
#define MB_REG_HOLDING_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
|
||||
#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 SLAVE_TAG "SLAVE_TEST"
|
||||
|
||||
static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
|
||||
#define MB_ID_BYTE0(id) ((uint8_t)(id))
|
||||
#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
|
||||
#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
|
||||
#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
|
||||
|
||||
#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
|
||||
|
||||
#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
|
||||
#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
|
||||
#endif
|
||||
|
||||
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
|
||||
|
||||
#define MB_MDNS_INSTANCE(pref) pref"mb_slave_tcp"
|
||||
|
||||
// convert mac from binary format to string
|
||||
static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
|
||||
{
|
||||
sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
|
||||
return mac_str;
|
||||
}
|
||||
|
||||
static inline char* gen_id_str(char* service_name, char* slave_id_str)
|
||||
{
|
||||
sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
|
||||
return slave_id_str;
|
||||
}
|
||||
|
||||
static inline char* gen_host_name_str(char* service_name, char* name)
|
||||
{
|
||||
sprintf(name, "%s_%02X", service_name, MB_SLAVE_ADDR);
|
||||
return name;
|
||||
}
|
||||
|
||||
static void start_mdns_service()
|
||||
{
|
||||
char temp_str[32] = {0};
|
||||
uint8_t sta_mac[6] = {0};
|
||||
ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
|
||||
char* hostname = gen_host_name_str(MB_MDNS_INSTANCE(""), temp_str);
|
||||
//initialize mDNS
|
||||
ESP_ERROR_CHECK(mdns_init());
|
||||
//set mDNS hostname (required if you want to advertise services)
|
||||
ESP_ERROR_CHECK(mdns_hostname_set(hostname));
|
||||
ESP_LOGI(SLAVE_TAG, "mdns hostname set to: [%s]", hostname);
|
||||
|
||||
//set default mDNS instance name
|
||||
ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
|
||||
|
||||
//structure with TXT records
|
||||
mdns_txt_item_t serviceTxtData[] = {
|
||||
{"board","esp32"}
|
||||
};
|
||||
|
||||
//initialize service
|
||||
ESP_ERROR_CHECK(mdns_service_add(hostname, "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
|
||||
//add mac key string text item
|
||||
ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
|
||||
//add slave id key txt item
|
||||
ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Set register values into known state
|
||||
static void setup_reg_data(void)
|
||||
{
|
||||
// Define initial state of parameters
|
||||
discrete_reg_params.discrete_input1 = 1;
|
||||
discrete_reg_params.discrete_input3 = 1;
|
||||
discrete_reg_params.discrete_input5 = 1;
|
||||
discrete_reg_params.discrete_input7 = 1;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on freemodbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main(void)
|
||||
{
|
||||
esp_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();
|
||||
}
|
||||
ESP_ERROR_CHECK(result);
|
||||
esp_netif_init();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
start_mdns_service();
|
||||
#endif
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(SLAVE_TAG, ESP_LOG_INFO);
|
||||
void* mbc_slave_handler = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(mbc_slave_init_tcp(&mbc_slave_handler)); // Initialization of Modbus controller
|
||||
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
mb_communication_info_t comm_info = { 0 };
|
||||
comm_info.ip_port = MB_TCP_PORT_NUMBER;
|
||||
#if !CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
comm_info.ip_addr_type = MB_IPV4;
|
||||
#else
|
||||
comm_info.ip_addr_type = MB_IPV6;
|
||||
#endif
|
||||
comm_info.ip_mode = MB_MODE_TCP;
|
||||
comm_info.ip_addr = NULL;
|
||||
comm_info.ip_netif_ptr = (void*)get_example_netif();
|
||||
// Setup communication parameters and start stack
|
||||
ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info));
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbc_slave_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register area.
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START; // Offset of register area in Modbus protocol
|
||||
reg_area.address = (void*)&holding_reg_params; // Set pointer to storage instance
|
||||
reg_area.size = sizeof(holding_reg_params); // Set the size of register storage instance
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Input Registers area
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START;
|
||||
reg_area.address = (void*)&input_reg_params;
|
||||
reg_area.size = sizeof(input_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Coils register area
|
||||
reg_area.type = MB_PARAM_COIL;
|
||||
reg_area.start_offset = MB_REG_COILS_START;
|
||||
reg_area.address = (void*)&coil_reg_params;
|
||||
reg_area.size = sizeof(coil_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Discrete Inputs register area
|
||||
reg_area.type = MB_PARAM_DISCRETE;
|
||||
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
|
||||
reg_area.address = (void*)&discrete_reg_params;
|
||||
reg_area.size = sizeof(discrete_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
setup_reg_data(); // Set values into known state
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
ESP_ERROR_CHECK(mbc_slave_start());
|
||||
|
||||
ESP_LOGI(SLAVE_TAG, "Modbus slave stack initialized.");
|
||||
ESP_LOGI(SLAVE_TAG, "Start modbus test...");
|
||||
// The cycle below will be terminated when parameter holding_data0
|
||||
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
|
||||
for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
|
||||
// Check for read/write events of Modbus master for certain events
|
||||
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
|
||||
const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
// Filter events and process them accordingly
|
||||
if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
|
||||
// Get parameter information from parameter queue
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
portENTER_CRITICAL(¶m_lock);
|
||||
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
|
||||
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
|
||||
coil_reg_params.coils_port1 = 0xFF;
|
||||
}
|
||||
portEXIT_CRITICAL(¶m_lock);
|
||||
}
|
||||
} else if (event & MB_EVENT_INPUT_REG_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & MB_EVENT_DISCRETE_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(SLAVE_TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (coil_reg_params.coils_port1 == 0xFF) break;
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller on alarm
|
||||
ESP_LOGI(SLAVE_TAG,"Modbus controller destroyed.");
|
||||
vTaskDelay(100);
|
||||
ESP_ERROR_CHECK(mbc_slave_destroy());
|
||||
#if CONFIG_MB_MDNS_IP_RESOLVER
|
||||
mdns_free();
|
||||
#endif
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_FMB_COMM_MODE_TCP_EN=y
|
||||
CONFIG_FMB_TCP_PORT_DEFAULT=502
|
||||
CONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20
|
||||
CONFIG_FMB_PORT_TASK_STACK_SIZE=4096
|
||||
CONFIG_FMB_PORT_TASK_PRIO=10
|
||||
CONFIG_FMB_COMM_MODE_RTU_EN=n
|
||||
CONFIG_FMB_COMM_MODE_ASCII_EN=n
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=1000
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=300
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=y
|
||||
CONFIG_FMB_TIMER_GROUP=0
|
||||
CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_MB_MDNS_IP_RESOLVER=n
|
||||
CONFIG_MB_SLAVE_IP_FROM_STDIN=y
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=n
|
Reference in New Issue
Block a user