mirror of
https://github.com/espressif/esp-modbus.git
synced 2026-08-03 20:24:09 +02:00
esp-modbus: initial component update
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../../../")
|
||||
set(EXCLUDE_COMPONENTS examples test_app test freemodbus)
|
||||
|
||||
# Include parameters from common modbus folder
|
||||
set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modbus_master)
|
||||
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_master
|
||||
|
||||
EXTRA_COMPONENT_DIRS := ../../../
|
||||
EXTRA_COMPONENT_DIRS += ../../mb_example_common
|
||||
EXCLUDE_COMPONENTS := examples test freemodbus
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,156 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus Master Example
|
||||
|
||||
This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device.
|
||||
This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file.
|
||||
The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment.
|
||||
The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of holding_data0 parameter exceeded limit.
|
||||
The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
Example parameters definition:
|
||||
--------------------------------------------------------------------------------------------------
|
||||
| Slave Address | Characteristic ID | Characteristic name | Description |
|
||||
|---------------------|----------------------|----------------------|----------------------------|
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 |
|
||||
| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature |
|
||||
| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off |
|
||||
| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off |
|
||||
--------------------------------------------------------------------------------------------------
|
||||
Note: The Slave Address is the same for all parameters for example test but it can be changed in the ```Example Data (Object) Dictionary``` table of master example to address parameters from other slaves.
|
||||
The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment.
|
||||
|
||||
Simplified Modbus connection schematic for example test:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
------------- -------------
|
||||
| | RS485 network | |
|
||||
| Slave 1 |---<>--+---<>---| Master |
|
||||
| | | |
|
||||
------------- -------------
|
||||
```
|
||||
Modbus multi slave segment connection schematic:
|
||||
```
|
||||
MB_DEVICE_ADDR1
|
||||
-------------
|
||||
| |
|
||||
| Slave 1 |---<>--+
|
||||
| | |
|
||||
------------- |
|
||||
MB_DEVICE_ADDR2 |
|
||||
------------- | -------------
|
||||
| | | | |
|
||||
| Slave 2 |---<>--+---<>---| Master |
|
||||
| | | | |
|
||||
------------- | -------------
|
||||
MB_DEVICE_ADDR3 |
|
||||
------------- RS485 network
|
||||
| | |
|
||||
| Slave 3 |---<>--+
|
||||
| |
|
||||
-------------
|
||||
```
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board
|
||||
|
||||
Option 2:
|
||||
Several ESP32 boards flashed with modbus_slave example software to represent slave device with specific slave address (See CONFIG_MB_SLAVE_ADDR). The slave addresses for each board have to be configured as defined in "connection schematic" above.
|
||||
One ESP32 board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below).
|
||||
|
||||
The MAX485 line driver is used as an example below but other similar chips can be used as well.
|
||||
RS485 example circuit schematic for connection of master and slave devices into segment:
|
||||
```
|
||||
VCC ---------------+ +--------------- VCC
|
||||
| |
|
||||
+-------x-------+ +-------x-------+
|
||||
RXD <------| RO | DIFFERENTIAL | RO|-----> RXD
|
||||
| B|---------------|B |
|
||||
TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD
|
||||
ESP32 BOARD | | RS-485 side | | External PC (emulator) with USB to serial or
|
||||
RTS --+--->| DE | / \ | DE|---+ ESP32 BOARD (slave)
|
||||
| | A|---------------|A | |
|
||||
+----| /RE | PAIR | /RE|---+-- RTS
|
||||
+-------x-------+ +-------x-------+
|
||||
| |
|
||||
--- ---
|
||||
Modbus Master device Modbus Slave device
|
||||
|
||||
```
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to setup configuration:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
Configure the UART pins used for modbus communication using and table below.
|
||||
Define the communication mode parameter for master and slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave devices in one segment).
|
||||
Configure the slave address for each slave in the Modbus segment (the CONFIG_MB_SLAVE_ADDR in Kconfig).
|
||||
```
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
| UART Interface | #define | Default ESP32 Pin | Default pins for | External RS485 Driver Pin |
|
||||
| | | | ESP32-S2(S3, C3) | |
|
||||
| ----------------------|--------------------|-----------------------|-----------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information.
|
||||
|
||||
Connect a USB-to-RS485 adapter to a computer, then connect the adapter's A/B output lines with the corresponding A/B output lines of the RS485 line driver connected to the ESP32 chip (see figure above).
|
||||
|
||||
The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help string of parameters for more information.
|
||||
|
||||
### Setup external Modbus slave devices or emulator
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices.
|
||||
|
||||
Option 2:
|
||||
Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above.
|
||||
|
||||
### Build and flash software of master device
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (9035) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (9045) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.539999 (0x40b147ac) read successful.
|
||||
I (9045) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
I (9055) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful.
|
||||
I (9065) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (9075) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (9085) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (9095) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful.
|
||||
I (9605) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful.
|
||||
I (9615) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.739999 (0x40b7ae12) read successful.
|
||||
I (9615) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful.
|
||||
I (9625) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful.
|
||||
I (9635) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful.
|
||||
I (9645) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful.
|
||||
I (9655) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful.
|
||||
I (9665) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful.
|
||||
I (10175) MASTER_TEST: Alarm triggered by cid #7.
|
||||
I (10175) MASTER_TEST: Destroy master...
|
||||
|
||||
```
|
||||
The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex).
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_master")
|
||||
|
||||
idf_component_register(SRCS "master.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,73 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
|
||||
default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
|
||||
range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 22 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART TX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_RTS
|
||||
int "UART RTS pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 18 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART RTS pin. This pin is connected to
|
||||
~RE/DE pin of RS485 transceiver to switch direction.
|
||||
See UART documentation for more information about available pin
|
||||
numbers for UART.
|
||||
|
||||
choice MB_COMM_MODE
|
||||
prompt "Modbus communication mode"
|
||||
default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN
|
||||
help
|
||||
Selection of Modbus communication mode option for Modbus.
|
||||
|
||||
config MB_COMM_MODE_RTU
|
||||
bool "RTU mode"
|
||||
depends on FMB_COMM_MODE_RTU_EN
|
||||
|
||||
config MB_COMM_MODE_ASCII
|
||||
bool "ASCII mode"
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "string.h"
|
||||
#include "esp_log.h"
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "mbcontroller.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection
|
||||
#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART
|
||||
|
||||
// Note: Some pins on target chip cannot be assigned for UART communication.
|
||||
// See UART documentation for selected board and target to configure pins using Kconfig.
|
||||
|
||||
// The number of parameters that intended to be used in the particular control process
|
||||
#define MASTER_MAX_CIDS num_device_parameters
|
||||
|
||||
// Number of reading of parameters from slave
|
||||
#define MASTER_MAX_RETRY 30
|
||||
|
||||
// Timeout to update cid over Modbus
|
||||
#define UPDATE_CIDS_TIMEOUT_MS (500)
|
||||
#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
|
||||
// Timeout between polls
|
||||
#define POLL_TIMEOUT_MS (1)
|
||||
#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS)
|
||||
|
||||
// The macro to get offset for parameter in the appropriate structure
|
||||
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1))
|
||||
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1))
|
||||
#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1))
|
||||
// Discrete offset macro
|
||||
#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1))
|
||||
|
||||
#define STR(fieldname) ((const char*)( fieldname ))
|
||||
// Options can be used as bit masks or parameter limits
|
||||
#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val }
|
||||
|
||||
static const char *TAG = "MASTER_TEST";
|
||||
|
||||
// Enumeration of modbus device addresses accessed by master device
|
||||
enum {
|
||||
MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here)
|
||||
};
|
||||
|
||||
// Enumeration of all supported CIDs for device (used in parameter definition table)
|
||||
enum {
|
||||
CID_INP_DATA_0 = 0,
|
||||
CID_HOLD_DATA_0,
|
||||
CID_INP_DATA_1,
|
||||
CID_HOLD_DATA_1,
|
||||
CID_INP_DATA_2,
|
||||
CID_HOLD_DATA_2,
|
||||
CID_HOLD_TEST_REG,
|
||||
CID_RELAY_P1,
|
||||
CID_RELAY_P2,
|
||||
CID_COUNT
|
||||
};
|
||||
|
||||
// Example Data (Object) Dictionary for Modbus parameters:
|
||||
// The CID field in the table must be unique.
|
||||
// Modbus Slave Addr field defines slave address of the device with correspond parameter.
|
||||
// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such).
|
||||
// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly.
|
||||
// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value.
|
||||
// Data Type, Data Size specify type of the characteristic and its data size.
|
||||
// Parameter Options field specifies the options that can be used to process parameter value (limits or masks).
|
||||
// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc).
|
||||
const mb_parameter_descriptor_t device_parameters[] = {
|
||||
// { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode}
|
||||
{ CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2,
|
||||
INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2,
|
||||
HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2,
|
||||
INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2,
|
||||
HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2,
|
||||
INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2,
|
||||
HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58,
|
||||
HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8,
|
||||
COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER },
|
||||
{ CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8,
|
||||
COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }
|
||||
};
|
||||
|
||||
// Calculate number of parameters in the table
|
||||
const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0]));
|
||||
|
||||
// The function to get pointer to parameter storage (instance) according to parameter description table
|
||||
static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor)
|
||||
{
|
||||
assert(param_descriptor != NULL);
|
||||
void* instance_ptr = NULL;
|
||||
if (param_descriptor->param_offset != 0) {
|
||||
switch(param_descriptor->mb_param_type)
|
||||
{
|
||||
case MB_PARAM_HOLDING:
|
||||
instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_INPUT:
|
||||
instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_COIL:
|
||||
instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
case MB_PARAM_DISCRETE:
|
||||
instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1);
|
||||
break;
|
||||
default:
|
||||
instance_ptr = NULL;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid);
|
||||
assert(instance_ptr != NULL);
|
||||
}
|
||||
return instance_ptr;
|
||||
}
|
||||
|
||||
// User operation function to read slave values and check alarm
|
||||
static void master_operation_func(void *arg)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
float value = 0;
|
||||
bool alarm_state = false;
|
||||
const mb_parameter_descriptor_t* param_descriptor = NULL;
|
||||
|
||||
ESP_LOGI(TAG, "Start modbus test...");
|
||||
|
||||
for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) {
|
||||
// Read all found characteristics from slave(s)
|
||||
for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++)
|
||||
{
|
||||
// Get data from parameters description table
|
||||
// and use this information to fill the characteristics description table
|
||||
// and having all required fields in just one table
|
||||
err = mbc_master_get_cid_info(cid, ¶m_descriptor);
|
||||
if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) {
|
||||
void* temp_data_ptr = master_get_param_data(param_descriptor);
|
||||
assert(temp_data_ptr);
|
||||
uint8_t type = 0;
|
||||
if ((param_descriptor->param_type == PARAM_TYPE_ASCII) &&
|
||||
(param_descriptor->cid == CID_HOLD_TEST_REG)) {
|
||||
// Check for long array of registers of type PARAM_TYPE_ASCII
|
||||
err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)temp_data_ptr, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
// Initialize data of test array and write to slave
|
||||
if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) {
|
||||
memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size);
|
||||
*(uint32_t*)temp_data_ptr = 0xAAAAAAAA;
|
||||
err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)temp_data_ptr, &type);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = (0x%08x), write successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key,
|
||||
(uint8_t*)&value, &type);
|
||||
if (err == ESP_OK) {
|
||||
*(float*)temp_data_ptr = value;
|
||||
if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) ||
|
||||
(param_descriptor->mb_param_type == MB_PARAM_INPUT)) {
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
value,
|
||||
*(uint32_t*)temp_data_ptr);
|
||||
if (((value > param_descriptor->param_opts.max) ||
|
||||
(value < param_descriptor->param_opts.min))) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uint16_t state = *(uint16_t*)temp_data_ptr;
|
||||
const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF";
|
||||
ESP_LOGI(TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(char*)param_descriptor->param_units,
|
||||
(const char*)rw_str,
|
||||
*(uint16_t*)temp_data_ptr);
|
||||
if (state & param_descriptor->param_opts.opt1) {
|
||||
alarm_state = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic #%d (%s) read fail, err = 0x%x (%s).",
|
||||
param_descriptor->cid,
|
||||
(char*)param_descriptor->param_key,
|
||||
(int)err,
|
||||
(char*)esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls
|
||||
}
|
||||
}
|
||||
vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); //
|
||||
}
|
||||
|
||||
if (alarm_state) {
|
||||
ESP_LOGI(TAG, "Alarm triggered by cid #%d.",
|
||||
param_descriptor->cid);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Alarm is not triggered after %d retries.",
|
||||
MASTER_MAX_RETRY);
|
||||
}
|
||||
ESP_LOGI(TAG, "Destroy master...");
|
||||
ESP_ERROR_CHECK(mbc_master_destroy());
|
||||
}
|
||||
|
||||
// Modbus master initialization
|
||||
static esp_err_t master_init(void)
|
||||
{
|
||||
// Initialize and start Modbus controller
|
||||
mb_communication_info_t comm = {
|
||||
.port = MB_PORT_NUM,
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
.mode = MB_MODE_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
.mode = MB_MODE_RTU,
|
||||
#endif
|
||||
.baudrate = MB_DEV_SPEED,
|
||||
.parity = MB_PARITY_NONE
|
||||
};
|
||||
void* master_handler = NULL;
|
||||
|
||||
esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler);
|
||||
MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller initialization fail.");
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller initialization fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
err = mbc_master_setup((void*)&comm);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller setup fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
// Set UART pin numbers
|
||||
err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD,
|
||||
CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE);
|
||||
|
||||
err = mbc_master_start();
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller start fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err);
|
||||
// Set driver mode to Half Duplex
|
||||
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err);
|
||||
|
||||
vTaskDelay(5);
|
||||
err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters);
|
||||
MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG,
|
||||
"mb controller set descriptor fail, returns(0x%x).",
|
||||
(uint32_t)err);
|
||||
ESP_LOGI(TAG, "Modbus master stack initialized...");
|
||||
return err;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialization of device peripheral and objects
|
||||
ESP_ERROR_CHECK(master_init());
|
||||
vTaskDelay(10);
|
||||
|
||||
master_operation_func(NULL);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=n
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
|
||||
CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
@@ -0,0 +1,14 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../../../")
|
||||
set(EXCLUDE_COMPONENTS examples test_app test freemodbus)
|
||||
|
||||
# Include parameters from common modbus folder
|
||||
set(MB_PARAMS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../mb_example_common")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "${MB_PARAMS_DIR}")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(modbus_slave)
|
||||
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := modbus_slave
|
||||
|
||||
EXTRA_COMPONENT_DIRS := ../../../
|
||||
EXTRA_COMPONENT_DIRS += ../../mb_example_common
|
||||
EXCLUDE_COMPONENTS := examples test freemodbus
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,97 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
||||
|
||||
# Modbus Slave Example
|
||||
|
||||
This example demonstrates using the port of the FreeModbus stack on an ESP32 target where the ESP32 target is operating as a network slave. The example allows an external Modbus host to read/write device parameters on the ESP32 target using the Modbus protocol. The parameters accessible through Modbus are located in `mb_example_common/modbus_params.h\c` source/header files that users can update to add/remove their own custom parameters.
|
||||
These are represented in structures `holding_reg_params`, `input_reg_params`, `coil_reg_params`, `discrete_reg_params` for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system.
|
||||
The FreeModbus stack located in `components/freemodbus` folder and contains the `/port` folder where the stack's port to the ESP32 is situated. There are some parameters of the port that can be configured in KConfig file to start stack correctly (See description below for more information).
|
||||
|
||||
The slave example uses shared parameter structures defined in `examples/protocols/modbus/mb_example_common` folder.
|
||||
|
||||
## Hardware required :
|
||||
Option 1:
|
||||
PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board.
|
||||
The MAX485 line driver is used as an example below but other similar chips can be used as well.
|
||||
|
||||
Option 2:
|
||||
The modbus_master example application configured as described in its README.md file and flashed into ESP32 based board.
|
||||
Note: The ```Example Data (Object) Dictionary``` in the modbus_master example can be edited to address parameters from other slaves connected into Modbus segment.
|
||||
|
||||
RS485 example circuit schematic:
|
||||
```
|
||||
VCC ---------------+ +--------------- VCC
|
||||
| |
|
||||
+-------x-------+ +-------x-------+
|
||||
RXD <------| RO | DIFFERENTIAL | RO|-----> RXD
|
||||
| B|---------------|B |
|
||||
TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD
|
||||
ESP32 board | | RS-485 side | | Modbus master
|
||||
RTS --+--->| DE | / \ | DE|---+
|
||||
| | A|---------------|A | |
|
||||
+----| /RE | PAIR | /RE|---+-- RTS
|
||||
+-------x--------+ +-------x-------+
|
||||
| |
|
||||
--- ---
|
||||
```
|
||||
|
||||
## How to setup and use an example:
|
||||
|
||||
### Configure the application
|
||||
Start the command below to show the configuration menu:
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
Select Modbus Example Configuration menu item.
|
||||
Configure the UART pins used for modbus communication using the command and table below.
|
||||
```
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
| UART Interface | #define | Default ESP32 Pin | Default pins for | External RS485 Driver Pin |
|
||||
| | | | ESP32-S2(S3, C3) | |
|
||||
| ----------------------|--------------------|-----------------------|-----------------------|---------------------------|
|
||||
| Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI |
|
||||
| Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO |
|
||||
| Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE |
|
||||
| Ground | n/a | GND | GND | GND |
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
```
|
||||
Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information.
|
||||
|
||||
Define the ```Modbus communiction mode``` for slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave application).
|
||||
Set ```Modbus slave address``` for the example application (by default for example script is set to 1).
|
||||
The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings.
|
||||
See the help strings of parameters for more information.
|
||||
|
||||
### Setup external Modbus master software
|
||||
Option 1:
|
||||
Configure the external Modbus master software according to port configuration parameters used in application.
|
||||
As an example the Modbus Poll application can be used with this example.
|
||||
Option 2:
|
||||
Setup ESP32 based board and set modbus_master example configuration as described in its README.md file.
|
||||
Setup one or more slave boards with different slave addresses and connect them into the same Modbus segment (See configuration above).
|
||||
Note: The ```Modbus communiction mode``` parameter must be the same for master and slave example application to be able to communicate with each other.
|
||||
|
||||
### Build and flash software
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
Example output of the application:
|
||||
```
|
||||
I (13941) SLAVE_TEST: INPUT READ (13651163 us), ADDR:1, TYPE:8, INST_ADDR:0x3ffb2fd0, SIZE:2
|
||||
I (13951) SLAVE_TEST: HOLDING READ (13656431 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb2fe0, SIZE:2
|
||||
I (13961) SLAVE_TEST: INPUT READ (13665877 us), ADDR:3, TYPE:8, INST_ADDR:0x3ffb2fd4, SIZE:2
|
||||
I (13971) SLAVE_TEST: HOLDING READ (13676010 us), ADDR:3, TYPE:2, INST_ADDR:0x3ffb2fe4, SIZE:2
|
||||
I (13981) SLAVE_TEST: INPUT READ (13686130 us), ADDR:5, TYPE:8, INST_ADDR:0x3ffb2fd8, SIZE:2
|
||||
I (13991) SLAVE_TEST: HOLDING READ (13696267 us), ADDR:5, TYPE:2, INST_ADDR:0x3ffb2fe8, SIZE:2
|
||||
I (14001) SLAVE_TEST: COILS READ (13706331 us), ADDR:0, TYPE:32, INST_ADDR:0x3ffb2fcc, SIZE:8
|
||||
I (14001) SLAVE_TEST: Modbus controller destroyed.
|
||||
```
|
||||
The output lines describe type of operation, its timestamp, modbus address, access type, storage address in parameter structure and number of registers accordingly.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
set(PROJECT_NAME "modbus_slave")
|
||||
|
||||
idf_component_register(SRCS "slave.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,82 @@
|
||||
menu "Modbus Example Configuration"
|
||||
|
||||
config MB_UART_PORT_NUM
|
||||
int "UART port number"
|
||||
range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
|
||||
default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3
|
||||
range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
UART communication port number for Modbus example.
|
||||
|
||||
config MB_UART_BAUD_RATE
|
||||
int "UART communication speed"
|
||||
range 1200 115200
|
||||
default 115200
|
||||
help
|
||||
UART communication speed for Modbus example.
|
||||
|
||||
config MB_UART_RXD
|
||||
int "UART RXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 22 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART RX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_TXD
|
||||
int "UART TXD pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART TX pin. See UART documentation for more information
|
||||
about available pin numbers for UART.
|
||||
|
||||
config MB_UART_RTS
|
||||
int "UART RTS pin number"
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
default 18 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 47 if IDF_TARGET_ESP32S3
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
GPIO number for UART RTS pin. This pin is connected to
|
||||
~RE/DE pin of RS485 transceiver to switch direction.
|
||||
See UART documentation for more information about available pin
|
||||
numbers for UART.
|
||||
|
||||
choice MB_COMM_MODE
|
||||
prompt "Modbus communication mode"
|
||||
default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN
|
||||
help
|
||||
Selection of Modbus communication mode option for Modbus.
|
||||
|
||||
config MB_COMM_MODE_RTU
|
||||
bool "RTU mode"
|
||||
depends on FMB_COMM_MODE_RTU_EN
|
||||
|
||||
config MB_COMM_MODE_ASCII
|
||||
bool "ASCII mode"
|
||||
depends on FMB_COMM_MODE_ASCII_EN
|
||||
|
||||
endchoice
|
||||
|
||||
config MB_SLAVE_ADDR
|
||||
int "Modbus slave address"
|
||||
range 1 127
|
||||
default 1
|
||||
help
|
||||
This is the Modbus slave address in the network.
|
||||
It is used to organize Modbus network with several slaves connected into the same segment.
|
||||
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// FreeModbus Slave Example ESP32
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "mbcontroller.h" // for mbcontroller defines and api
|
||||
#include "modbus_params.h" // for modbus parameters structures
|
||||
#include "esp_log.h" // for log_write
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection
|
||||
#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR) // The address of device in Modbus network
|
||||
#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART
|
||||
|
||||
// Note: Some pins on target chip cannot be assigned for UART communication.
|
||||
// Please refer to documentation for selected board and target to configure pins using Kconfig.
|
||||
|
||||
// Defines below are used to define register start address for each type of Modbus registers
|
||||
#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
|
||||
#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
|
||||
#define MB_REG_DISCRETE_INPUT_START (0x0000)
|
||||
#define MB_REG_COILS_START (0x0000)
|
||||
#define MB_REG_INPUT_START_AREA0 (INPUT_OFFSET(input_data0)) // register offset input area 0
|
||||
#define MB_REG_INPUT_START_AREA1 (INPUT_OFFSET(input_data4)) // register offset input area 1
|
||||
#define MB_REG_HOLDING_START_AREA0 (HOLD_OFFSET(holding_data0))
|
||||
#define MB_REG_HOLDING_START_AREA1 (HOLD_OFFSET(holding_data4))
|
||||
|
||||
#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info
|
||||
#define MB_CHAN_DATA_MAX_VAL (6)
|
||||
#define MB_CHAN_DATA_OFFSET (0.2f)
|
||||
#define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \
|
||||
| MB_EVENT_HOLDING_REG_RD \
|
||||
| MB_EVENT_DISCRETE_RD \
|
||||
| MB_EVENT_COILS_RD)
|
||||
#define MB_WRITE_MASK (MB_EVENT_HOLDING_REG_WR \
|
||||
| MB_EVENT_COILS_WR)
|
||||
#define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK)
|
||||
|
||||
static const char *TAG = "SLAVE_TEST";
|
||||
|
||||
static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
// Set register values into known state
|
||||
static void setup_reg_data(void)
|
||||
{
|
||||
// Define initial state of parameters
|
||||
discrete_reg_params.discrete_input0 = 1;
|
||||
discrete_reg_params.discrete_input1 = 0;
|
||||
discrete_reg_params.discrete_input2 = 1;
|
||||
discrete_reg_params.discrete_input3 = 0;
|
||||
discrete_reg_params.discrete_input4 = 1;
|
||||
discrete_reg_params.discrete_input5 = 0;
|
||||
discrete_reg_params.discrete_input6 = 1;
|
||||
discrete_reg_params.discrete_input7 = 0;
|
||||
|
||||
holding_reg_params.holding_data0 = 1.34;
|
||||
holding_reg_params.holding_data1 = 2.56;
|
||||
holding_reg_params.holding_data2 = 3.78;
|
||||
holding_reg_params.holding_data3 = 4.90;
|
||||
|
||||
holding_reg_params.holding_data4 = 5.67;
|
||||
holding_reg_params.holding_data5 = 6.78;
|
||||
holding_reg_params.holding_data6 = 7.79;
|
||||
holding_reg_params.holding_data7 = 8.80;
|
||||
|
||||
coil_reg_params.coils_port0 = 0x55;
|
||||
coil_reg_params.coils_port1 = 0xAA;
|
||||
|
||||
input_reg_params.input_data0 = 1.12;
|
||||
input_reg_params.input_data1 = 2.34;
|
||||
input_reg_params.input_data2 = 3.56;
|
||||
input_reg_params.input_data3 = 4.78;
|
||||
|
||||
input_reg_params.input_data4 = 1.12;
|
||||
input_reg_params.input_data5 = 2.34;
|
||||
input_reg_params.input_data6 = 3.56;
|
||||
input_reg_params.input_data7 = 4.78;
|
||||
}
|
||||
|
||||
// An example application of Modbus slave. It is based on freemodbus stack.
|
||||
// See deviceparams.h file for more information about assigned Modbus parameters.
|
||||
// These parameters can be accessed from main application and also can be changed
|
||||
// by external Modbus master host.
|
||||
void app_main(void)
|
||||
{
|
||||
mb_param_info_t reg_info; // keeps the Modbus registers access information
|
||||
mb_communication_info_t comm_info; // Modbus communication parameters
|
||||
mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
|
||||
|
||||
// Set UART log level
|
||||
esp_log_level_set(TAG, ESP_LOG_INFO);
|
||||
void* mbc_slave_handler = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(mbc_slave_init(MB_PORT_SERIAL_SLAVE, &mbc_slave_handler)); // Initialization of Modbus controller
|
||||
|
||||
// Setup communication parameters and start stack
|
||||
#if CONFIG_MB_COMM_MODE_ASCII
|
||||
comm_info.mode = MB_MODE_ASCII,
|
||||
#elif CONFIG_MB_COMM_MODE_RTU
|
||||
comm_info.mode = MB_MODE_RTU,
|
||||
#endif
|
||||
comm_info.slave_addr = MB_SLAVE_ADDR;
|
||||
comm_info.port = MB_PORT_NUM;
|
||||
comm_info.baudrate = MB_DEV_SPEED;
|
||||
comm_info.parity = MB_PARITY_NONE;
|
||||
ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info));
|
||||
|
||||
// The code below initializes Modbus register area descriptors
|
||||
// for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
|
||||
// Initialization should be done for each supported Modbus register area according to register map.
|
||||
// When external master trying to access the register in the area that is not initialized
|
||||
// by mbc_slave_set_descriptor() API call then Modbus stack
|
||||
// will send exception response for this register area.
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START_AREA0; // Offset of register area in Modbus protocol
|
||||
reg_area.address = (void*)&holding_reg_params.holding_data0; // Set pointer to storage instance
|
||||
// Set the size of register storage instance = 150 holding registers
|
||||
reg_area.size = (size_t)(HOLD_OFFSET(holding_data4) - HOLD_OFFSET(test_regs));
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
reg_area.type = MB_PARAM_HOLDING; // Set type of register area
|
||||
reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // Offset of register area in Modbus protocol
|
||||
reg_area.address = (void*)&holding_reg_params.holding_data4; // Set pointer to storage instance
|
||||
reg_area.size = sizeof(float) << 2; // Set the size of register storage instance
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Input Registers area
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
|
||||
reg_area.address = (void*)&input_reg_params.input_data0;
|
||||
reg_area.size = sizeof(float) << 2;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
reg_area.type = MB_PARAM_INPUT;
|
||||
reg_area.start_offset = MB_REG_INPUT_START_AREA1;
|
||||
reg_area.address = (void*)&input_reg_params.input_data4;
|
||||
reg_area.size = sizeof(float) << 2;
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Coils register area
|
||||
reg_area.type = MB_PARAM_COIL;
|
||||
reg_area.start_offset = MB_REG_COILS_START;
|
||||
reg_area.address = (void*)&coil_reg_params;
|
||||
reg_area.size = sizeof(coil_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
// Initialization of Discrete Inputs register area
|
||||
reg_area.type = MB_PARAM_DISCRETE;
|
||||
reg_area.start_offset = MB_REG_DISCRETE_INPUT_START;
|
||||
reg_area.address = (void*)&discrete_reg_params;
|
||||
reg_area.size = sizeof(discrete_reg_params);
|
||||
ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));
|
||||
|
||||
setup_reg_data(); // Set values into known state
|
||||
|
||||
// Starts of modbus controller and stack
|
||||
ESP_ERROR_CHECK(mbc_slave_start());
|
||||
|
||||
// Set UART pin numbers
|
||||
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD,
|
||||
CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS,
|
||||
UART_PIN_NO_CHANGE));
|
||||
|
||||
// Set UART driver mode to Half Duplex
|
||||
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
|
||||
|
||||
ESP_LOGI(TAG, "Modbus slave stack initialized.");
|
||||
ESP_LOGI(TAG, "Start modbus test...");
|
||||
|
||||
// The cycle below will be terminated when parameter holdingRegParams.dataChan0
|
||||
// incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
|
||||
for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
|
||||
// Check for read/write events of Modbus master for certain events
|
||||
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);
|
||||
const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";
|
||||
|
||||
// Filter events and process them accordingly
|
||||
if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
|
||||
// Get parameter information from parameter queue
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
|
||||
{
|
||||
portENTER_CRITICAL(¶m_lock);
|
||||
holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
|
||||
if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
|
||||
coil_reg_params.coils_port1 = 0xFF;
|
||||
}
|
||||
portEXIT_CRITICAL(¶m_lock);
|
||||
}
|
||||
} else if (event & MB_EVENT_INPUT_REG_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & MB_EVENT_DISCRETE_RD) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
} else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
|
||||
ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT));
|
||||
ESP_LOGI(TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
|
||||
rw_str,
|
||||
(uint32_t)reg_info.time_stamp,
|
||||
(uint32_t)reg_info.mb_offset,
|
||||
(uint32_t)reg_info.type,
|
||||
(uint32_t)reg_info.address,
|
||||
(uint32_t)reg_info.size);
|
||||
if (coil_reg_params.coils_port1 == 0xFF) break;
|
||||
}
|
||||
}
|
||||
// Destroy of Modbus controller on alarm
|
||||
ESP_LOGI(TAG,"Modbus controller destroyed.");
|
||||
vTaskDelay(100);
|
||||
ESP_ERROR_CHECK(mbc_slave_destroy());
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# Modbus configuration
|
||||
#
|
||||
CONFIG_MB_COMM_MODE_ASCII=y
|
||||
CONFIG_MB_SLAVE_ADDR=1
|
||||
CONFIG_MB_UART_BAUD_RATE=115200
|
||||
CONFIG_FMB_TIMER_PORT_ENABLED=n
|
||||
CONFIG_FMB_TIMER_ISR_IN_IRAM=y
|
||||
CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y
|
||||
Reference in New Issue
Block a user