diff --git a/modbus_serial_cpp_test/CMakeLists.txt b/modbus_serial_cpp_test/CMakeLists.txt new file mode 100644 index 0000000..335f818 --- /dev/null +++ b/modbus_serial_cpp_test/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mb_serial_master_cpp) diff --git a/modbus_serial_cpp_test/README.md b/modbus_serial_cpp_test/README.md new file mode 100644 index 0000000..ba3e93d --- /dev/null +++ b/modbus_serial_cpp_test/README.md @@ -0,0 +1,105 @@ +# Simplified Modbus Master example +Initializes Modbus master interface and reads and writes holding registers and coild in the connected Modbus device. +This example uses preliminary CPP ModbusMaster class `main/ModbusMaster.cpp/h` to read/write Modbus registers. +The class is updated from Arduino project and allow to use ESP_Modbus library in CPP projects. + +Note: This project is not final and is prepared just for demonstration for CPP users. + +# Setup example + +All required configuration data is in one source file master.c. + +Configure the parameters below or leave them as is: +``` +#define MASTER_PORT_NUM 2 // UART port number +#define MASTER_SPEED 115200 // Modbus communication speed +#define MB_UART_RXD_PIN 22 // Modbus RS485 interface pins accordingly +#define MB_UART_TXD_PIN 23 +#define MB_UART_RTS_PIN 18 +``` + +Simplified Modbus connection schematic for example test: + ``` + MB_SLAVE_SHORT_ADDRESS + ------------- ------------- + | | RS485 network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` + +# Setup connections + +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 + +``` + + +``` +idf.py build +idf.py -p PORT -b 921600 flash monitor +``` + +In order to read or write other registers of device, edit the device_parameters table. + +By default it is supposed that your device contains five holding registers at Modbus address = 0. +Every retry cycle Master reads the holding registers of slave defined as `MB_SLAVE_SHORT_ADDRESS`. Also reads the coil registers and invert some bits defined in masked value. + +Note: Enable `CONFIG_FMB_TIMER_ISR_IN_IRAM` option to decrease handling delays if you use NVS flash functions in your application. +`CONFIG_FMB_TIMER_PORT_ENABLED` should be disabled to avoid failures when you have other tasks started on the same CPU. +This increases delays of MB processing but allows to decrease possible issues. +`CONFIG_FMB_SERIAL_TASK_PRIO` - should be set to your (highest task priority + 1) executed on the same CPU. +As an alternative replace the sources in ESP_IDF folders with the `*.c/h files` from components folder of this project. + +## Example Output: +``` +I (360) MB_MASTER: Modbus master stack initialized... +I (550) MB: COILS: 0x5555 +I (550) MB: 11 11 22 22 33 33 44 44 55 55 +I (790) MB: COILS: 0xFFFF +I (790) MB: 11 11 22 22 33 33 44 44 55 55 +I (1010) MB: COILS: 0x5555 +I (1010) MB: 11 11 22 22 33 33 44 44 55 55 +I (1190) MB: COILS: 0xFFFF +I (1190) MB: 11 11 22 22 33 33 44 44 55 55 +I (1380) MB: COILS: 0x5555 +I (1380) MB: 11 11 22 22 33 33 44 44 55 55 +I (1590) MB: COILS: 0xFFFF +I (1590) MB: 11 11 22 22 33 33 44 44 55 55 +I (1840) MB: COILS: 0x5555 +I (1840) MB: 11 11 22 22 33 33 44 44 55 55 +I (2040) MB: COILS: 0xFFFF +I (2040) MB: 11 11 22 22 33 33 44 44 55 55 +I (2230) MB: COILS: 0x5555 +I (2230) MB: 11 11 22 22 33 33 44 44 55 55 +I (2450) MB: COILS: 0xFFFF +I (2450) MB: 11 11 22 22 33 33 44 44 55 55 +I (2450) MODBUS: Modbus Test completed. +``` + +Possible issues: + +E (9714) MODBUS_MASTER: Characteristic #0 MB_hold_reg-0 (Data), parameter read fail. +E (9874) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT). +These errors mean that slave device is not able to communicate. Check board connections. + + + + + diff --git a/modbus_serial_cpp_test/main/CMakeLists.txt b/modbus_serial_cpp_test/main/CMakeLists.txt new file mode 100644 index 0000000..2baee1b --- /dev/null +++ b/modbus_serial_cpp_test/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "master.cpp" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/modbus_serial_cpp_test/main/Kconfig.projbuild b/modbus_serial_cpp_test/main/Kconfig.projbuild new file mode 100644 index 0000000..55b325b --- /dev/null +++ b/modbus_serial_cpp_test/main/Kconfig.projbuild @@ -0,0 +1,99 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_ONE + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1) + + config MB_UART_PORT_TWO + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2) + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if MB_UART_PORT_TWO + default 2 if MB_UART_PORT_TWO + range 0 1 if MB_UART_PORT_ONE + default 1 if MB_UART_PORT_ONE + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 23 if IDF_TARGET_ESP32C6 + range 0 56 if IDF_TARGET_ESP32P4 + default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + range 0 20 if IDF_TARGET_ESP32C2 + range 0 27 if IDF_TARGET_ESP32H2 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_TXD + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 23 if IDF_TARGET_ESP32C6 + range 0 56 if IDF_TARGET_ESP32P4 + default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + range 0 20 if IDF_TARGET_ESP32C2 + range 0 27 if IDF_TARGET_ESP32H2 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + default 9 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_RTS + int "UART RTS pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 56 if IDF_TARGET_ESP32P4 + range 0 23 if IDF_TARGET_ESP32C6 + default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 20 if IDF_TARGET_ESP32P4 + range 0 46 if IDF_TARGET_ESP32S2 + range 0 47 if IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + range 0 20 if IDF_TARGET_ESP32C2 + range 0 27 if IDF_TARGET_ESP32H2 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + +endmenu diff --git a/modbus_serial_cpp_test/main/component.mk b/modbus_serial_cpp_test/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/modbus_serial_cpp_test/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/modbus_serial_cpp_test/main/idf_component.yml b/modbus_serial_cpp_test/main/idf_component.yml new file mode 100644 index 0000000..b3bd1a3 --- /dev/null +++ b/modbus_serial_cpp_test/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: ">=5.0" + espressif/esp-modbus: + version: "^2.0.0" + override_path: "/home/esp-man/esp32/5_modbus/esp-modbus" + diff --git a/modbus_serial_cpp_test/main/master.cpp b/modbus_serial_cpp_test/main/master.cpp new file mode 100644 index 0000000..cc45820 --- /dev/null +++ b/modbus_serial_cpp_test/main/master.cpp @@ -0,0 +1,105 @@ +#include "string.h" +#include "esp_log.h" + +#define MASTER_MAX_CIDS 2 +#define MASTER_MAX_RETRY 100 +#define MASTER_PORT_NUM (uart_port_t)2 +#define MASTER_SPEED 115200 +#define MASTER_TAG "MODBUS_MASTER" +#define MB_UART_RXD_PIN 22 +#define MB_UART_TXD_PIN 23 +#define MB_UART_RTS_PIN 18 + +#define TAG "MB_MASTER_MAIN" +#define MB_SLAVE_SHORT_ADDRESS 1 +#define MB_RETRIES 50 + +#include "sdkconfig.h" + +#include "mbcontroller.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + MB_DEVICE_ADDR1 = 1 +}; + +// Enumeration of all supported CIDs for device (used in parameter definition table) +enum { + CID_DEV_REG0 = 0, + CID_DEV_REG1 +}; + + +#define STR(fieldname) ((const char*)( fieldname )) +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +static void *master_handle = NULL; + +// Example Data (Object) Dictionary for Modbus parameters +const mb_parameter_descriptor_t device_parameters[2] = { + // CID, Name, Units, Modbus addr, register type, Modbus Reg Start Addr, Modbus Reg read length, + // Instance offset (NA), Instance type, Instance length (bytes), Options (NA), Permissions + { CID_DEV_REG0, STR("MB_hold_reg-0"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 1, + 0, PARAM_TYPE_U16, PARAM_SIZE_U16, OPTS( 0,0,0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_DEV_REG1, STR("MB_hold_reg-1"), STR("Data"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 3, 1, + 0, PARAM_TYPE_U16, PARAM_SIZE_U16, OPTS( 0,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])); + +// Modbus master initialization +static esp_err_t master_init(void) +{ + // Initialize Modbus controller + mb_communication_info_t comm; + comm.ser_opts.port = (uart_port_t)MASTER_PORT_NUM; + comm.ser_opts.mode = (mb_comm_mode_t)MB_RTU; + comm.ser_opts.baudrate = MASTER_SPEED; + comm.ser_opts.parity = MB_PARITY_NONE; + comm.ser_opts.uid = 0; + comm.ser_opts.response_tout_ms = 1000; + comm.ser_opts.data_bits = UART_DATA_8_BITS; + comm.ser_opts.stop_bits = UART_STOP_BITS_1; + + esp_err_t err = mbc_master_create_serial(&comm, &master_handle); + MB_RETURN_ON_FALSE((master_handle != NULL), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail, returns(0x%x).", (int)err); + + // Set UART pin numbers + err = uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, + MB_UART_RTS_PIN, UART_PIN_NO_CHANGE); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err); + + err = mbc_master_start(master_handle); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller start fail, returned (0x%x).", (int)err); + + // Set driver mode to Half Duplex + err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err); + + err = mbc_master_set_descriptor(master_handle, &device_parameters[0], num_device_parameters); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller set descriptor fail, returns(0x%x).", (int)err); + + ESP_LOGI(TAG, "Modbus master stack initialized..."); + return err; +} + +void app_main(void) +{ + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(master_init()); +} + +#ifdef __cplusplus +} +#endif diff --git a/modbus_simple_master/CMakeLists.txt b/modbus_simple_master/CMakeLists.txt new file mode 100644 index 0000000..99888a0 --- /dev/null +++ b/modbus_simple_master/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(simple_mb_master) diff --git a/modbus_simple_master/Makefile b/modbus_simple_master/Makefile new file mode 100644 index 0000000..6f8b9fa --- /dev/null +++ b/modbus_simple_master/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := blink + +include $(IDF_PATH)/make/project.mk + diff --git a/modbus_simple_master/README.md b/modbus_simple_master/README.md new file mode 100644 index 0000000..ba3e93d --- /dev/null +++ b/modbus_simple_master/README.md @@ -0,0 +1,105 @@ +# Simplified Modbus Master example +Initializes Modbus master interface and reads and writes holding registers and coild in the connected Modbus device. +This example uses preliminary CPP ModbusMaster class `main/ModbusMaster.cpp/h` to read/write Modbus registers. +The class is updated from Arduino project and allow to use ESP_Modbus library in CPP projects. + +Note: This project is not final and is prepared just for demonstration for CPP users. + +# Setup example + +All required configuration data is in one source file master.c. + +Configure the parameters below or leave them as is: +``` +#define MASTER_PORT_NUM 2 // UART port number +#define MASTER_SPEED 115200 // Modbus communication speed +#define MB_UART_RXD_PIN 22 // Modbus RS485 interface pins accordingly +#define MB_UART_TXD_PIN 23 +#define MB_UART_RTS_PIN 18 +``` + +Simplified Modbus connection schematic for example test: + ``` + MB_SLAVE_SHORT_ADDRESS + ------------- ------------- + | | RS485 network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` + +# Setup connections + +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 + +``` + + +``` +idf.py build +idf.py -p PORT -b 921600 flash monitor +``` + +In order to read or write other registers of device, edit the device_parameters table. + +By default it is supposed that your device contains five holding registers at Modbus address = 0. +Every retry cycle Master reads the holding registers of slave defined as `MB_SLAVE_SHORT_ADDRESS`. Also reads the coil registers and invert some bits defined in masked value. + +Note: Enable `CONFIG_FMB_TIMER_ISR_IN_IRAM` option to decrease handling delays if you use NVS flash functions in your application. +`CONFIG_FMB_TIMER_PORT_ENABLED` should be disabled to avoid failures when you have other tasks started on the same CPU. +This increases delays of MB processing but allows to decrease possible issues. +`CONFIG_FMB_SERIAL_TASK_PRIO` - should be set to your (highest task priority + 1) executed on the same CPU. +As an alternative replace the sources in ESP_IDF folders with the `*.c/h files` from components folder of this project. + +## Example Output: +``` +I (360) MB_MASTER: Modbus master stack initialized... +I (550) MB: COILS: 0x5555 +I (550) MB: 11 11 22 22 33 33 44 44 55 55 +I (790) MB: COILS: 0xFFFF +I (790) MB: 11 11 22 22 33 33 44 44 55 55 +I (1010) MB: COILS: 0x5555 +I (1010) MB: 11 11 22 22 33 33 44 44 55 55 +I (1190) MB: COILS: 0xFFFF +I (1190) MB: 11 11 22 22 33 33 44 44 55 55 +I (1380) MB: COILS: 0x5555 +I (1380) MB: 11 11 22 22 33 33 44 44 55 55 +I (1590) MB: COILS: 0xFFFF +I (1590) MB: 11 11 22 22 33 33 44 44 55 55 +I (1840) MB: COILS: 0x5555 +I (1840) MB: 11 11 22 22 33 33 44 44 55 55 +I (2040) MB: COILS: 0xFFFF +I (2040) MB: 11 11 22 22 33 33 44 44 55 55 +I (2230) MB: COILS: 0x5555 +I (2230) MB: 11 11 22 22 33 33 44 44 55 55 +I (2450) MB: COILS: 0xFFFF +I (2450) MB: 11 11 22 22 33 33 44 44 55 55 +I (2450) MODBUS: Modbus Test completed. +``` + +Possible issues: + +E (9714) MODBUS_MASTER: Characteristic #0 MB_hold_reg-0 (Data), parameter read fail. +E (9874) MB_CONTROLLER_MASTER: mbc_master_get_parameter(111): SERIAL master get parameter failure error=(0x107) (ESP_ERR_TIMEOUT). +These errors mean that slave device is not able to communicate. Check board connections. + + + + + diff --git a/modbus_simple_master/main/CMakeLists.txt b/modbus_simple_master/main/CMakeLists.txt new file mode 100644 index 0000000..e055c01 --- /dev/null +++ b/modbus_simple_master/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "master.cpp" "ModbusMaster.cpp" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/modbus_simple_master/main/Kconfig.projbuild b/modbus_simple_master/main/Kconfig.projbuild new file mode 100644 index 0000000..4dba2be --- /dev/null +++ b/modbus_simple_master/main/Kconfig.projbuild @@ -0,0 +1,83 @@ +menu "Modbus Example Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + config MB_UART_PORT_ONE + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_HP_NUM > 1) + + config MB_UART_PORT_TWO + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_HP_NUM > 2) + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if MB_UART_PORT_TWO + default 2 if MB_UART_PORT_TWO + range 0 1 if MB_UART_PORT_ONE + default 1 if MB_UART_PORT_ONE + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 8 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 8 if IDF_TARGET_ESP32C61 + 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 ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 9 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 9 if IDF_TARGET_ESP32C61 + 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 ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 20 if IDF_TARGET_ESP32P4 + default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 10 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 10 if IDF_TARGET_ESP32C61 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + +endmenu diff --git a/modbus_simple_master/main/ModbusMaster.cpp b/modbus_simple_master/main/ModbusMaster.cpp new file mode 100644 index 0000000..07e0187 --- /dev/null +++ b/modbus_simple_master/main/ModbusMaster.cpp @@ -0,0 +1,442 @@ +/* + * SPDX-FileCopyrightText: 2021 - 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** +@file +The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/485 (via RTU protocol). +*/ + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "ModbusMaster.h" + +/* _____GLOBAL VARIABLES_____________________________________________________ */ +static const char *TAG = "ModbusMaster"; + +/* _____PUBLIC FUNCTIONS_____________________________________________________ */ +/** +Constructor. + +Creates class object; initialize it using ModbusMaster::begin(). +@param pCommOpts a pointer to communication options + +@ingroup setup +*/ +ModbusMaster::ModbusMaster(mb_communication_info_t *pCommOpts) +{ + void* master_handle = NULL; + MB_RETURN_ON_FALSE((pCommOpts != NULL), ; , TAG, + "mb controller initialization fail."); + _comm_opts = *pCommOpts; + _pmaster_handle = NULL; + esp_err_t err = mbc_master_create_serial(&_comm_opts, &master_handle); + MB_RETURN_ON_FALSE((master_handle != NULL), ; , TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb controller initialization fail, returns(0x%x).", (int)err); + _pmaster_handle = master_handle; +} + +/** +Initialize Master class object to interract with defined slave UID. + +Assigns the Modbus slave ID. +Call once class has been instantiated, typically within start of communication. + +@param slave Modbus slave ID (1..247) +@ingroup setup +*/ +esp_err_t ModbusMaster::begin(uint8_t slave) +{ + _u8MBSlave = slave; + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + esp_err_t err = mbc_master_start(_pmaster_handle); + MB_RETURN_ON_FALSE((err == ESP_OK), err , TAG, + "mb controller start fail, returns(0x%x).", (int)err); + ESP_LOGI(TAG, "Modbus master stack initialized..."); + return err; +} + +/** +Stops Modbus Master object + +Assigns the Modbus slave ID and serial port. +Call once class has been instantiated, typically within setup(). + +@ingroup setup +*/ +esp_err_t ModbusMaster::end() +{ + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + esp_err_t err = mbc_master_stop(_pmaster_handle); + MB_RETURN_ON_FALSE((err == ESP_OK), err , TAG, + "mb controller stop fail, returns(0x%x).", (int)err); + _u8MBSlave = 0; + return err; +} + +/** +Modbus function 0x01 Read Coils. + +This function code is used to read from 1 to 2000 contiguous status of +coils in a remote device. The request specifies the starting address, +i.e. the address of the first coil specified, and the number of coils. +Coils are addressed starting at zero. + +The coils in the response buffer are packed as one coil per bit of the +data field. Status is indicated as 1=ON and 0=OFF. The LSB of the first +data word contains the output addressed in the query. The other coils +follow toward the high order end of this word and from low order to high +order in subsequent words. + +If the returned quantity is not a multiple of sixteen, the remaining +bits in the final data word will be padded with zeros (toward the high +order end of the word). + +@param u16ReadAddress address of first coil (00001..9999) +@param u16BitQty quantity of coils to read (1..2000, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup discrete +*/ +esp_err_t ModbusMaster::readCoils(uint16_t u16ReadAddress, uint16_t u16BitQty, void* pvBufPtr) +{ + mb_param_type_t reg_type; + uint16_t reg_off = 0; + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16ReadAddress, ®_type, ®_off); + MB_RETURN_ON_FALSE((reg_type == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , TAG, + "Incorrect coil address."); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadCoils; + _param_request.reg_start = (u16ReadAddress - reg_off); + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + +/** +Modbus function 0x02 Read Discrete Inputs. + +This function code is used to read from 1 to 2000 contiguous status of +discrete inputs in a remote device. The request specifies the starting +address, i.e. the address of the first input specified, and the number +of inputs. Discrete inputs are addressed starting at zero. + +The discrete inputs in the response buffer are packed as one input per +bit of the data field. Status is indicated as 1=ON; 0=OFF. The LSB of +the first data word contains the input addressed in the query. The other +inputs follow toward the high order end of this word, and from low order +to high order in subsequent words. + +If the returned quantity is not a multiple of sixteen, the remaining +bits in the final data word will be padded with zeros (toward the high +order end of the word). + +@param u16ReadAddress address of first discrete input (10001 .. 20000) +@param u16BitQty quantity of discrete inputs to read (1..2000, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup discrete +*/ +esp_err_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress, uint16_t u16BitQty, void* pvBufPtr) +{ + mb_param_type_t reg_type; + uint16_t reg_off = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16ReadAddress, ®_type, ®_off); + MB_RETURN_ON_FALSE((reg_type == MB_PARAM_DISCRETE), ESP_ERR_INVALID_ARG , TAG, + "Incorrect discrete address."); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadDiscreteInputs; + _param_request.reg_start = (u16ReadAddress - reg_off); + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + + +/** +Modbus function 0x03 Read Holding Registers. + +This function code is used to read the contents of a contiguous block of +holding registers in a remote device. The request specifies the starting +register address and the number of registers. Registers are addressed +starting at zero. + +The register data in the response buffer is packed as one word per +register. + +@param u16ReadAddress address of the first holding register (40001..49999) +@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup register +*/ +esp_err_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, uint16_t u16ReadQty, void* pvBufPtr) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16ReadAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , TAG, + "Incorrect register address %d.", u16ReadAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadHoldingRegisters; + _param_request.reg_start = (u16ReadAddress - u16Regbase); + _param_request.reg_size = u16ReadQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + + +/** +Modbus function 0x04 Read Input Registers. + +This function code is used to read from 1 to 125 contiguous input +registers in a remote device. The request specifies the starting +register address and the number of registers. Registers are addressed +starting at zero. + +The register data in the response buffer is packed as one word per +register. + +@param u16ReadAddress address of the first input register (30001..39999) +@param u16ReadQty quantity of input registers to read (1..125, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup register +*/ +esp_err_t ModbusMaster::readInputRegisters(uint16_t u16ReadAddress, uint8_t u16ReadQty, void* pvBufPtr) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16ReadAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_INPUT), ESP_ERR_INVALID_ARG , TAG, + "Incorrect register address %d.", u16ReadAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadInputRegisters; + _param_request.reg_start = (u16ReadAddress - u16Regbase); + _param_request.reg_size = u16ReadQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + + +/** +Modbus function 0x05 Write Single Coil. + +This function code is used to write a single output to either ON or OFF +in a remote device. The requested ON/OFF state is specified by a +constant in the state field. A non-zero value requests the output to be +ON and a value of 0 requests it to be OFF. The request specifies the +address of the coil to be forced. Coils are addressed starting at zero. + +@param u16WriteAddress address of the coil (00001..09999) +@param u8State 0=OFF, non-zero=ON (0x00..0xFF) +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup discrete +*/ +esp_err_t ModbusMaster::writeSingleCoil(uint16_t u16WriteAddress, uint8_t u8State) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , TAG, + "Incorrect coil address %d.", u16WriteAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteSingleCoil; + _param_request.reg_start = (u16WriteAddress - u16Regbase); + _param_request.reg_size = 1; + uint16_t u16StateTemp = (u8State ? 0xFF00 : 0x0000); + return mbc_master_send_request(_pmaster_handle, &_param_request, &u16StateTemp); +} + + +/** +Modbus function 0x06 Write Single Register. + +This function code is used to write a single holding register in a +remote device. The request specifies the address of the register to be +written. Registers are addressed starting at zero. + +@param u16WriteAddress address of the holding register (40001..49999) +@param u16WriteValue value to be written to holding register (0x0000..0xFFFF) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup register +*/ +esp_err_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, uint16_t u16WriteValue) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , TAG, + "Incorrect holding address %d.", u16WriteAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleRegisters; + _param_request.reg_start = (u16WriteAddress - u16Regbase); + _param_request.reg_size = 1; + uint16_t u16WriteValue_temp = u16WriteValue; + return mbc_master_send_request(_pmaster_handle, &_param_request, &u16WriteValue_temp); +} + + +/** +Modbus function 0x0F Write Multiple Coils. + +This function code is used to force each coil in a sequence of coils to +either ON or OFF in a remote device. The request specifies the coil +references to be forced. Coils are addressed starting at zero. + +The requested ON/OFF states are specified by contents of the transmit +buffer. A logical '1' in a bit position of the buffer requests the +corresponding output to be ON. A logical '0' requests it to be OFF. + +@param u16WriteAddress address of the first coil (00001..09999) +@param u16BitQty quantity of coils to write (1..2000, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup discrete +*/ +esp_err_t ModbusMaster::writeMultipleCoils(uint16_t u16WriteAddress, uint16_t u16BitQty, void* pvBufPtr) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , TAG, + "Incorrect coild register address %d.", u16WriteAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleCoils; + _param_request.reg_start = (u16WriteAddress - u16Regbase); + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + +/** +Modbus function 0x10 Write Multiple Registers. + +This function code is used to write a block of contiguous registers (1 +to 123 registers) in a remote device. + +The requested written values are specified in the transmit buffer. Data +is packed as one word per register. + +@param u16WriteAddress address of the holding register (40001..49999) +@param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device) +@param pvBufPtr - pointer to data buffer +@return ESP_OK on success; See ESP_Modbus errors for more information +@ingroup register +*/ +esp_err_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress, uint16_t u16WriteQty, void* pvBufPtr) +{ + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ESP_ERR_INVALID_ARG , TAG, + "mb controller initialization fail."); + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MB_RETURN_ON_FALSE((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , TAG, + "Incorrect holding register address %d.", u16WriteAddress); + + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleRegisters; + _param_request.reg_start = (u16WriteAddress - u16Regbase); + _param_request.reg_size = u16WriteQty; + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); +} + +/** +Modbus function 0x17 Read Write Multiple Registers. + +This function code performs a combination of one read operation and one +write operation in a single MODBUS transaction. The write operation is +performed before the read. Holding registers are addressed starting at +zero. + +The request specifies the starting address and number of holding +registers to be read as well as the starting address, and the number of +holding registers. The data to be written is specified in the transmit +buffer. + +@param u16ReadAddress address of the first holding register (30001..39999) +@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device) +@param u16WriteAddress address of the first holding register (0x0000..0xFFFF) +@param u16WriteQty quantity of holding registers to write (1..121, enforced by remote device) +@return 0 on success; exception number on failure +@ingroup register +*/ +esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, + uint16_t u16ReadQty, uint16_t u16WriteAddress, uint16_t u16WriteQty) +{ + ESP_LOGE(TAG, "Temporary unsupported command %d.", ku8MBReadWriteMultipleRegisters); + return ESP_FAIL; +} + +esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, + uint16_t u16ReadQty) +{ + ESP_LOGE(TAG, "Temporarily unsupported command %d.", ku8MBReadWriteMultipleRegisters); + return ESP_FAIL; +} + +ModbusMaster::~ModbusMaster(void) +{ + MB_RETURN_ON_FALSE((_pmaster_handle != NULL), ; , TAG, + "mb controller initialization fail."); + // The delete will check and stop object if needed + ESP_ERROR_CHECK(mbc_master_delete(_pmaster_handle)); + _pmaster_handle = NULL; +} + +/* _____PRIVATE FUNCTIONS____________________________________________________ */ + +void ModbusMaster::getRegType(uint16_t u16RegAddress, mb_param_type_t* regType, uint16_t* u16BaseAddr) +{ + if (u16RegAddress <= MODBUS_COIL_END) { + *regType = MB_PARAM_COIL; + *u16BaseAddr = MODBUS_COIL_START; + } else if ((u16RegAddress >= MODBUS_DISC_INPUT_START) && (u16RegAddress <= MODBUS_DISC_INPUT_END)) { + *regType = MB_PARAM_DISCRETE; + *u16BaseAddr = MODBUS_DISC_INPUT_START; + } else if ((u16RegAddress >= MODBUS_INPUT_START) && (u16RegAddress <= MODBUS_INPUT_END)) { + *regType = MB_PARAM_INPUT; + *u16BaseAddr = MODBUS_INPUT_START; + } else if ((u16RegAddress >= MODBUS_HOLD_START) && (u16RegAddress <= MODBUS_HOLD_END)) { + *regType = MB_PARAM_HOLDING; + *u16BaseAddr = MODBUS_HOLD_START; + } + return; +} diff --git a/modbus_simple_master/main/ModbusMaster.h b/modbus_simple_master/main/ModbusMaster.h new file mode 100644 index 0000000..8f20942 --- /dev/null +++ b/modbus_simple_master/main/ModbusMaster.h @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2021 - 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** +@file +Wrapper class for ESP_Modbus API to access Modbus slaves over RS232/485 (via RTU or ASCII protocol). + +@defgroup setup ModbusMaster Object Instantiation/Initialization +@defgroup buffer ModbusMaster Buffer Management +@defgroup discrete Modbus Function Codes for Discrete Coils/Inputs +@defgroup register Modbus Function Codes for Holding/Input Registers +@defgroup constant Modbus Function Codes, Exception Codes +*/ + +#ifndef ModbusMaster_h +#define ModbusMaster_h + +/** +@def __MODBUSMASTER_DEBUG__ (0) +Set to 1 to enable debugging features within class: + - PIN A cycles for each byte read in the Modbus response + - PIN B cycles for each millisecond timeout during the Modbus response +*/ + +/* _____STANDARD INCLUDES____________________________________________________ */ +#include +#include "esp_log.h" +#include "esp_err.h" + +/* _____UTILITY MACROS_______________________________________________________ */ +// Defines for register areas +#define MODBUS_COIL_START 1 +#define MODBUS_COIL_END 10000 +#define MODBUS_DISC_INPUT_START MODBUS_COIL_START + MODBUS_COIL_END +#define MODBUS_DISC_INPUT_END 20000 +#define MODBUS_INPUT_START 30001 +#define MODBUS_INPUT_END 39999 +#define MODBUS_HOLD_START 40001 +#define MODBUS_HOLD_END 49999 + + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "mbcontroller.h" // include file for common Modbus types + +/* _____CLASS DEFINITIONS____________________________________________________ */ + +class ModbusMaster +{ + public: + ModbusMaster(mb_communication_info_t *); + virtual ~ModbusMaster(); + + esp_err_t begin(uint8_t); + esp_err_t end(); + + esp_err_t readCoils(uint16_t, uint16_t, void*); + esp_err_t readDiscreteInputs(uint16_t, uint16_t, void*); + esp_err_t readHoldingRegisters(uint16_t, uint16_t, void*); + esp_err_t readInputRegisters(uint16_t, uint8_t, void*); + esp_err_t writeSingleCoil(uint16_t, uint8_t); + esp_err_t writeSingleRegister(uint16_t, uint16_t); + esp_err_t writeMultipleCoils(uint16_t, uint16_t, void*); + esp_err_t writeMultipleRegisters(uint16_t, uint16_t, void*); + //esp_err_t writeMultipleRegisters(); + //esp_err_t maskWriteRegister(uint16_t, uint16_t, uint16_t); + esp_err_t readWriteMultipleRegisters(uint16_t, uint16_t, uint16_t, uint16_t); + esp_err_t readWriteMultipleRegisters(uint16_t, uint16_t); + + private: + uint8_t _u8MBSlave; ///< Modbus current slave UID (1..255) initialized in begin() + mb_communication_info_t _comm_opts; ///< Modbus communication options + mb_param_request_t _param_request; ///< Modbus request structure + void* _pmaster_handle = NULL; ///< Modbus master handle + + // Modbus function codes for bit access + static const uint8_t ku8MBReadCoils = 0x01; ///< Modbus function 0x01 Read Coils + static const uint8_t ku8MBReadDiscreteInputs = 0x02; ///< Modbus function 0x02 Read Discrete Inputs + static const uint8_t ku8MBWriteSingleCoil = 0x05; ///< Modbus function 0x05 Write Single Coil + static const uint8_t ku8MBWriteMultipleCoils = 0x0F; ///< Modbus function 0x0F Write Multiple Coils + + // Modbus function codes for 16 bit access + static const uint8_t ku8MBReadHoldingRegisters = 0x03; ///< Modbus function 0x03 Read Holding Registers + static const uint8_t ku8MBReadInputRegisters = 0x04; ///< Modbus function 0x04 Read Input Registers + static const uint8_t ku8MBWriteSingleRegister = 0x06; ///< Modbus function 0x06 Write Single Register + static const uint8_t ku8MBWriteMultipleRegisters = 0x10; ///< Modbus function 0x10 Write Multiple Registers + static const uint8_t ku8MBMaskWriteRegister = 0x16; ///< Modbus function 0x16 Mask Write Register + static const uint8_t ku8MBReadWriteMultipleRegisters = 0x17; ///< Modbus function 0x17 Read Write Multiple Registers + + // Modbus timeout [milliseconds] + static const uint16_t ku16MBResponseTimeout = 2000; ///< Modbus timeout [milliseconds] + void getRegType(uint16_t, mb_param_type_t*, uint16_t*); +}; +#endif + diff --git a/modbus_simple_master/main/component.mk b/modbus_simple_master/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/modbus_simple_master/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/modbus_simple_master/main/idf_component.yml b/modbus_simple_master/main/idf_component.yml new file mode 100644 index 0000000..386615a --- /dev/null +++ b/modbus_simple_master/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: ">=5.0" + espressif/esp-modbus: + version: "^2.0.0" + + diff --git a/modbus_simple_master/main/master.cpp b/modbus_simple_master/main/master.cpp new file mode 100644 index 0000000..1e485c3 --- /dev/null +++ b/modbus_simple_master/main/master.cpp @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2021 - 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "string.h" +#include "esp_log.h" + +#include "ModbusMaster.h" +#include "sdkconfig.h" + +#define MASTER_TAG "MODBUS_MASTER" + +#define MASTER_MAX_RETRY (10) +#define MASTER_PORT_NUM (uart_port_t)(CONFIG_MB_UART_PORT_NUM) +#define MASTER_SPEED (CONFIG_MB_UART_BAUD_RATE) +#define MB_SLAVE_SHORT_ADDRESS (1) +#define MB_UART_RXD_PIN (CONFIG_MB_UART_RXD) +#define MB_UART_TXD_PIN (CONFIG_MB_UART_TXD) +#define MB_UART_RTS_PIN (CONFIG_MB_UART_RTS) + +#define TAG "MB_MASTER_MAIN" + +// Example code to read and write Modbus registers using CPP wrapper class +extern "C" void app_main() +{ + // Initialize and start Modbus controller + mb_communication_info_t comm; + comm.ser_opts.port = MASTER_PORT_NUM; +#if CONFIG_MB_COMM_MODE_ASCII + comm.ser_opts.mode = MB_ASCII; +#elif CONFIG_MB_COMM_MODE_RTU + comm.ser_opts.mode = MB_RTU; +#endif + comm.ser_opts.baudrate = MASTER_SPEED; + comm.ser_opts.parity = MB_PARITY_NONE; + comm.ser_opts.uid = 0; + comm.ser_opts.response_tout_ms = 1000; + comm.ser_opts.data_bits = UART_DATA_8_BITS; + comm.ser_opts.stop_bits = UART_STOP_BITS_1; + ModbusMaster* pModbusMaster = new ModbusMaster(&comm); + + // Set UART pin numbers + esp_err_t err = uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, + MB_UART_RTS_PIN, UART_PIN_NO_CHANGE); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err); + + // Set driver mode to Half Duplex + err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err); + + uint16_t reg_holding_array[6] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; + uint16_t reg_input_array[6] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; + uint16_t temp_coils = 0xFFFF; + + // Initialization of device peripheral and objects + err = pModbusMaster->begin(MB_SLAVE_SHORT_ADDRESS); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb controller initialization fail, returns(0x%x).", (int)err); + + for (int i = 0; i < MASTER_MAX_RETRY; i++) { + err = pModbusMaster->writeMultipleRegisters(40001, 5, ®_holding_array[0]); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "Modbus Write Holding registers error: (0x%x).", (int)err); + } else { + ESP_LOGI(TAG, "Modbus Write Holding registers successful."); + } + + memset(reg_holding_array, 0x00, 10); + err = pModbusMaster->readHoldingRegisters(40001, 5, ®_holding_array[0]); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "Modbus Read Holding registers error: (0x%x).", (int)err); + } else { + ESP_LOGI(TAG, "Modbus Read Holding registers successful."); + } + ESP_LOG_BUFFER_HEX_LEVEL("HOLDING_REGS", reg_holding_array, 10, ESP_LOG_INFO); + + err = pModbusMaster->readInputRegisters(30001, 5, ®_input_array[0]); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "Modbus Read Inputs error: (0x%x).", (int)err); + } else { + ESP_LOGI(TAG, "Modbus Read Inputs successful."); + } + + temp_coils ^= 0xAAAA; + + err = pModbusMaster->writeMultipleCoils(00001, 10, &temp_coils); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "Modbus Write Multiple Coils error: (0x%x).", (int)err); + } else { + ESP_LOGI(TAG, "Modbus Write Multiple Coils successful."); + } + + err = pModbusMaster->readCoils(00001, 10, &temp_coils); + if ((err != ESP_OK)) { + ESP_LOGE(TAG, "Modbus Read Coils error: (0x%x).", (int)err); + } else { + ESP_LOGI(TAG, "Modbus Read Coils successful."); + } + ESP_LOGI("COILS", "Actual coil value: 0x%X", temp_coils); + + } + err = pModbusMaster->end(); + if ((err != ESP_OK)) { + ESP_LOGW(TAG, "Modbus stop fail, or already stopped, error: (0x%x).", (int)err); + } + ESP_LOGI(TAG, "Modbus Test completed, destroy Moddbus controller."); + + delete pModbusMaster; +} + diff --git a/modbus_simple_master/sdkconfig.defaults b/modbus_simple_master/sdkconfig.defaults new file mode 100644 index 0000000..6ebe677 --- /dev/null +++ b/modbus_simple_master/sdkconfig.defaults @@ -0,0 +1,9 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y +CONFIG_CPP_PROJ=y + diff --git a/modbus_simple_slave/CMakeLists.txt b/modbus_simple_slave/CMakeLists.txt new file mode 100644 index 0000000..3c52567 --- /dev/null +++ b/modbus_simple_slave/CMakeLists.txt @@ -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_slave) diff --git a/modbus_simple_slave/Makefile b/modbus_simple_slave/Makefile new file mode 100644 index 0000000..851d87e --- /dev/null +++ b/modbus_simple_slave/Makefile @@ -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/serial/mb_example_common + +include $(IDF_PATH)/make/project.mk + diff --git a/modbus_simple_slave/README.md b/modbus_simple_slave/README.md new file mode 100644 index 0000000..db9289b --- /dev/null +++ b/modbus_simple_slave/README.md @@ -0,0 +1,95 @@ +# Modbus Slave Example + +This example demonstrates using of FreeModbus stack port implementation for ESP32 for CPP. The external Modbus host is able to read/write device parameters using Modbus protocol transport. The parameters accessible thorough Modbus are located in deviceparams.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 ModbusSlave wrapper class to transfer calls to ESP_MODBUS API. +This is prepared for demonstration on how to adopt the API for CPP implementation. + + +## 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. + diff --git a/modbus_simple_slave/main/CMakeLists.txt b/modbus_simple_slave/main/CMakeLists.txt new file mode 100644 index 0000000..e50fab1 --- /dev/null +++ b/modbus_simple_slave/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(PROJECT_NAME "modbus_slave") + +idf_component_register(SRCS "slave.cpp" "ModbusSlave.cpp" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/modbus_simple_slave/main/Kconfig.projbuild b/modbus_simple_slave/main/Kconfig.projbuild new file mode 100644 index 0000000..56fc211 --- /dev/null +++ b/modbus_simple_slave/main/Kconfig.projbuild @@ -0,0 +1,92 @@ +menu "Modbus Example Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + config MB_UART_PORT_ONE + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_HP_NUM > 1) + + config MB_UART_PORT_TWO + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_HP_NUM > 2) + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if MB_UART_PORT_TWO + default 2 if MB_UART_PORT_TWO + range 0 1 if MB_UART_PORT_ONE + default 1 if MB_UART_PORT_ONE + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 8 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 8 if IDF_TARGET_ESP32C61 + 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 ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 9 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 9 if IDF_TARGET_ESP32C61 + 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 ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 20 if IDF_TARGET_ESP32P4 + default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 + default 10 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C5 + default 10 if IDF_TARGET_ESP32C61 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + + config MB_SLAVE_ADDR + int "Modbus slave address" + range 1 127 + default 1 + help + This is the Modbus slave address in the network. + It is used to organize Modbus network with several slaves connected into the same segment. + + +endmenu diff --git a/modbus_simple_slave/main/ModbusSlave.cpp b/modbus_simple_slave/main/ModbusSlave.cpp new file mode 100644 index 0000000..55406b9 --- /dev/null +++ b/modbus_simple_slave/main/ModbusSlave.cpp @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** +@file +The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/485 (via RTU protocol). +*/ + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "ModbusSlave.h" + +/* _____GLOBAL VARIABLES_____________________________________________________ */ +static const char *TAG = "ModbusSlave"; + +/* _____PUBLIC FUNCTIONS_____________________________________________________ */ +/** +Constructor. + +Creates class object; initialize it using ModbusSlave::begin(). + +@ingroup setup +*/ +ModbusSlave::ModbusSlave(mb_communication_info_t* pxCommOpts) +{ + void* slave_handle = NULL; + MB_RETURN_ON_FALSE((pxCommOpts != NULL), ; , TAG, + "mb controller initialization fail."); + _comm_opts = *pxCommOpts; + _pslave_handle = NULL; + esp_err_t err = mbc_slave_create_serial(&_comm_opts, &slave_handle); + MB_RETURN_ON_FALSE((slave_handle != NULL), ; , TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb controller initialization fail, returns(0x%x).", (int)err); + ESP_LOGW("TEST", "Inst pointer constructor: %p", slave_handle); + _pslave_handle = slave_handle; +} + +void *ModbusSlave::getInstance(void) +{ + //assert(_pslave_handle); + return _pslave_handle; +} + +/** +Initialize class object. + +Assigns the Modbus slave ID and serial communication parameters. +Call once class has been instantiated. + +@ingroup setup +*/ +esp_err_t ModbusSlave::begin() +{ + _u8MBSlave = _comm_opts.ser_opts.uid; + esp_err_t err = ESP_FAIL; + + // Starts communication object and stack + err = mbc_slave_start(_pslave_handle); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller start fail, returns(0x%x).", (int)err); + return err; +} + +/** +Add Modbus register area to be accessed by slave + +@param regAddress the address of Modbus register in the area using Modicon notation ( example: 40005 - Holding register with offset 5 ) +@param instanceAddress - pointer to the data which will store the register data +@param regAreaSize - length of area to store register data +@ingroup setup +*/ +esp_err_t ModbusSlave::addRegisterBank(uint16_t regAddress, void* instanceAddress, uint16_t regAreaSize) +{ + mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure + uint16_t reg_off = 0; + + getRegType(regAddress, ®_area.type, ®_off); + + reg_area.start_offset = regAddress - reg_off; // Offset of register area in Modbus protocol + reg_area.address = (void*)instanceAddress; // Set pointer to storage instance + // Set the size of register storage instance + reg_area.size = (size_t)(regAreaSize << 1); + reg_area.access = MB_ACCESS_RW; + + return mbc_slave_set_descriptor(_pslave_handle, reg_area); +} + +/** +Waits the event defined in the event mask and returns actual event (register read/write type event) + +@param EventMask - event mask to wait for +@ingroup setup +*/ +mb_event_group_t ModbusSlave::run(mb_event_group_t EventMask) +{ + MB_RETURN_ON_FALSE((_pslave_handle != NULL), MB_EVENT_NO_EVENTS, TAG, + "mb controller initialization fail."); + (void)mbc_slave_check_event(_pslave_handle, EventMask); + ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(_pslave_handle, &_reg_info, MODBUS_PAR_INFO_GET_TOUT)); + return _reg_info.type; +} + +/** +Modbus get information about accessed Modbus register +The register access information is delivered in method run() +*/ +mb_param_info_t ModbusSlave::getRegisterInfo() +{ + return _reg_info; +} + +/** +Check the register access information for register area defined as parameters +The register access information is delivered in method run() +*/ +bool ModbusSlave::isBankAccessed(uint16_t regAddress, uint16_t regAreaSize) +{ + mb_param_type_t reg_type; + uint16_t base_off = 0; + mb_param_info_t par_info = _reg_info; + + getRegType(regAddress, ®_type, &base_off); + mb_event_group_t reg_mask = getRegEventMask(reg_type); + uint16_t offset = regAddress - base_off; + + //ESP_LOGI("TEST", "test mask %d", (reg_mask & par_info.type)); + bool result = ((reg_mask & par_info.type) && + (offset >= par_info.mb_offset) && + ((offset + regAreaSize) >= (par_info.mb_offset + par_info.size))) ? true : false; + ESP_LOGD("TEST", "Bank accessed type %d, offset %d, par_info.mb_off %d, par_info.size %d, reg_mask %x, par_info.type %x, result=%d", + reg_type, offset, par_info.mb_offset, par_info.size, (int)reg_mask, (int)par_info.type, (int)result); + return result; +} + +ModbusSlave::~ModbusSlave(void) +{ + MB_RETURN_ON_FALSE((_pslave_handle != NULL), ; , TAG, + "mb controller initialization fail."); + esp_err_t err = mbc_slave_stop(_pslave_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "mb controller stop fail or already stopped, returns(0x%x).", (int)err); + } + ESP_ERROR_CHECK(mbc_slave_delete(_pslave_handle)); + _pslave_handle = NULL; +} + +/* _____PRIVATE FUNCTIONS____________________________________________________ */ + +// This helper temporarily not used +void ModbusSlave::getRegType(uint16_t u16RegAddress, mb_param_type_t* regType, uint16_t* u16BaseAddr) +{ + if (u16RegAddress <= MODBUS_COIL_END) { + *regType = MB_PARAM_COIL; + *u16BaseAddr = MODBUS_COIL_START; + } else if ((u16RegAddress >= MODBUS_DISC_INPUT_START) && (u16RegAddress <= MODBUS_DISC_INPUT_END)) { + *regType = MB_PARAM_DISCRETE; + *u16BaseAddr = MODBUS_DISC_INPUT_START; + } else if ((u16RegAddress >= MODBUS_INPUT_START) && (u16RegAddress <= MODBUS_INPUT_END)) { + *regType = MB_PARAM_INPUT; + *u16BaseAddr = MODBUS_INPUT_START; + } else if ((u16RegAddress >= MODBUS_HOLD_START) && (u16RegAddress <= MODBUS_HOLD_END)) { + *regType = MB_PARAM_HOLDING; + *u16BaseAddr = MODBUS_HOLD_START; + } + return; +} + +// This helper temporarily not used +mb_event_group_t ModbusSlave::getRegEventMask(mb_param_type_t regType) +{ + mb_event_group_t eventMask = MB_EVENT_NO_EVENTS; + switch(regType) { + case MB_PARAM_COIL: + eventMask = (mb_event_group_t)(MB_EVENT_COILS_RD | MB_EVENT_COILS_WR); + break; + case MB_PARAM_DISCRETE: + eventMask = (mb_event_group_t)(MB_EVENT_DISCRETE_RD); + break; + case MB_PARAM_INPUT: + eventMask = (mb_event_group_t)(MB_EVENT_INPUT_REG_RD); + break; + case MB_PARAM_HOLDING: + eventMask = (mb_event_group_t)(MB_EVENT_HOLDING_REG_RD | MB_EVENT_HOLDING_REG_WR); + break; + default: + eventMask = MB_EVENT_NO_EVENTS; + break; + } + return eventMask; +} diff --git a/modbus_simple_slave/main/ModbusSlave.h b/modbus_simple_slave/main/ModbusSlave.h new file mode 100644 index 0000000..dbc9ab7 --- /dev/null +++ b/modbus_simple_slave/main/ModbusSlave.h @@ -0,0 +1,86 @@ +/** +@file +The wrapper class for Modbus Slave for communication over RS232/485 (via RTU or ASCII protocol). + +@defgroup setup ModbusSlave Object Instantiation/Initialization +@defgroup buffer ModbusSlave register bank management +*/ +/* + + ModbusSlave.h - The wrapper class for Modbus Serial Slave to communicate over RS232/485 (via RTU protocol). + + Library:: ModbusSlave + + Copyright:: 2009-2016 Espressif Systems + + 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. + +*/ + + +#ifndef ModbusSlave_h +#define ModbusSlave_h + +// Defines for register areas +#define MODBUS_COIL_START 1 +#define MODBUS_COIL_END 10000 +#define MODBUS_DISC_INPUT_START MODBUS_COIL_START + MODBUS_COIL_END +#define MODBUS_DISC_INPUT_END 20000 +#define MODBUS_INPUT_START 30001 +#define MODBUS_INPUT_END 39999 +#define MODBUS_HOLD_START 40001 +#define MODBUS_HOLD_END 49999 + +#define MODBUS_PAR_INFO_GET_TOUT pdMS_TO_TICKS(10) + +/* _____STANDARD INCLUDES____________________________________________________ */ +#include +#include "esp_log.h" + +/* _____UTILITY MACROS_______________________________________________________ */ +#define CRITICAL_SECT(inst) for (int st = mbc_slave_lock(inst); (st == 0); mbc_slave_unlock(inst), st = 1) + + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "mbcontroller.h" // include file for common Modbus types + +/* _____CLASS DEFINITIONS____________________________________________________ */ + +class ModbusSlave +{ + public: + ModbusSlave(mb_communication_info_t *); + virtual ~ModbusSlave(); + void *getInstance(void); + + esp_err_t begin(); + esp_err_t addRegisterBank(uint16_t regAddress, void* instanceAddress, uint16_t regAreaSize); + mb_event_group_t run(mb_event_group_t); + mb_param_info_t getRegisterInfo(void); + bool isBankAccessed(uint16_t, uint16_t); + + + private: + uint8_t _u8MBSlave; ///< Modbus slave (1..255) initialized in begin() + mb_communication_info_t _comm_opts; ///< Modbus communication options + void* _pslave_handle; ///< Modbus slave handle + mb_param_info_t _reg_info; ///< Modbus slave accessed register information + mb_event_group_t _reg_event; ///< Modbus slave register access event + + // The helper functions for Modbus Slave + void getRegType(uint16_t, mb_param_type_t*, uint16_t*); + mb_event_group_t getRegEventMask(mb_param_type_t); +}; + +#endif + diff --git a/modbus_simple_slave/main/component.mk b/modbus_simple_slave/main/component.mk new file mode 100644 index 0000000..b4fa727 --- /dev/null +++ b/modbus_simple_slave/main/component.mk @@ -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.) diff --git a/modbus_simple_slave/main/idf_component.yml b/modbus_simple_slave/main/idf_component.yml new file mode 100644 index 0000000..97ea52b --- /dev/null +++ b/modbus_simple_slave/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + idf: ">=5.0" + espressif/esp-modbus: + version: "^2.0.0" diff --git a/modbus_simple_slave/main/slave.cpp b/modbus_simple_slave/main/slave.cpp new file mode 100644 index 0000000..96c48f8 --- /dev/null +++ b/modbus_simple_slave/main/slave.cpp @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2021 - 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* CPP Wrapper 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 +#include +#include "esp_err.h" +#include "mbcontroller.h" // for mbcontroller defines and api +#include "esp_log.h" // for log_write +#include "esp_random.h" +#include "sdkconfig.h" + +#include "ModbusSlave.h" + +#define MB_RETRIES (100) +#define MB_PORT_NUM ((uart_port_t)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. + +#define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info +#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_event_group_t)(MB_READ_MASK | MB_WRITE_MASK)) + +static const char *TAG = "SLAVE_TEST"; + +// An example application of Modbus CPP Slave. +extern "C" void app_main(void) +{ + // Initialize and start Modbus controller + mb_communication_info_t comm; + + // Setup communication parameters and start stack + #if CONFIG_MB_COMM_MODE_ASCII + comm.ser_opts.mode = MB_ASCII; + #elif CONFIG_MB_COMM_MODE_RTU + comm.ser_opts.mode = MB_RTU; + #endif + comm.ser_opts.uid = MB_SLAVE_ADDR; + comm.ser_opts.port = MB_PORT_NUM; + comm.ser_opts.baudrate = MB_DEV_SPEED; + comm.ser_opts.parity = MB_PARITY_NONE; + + // Create the class instance + ModbusSlave *pModbusSlave = new ModbusSlave(&comm); + + // Set UART pin numbers + uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD, + CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE); + + // Set driver mode to Half Duplex + esp_err_t err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MB_RETURN_ON_FALSE((err == ESP_OK), ; , TAG, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err); + + // The register areas defined to store registers data + uint16_t hold_array[6] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; + uint16_t input_array[6] = { 0xAAAA, 0x9999, 0x8888, 0x7777, 0x6666, 0x5555 }; + uint16_t coil_array[6] = { 0xAAAA, 0xFFFF, 0xAAAA, 0xFFFF, 0xAAAA, 0xFFFF }; + size_t reg_size = (sizeof(hold_array) / sizeof(hold_array[0])); + + // Add registers below into assiciated register bank (use PLC based address) + // These registers are accessed inside this defined area only (exception returned outside of this range). + // Make sure the Master reads/writes to these areas!!! + pModbusSlave->addRegisterBank(40001, &hold_array[0], reg_size); + pModbusSlave->addRegisterBank(30001, &input_array[0], (sizeof(input_array) << 1)); + pModbusSlave->addRegisterBank(1, &coil_array[0], (sizeof(coil_array) * 2 * 8)); + + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(pModbusSlave->begin()); + + for (int i = 0; i < MB_RETRIES; i++) { + ESP_LOG_BUFFER_HEX_LEVEL("Holding Register bank", hold_array, sizeof(hold_array), ESP_LOG_INFO); + ESP_LOG_BUFFER_HEX_LEVEL("Input Register bank", input_array, sizeof(input_array), ESP_LOG_INFO); + ESP_LOG_BUFFER_HEX_LEVEL("Coil Register bank", input_array, sizeof(coil_array), ESP_LOG_INFO); + + // Wait for update events when access from Modbus Master is completed (register areas accessed in wr/rd commands) + mb_event_group_t event = pModbusSlave->run(MB_READ_WRITE_MASK); + if (!(event & MB_READ_WRITE_MASK)) { + ESP_LOGE(TAG, "Incorrect modbus access type: %d.", event); + } + // Get register access information + mb_param_info_t info = pModbusSlave->getRegisterInfo(); + ESP_LOGW(TAG, "Got Event: %d.", event); + const char* rw_str = (info.type & MB_READ_MASK) ? "READ" : "WRITE"; + + // Check if the selected holding register area is accessed by Master + if (pModbusSlave->isBankAccessed(40001, reg_size)) { + CRITICAL_SECT(pModbusSlave->getInstance()) { + ESP_LOGW(TAG, "Holding bank address offset: %d, size %d, %s accessed.", info.mb_offset, info.size, rw_str); + esp_fill_random(info.address, (info.size << 1)); + } + } + + // // Check if the selected input register area is accessed by Master + if (pModbusSlave->isBankAccessed(30001, reg_size)) { + ESP_LOGW(TAG, "Input bank address offset: %d, size %d, %s accessed.", info.mb_offset, info.size, rw_str); + CRITICAL_SECT(pModbusSlave->getInstance()) { + // Reinitialize the accessed register values with random numbers + esp_fill_random(info.address, (info.size << 1)); + } + } + + if (pModbusSlave->isBankAccessed(1, 10)) { + ESP_LOGW(TAG, "Coils address offset: %d, size %d, %s accessed.", info.mb_offset, info.size, rw_str); + CRITICAL_SECT(pModbusSlave->getInstance()) { + // Reinitialize the accessed register values with random numbers + esp_fill_random(info.address, (info.size >> 3)); + } + } + vTaskDelay(1); + } + ESP_LOGI("MODBUS", "Modbus Test completed."); + delete pModbusSlave; +} + diff --git a/modbus_simple_slave/sdkconfig.defaults b/modbus_simple_slave/sdkconfig.defaults new file mode 100644 index 0000000..de235f4 --- /dev/null +++ b/modbus_simple_slave/sdkconfig.defaults @@ -0,0 +1,9 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_SLAVE_ADDR=1 +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y +