From 219d8e62674a588b81aba4e1a88e5a50f2fe83a7 Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn <37483886+alisitsyn@users.noreply.github.com> Date: Tue, 21 Sep 2021 12:20:44 +0200 Subject: [PATCH 1/5] modbus: add CPP ModbusMaster wrapper class to use ESP_Modbus similar to arduino approach --- modbus_simple_master/CMakeLists.txt | 6 + modbus_simple_master/Makefile | 9 + modbus_simple_master/README.md | 105 +++++++ modbus_simple_master/main/CMakeLists.txt | 2 + modbus_simple_master/main/ModbusMaster.cpp | 331 +++++++++++++++++++++ modbus_simple_master/main/ModbusMaster.h | 113 +++++++ modbus_simple_master/main/component.mk | 4 + modbus_simple_master/main/master.c | 195 ++++++++++++ modbus_simple_master/main/master.cpp | 85 ++++++ 9 files changed, 850 insertions(+) create mode 100644 modbus_simple_master/CMakeLists.txt create mode 100644 modbus_simple_master/Makefile create mode 100644 modbus_simple_master/README.md create mode 100644 modbus_simple_master/main/CMakeLists.txt create mode 100644 modbus_simple_master/main/ModbusMaster.cpp create mode 100644 modbus_simple_master/main/ModbusMaster.h create mode 100644 modbus_simple_master/main/component.mk create mode 100644 modbus_simple_master/main/master.c create mode 100644 modbus_simple_master/main/master.cpp 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/ModbusMaster.cpp b/modbus_simple_master/main/ModbusMaster.cpp new file mode 100644 index 0000000..bfbb24d --- /dev/null +++ b/modbus_simple_master/main/ModbusMaster.cpp @@ -0,0 +1,331 @@ +/** +@file +The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/485 (via RTU protocol). +*/ + +#define TAG "MB_MASTER" + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "ModbusMaster.h" + +/* _____GLOBAL VARIABLES_____________________________________________________ */ + + +/* _____PUBLIC FUNCTIONS_____________________________________________________ */ +/** +Constructor. + +Creates class object; initialize it using ModbusMaster::begin(). + +@ingroup setup +*/ +ModbusMaster::ModbusMaster(void) +{ + +} + +/** +Initialize class object. + +Assigns the Modbus slave ID and serial port. +Call once class has been instantiated, typically within setup(). + +@param slave Modbus slave ID (1..247) +@param pxCommOpts - pointer to communication options structure +@ingroup setup +*/ +esp_err_t ModbusMaster::begin(uint8_t slave, mb_communication_info_t* commOpts) +{ + _u8MBSlave = slave; + _comm_opts = *commOpts; + void* master_handler = NULL; + + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + + MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_ARG , "mb controller initialization fail."); + MASTER_CHECK((err == ESP_OK), err , "mb controller initialization fail, returns(0x%x).", (uint32_t)err); + + err = mbc_master_setup((void*)&_comm_opts); + MASTER_CHECK((err == ESP_OK), err , "mb controller setup fail, returns(0x%x).", (uint32_t)err); + + err = mbc_master_start(); + MASTER_CHECK((err == ESP_OK), err , "mb controller start fail, returns(0x%x).", (uint32_t)err); + + ESP_LOGI(TAG, "Modbus master stack initialized..."); + 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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadCoils; + _param_request.reg_start = u16ReadAddress; + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadDiscreteInputs; + _param_request.reg_start = u16ReadAddress; + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadHoldingRegisters; + _param_request.reg_start = u16ReadAddress; + _param_request.reg_size = u16ReadQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBReadInputRegisters; + _param_request.reg_start = u16ReadAddress; + _param_request.reg_size = u16ReadQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteSingleCoil; + _param_request.reg_start = u16WriteAddress; + _param_request.reg_size = 1; + uint16_t u16State_temp = (u8State ? 0xFF00 : 0x0000); + return mbc_master_send_request(&_param_request, &u16State_temp); +} + + +/** +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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleRegisters; + _param_request.reg_start = u16WriteAddress; + _param_request.reg_size = 1; + uint16_t u16WriteValue_temp = u16WriteValue; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleCoils; + _param_request.reg_start = u16WriteAddress; + _param_request.reg_size = u16BitQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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) +{ + _param_request.slave_addr = _u8MBSlave; + _param_request.command = ku8MBWriteMultipleRegisters; + _param_request.reg_start = u16WriteAddress; + _param_request.reg_size = u16WriteQty; + return mbc_master_send_request(&_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 (0x0000..0xFFFF) +@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("ERROR", "Temporary unsupported command %d.", ku8MBReadWriteMultipleRegisters); + return ESP_FAIL; +} + +esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, + uint16_t u16ReadQty) +{ + ESP_LOGE("ERROR", "Temporarily unsupported command %d.", ku8MBReadWriteMultipleRegisters); + return ESP_FAIL; +} + +/* _____PRIVATE FUNCTIONS____________________________________________________ */ +/** +Modbus transaction engine. +Sequence: + - assemble Modbus Request Application Data Unit (ADU), + based on particular function called + - transmit request over selected serial port + - wait for/retrieve response + - evaluate/disassemble response + - return status (success/exception) + +@param u8MBFunction Modbus function (0x01..0xFF) +@return 0 on success; exception number on failure +*/ +// This helper temporarily not used +esp_err_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) +{ + ESP_LOGE("ERROR", "Temporary unsupported command %s %d.", __func__, u8MBFunction); + return ESP_FAIL; +} diff --git a/modbus_simple_master/main/ModbusMaster.h b/modbus_simple_master/main/ModbusMaster.h new file mode 100644 index 0000000..e6fca15 --- /dev/null +++ b/modbus_simple_master/main/ModbusMaster.h @@ -0,0 +1,113 @@ +/** +@file +Arduino library for communicating with Modbus slaves over RS232/485 (via RTU 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 +*/ +/* + + ModbusMaster.h - Arduino library for communicating with Modbus slaves + over RS232/485 (via RTU protocol). + + Library:: ModbusMaster + + Copyright:: 2009-2016 Doc Walker + + 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 ModbusMaster_h +#define ModbusMaster_h + +#define MASTER_CHECK(a, ret_val, str, ...) \ + if (!(a)) { \ + ESP_LOGE(TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return ret_val; \ + } + + +/** +@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 "mbcontroller.h" // include file for common Modbus types + + +/* _____UTILITY MACROS_______________________________________________________ */ + + +/* _____PROJECT INCLUDES_____________________________________________________ */ + + +/* _____CLASS DEFINITIONS____________________________________________________ */ + +class ModbusMaster +{ + public: + ModbusMaster(); + + esp_err_t begin(uint8_t, mb_communication_info_t*); + + 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 writeMultipleCoils(); + 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 slave (1..255) initialized in begin() + mb_communication_info_t _comm_opts; ///< Modbus communication options + mb_param_request_t _param_request; ///< Modbus request structure + + // 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] + + esp_err_t ModbusMasterTransaction(uint8_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/master.c b/modbus_simple_master/main/master.c new file mode 100644 index 0000000..34c085d --- /dev/null +++ b/modbus_simple_master/main/master.c @@ -0,0 +1,195 @@ +#include "mbcontroller.h" // for common Modbus defines +#include "string.h" +#include "esp_log.h" + +#define MASTER_MAX_CIDS 2 +#define MASTER_MAX_RETRY 100 +#define MASTER_PORT_NUM 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 MASTER_CHECK(a, ret_val, str, ...) \ + if (!(a)) { \ + ESP_LOGE(MASTER_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return (ret_val); \ + } + +#define STR(fieldname) ((const char*)( fieldname )) +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +// Enumeration of modbus slave addresses accessed by master device +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 +}; + +extern "C" { + +// 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, 2, 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, 2, 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])); + + +static esp_err_t read_modbus_parameter(uint16_t cid, uint16_t *par_data) +{ + const mb_parameter_descriptor_t* param_descriptor = NULL; + + esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + uint8_t type = 0; + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)par_data, &type); + if (err == ESP_OK) { + ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%04x) parameter read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint16_t*)par_data); + } + else + { + ESP_LOGE(MASTER_TAG, "Characteristic #%d %s (%s), parameter read fail.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units); + } + } + return err; +} + +static esp_err_t write_modbus_parameter(uint16_t cid, uint16_t *par_data) +{ + const mb_parameter_descriptor_t* param_descriptor = NULL; + + esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + uint8_t type = 0; // type of parameter from dictionary + err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)par_data, &type); + if (err == ESP_OK) { + ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%04x), write successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + *(uint16_t*)par_data); + } else { + ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + return err; +} + +// This is user function to read and write modbus holding registers +static void master_read_write_func(void *arg) +{ + esp_err_t err = ESP_OK; + uint16_t register_data = 0; + + ESP_LOGI(MASTER_TAG, "Start modbus test..."); + + for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY; retry++) { + // Simply read your register here CID_DEV_REG0 - one register from address 0 (see device_parameters) + err = read_modbus_parameter(CID_DEV_REG0, ®ister_data); + if (err == ESP_OK) { + // if read successful then increase value of the parameter + // Insert your modbus read_write register functionality here + register_data += 1; + err = write_modbus_parameter(CID_DEV_REG0, ®ister_data); + } + err = read_modbus_parameter(CID_DEV_REG1, ®ister_data); + if (err == ESP_OK) { + register_data += 1; + err = write_modbus_parameter(CID_DEV_REG1, ®ister_data); + } + } + ESP_LOGI(MASTER_TAG, "Modbus test is completed."); +} + +// Modbus master initialization +static esp_err_t master_init(void) +{ + // Initialize and start Modbus controller + mb_communication_info_t comm; + comm.port = MASTER_PORT_NUM; + comm.port = MASTER_PORT_NUM; + comm.mode = MB_MODE_RTU; + comm.baudrate = MASTER_SPEED; + comm.parity = MB_PARITY_NONE; + + void* master_handler = NULL; + + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_STATE, + "mb controller initialization fail."); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller initialization fail, returns(0x%x).", + (uint32_t)err); + err = mbc_master_setup((void*)&comm); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller setup fail, returns(0x%x).", + (uint32_t)err); + + // Set UART pin numbers + err = uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, + MB_UART_RTS_PIN, UART_PIN_NO_CHANGE); + + err = mbc_master_start(); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller start fail, returns(0x%x).", + (uint32_t)err); + + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err); + // Set driver mode to Half Duplex + err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + + vTaskDelay(5); + err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller set descriptor fail, returns(0x%x).", + (uint32_t)err); + ESP_LOGI(MASTER_TAG, "Modbus master stack initialized..."); + return err; +} + + +void app_main() +{ + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(master_init()); + vTaskDelay(10); + + // Write registers to predefined state + uint16_t register_data = 0x1234; + write_modbus_parameter(CID_DEV_REG0, ®ister_data); + register_data = 0x5678; + write_modbus_parameter(CID_DEV_REG1, ®ister_data); + + master_read_write_func(NULL); +} + + +} \ No newline at end of file diff --git a/modbus_simple_master/main/master.cpp b/modbus_simple_master/main/master.cpp new file mode 100644 index 0000000..e928628 --- /dev/null +++ b/modbus_simple_master/main/master.cpp @@ -0,0 +1,85 @@ +//#include "mbcontroller.h" // for common Modbus defines +#include "string.h" +#include "esp_log.h" + +#include "ModbusMaster.h" + +#define MASTER_MAX_CIDS 2 +#define MASTER_MAX_RETRY 100 +#define MASTER_PORT_NUM 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#define TAG "MB_MASTER_MAIN" +#define MB_SLAVE_SHORT_ADDRESS 1 +#define MB_RETRIES 10 + + +// Example code to read and write Modbus registers using CPP wrapper class +void app_main() +{ + ModbusMaster cModbusMaster; + + // Initialize and start Modbus controller + mb_communication_info_t comm; + + comm.port = MASTER_PORT_NUM; + comm.mode = MB_MODE_RTU; + comm.baudrate = MASTER_SPEED; + comm.parity = MB_PARITY_NONE; + + + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(cModbusMaster.begin(MB_SLAVE_SHORT_ADDRESS, &comm)); + + // Set UART pin numbers + uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, + MB_UART_RTS_PIN, UART_PIN_NO_CHANGE); + + // Set driver mode to Half Duplex + esp_err_t err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MASTER_CHECK((err == ESP_OK), ; , + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + + uint16_t reg_array[6] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; + uint16_t temp_coils = 0xFFFF; + + for (int i = 0; i < MB_RETRIES; i++) { + err = cModbusMaster.writeMultipleRegisters(0, 5, ®_array[0]); + MASTER_CHECK((err == ESP_OK), ; , + "Modbus Write error: (0x%x).", (uint32_t)err); + + memset(reg_array, 0x00, 10); + err = cModbusMaster.readHoldingRegisters(0, 5, ®_array[0]); + MASTER_CHECK((err == ESP_OK), ; , + "Modbus Read error: (0x%x).", (uint32_t)err); + + temp_coils ^= 0xAAAA; + + err = cModbusMaster.writeMultipleCoils(0, 10, &temp_coils); + MASTER_CHECK((err == ESP_OK), ; , + "Modbus Write Coils error: (0x%x).", (uint32_t)err); + + err = cModbusMaster.readCoils(0, 10, &temp_coils); + MASTER_CHECK((err == ESP_OK), ; , + "Modbus Read Coils error: (0x%x).", (uint32_t)err); + + ESP_LOGI("MB", "COILS: 0x%X", temp_coils); + + ESP_LOG_BUFFER_HEX_LEVEL("MB", reg_array, 10, ESP_LOG_INFO); + + } + ESP_LOGI("MODBUS", "Modbus Test completed."); +} + + +#ifdef __cplusplus +} +#endif From dfa6ee1de635e20e2716d19b8f68d04f02f37b37 Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn <37483886+alisitsyn@users.noreply.github.com> Date: Tue, 19 Oct 2021 21:44:54 +0200 Subject: [PATCH 2/5] add cpp slave class --- modbus_simple_slave/CMakeLists.txt | 8 + modbus_simple_slave/Makefile | 11 ++ modbus_simple_slave/README.md | 95 +++++++++++ modbus_simple_slave/main/CMakeLists.txt | 4 + modbus_simple_slave/main/Kconfig.projbuild | 75 +++++++++ modbus_simple_slave/main/ModbusSlave.cpp | 173 +++++++++++++++++++++ modbus_simple_slave/main/ModbusSlave.h | 94 +++++++++++ modbus_simple_slave/main/component.mk | 4 + modbus_simple_slave/main/slave.cpp | 125 +++++++++++++++ modbus_simple_slave/sdkconfig.defaults | 10 ++ 10 files changed, 599 insertions(+) create mode 100644 modbus_simple_slave/CMakeLists.txt create mode 100644 modbus_simple_slave/Makefile create mode 100644 modbus_simple_slave/README.md create mode 100644 modbus_simple_slave/main/CMakeLists.txt create mode 100644 modbus_simple_slave/main/Kconfig.projbuild create mode 100644 modbus_simple_slave/main/ModbusSlave.cpp create mode 100644 modbus_simple_slave/main/ModbusSlave.h create mode 100644 modbus_simple_slave/main/component.mk create mode 100644 modbus_simple_slave/main/slave.cpp create mode 100644 modbus_simple_slave/sdkconfig.defaults diff --git a/modbus_simple_slave/CMakeLists.txt b/modbus_simple_slave/CMakeLists.txt new file mode 100644 index 0000000..a941d4e --- /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/serial/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..6ffc4db --- /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..e722f86 --- /dev/null +++ b/modbus_simple_slave/main/Kconfig.projbuild @@ -0,0 +1,75 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 + default 2 if IDF_TARGET_ESP32 + range 0 1 if IDF_TARGET_ESP32S2 + default 1 if IDF_TARGET_ESP32S2 + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 22 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + default 19 if IDF_TARGET_ESP32S2 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_TXD + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + default 23 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + default 20 if IDF_TARGET_ESP32S2 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_RTS + int "UART RTS pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 + default 18 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + + config MB_SLAVE_ADDR + int "Modbus slave address" + range 1 127 + default 1 + help + This is the Modbus slave address in the network. + It is used to organize Modbus network with several slaves connected into the same segment. + + +endmenu diff --git a/modbus_simple_slave/main/ModbusSlave.cpp b/modbus_simple_slave/main/ModbusSlave.cpp new file mode 100644 index 0000000..5876e0c --- /dev/null +++ b/modbus_simple_slave/main/ModbusSlave.cpp @@ -0,0 +1,173 @@ +/** +@file +The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/485 (via RTU protocol). +*/ + +/* _____PROJECT INCLUDES_____________________________________________________ */ +#include "ModbusSlave.h" + +/* _____GLOBAL VARIABLES_____________________________________________________ */ + + +/* _____PUBLIC FUNCTIONS_____________________________________________________ */ +/** +Constructor. + +Creates class object; initialize it using ModbusSlave::begin(). + +@ingroup setup +*/ +ModbusSlave::ModbusSlave(void) +{ + // Do not perform initialization in constructor. +} + +/** +Initialize class object. + +Assigns the Modbus slave ID and serial communication parameters. +Call once class has been instantiated. + +@param pxCommOpts - pointer to communication options structure +@ingroup setup +*/ +esp_err_t ModbusSlave::begin(mb_communication_info_t* pxCommOpts) +{ + _u8MBSlave = pxCommOpts->slave_addr; + _comm_opts = *pxCommOpts; + esp_err_t err = ESP_FAIL; + + // Initialization of Modbus controller + err = mbc_slave_init(MB_PORT_SERIAL_SLAVE, &_slave_handler); + SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller init fail, returns(0x%x).", + (uint32_t)err); + // Setup communication parameters and start stack + err = mbc_slave_setup((void*)&_comm_opts); + SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller setup fail, returns(0x%x).", + (uint32_t)err); + err = mbc_slave_start(); + SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller start fail, returns(0x%x).", + (uint32_t)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); + + return mbc_slave_set_descriptor(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_event_group_t event = mbc_slave_check_event(EventMask); + + ESP_ERROR_CHECK(mbc_slave_get_param_info(&_reg_info, MODBUS_PAR_INFO_GET_TOUT)); + return event; +} + +/** +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", "Bank accessed type %d, offset %d, par_info.mb_off %d, par_info.size %d, reg_mask %d, par_info.type %d", reg_type, offset, par_info.mb_offset, par_info.size, 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; + + return result; +} + +ModbusSlave::~ModbusSlave(void) +{ + ESP_ERROR_CHECK(mbc_slave_destroy()); +} + +/* _____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..d7ad614 --- /dev/null +++ b/modbus_simple_slave/main/ModbusSlave.h @@ -0,0 +1,94 @@ +/** +@file +The wrapper class for Modbus Slave for communication over RS232/485 (via RTU 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) + +#define SLAVE_CHECK(a, ret_val, str, ...) \ + if (!(a)) { \ + ESP_LOGE(TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return ret_val; \ + } + +/* _____STANDARD INCLUDES____________________________________________________ */ +#include +#include "esp_log.h" + +#include "mbcontroller.h" // include file for common Modbus types + +#define TAG "ModbusSlave" + + +/* _____UTILITY MACROS_______________________________________________________ */ + + +/* _____PROJECT INCLUDES_____________________________________________________ */ + + +/* _____CLASS DEFINITIONS____________________________________________________ */ + +class ModbusSlave +{ + public: + ModbusSlave(void); + ~ModbusSlave(void); + + esp_err_t begin(mb_communication_info_t*); + 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* _slave_handler; ///< Modbus slave handler + 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/slave.cpp b/modbus_simple_slave/main/slave.cpp new file mode 100644 index 0000000..a811025 --- /dev/null +++ b/modbus_simple_slave/main/slave.cpp @@ -0,0 +1,125 @@ +/* FreeModbus Slave Example ESP32 + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include "esp_err.h" +#include "mbcontroller.h" // for mbcontroller defines and api +#include "esp_log.h" // for log_write +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ModbusSlave.h" + +#define MB_RETRIES (100) +#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. + +#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 *SLAVE_TAG = "SLAVE_TEST"; + +static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED; + +// 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) +{ + // Create the class instance + ModbusSlave* pModbusSlave = new ModbusSlave(); + + // Initialize and start Modbus controller + mb_communication_info_t comm; + + // Setup communication parameters and start stack + #if CONFIG_MB_COMM_MODE_ASCII + comm.mode = MB_MODE_ASCII, + #elif CONFIG_MB_COMM_MODE_RTU + comm.mode = MB_MODE_RTU, + #endif + + comm.slave_addr = MB_SLAVE_ADDR; + comm.port = MB_PORT_NUM; + comm.baudrate = MB_DEV_SPEED; + comm.parity = MB_PARITY_NONE; + + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(pModbusSlave->begin(&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); + SLAVE_CHECK((err == ESP_OK), ; , + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)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 }; + size_t reg_size = (sizeof(hold_array) / sizeof(hold_array[0])); + + // Add registers below into register bank + pModbusSlave->addRegisterBank(40003, &hold_array[0], reg_size); + pModbusSlave->addRegisterBank(30005, &input_array[0], reg_size); + + 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); + + // 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_LOGI(SLAVE_TAG, "Incorrect modbus access type: %d.", event); + } + + // Get register access information + mb_param_info_t info = pModbusSlave->getRegisterInfo(); + + // Check if the selected holding register area is accessed by Master + if (pModbusSlave->isBankAccessed(40003, reg_size)) { + ESP_LOGI(SLAVE_TAG, "Holding bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); + portENTER_CRITICAL(¶m_lock); + // Reinitialize the accessed register values with random numbers + esp_fill_random(info.address, (info.size << 1)); + portEXIT_CRITICAL(¶m_lock); + } + + // Check if the selected input register area is accessed by Master + if (pModbusSlave->isBankAccessed(30005, reg_size)) { + ESP_LOGI(SLAVE_TAG, "Input bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); + portENTER_CRITICAL(¶m_lock); + // Reinitialize the accessed register values with random numbers + esp_fill_random(info.address, (info.size << 1)); + portEXIT_CRITICAL(¶m_lock); + } + + } + ESP_LOGI("MODBUS", "Modbus Test completed."); + delete pModbusSlave; +} + +#ifdef __cplusplus +} +#endif diff --git a/modbus_simple_slave/sdkconfig.defaults b/modbus_simple_slave/sdkconfig.defaults new file mode 100644 index 0000000..334a421 --- /dev/null +++ b/modbus_simple_slave/sdkconfig.defaults @@ -0,0 +1,10 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_SLAVE_ADDR=1 +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_GROUP=0 +CONFIG_FMB_TIMER_INDEX=0 +CONFIG_FMB_TIMER_ISR_IN_IRAM=y From e4784753e13ec265e5567cd6f95a8ac50dfa60b3 Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn <37483886+alisitsyn@users.noreply.github.com> Date: Thu, 21 Oct 2021 21:10:04 +0200 Subject: [PATCH 3/5] fix for addressing for master and slave fix addressing for master and slave with PLC mode apdate examples to work with each other --- modbus_simple_master/main/ModbusMaster.cpp | 167 ++++++++++++++------- modbus_simple_master/main/ModbusMaster.h | 50 +++--- modbus_simple_master/main/master.cpp | 15 +- modbus_simple_slave/main/ModbusSlave.cpp | 14 +- modbus_simple_slave/main/slave.cpp | 24 ++- 5 files changed, 173 insertions(+), 97 deletions(-) diff --git a/modbus_simple_master/main/ModbusMaster.cpp b/modbus_simple_master/main/ModbusMaster.cpp index bfbb24d..125e9d3 100644 --- a/modbus_simple_master/main/ModbusMaster.cpp +++ b/modbus_simple_master/main/ModbusMaster.cpp @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2016-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). @@ -36,23 +42,23 @@ Call once class has been instantiated, typically within setup(). */ esp_err_t ModbusMaster::begin(uint8_t slave, mb_communication_info_t* commOpts) { - _u8MBSlave = slave; - _comm_opts = *commOpts; - void* master_handler = NULL; + _u8MBSlave = slave; + _comm_opts = *commOpts; + void* master_handler = NULL; - esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); - MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_ARG , "mb controller initialization fail."); - MASTER_CHECK((err == ESP_OK), err , "mb controller initialization fail, returns(0x%x).", (uint32_t)err); + MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_ARG , "mb controller initialization fail."); + MASTER_CHECK((err == ESP_OK), err , "mb controller initialization fail, returns(0x%x).", (uint32_t)err); - err = mbc_master_setup((void*)&_comm_opts); - MASTER_CHECK((err == ESP_OK), err , "mb controller setup fail, returns(0x%x).", (uint32_t)err); + err = mbc_master_setup((void*)&_comm_opts); + MASTER_CHECK((err == ESP_OK), err , "mb controller setup fail, returns(0x%x).", (uint32_t)err); - err = mbc_master_start(); - MASTER_CHECK((err == ESP_OK), err , "mb controller start fail, returns(0x%x).", (uint32_t)err); + err = mbc_master_start(); + MASTER_CHECK((err == ESP_OK), err , "mb controller start fail, returns(0x%x).", (uint32_t)err); - ESP_LOGI(TAG, "Modbus master stack initialized..."); - return err; + ESP_LOGI(TAG, "Modbus master stack initialized..."); + return err; } /** @@ -73,7 +79,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -81,11 +87,17 @@ order end of the word). */ esp_err_t ModbusMaster::readCoils(uint16_t u16ReadAddress, uint16_t u16BitQty, void* pvBufPtr) { - _param_request.slave_addr = _u8MBSlave; - _param_request.command = ku8MBReadCoils; - _param_request.reg_start = u16ReadAddress; - _param_request.reg_size = u16BitQty; - return mbc_master_send_request(&_param_request, pvBufPtr); + mb_param_type_t reg_type; + uint16_t reg_off = 0; + + getRegType(u16ReadAddress, ®_type, ®_off); + MASTER_CHECK((reg_type == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , "Incorrect 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(&_param_request, pvBufPtr); } /** @@ -106,7 +118,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -114,9 +126,15 @@ order end of the word). */ esp_err_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress, uint16_t u16BitQty, void* pvBufPtr) { + mb_param_type_t reg_type; + uint16_t reg_off = 0; + + getRegType(u16ReadAddress, ®_type, ®_off); + MASTER_CHECK((reg_type == MB_PARAM_DISCRETE), ESP_ERR_INVALID_ARG , "Incorrect address."); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBReadDiscreteInputs; - _param_request.reg_start = u16ReadAddress; + _param_request.reg_start = (u16ReadAddress - reg_off); _param_request.reg_size = u16BitQty; return mbc_master_send_request(&_param_request, pvBufPtr); } @@ -133,7 +151,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -141,9 +159,16 @@ register. */ esp_err_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, uint16_t u16ReadQty, void* pvBufPtr) { + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + getRegType(u16ReadAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , + "Incorrect register address %d.", u16ReadAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBReadHoldingRegisters; - _param_request.reg_start = u16ReadAddress; + _param_request.reg_start = (u16ReadAddress - u16Regbase); _param_request.reg_size = u16ReadQty; return mbc_master_send_request(&_param_request, pvBufPtr); } @@ -160,7 +185,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -168,9 +193,16 @@ register. */ esp_err_t ModbusMaster::readInputRegisters(uint16_t u16ReadAddress, uint8_t u16ReadQty, void* pvBufPtr) { + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + getRegType(u16ReadAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_INPUT), ESP_ERR_INVALID_ARG , + "Incorrect register address %d.", u16ReadAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBReadInputRegisters; - _param_request.reg_start = u16ReadAddress; + _param_request.reg_start = (u16ReadAddress - u16Regbase); _param_request.reg_size = u16ReadQty; return mbc_master_send_request(&_param_request, pvBufPtr); } @@ -185,19 +217,26 @@ 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 (0x0000..0xFFFF) +@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; + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , + "Incorrect register address %d.", u16WriteAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBWriteSingleCoil; - _param_request.reg_start = u16WriteAddress; + _param_request.reg_start = (u16WriteAddress - u16Regbase); _param_request.reg_size = 1; - uint16_t u16State_temp = (u8State ? 0xFF00 : 0x0000); - return mbc_master_send_request(&_param_request, &u16State_temp); + uint16_t u16StateTemp = (u8State ? 0xFF00 : 0x0000); + return mbc_master_send_request(&_param_request, &u16StateTemp); } @@ -208,7 +247,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -216,9 +255,16 @@ written. Registers are addressed starting at zero. */ esp_err_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, uint16_t u16WriteValue) { + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , + "Incorrect register address %d.", u16WriteAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBWriteMultipleRegisters; - _param_request.reg_start = u16WriteAddress; + _param_request.reg_start = (u16WriteAddress - u16Regbase); _param_request.reg_size = 1; uint16_t u16WriteValue_temp = u16WriteValue; return mbc_master_send_request(&_param_request, &u16WriteValue_temp); @@ -236,7 +282,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -244,9 +290,15 @@ corresponding output to be ON. A logical '0' requests it to be OFF. */ esp_err_t ModbusMaster::writeMultipleCoils(uint16_t u16WriteAddress, uint16_t u16BitQty, void* pvBufPtr) { + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , "Incorrect register address %d.", u16WriteAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBWriteMultipleCoils; - _param_request.reg_start = u16WriteAddress; + _param_request.reg_start = (u16WriteAddress - u16Regbase); _param_request.reg_size = u16BitQty; return mbc_master_send_request(&_param_request, pvBufPtr); } @@ -260,7 +312,7 @@ 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 (0x0000..0xFFFF) +@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 @@ -268,9 +320,16 @@ is packed as one word per register. */ esp_err_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress, uint16_t u16WriteQty, void* pvBufPtr) { + mb_param_type_t regType; + uint16_t u16Regbase = 0; + + getRegType(u16WriteAddress, ®Type, &u16Regbase); + MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , + "Incorrect register address %d.", u16WriteAddress); + _param_request.slave_addr = _u8MBSlave; _param_request.command = ku8MBWriteMultipleRegisters; - _param_request.reg_start = u16WriteAddress; + _param_request.reg_start = (u16WriteAddress - u16Regbase); _param_request.reg_size = u16WriteQty; return mbc_master_send_request(&_param_request, pvBufPtr); } @@ -288,7 +347,7 @@ 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 (0x0000..0xFFFF) +@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) @@ -309,23 +368,27 @@ esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, return ESP_FAIL; } +ModbusMaster::~ModbusMaster(void) +{ + ESP_ERROR_CHECK(mbc_master_destroy()); +} + /* _____PRIVATE FUNCTIONS____________________________________________________ */ -/** -Modbus transaction engine. -Sequence: - - assemble Modbus Request Application Data Unit (ADU), - based on particular function called - - transmit request over selected serial port - - wait for/retrieve response - - evaluate/disassemble response - - return status (success/exception) - -@param u8MBFunction Modbus function (0x01..0xFF) -@return 0 on success; exception number on failure -*/ -// This helper temporarily not used -esp_err_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction) + +void ModbusMaster::getRegType(uint16_t u16RegAddress, mb_param_type_t* regType, uint16_t* u16BaseAddr) { - ESP_LOGE("ERROR", "Temporary unsupported command %s %d.", __func__, u8MBFunction); - return ESP_FAIL; + 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 index e6fca15..b7a62ce 100644 --- a/modbus_simple_master/main/ModbusMaster.h +++ b/modbus_simple_master/main/ModbusMaster.h @@ -1,6 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + /** @file -Arduino library for communicating with Modbus slaves over RS232/485 (via RTU protocol). +Wrapper class for ESP_Modbus API to access Modbus slaves over RS232/485 (via RTU protocol). @defgroup setup ModbusMaster Object Instantiation/Initialization @defgroup buffer ModbusMaster Buffer Management @@ -8,30 +14,7 @@ Arduino library for communicating with Modbus slaves over RS232/485 (via RTU pro @defgroup register Modbus Function Codes for Holding/Input Registers @defgroup constant Modbus Function Codes, Exception Codes */ -/* - - ModbusMaster.h - Arduino library for communicating with Modbus slaves - over RS232/485 (via RTU protocol). - - Library:: ModbusMaster - - Copyright:: 2009-2016 Doc Walker - - 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 ModbusMaster_h #define ModbusMaster_h @@ -40,7 +23,16 @@ Arduino library for communicating with Modbus slaves over RS232/485 (via RTU pro ESP_LOGE(TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ return ret_val; \ } - + +// 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 /** @def __MODBUSMASTER_DEBUG__ (0) @@ -68,7 +60,8 @@ class ModbusMaster { public: ModbusMaster(); - + ~ModbusMaster(); + esp_err_t begin(uint8_t, mb_communication_info_t*); esp_err_t readCoils(uint16_t, uint16_t, void*); @@ -84,7 +77,7 @@ class ModbusMaster //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 slave (1..255) initialized in begin() mb_communication_info_t _comm_opts; ///< Modbus communication options @@ -106,8 +99,7 @@ class ModbusMaster // Modbus timeout [milliseconds] static const uint16_t ku16MBResponseTimeout = 2000; ///< Modbus timeout [milliseconds] - - esp_err_t ModbusMasterTransaction(uint8_t); + void getRegType(uint16_t, mb_param_type_t*, uint16_t*); }; #endif diff --git a/modbus_simple_master/main/master.cpp b/modbus_simple_master/main/master.cpp index e928628..2a0e810 100644 --- a/modbus_simple_master/main/master.cpp +++ b/modbus_simple_master/main/master.cpp @@ -19,13 +19,13 @@ extern "C" { #define TAG "MB_MASTER_MAIN" #define MB_SLAVE_SHORT_ADDRESS 1 -#define MB_RETRIES 10 +#define MB_RETRIES 50 // Example code to read and write Modbus registers using CPP wrapper class void app_main() { - ModbusMaster cModbusMaster; + ModbusMaster* pModbusMaster = new ModbusMaster(); // Initialize and start Modbus controller mb_communication_info_t comm; @@ -37,7 +37,7 @@ void app_main() // Initialization of device peripheral and objects - ESP_ERROR_CHECK(cModbusMaster.begin(MB_SLAVE_SHORT_ADDRESS, &comm)); + ESP_ERROR_CHECK(pModbusMaster->begin(MB_SLAVE_SHORT_ADDRESS, &comm)); // Set UART pin numbers uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, @@ -52,22 +52,22 @@ void app_main() uint16_t temp_coils = 0xFFFF; for (int i = 0; i < MB_RETRIES; i++) { - err = cModbusMaster.writeMultipleRegisters(0, 5, ®_array[0]); + err = pModbusMaster->writeMultipleRegisters(40001, 5, ®_array[0]); MASTER_CHECK((err == ESP_OK), ; , "Modbus Write error: (0x%x).", (uint32_t)err); memset(reg_array, 0x00, 10); - err = cModbusMaster.readHoldingRegisters(0, 5, ®_array[0]); + err = pModbusMaster->readHoldingRegisters(40001, 5, ®_array[0]); MASTER_CHECK((err == ESP_OK), ; , "Modbus Read error: (0x%x).", (uint32_t)err); temp_coils ^= 0xAAAA; - err = cModbusMaster.writeMultipleCoils(0, 10, &temp_coils); + err = pModbusMaster->writeMultipleCoils(00001, 10, &temp_coils); MASTER_CHECK((err == ESP_OK), ; , "Modbus Write Coils error: (0x%x).", (uint32_t)err); - err = cModbusMaster.readCoils(0, 10, &temp_coils); + err = pModbusMaster->readCoils(00001, 10, &temp_coils); MASTER_CHECK((err == ESP_OK), ; , "Modbus Read Coils error: (0x%x).", (uint32_t)err); @@ -77,6 +77,7 @@ void app_main() } ESP_LOGI("MODBUS", "Modbus Test completed."); + delete pModbusMaster; } diff --git a/modbus_simple_slave/main/ModbusSlave.cpp b/modbus_simple_slave/main/ModbusSlave.cpp index 5876e0c..3bbfabc 100644 --- a/modbus_simple_slave/main/ModbusSlave.cpp +++ b/modbus_simple_slave/main/ModbusSlave.cpp @@ -1,3 +1,9 @@ +/* + * 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). @@ -114,11 +120,13 @@ bool ModbusSlave::isBankAccessed(uint16_t regAddress, uint16_t regAreaSize) mb_event_group_t reg_mask = getRegEventMask(reg_type); uint16_t offset = regAddress - base_off; - //ESP_LOGI("TEST", "Bank accessed type %d, offset %d, par_info.mb_off %d, par_info.size %d, reg_mask %d, par_info.type %d", reg_type, offset, par_info.mb_offset, par_info.size, reg_mask, par_info.type); + + //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; - + ((offset + regAreaSize) >= (par_info.mb_offset + par_info.size))) ? true : false; + //ESP_LOGI("TEST", "Bank accessed type %d, offset %d, par_info.mb_off %d, par_info.size %d, reg_mask %d, par_info.type %d, result=%d", + reg_type, offset, par_info.mb_offset, par_info.size, reg_mask, par_info.type, (uint8_t)result); return result; } diff --git a/modbus_simple_slave/main/slave.cpp b/modbus_simple_slave/main/slave.cpp index a811025..40f0d1b 100644 --- a/modbus_simple_slave/main/slave.cpp +++ b/modbus_simple_slave/main/slave.cpp @@ -74,19 +74,23 @@ void app_main(void) SLAVE_CHECK((err == ESP_OK), ; , "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)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 register bank - pModbusSlave->addRegisterBank(40003, &hold_array[0], reg_size); - pModbusSlave->addRegisterBank(30005, &input_array[0], reg_size); + // Add registers below into assiciated register bank (use PLC based address) + // These registers 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], reg_size); + pModbusSlave->addRegisterBank(1, &coil_array[0], (reg_size * 2 * 8)); 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); @@ -98,7 +102,7 @@ void app_main(void) mb_param_info_t info = pModbusSlave->getRegisterInfo(); // Check if the selected holding register area is accessed by Master - if (pModbusSlave->isBankAccessed(40003, reg_size)) { + if (pModbusSlave->isBankAccessed(40001, reg_size)) { ESP_LOGI(SLAVE_TAG, "Holding bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); portENTER_CRITICAL(¶m_lock); // Reinitialize the accessed register values with random numbers @@ -107,7 +111,7 @@ void app_main(void) } // Check if the selected input register area is accessed by Master - if (pModbusSlave->isBankAccessed(30005, reg_size)) { + if (pModbusSlave->isBankAccessed(30001, reg_size)) { ESP_LOGI(SLAVE_TAG, "Input bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); portENTER_CRITICAL(¶m_lock); // Reinitialize the accessed register values with random numbers @@ -115,6 +119,14 @@ void app_main(void) portEXIT_CRITICAL(¶m_lock); } + if (pModbusSlave->isBankAccessed(1, 8)) { + ESP_LOGI(SLAVE_TAG, "Cols address offset: %d, size %d is accessed.", info.mb_offset, info.size); + portENTER_CRITICAL(¶m_lock); + // Reinitialize the accessed register values with random numbers + esp_fill_random(info.address, (info.size >> 3)); + portEXIT_CRITICAL(¶m_lock); + } + vTaskDelay(1); } ESP_LOGI("MODBUS", "Modbus Test completed."); delete pModbusSlave; From 50a7da4741cc13b59bea1a2d51c819275e02ddb2 Mon Sep 17 00:00:00 2001 From: aleks Date: Thu, 6 Feb 2025 17:06:58 +0100 Subject: [PATCH 4/5] fix master, slave cpp classes and examples for v2 --- modbus_simple_master/main/Kconfig.projbuild | 83 +++++++++ modbus_simple_master/main/ModbusMaster.cpp | 138 +++++++++----- modbus_simple_master/main/ModbusMaster.h | 46 ++--- modbus_simple_master/main/idf_component.yml | 6 + modbus_simple_master/main/master.c | 195 -------------------- modbus_simple_master/main/master.cpp | 134 ++++++++------ modbus_simple_master/sdkconfig.defaults | 8 + modbus_simple_slave/CMakeLists.txt | 2 +- modbus_simple_slave/main/CMakeLists.txt | 2 +- modbus_simple_slave/main/Kconfig.projbuild | 47 +++-- modbus_simple_slave/main/ModbusSlave.cpp | 75 ++++---- modbus_simple_slave/main/ModbusSlave.h | 28 +-- modbus_simple_slave/main/idf_component.yml | 4 + modbus_simple_slave/main/slave.cpp | 103 +++++------ modbus_simple_slave/sdkconfig.defaults | 5 +- 15 files changed, 432 insertions(+), 444 deletions(-) create mode 100644 modbus_simple_master/main/Kconfig.projbuild create mode 100644 modbus_simple_master/main/idf_component.yml delete mode 100644 modbus_simple_master/main/master.c create mode 100644 modbus_simple_master/sdkconfig.defaults create mode 100644 modbus_simple_slave/main/idf_component.yml 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 index 125e9d3..07e0187 100644 --- a/modbus_simple_master/main/ModbusMaster.cpp +++ b/modbus_simple_master/main/ModbusMaster.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021 - 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,55 +9,73 @@ The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/485 (via RTU protocol). */ -#define TAG "MB_MASTER" - /* _____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(void) +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 class object. +Initialize Master class object to interract with defined slave UID. -Assigns the Modbus slave ID and serial port. -Call once class has been instantiated, typically within setup(). +Assigns the Modbus slave ID. +Call once class has been instantiated, typically within start of communication. @param slave Modbus slave ID (1..247) -@param pxCommOpts - pointer to communication options structure @ingroup setup */ -esp_err_t ModbusMaster::begin(uint8_t slave, mb_communication_info_t* commOpts) +esp_err_t ModbusMaster::begin(uint8_t slave) { _u8MBSlave = slave; - _comm_opts = *commOpts; - void* master_handler = NULL; - - esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); - - MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_ARG , "mb controller initialization fail."); - MASTER_CHECK((err == ESP_OK), err , "mb controller initialization fail, returns(0x%x).", (uint32_t)err); + 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; +} - err = mbc_master_setup((void*)&_comm_opts); - MASTER_CHECK((err == ESP_OK), err , "mb controller setup fail, returns(0x%x).", (uint32_t)err); +/** +Stops Modbus Master object - err = mbc_master_start(); - MASTER_CHECK((err == ESP_OK), err , "mb controller start fail, returns(0x%x).", (uint32_t)err); +Assigns the Modbus slave ID and serial port. +Call once class has been instantiated, typically within setup(). - ESP_LOGI(TAG, "Modbus master stack initialized..."); +@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; } @@ -89,15 +107,18 @@ esp_err_t ModbusMaster::readCoils(uint16_t u16ReadAddress, uint16_t u16BitQty, v { 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); - MASTER_CHECK((reg_type == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , "Incorrect address."); + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } /** @@ -129,14 +150,18 @@ esp_err_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress, uint16_t u16 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); - MASTER_CHECK((reg_type == MB_PARAM_DISCRETE), ESP_ERR_INVALID_ARG , "Incorrect address."); + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } @@ -162,15 +187,18 @@ esp_err_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress, uint16_t u 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); - MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } @@ -196,15 +224,18 @@ esp_err_t ModbusMaster::readInputRegisters(uint16_t u16ReadAddress, uint8_t u16R 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); - MASTER_CHECK((regType == MB_PARAM_INPUT), ESP_ERR_INVALID_ARG , + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } @@ -227,16 +258,19 @@ esp_err_t ModbusMaster::writeSingleCoil(uint16_t u16WriteAddress, uint8_t u8Stat 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); - MASTER_CHECK((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , - "Incorrect register address %d.", u16WriteAddress); + 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(&_param_request, &u16StateTemp); + return mbc_master_send_request(_pmaster_handle, &_param_request, &u16StateTemp); } @@ -258,16 +292,19 @@ esp_err_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress, uint16_t u 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); - MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , - "Incorrect register address %d.", u16WriteAddress); + 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(&_param_request, &u16WriteValue_temp); + return mbc_master_send_request(_pmaster_handle, &_param_request, &u16WriteValue_temp); } @@ -293,14 +330,18 @@ esp_err_t ModbusMaster::writeMultipleCoils(uint16_t u16WriteAddress, uint16_t u1 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); - MASTER_CHECK((regType == MB_PARAM_COIL), ESP_ERR_INVALID_ARG , "Incorrect register address %d.", u16WriteAddress); + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } /** @@ -323,15 +364,18 @@ esp_err_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress, uint16_ 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); - MASTER_CHECK((regType == MB_PARAM_HOLDING), ESP_ERR_INVALID_ARG , - "Incorrect register address %d.", u16WriteAddress); + 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(&_param_request, pvBufPtr); + return mbc_master_send_request(_pmaster_handle, &_param_request, pvBufPtr); } /** @@ -357,20 +401,24 @@ buffer. esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, uint16_t u16ReadQty, uint16_t u16WriteAddress, uint16_t u16WriteQty) { - ESP_LOGE("ERROR", "Temporary unsupported command %d.", ku8MBReadWriteMultipleRegisters); + ESP_LOGE(TAG, "Temporary unsupported command %d.", ku8MBReadWriteMultipleRegisters); return ESP_FAIL; } esp_err_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress, uint16_t u16ReadQty) { - ESP_LOGE("ERROR", "Temporarily unsupported command %d.", ku8MBReadWriteMultipleRegisters); + ESP_LOGE(TAG, "Temporarily unsupported command %d.", ku8MBReadWriteMultipleRegisters); return ESP_FAIL; } ModbusMaster::~ModbusMaster(void) { - ESP_ERROR_CHECK(mbc_master_destroy()); + 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____________________________________________________ */ diff --git a/modbus_simple_master/main/ModbusMaster.h b/modbus_simple_master/main/ModbusMaster.h index b7a62ce..8f20942 100644 --- a/modbus_simple_master/main/ModbusMaster.h +++ b/modbus_simple_master/main/ModbusMaster.h @@ -1,12 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * 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 protocol). +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 @@ -18,22 +18,6 @@ Wrapper class for ESP_Modbus API to access Modbus slaves over RS232/485 (via RTU #ifndef ModbusMaster_h #define ModbusMaster_h -#define MASTER_CHECK(a, ret_val, str, ...) \ - if (!(a)) { \ - ESP_LOGE(TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return ret_val; \ - } - -// 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 - /** @def __MODBUSMASTER_DEBUG__ (0) Set to 1 to enable debugging features within class: @@ -44,25 +28,33 @@ Set to 1 to enable debugging features within class: /* _____STANDARD INCLUDES____________________________________________________ */ #include #include "esp_log.h" - -#include "mbcontroller.h" // include file for common Modbus types - +#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(); - ~ModbusMaster(); + ModbusMaster(mb_communication_info_t *); + virtual ~ModbusMaster(); - esp_err_t begin(uint8_t, mb_communication_info_t*); + 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*); @@ -71,7 +63,6 @@ class ModbusMaster 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 writeMultipleCoils(); esp_err_t writeMultipleRegisters(uint16_t, uint16_t, void*); //esp_err_t writeMultipleRegisters(); //esp_err_t maskWriteRegister(uint16_t, uint16_t, uint16_t); @@ -79,9 +70,10 @@ class ModbusMaster esp_err_t readWriteMultipleRegisters(uint16_t, uint16_t); private: - uint8_t _u8MBSlave; ///< Modbus slave (1..255) initialized in begin() + 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 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.c b/modbus_simple_master/main/master.c deleted file mode 100644 index 34c085d..0000000 --- a/modbus_simple_master/main/master.c +++ /dev/null @@ -1,195 +0,0 @@ -#include "mbcontroller.h" // for common Modbus defines -#include "string.h" -#include "esp_log.h" - -#define MASTER_MAX_CIDS 2 -#define MASTER_MAX_RETRY 100 -#define MASTER_PORT_NUM 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 MASTER_CHECK(a, ret_val, str, ...) \ - if (!(a)) { \ - ESP_LOGE(MASTER_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return (ret_val); \ - } - -#define STR(fieldname) ((const char*)( fieldname )) -#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } - -// Enumeration of modbus slave addresses accessed by master device -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 -}; - -extern "C" { - -// 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, 2, 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, 2, 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])); - - -static esp_err_t read_modbus_parameter(uint16_t cid, uint16_t *par_data) -{ - const mb_parameter_descriptor_t* param_descriptor = NULL; - - esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); - if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { - uint8_t type = 0; - err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, - (uint8_t*)par_data, &type); - if (err == ESP_OK) { - ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%04x) parameter read successful.", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (char*)param_descriptor->param_units, - *(uint16_t*)par_data); - } - else - { - ESP_LOGE(MASTER_TAG, "Characteristic #%d %s (%s), parameter read fail.", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (char*)param_descriptor->param_units); - } - } - return err; -} - -static esp_err_t write_modbus_parameter(uint16_t cid, uint16_t *par_data) -{ - const mb_parameter_descriptor_t* param_descriptor = NULL; - - esp_err_t err = mbc_master_get_cid_info(cid, ¶m_descriptor); - if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { - uint8_t type = 0; // type of parameter from dictionary - err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key, - (uint8_t*)par_data, &type); - if (err == ESP_OK) { - ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = (0x%04x), write successful.", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (char*)param_descriptor->param_units, - *(uint16_t*)par_data); - } else { - ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) write fail, err = 0x%x (%s).", - param_descriptor->cid, - (char*)param_descriptor->param_key, - (int)err, - (char*)esp_err_to_name(err)); - } - } - return err; -} - -// This is user function to read and write modbus holding registers -static void master_read_write_func(void *arg) -{ - esp_err_t err = ESP_OK; - uint16_t register_data = 0; - - ESP_LOGI(MASTER_TAG, "Start modbus test..."); - - for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY; retry++) { - // Simply read your register here CID_DEV_REG0 - one register from address 0 (see device_parameters) - err = read_modbus_parameter(CID_DEV_REG0, ®ister_data); - if (err == ESP_OK) { - // if read successful then increase value of the parameter - // Insert your modbus read_write register functionality here - register_data += 1; - err = write_modbus_parameter(CID_DEV_REG0, ®ister_data); - } - err = read_modbus_parameter(CID_DEV_REG1, ®ister_data); - if (err == ESP_OK) { - register_data += 1; - err = write_modbus_parameter(CID_DEV_REG1, ®ister_data); - } - } - ESP_LOGI(MASTER_TAG, "Modbus test is completed."); -} - -// Modbus master initialization -static esp_err_t master_init(void) -{ - // Initialize and start Modbus controller - mb_communication_info_t comm; - comm.port = MASTER_PORT_NUM; - comm.port = MASTER_PORT_NUM; - comm.mode = MB_MODE_RTU; - comm.baudrate = MASTER_SPEED; - comm.parity = MB_PARITY_NONE; - - void* master_handler = NULL; - - esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); - MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_STATE, - "mb controller initialization fail."); - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller initialization fail, returns(0x%x).", - (uint32_t)err); - err = mbc_master_setup((void*)&comm); - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller setup fail, returns(0x%x).", - (uint32_t)err); - - // Set UART pin numbers - err = uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, - MB_UART_RTS_PIN, UART_PIN_NO_CHANGE); - - err = mbc_master_start(); - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller start fail, returns(0x%x).", - (uint32_t)err); - - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err); - // Set driver mode to Half Duplex - err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); - - vTaskDelay(5); - err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); - MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller set descriptor fail, returns(0x%x).", - (uint32_t)err); - ESP_LOGI(MASTER_TAG, "Modbus master stack initialized..."); - return err; -} - - -void app_main() -{ - // Initialization of device peripheral and objects - ESP_ERROR_CHECK(master_init()); - vTaskDelay(10); - - // Write registers to predefined state - uint16_t register_data = 0x1234; - write_modbus_parameter(CID_DEV_REG0, ®ister_data); - register_data = 0x5678; - write_modbus_parameter(CID_DEV_REG1, ®ister_data); - - master_read_write_func(NULL); -} - - -} \ No newline at end of file diff --git a/modbus_simple_master/main/master.cpp b/modbus_simple_master/main/master.cpp index 2a0e810..1e485c3 100644 --- a/modbus_simple_master/main/master.cpp +++ b/modbus_simple_master/main/master.cpp @@ -1,86 +1,114 @@ -//#include "mbcontroller.h" // for common Modbus defines +/* + * 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_MAX_CIDS 2 -#define MASTER_MAX_RETRY 100 -#define MASTER_PORT_NUM 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 -#ifdef __cplusplus -extern "C" { -#endif +#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" -#define MB_SLAVE_SHORT_ADDRESS 1 -#define MB_RETRIES 50 - // Example code to read and write Modbus registers using CPP wrapper class -void app_main() +extern "C" void app_main() { - ModbusMaster* pModbusMaster = new ModbusMaster(); - // Initialize and start Modbus controller mb_communication_info_t comm; - - comm.port = MASTER_PORT_NUM; - comm.mode = MB_MODE_RTU; - comm.baudrate = MASTER_SPEED; - comm.parity = MB_PARITY_NONE; - - - // Initialization of device peripheral and objects - ESP_ERROR_CHECK(pModbusMaster->begin(MB_SLAVE_SHORT_ADDRESS, &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 - uart_set_pin(MASTER_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN, + 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 - esp_err_t err = uart_set_mode(MASTER_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); - MASTER_CHECK((err == ESP_OK), ; , - "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + 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_array[6] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; + 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; - for (int i = 0; i < MB_RETRIES; i++) { - err = pModbusMaster->writeMultipleRegisters(40001, 5, ®_array[0]); - MASTER_CHECK((err == ESP_OK), ; , - "Modbus Write error: (0x%x).", (uint32_t)err); - - memset(reg_array, 0x00, 10); - err = pModbusMaster->readHoldingRegisters(40001, 5, ®_array[0]); - MASTER_CHECK((err == ESP_OK), ; , - "Modbus Read error: (0x%x).", (uint32_t)err); + // 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); - MASTER_CHECK((err == ESP_OK), ; , - "Modbus Write Coils error: (0x%x).", (uint32_t)err); + 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); - MASTER_CHECK((err == ESP_OK), ; , - "Modbus Read Coils error: (0x%x).", (uint32_t)err); - - ESP_LOGI("MB", "COILS: 0x%X", temp_coils); - - ESP_LOG_BUFFER_HEX_LEVEL("MB", reg_array, 10, ESP_LOG_INFO); + 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); } - ESP_LOGI("MODBUS", "Modbus Test completed."); + 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; } - -#ifdef __cplusplus -} -#endif diff --git a/modbus_simple_master/sdkconfig.defaults b/modbus_simple_master/sdkconfig.defaults new file mode 100644 index 0000000..bc78b11 --- /dev/null +++ b/modbus_simple_master/sdkconfig.defaults @@ -0,0 +1,8 @@ +# +# 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 + diff --git a/modbus_simple_slave/CMakeLists.txt b/modbus_simple_slave/CMakeLists.txt index a941d4e..3c52567 100644 --- a/modbus_simple_slave/CMakeLists.txt +++ b/modbus_simple_slave/CMakeLists.txt @@ -2,7 +2,7 @@ # 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/serial/mb_example_common) +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/main/CMakeLists.txt b/modbus_simple_slave/main/CMakeLists.txt index 6ffc4db..e50fab1 100644 --- a/modbus_simple_slave/main/CMakeLists.txt +++ b/modbus_simple_slave/main/CMakeLists.txt @@ -1,4 +1,4 @@ set(PROJECT_NAME "modbus_slave") idf_component_register(SRCS "slave.cpp" "ModbusSlave.cpp" - INCLUDE_DIRS ".") \ No newline at end of file + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/modbus_simple_slave/main/Kconfig.projbuild b/modbus_simple_slave/main/Kconfig.projbuild index e722f86..56fc211 100644 --- a/modbus_simple_slave/main/Kconfig.projbuild +++ b/modbus_simple_slave/main/Kconfig.projbuild @@ -1,11 +1,23 @@ 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 IDF_TARGET_ESP32 - default 2 if IDF_TARGET_ESP32 - range 0 1 if IDF_TARGET_ESP32S2 - default 1 if IDF_TARGET_ESP32S2 + 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. @@ -18,29 +30,34 @@ menu "Modbus Example Configuration" config MB_UART_RXD int "UART RXD pin number" - range 0 34 if IDF_TARGET_ESP32 - default 22 if IDF_TARGET_ESP32 - range 0 46 if IDF_TARGET_ESP32S2 - default 19 if IDF_TARGET_ESP32S2 + 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 0 34 if IDF_TARGET_ESP32 - default 23 if IDF_TARGET_ESP32 - range 0 46 if IDF_TARGET_ESP32S2 - default 20 if IDF_TARGET_ESP32S2 + 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 0 34 if IDF_TARGET_ESP32 - range 0 46 if IDF_TARGET_ESP32S2 - default 18 + 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. diff --git a/modbus_simple_slave/main/ModbusSlave.cpp b/modbus_simple_slave/main/ModbusSlave.cpp index 3bbfabc..55406b9 100644 --- a/modbus_simple_slave/main/ModbusSlave.cpp +++ b/modbus_simple_slave/main/ModbusSlave.cpp @@ -13,7 +13,7 @@ The wrapper for ESP_Modbus library communicating with Modbus slaves over RS232/4 #include "ModbusSlave.h" /* _____GLOBAL VARIABLES_____________________________________________________ */ - +static const char *TAG = "ModbusSlave"; /* _____PUBLIC FUNCTIONS_____________________________________________________ */ /** @@ -23,9 +23,26 @@ Creates class object; initialize it using ModbusSlave::begin(). @ingroup setup */ -ModbusSlave::ModbusSlave(void) +ModbusSlave::ModbusSlave(mb_communication_info_t* pxCommOpts) { - // Do not perform initialization in constructor. + 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; } /** @@ -34,29 +51,17 @@ Initialize class object. Assigns the Modbus slave ID and serial communication parameters. Call once class has been instantiated. -@param pxCommOpts - pointer to communication options structure @ingroup setup */ -esp_err_t ModbusSlave::begin(mb_communication_info_t* pxCommOpts) +esp_err_t ModbusSlave::begin() { - _u8MBSlave = pxCommOpts->slave_addr; - _comm_opts = *pxCommOpts; + _u8MBSlave = _comm_opts.ser_opts.uid; esp_err_t err = ESP_FAIL; - // Initialization of Modbus controller - err = mbc_slave_init(MB_PORT_SERIAL_SLAVE, &_slave_handler); - SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller init fail, returns(0x%x).", - (uint32_t)err); - // Setup communication parameters and start stack - err = mbc_slave_setup((void*)&_comm_opts); - SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller setup fail, returns(0x%x).", - (uint32_t)err); - err = mbc_slave_start(); - SLAVE_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, - "mb controller start fail, returns(0x%x).", - (uint32_t)err); + // 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; } @@ -79,8 +84,9 @@ esp_err_t ModbusSlave::addRegisterBank(uint16_t regAddress, void* instanceAddres 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(reg_area); + return mbc_slave_set_descriptor(_pslave_handle, reg_area); } /** @@ -91,10 +97,11 @@ Waits the event defined in the event mask and returns actual event (register rea */ mb_event_group_t ModbusSlave::run(mb_event_group_t EventMask) { - mb_event_group_t event = mbc_slave_check_event(EventMask); - - ESP_ERROR_CHECK(mbc_slave_get_param_info(&_reg_info, MODBUS_PAR_INFO_GET_TOUT)); - return event; + 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; } /** @@ -102,7 +109,7 @@ 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; } @@ -118,21 +125,27 @@ bool ModbusSlave::isBankAccessed(uint16_t regAddress, uint16_t regAreaSize) 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_LOGI("TEST", "Bank accessed type %d, offset %d, par_info.mb_off %d, par_info.size %d, reg_mask %d, par_info.type %d, result=%d", - reg_type, offset, par_info.mb_offset, par_info.size, reg_mask, par_info.type, (uint8_t)result); + 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) { - ESP_ERROR_CHECK(mbc_slave_destroy()); + 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____________________________________________________ */ diff --git a/modbus_simple_slave/main/ModbusSlave.h b/modbus_simple_slave/main/ModbusSlave.h index d7ad614..dbc9ab7 100644 --- a/modbus_simple_slave/main/ModbusSlave.h +++ b/modbus_simple_slave/main/ModbusSlave.h @@ -1,6 +1,6 @@ /** @file -The wrapper class for Modbus Slave for communication over RS232/485 (via RTU protocol). +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 @@ -43,45 +43,37 @@ The wrapper class for Modbus Slave for communication over RS232/485 (via RTU pro #define MODBUS_PAR_INFO_GET_TOUT pdMS_TO_TICKS(10) -#define SLAVE_CHECK(a, ret_val, str, ...) \ - if (!(a)) { \ - ESP_LOGE(TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return ret_val; \ - } - /* _____STANDARD INCLUDES____________________________________________________ */ #include #include "esp_log.h" -#include "mbcontroller.h" // include file for common Modbus types - -#define TAG "ModbusSlave" - - /* _____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(void); - ~ModbusSlave(void); - - esp_err_t begin(mb_communication_info_t*); + 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* _slave_handler; ///< Modbus slave handler + 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 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 index 40f0d1b..96c48f8 100644 --- a/modbus_simple_slave/main/slave.cpp +++ b/modbus_simple_slave/main/slave.cpp @@ -1,4 +1,9 @@ -/* FreeModbus Slave Example ESP32 +/* + * 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 @@ -9,16 +14,13 @@ #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" -#ifdef __cplusplus -extern "C" { -#endif - #include "ModbusSlave.h" #define MB_RETRIES (100) -#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection +#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 @@ -32,38 +34,29 @@ extern "C" { | 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 *SLAVE_TAG = "SLAVE_TEST"; +#define MB_READ_WRITE_MASK ((mb_event_group_t)(MB_READ_MASK | MB_WRITE_MASK)) -static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED; +static const char *TAG = "SLAVE_TEST"; -// 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) +// An example application of Modbus CPP Slave. +extern "C" void app_main(void) { - // Create the class instance - ModbusSlave* pModbusSlave = new ModbusSlave(); - // Initialize and start Modbus controller mb_communication_info_t comm; // Setup communication parameters and start stack #if CONFIG_MB_COMM_MODE_ASCII - comm.mode = MB_MODE_ASCII, + comm.ser_opts.mode = MB_ASCII; #elif CONFIG_MB_COMM_MODE_RTU - comm.mode = MB_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; - comm.slave_addr = MB_SLAVE_ADDR; - comm.port = MB_PORT_NUM; - comm.baudrate = MB_DEV_SPEED; - comm.parity = MB_PARITY_NONE; - - // Initialization of device peripheral and objects - ESP_ERROR_CHECK(pModbusSlave->begin(&comm)); + // 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, @@ -71,8 +64,8 @@ void app_main(void) // Set driver mode to Half Duplex esp_err_t err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); - SLAVE_CHECK((err == ESP_OK), ; , - "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + 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 }; @@ -81,11 +74,14 @@ void app_main(void) size_t reg_size = (sizeof(hold_array) / sizeof(hold_array[0])); // Add registers below into assiciated register bank (use PLC based address) - // These registers accessed inside this defined area only (exception returned outside of this range). + // 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], reg_size); - pModbusSlave->addRegisterBank(1, &coil_array[0], (reg_size * 2 * 8)); + 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); @@ -95,36 +91,36 @@ void app_main(void) // 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_LOGI(SLAVE_TAG, "Incorrect modbus access type: %d.", event); + 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)) { - ESP_LOGI(SLAVE_TAG, "Holding bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); - portENTER_CRITICAL(¶m_lock); - // Reinitialize the accessed register values with random numbers - esp_fill_random(info.address, (info.size << 1)); - portEXIT_CRITICAL(¶m_lock); + 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 + // // Check if the selected input register area is accessed by Master if (pModbusSlave->isBankAccessed(30001, reg_size)) { - ESP_LOGI(SLAVE_TAG, "Input bank address offset: %d, size %d is accessed.", info.mb_offset, info.size); - portENTER_CRITICAL(¶m_lock); - // Reinitialize the accessed register values with random numbers - esp_fill_random(info.address, (info.size << 1)); - portEXIT_CRITICAL(¶m_lock); + 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, 8)) { - ESP_LOGI(SLAVE_TAG, "Cols address offset: %d, size %d is accessed.", info.mb_offset, info.size); - portENTER_CRITICAL(¶m_lock); - // Reinitialize the accessed register values with random numbers - esp_fill_random(info.address, (info.size >> 3)); - portEXIT_CRITICAL(¶m_lock); + 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); } @@ -132,6 +128,3 @@ void app_main(void) delete pModbusSlave; } -#ifdef __cplusplus -} -#endif diff --git a/modbus_simple_slave/sdkconfig.defaults b/modbus_simple_slave/sdkconfig.defaults index 334a421..de235f4 100644 --- a/modbus_simple_slave/sdkconfig.defaults +++ b/modbus_simple_slave/sdkconfig.defaults @@ -5,6 +5,5 @@ CONFIG_MB_COMM_MODE_ASCII=y CONFIG_MB_SLAVE_ADDR=1 CONFIG_MB_UART_BAUD_RATE=115200 CONFIG_FMB_TIMER_PORT_ENABLED=y -CONFIG_FMB_TIMER_GROUP=0 -CONFIG_FMB_TIMER_INDEX=0 -CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y + From 78223ee9a18b21ad7d0e410c841b0d5957e48523 Mon Sep 17 00:00:00 2001 From: aleks Date: Thu, 18 Dec 2025 16:54:38 +0100 Subject: [PATCH 5/5] add simple cpp example --- modbus_serial_cpp_test/CMakeLists.txt | 6 + modbus_serial_cpp_test/README.md | 105 ++++++++++++++++++ modbus_serial_cpp_test/main/CMakeLists.txt | 2 + modbus_serial_cpp_test/main/Kconfig.projbuild | 99 +++++++++++++++++ modbus_serial_cpp_test/main/component.mk | 4 + modbus_serial_cpp_test/main/idf_component.yml | 6 + modbus_serial_cpp_test/main/master.cpp | 105 ++++++++++++++++++ modbus_simple_master/sdkconfig.defaults | 1 + 8 files changed, 328 insertions(+) create mode 100644 modbus_serial_cpp_test/CMakeLists.txt create mode 100644 modbus_serial_cpp_test/README.md create mode 100644 modbus_serial_cpp_test/main/CMakeLists.txt create mode 100644 modbus_serial_cpp_test/main/Kconfig.projbuild create mode 100644 modbus_serial_cpp_test/main/component.mk create mode 100644 modbus_serial_cpp_test/main/idf_component.yml create mode 100644 modbus_serial_cpp_test/main/master.cpp 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/sdkconfig.defaults b/modbus_simple_master/sdkconfig.defaults index bc78b11..6ebe677 100644 --- a/modbus_simple_master/sdkconfig.defaults +++ b/modbus_simple_master/sdkconfig.defaults @@ -5,4 +5,5 @@ 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