From 67f62a79c192729915f9b41d37c45f3d0c31a426 Mon Sep 17 00:00:00 2001 From: Alex Lisitsyn Date: Tue, 26 Nov 2019 13:16:25 +0800 Subject: [PATCH] freemodbus: add modbus master ascii add support of modbus master ascii rename base dir name of master and slave example to be mb_slave, mb_master to avoid conflict with sdio/slave example test add Kconfig option to enable ASCII and RTU mode separately update ASCII options + remove cast for errors added baudrate for examples into Kconfig updated magic numbers for timer timeout put ascii private definitions into one file --- components/freemodbus/CMakeLists.txt | 1 + components/freemodbus/Kconfig | 31 +- .../freemodbus/common/esp_modbus_master.c | 10 +- .../freemodbus/common/esp_modbus_slave.c | 13 +- components/freemodbus/modbus/ascii/mbascii.c | 37 +- components/freemodbus/modbus/ascii/mbascii.h | 23 + .../freemodbus/modbus/ascii/mbascii_m.c | 573 ++++++++++++++++++ .../freemodbus/modbus/functions/mbfunccoils.c | 5 +- .../modbus/functions/mbfunccoils_m.c | 12 +- .../freemodbus/modbus/functions/mbfuncdisc.c | 8 +- .../modbus/functions/mbfuncdisc_m.c | 7 +- .../modbus/functions/mbfuncholding.c | 5 +- .../modbus/functions/mbfuncholding_m.c | 13 +- .../freemodbus/modbus/functions/mbfuncinput.c | 6 +- .../modbus/functions/mbfuncinput_m.c | 2 +- .../freemodbus/modbus/functions/mbfuncother.c | 6 +- components/freemodbus/modbus/include/mb_m.h | 2 + .../freemodbus/modbus/include/mbconfig.h | 25 +- .../freemodbus/modbus/include/mbframe.h | 6 + components/freemodbus/modbus/include/mbport.h | 5 +- components/freemodbus/modbus/mb.c | 11 + components/freemodbus/modbus/mb_m.c | 66 +- components/freemodbus/modbus/rtu/mbcrc.c | 5 + components/freemodbus/modbus/rtu/mbrtu.c | 29 +- components/freemodbus/modbus/rtu/mbrtu.h | 8 +- components/freemodbus/modbus/rtu/mbrtu_m.c | 191 +++--- components/freemodbus/port/port.h | 5 + components/freemodbus/port/portevent.c | 2 +- components/freemodbus/port/portevent_m.c | 2 +- components/freemodbus/port/portserial.c | 47 +- components/freemodbus/port/portserial_m.c | 41 +- components/freemodbus/port/porttimer.c | 7 +- .../modbus_controller/mbc_serial_master.c | 48 +- .../modbus_controller/mbc_serial_slave.c | 6 +- docs/en/api-reference/protocols/modbus.rst | 6 +- examples/protocols/modbus/serial/README.md | 31 + .../serial/common_components/CMakeLists.txt | 3 + .../serial/common_components/component.mk | 5 + .../common_components/include/modbus_params.h | 60 ++ .../serial/common_components/modbus_params.c} | 2 +- .../protocols/modbus/serial/example_test.py | 297 +++++++++ .../modbus/serial/mb_master/CMakeLists.txt | 10 + .../serial/mb_master}/Makefile | 5 +- .../modbus/serial/mb_master/README.md | 150 +++++ .../serial/mb_master/main/CMakeLists.txt | 5 + .../serial/mb_master/main/Kconfig.projbuild | 57 ++ .../serial/mb_master}/main/component.mk | 0 .../modbus/serial/mb_master/main/master.c | 273 +++++++++ .../serial/mb_master}/sdkconfig.defaults | 5 +- .../modbus/serial/mb_slave/CMakeLists.txt | 11 + .../serial/mb_slave}/Makefile | 2 + .../serial/mb_slave}/README.md | 36 +- .../serial/mb_slave/main/CMakeLists.txt | 4 + .../serial/mb_slave/main/Kconfig.projbuild | 65 ++ .../serial/mb_slave}/main/component.mk | 2 +- .../serial/mb_slave/main/slave.c} | 92 +-- .../modbus/serial/mb_slave/sdkconfig.defaults | 10 + .../protocols/modbus_master/CMakeLists.txt | 7 - examples/protocols/modbus_master/README.md | 134 ---- .../modbus_master/main/CMakeLists.txt | 4 - .../modbus_master/main/Kconfig.projbuild | 27 - .../modbus_master/main/device_params.c | 70 --- .../main/include/device_params.h | 140 ----- .../modbus_master/main/include/sense_modbus.h | 60 -- .../protocols/modbus_master/main/sense_main.c | 181 ------ .../protocols/modbus_master/tool/Mbslav1.mbs | Bin 102266 -> 0 bytes .../protocols/modbus_master/tool/Mbslav2.mbs | Bin 102266 -> 0 bytes .../protocols/modbus_master/tool/Mbslav3.mbs | Bin 106050 -> 0 bytes .../protocols/modbus_master/tool/Mbslav4.mbs | Bin 102140 -> 0 bytes .../tool/ModbusSlave_workspace.png | Bin 49614 -> 0 bytes .../modbus_master/tool/workspace.msw | Bin 2502 -> 0 bytes .../protocols/modbus_slave/CMakeLists.txt | 6 - .../modbus_slave/main/CMakeLists.txt | 3 - .../modbus_slave/main/Kconfig.projbuild | 27 - .../modbus_slave/main/deviceparams.h | 131 ---- tools/ci/config/target-test.yml | 56 ++ 76 files changed, 2080 insertions(+), 1155 deletions(-) create mode 100644 components/freemodbus/modbus/ascii/mbascii_m.c create mode 100644 examples/protocols/modbus/serial/README.md create mode 100644 examples/protocols/modbus/serial/common_components/CMakeLists.txt create mode 100644 examples/protocols/modbus/serial/common_components/component.mk create mode 100644 examples/protocols/modbus/serial/common_components/include/modbus_params.h rename examples/protocols/{modbus_slave/main/deviceparams.c => modbus/serial/common_components/modbus_params.c} (95%) create mode 100644 examples/protocols/modbus/serial/example_test.py create mode 100644 examples/protocols/modbus/serial/mb_master/CMakeLists.txt rename examples/protocols/{modbus_master => modbus/serial/mb_master}/Makefile (56%) create mode 100644 examples/protocols/modbus/serial/mb_master/README.md create mode 100644 examples/protocols/modbus/serial/mb_master/main/CMakeLists.txt create mode 100644 examples/protocols/modbus/serial/mb_master/main/Kconfig.projbuild rename examples/protocols/{modbus_master => modbus/serial/mb_master}/main/component.mk (100%) create mode 100644 examples/protocols/modbus/serial/mb_master/main/master.c rename examples/protocols/{modbus_master => modbus/serial/mb_master}/sdkconfig.defaults (76%) create mode 100644 examples/protocols/modbus/serial/mb_slave/CMakeLists.txt rename examples/protocols/{modbus_slave => modbus/serial/mb_slave}/Makefile (67%) rename examples/protocols/{modbus_slave => modbus/serial/mb_slave}/README.md (63%) create mode 100644 examples/protocols/modbus/serial/mb_slave/main/CMakeLists.txt create mode 100644 examples/protocols/modbus/serial/mb_slave/main/Kconfig.projbuild rename examples/protocols/{modbus_slave => modbus/serial/mb_slave}/main/component.mk (61%) rename examples/protocols/{modbus_slave/main/freemodbus.c => modbus/serial/mb_slave/main/slave.c} (73%) create mode 100644 examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults delete mode 100644 examples/protocols/modbus_master/CMakeLists.txt delete mode 100644 examples/protocols/modbus_master/README.md delete mode 100644 examples/protocols/modbus_master/main/CMakeLists.txt delete mode 100644 examples/protocols/modbus_master/main/Kconfig.projbuild delete mode 100644 examples/protocols/modbus_master/main/device_params.c delete mode 100644 examples/protocols/modbus_master/main/include/device_params.h delete mode 100644 examples/protocols/modbus_master/main/include/sense_modbus.h delete mode 100644 examples/protocols/modbus_master/main/sense_main.c delete mode 100644 examples/protocols/modbus_master/tool/Mbslav1.mbs delete mode 100644 examples/protocols/modbus_master/tool/Mbslav2.mbs delete mode 100644 examples/protocols/modbus_master/tool/Mbslav3.mbs delete mode 100644 examples/protocols/modbus_master/tool/Mbslav4.mbs delete mode 100644 examples/protocols/modbus_master/tool/ModbusSlave_workspace.png delete mode 100644 examples/protocols/modbus_master/tool/workspace.msw delete mode 100644 examples/protocols/modbus_slave/CMakeLists.txt delete mode 100644 examples/protocols/modbus_slave/main/CMakeLists.txt delete mode 100644 examples/protocols/modbus_slave/main/Kconfig.projbuild delete mode 100644 examples/protocols/modbus_slave/main/deviceparams.h diff --git a/components/freemodbus/CMakeLists.txt b/components/freemodbus/CMakeLists.txt index 97112a53b..f4cc4a38c 100644 --- a/components/freemodbus/CMakeLists.txt +++ b/components/freemodbus/CMakeLists.txt @@ -6,6 +6,7 @@ set(srcs "modbus/mb.c" "modbus/mb_m.c" "modbus/ascii/mbascii.c" + "modbus/ascii/mbascii_m.c" "modbus/rtu/mbrtu_m.c" "modbus/rtu/mbrtu.c" "modbus/rtu/mbcrc.c" diff --git a/components/freemodbus/Kconfig b/components/freemodbus/Kconfig index 5de67fe01..b1240e7d7 100644 --- a/components/freemodbus/Kconfig +++ b/components/freemodbus/Kconfig @@ -1,10 +1,21 @@ menu "Modbus configuration" + config FMB_COMM_MODE_RTU_EN + bool "Enable Modbus stack support for RTU mode" + default y + help + Enable RTU Modbus communication mode option for Modbus serial stack. + + config FMB_COMM_MODE_ASCII_EN + bool "Enable Modbus stack support for ASCII mode" + default y + help + Enable ASCII Modbus communication mode option for Modbus serial stack. + config FMB_MASTER_TIMEOUT_MS_RESPOND int "Slave respond timeout (Milliseconds)" default 150 range 50 400 - help If master sends a frame which is not broadcast, it has to wait sometime for slave response. if slave is not respond in this time, the master will process timeout error. @@ -13,7 +24,6 @@ menu "Modbus configuration" int "Slave conversion delay (Milliseconds)" default 200 range 50 400 - help If master sends a broadcast frame, it has to wait conversion time to delay, then master can send next frame. @@ -43,6 +53,23 @@ menu "Modbus configuration" This buffer is used for modbus frame transfer. The Modbus protocol maximum frame size is 256 bytes. Bigger size can be used for non standard implementations. + config FMB_SERIAL_ASCII_BITS_PER_SYMB + int "Number of data bits per ASCII character" + default 8 + range 7 8 + depends on FMB_COMM_MODE_ASCII_EN + help + This option defines the number of data bits per ASCII character. + + config FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS + int "Response timeout for ASCII communication mode (ms)" + default 1000 + range 300 2000 + depends on FMB_COMM_MODE_ASCII_EN + help + This option defines response timeout of slave in milliseconds for ASCII communication mode. + Thus the timeout will expire and allow the master’s program to handle the error. + config FMB_SERIAL_TASK_PRIO int "Modbus serial task priority" range 3 10 diff --git a/components/freemodbus/common/esp_modbus_master.c b/components/freemodbus/common/esp_modbus_master.c index 0dafd8b96..37a3381df 100644 --- a/components/freemodbus/common/esp_modbus_master.c +++ b/components/freemodbus/common/esp_modbus_master.c @@ -69,9 +69,9 @@ esp_err_t mbc_master_destroy(void) "Master interface is not correctly initialized."); error = master_interface_ptr->destroy(); MB_MASTER_CHECK((error == ESP_OK), - ESP_ERR_INVALID_STATE, + error, "SERIAL master destroy failure error=(0x%x).", - (uint16_t)error); + error); return error; } @@ -86,9 +86,9 @@ esp_err_t mbc_master_get_cid_info(uint16_t cid, const mb_parameter_descriptor_t* "Master interface is not correctly initialized."); error = master_interface_ptr->get_cid_info(cid, param_info); MB_MASTER_CHECK((error == ESP_OK), - ESP_ERR_INVALID_STATE, + error, "SERIAL master get cid info failure error=(0x%x).", - (uint16_t)error); + error); return error; } @@ -268,4 +268,4 @@ eMBErrorCode eMBMasterRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, "Master interface is not correctly initialized."); error = master_interface_ptr->master_reg_cb_input(pucRegBuffer, usAddress, usNRegs); return error; -} \ No newline at end of file +} diff --git a/components/freemodbus/common/esp_modbus_slave.c b/components/freemodbus/common/esp_modbus_slave.c index 3765d727d..0b681ba89 100644 --- a/components/freemodbus/common/esp_modbus_slave.c +++ b/components/freemodbus/common/esp_modbus_slave.c @@ -29,7 +29,7 @@ #define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF)) #define MB_CONTROLLER_SLAVE_ID (CONFIG_FMB_CONTROLLER_SLAVE_ID) -#define MB_SLAVE_ID_SHORT (MB_ID_BYTE3(MB_CONTROLLER_SLAVE_ID)) +#define MB_SLAVE_ID_SHORT (MB_ID_BYTE3(CONFIG_FMB_CONTROLLER_SLAVE_ID)) // Slave ID constant static uint8_t mb_slave_id[] = { MB_ID_BYTE0(MB_CONTROLLER_SLAVE_ID), @@ -134,8 +134,7 @@ esp_err_t mbc_slave_start(void) #endif error = slave_interface_ptr->start(); MB_SLAVE_CHECK((error == ESP_OK), - ESP_ERR_INVALID_STATE, - "SERIAL slave start failure error=(0x%x).", error); + error, "SERIAL slave start failure error=(0x%x).", error); return error; } @@ -168,8 +167,7 @@ esp_err_t mbc_slave_get_param_info(mb_param_info_t* reg_info, uint32_t timeout) "Slave interface is not correctly initialized."); error = slave_interface_ptr->get_param_info(reg_info, timeout); MB_SLAVE_CHECK((error == ESP_OK), - ESP_ERR_INVALID_STATE, - "SERIAL slave get parameter info failure error=(0x%x).", error); + error, "SERIAL slave get parameter info failure error=(0x%x).", error); return error; } @@ -187,8 +185,7 @@ esp_err_t mbc_slave_set_descriptor(mb_register_area_descriptor_t descr_data) "Slave interface is not correctly initialized."); error = slave_interface_ptr->set_descriptor(descr_data); MB_SLAVE_CHECK((error == ESP_OK), - ESP_ERR_INVALID_STATE, - "SERIAL slave set descriptor failure error=(0x%x).", error); + error, "SERIAL slave set descriptor failure error=(0x%x).", error); return error; } @@ -203,7 +200,7 @@ eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, ESP_ERR_INVALID_STATE, "Slave interface is not correctly initialized."); MB_SLAVE_CHECK((slave_interface_ptr->slave_reg_cb_discrete != NULL), - ESP_ERR_INVALID_STATE, + error, "Slave interface is not correctly initialized."); error = slave_interface_ptr->slave_reg_cb_discrete(pucRegBuffer, usAddress, usNDiscrete); diff --git a/components/freemodbus/modbus/ascii/mbascii.c b/components/freemodbus/modbus/ascii/mbascii.c index a8c6ac6af..0918498bc 100644 --- a/components/freemodbus/modbus/ascii/mbascii.c +++ b/components/freemodbus/modbus/ascii/mbascii.c @@ -46,15 +46,6 @@ #if MB_SLAVE_ASCII_ENABLED > 0 -/* ----------------------- Defines ------------------------------------------*/ -#define MB_ASCII_DEFAULT_CR '\r' /*!< Default CR character for Modbus ASCII. */ -#define MB_ASCII_DEFAULT_LF '\n' /*!< Default LF character for Modbus ASCII. */ -#define MB_SER_PDU_SIZE_MIN 3 /*!< Minimum size of a Modbus ASCII frame. */ -#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus ASCII frame. */ -#define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */ -#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ -#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ - /* ----------------------- Type definitions ---------------------------------*/ typedef enum { @@ -78,6 +69,10 @@ typedef enum BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */ } eMBBytePos; +/* ----------------------- Shared variables ---------------------------------*/ +/* We reuse the Modbus RTU buffer because only one driver is active */ +extern volatile UCHAR ucMbSlaveBuf[]; + /* ----------------------- Static functions ---------------------------------*/ static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter ); @@ -89,10 +84,7 @@ static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen ); static volatile eMBSndState eSndState; static volatile eMBRcvState eRcvState; -/* We reuse the Modbus RTU buffer because only one buffer is needed and the - * RTU buffer is bigger. */ -extern volatile UCHAR ucRTUBuf[]; -static volatile UCHAR *ucASCIIBuf = ucRTUBuf; +static volatile UCHAR *ucASCIIBuf = ucMbSlaveBuf; static volatile USHORT usRcvBufferPos; static volatile eMBBytePos eBytePos; @@ -113,11 +105,11 @@ eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eP ENTER_CRITICAL_SECTION( ); ucMBLFCharacter = MB_ASCII_DEFAULT_LF; - if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE ) + if( xMBPortSerialInit( ucPort, ulBaudRate, MB_ASCII_BITS_PER_SYMB, eParity ) != TRUE ) { eStatus = MB_EPORTERR; } - else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE ) + else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_MS * 20UL ) != TRUE ) { eStatus = MB_EPORTERR; } @@ -157,7 +149,7 @@ eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); /* Length and CRC check */ - if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) + if( ( usRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN ) && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed @@ -227,7 +219,7 @@ xMBASCIIReceiveFSM( void ) assert( eSndState == STATE_TX_IDLE ); - ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); + xNeedPoll = xMBPortSerialGetByte( ( CHAR * ) & ucByte ); switch ( eRcvState ) { /* A new character is received. If the character is a ':' the input @@ -292,7 +284,7 @@ xMBASCIIReceiveFSM( void ) /* Notify the caller of eMBASCIIReceive that a new frame * was received. */ - xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); + (void)xMBPortEventPost( EV_FRAME_RECEIVED ); } else if( ucByte == ':' ) { @@ -317,7 +309,7 @@ xMBASCIIReceiveFSM( void ) /* Enable timer for character timeout. */ vMBPortTimersEnable( ); /* Reset the input buffers to store the frame. */ - usRcvBufferPos = 0;; + usRcvBufferPos = 0; eBytePos = BYTE_HIGH_NIBBLE; eRcvState = STATE_RX_RCV; } @@ -330,7 +322,7 @@ xMBASCIIReceiveFSM( void ) BOOL xMBASCIITransmitFSM( void ) { - BOOL xNeedPoll = FALSE; + BOOL xNeedPoll = TRUE; UCHAR ucByte; assert( eRcvState == STATE_RX_IDLE ); @@ -388,9 +380,10 @@ xMBASCIITransmitFSM( void ) * been sent. */ case STATE_TX_NOTIFY: eSndState = STATE_TX_IDLE; - xMBPortEventPost( EV_FRAME_SENT ); - xNeedPoll = TRUE; + xMBPortEventPost( EV_FRAME_TRANSMIT ); + xNeedPoll = FALSE; + eSndState = STATE_TX_IDLE; break; /* We should not get a transmitter event if the transmitter is in diff --git a/components/freemodbus/modbus/ascii/mbascii.h b/components/freemodbus/modbus/ascii/mbascii.h index 0be1b3ce6..535701b6d 100644 --- a/components/freemodbus/modbus/ascii/mbascii.h +++ b/components/freemodbus/modbus/ascii/mbascii.h @@ -34,6 +34,14 @@ #ifdef __cplusplus PR_BEGIN_EXTERN_C #endif + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_ASCII_DEFAULT_CR '\r' /*!< Default CR character for Modbus ASCII. */ +#define MB_ASCII_DEFAULT_LF '\n' /*!< Default LF character for Modbus ASCII. */ +#define MB_ASCII_SER_PDU_SIZE_MIN 3 /*!< Minimum size of a Modbus ASCII frame. */ + +/* ----------------------- Function declaration -----------------------------*/ + #if MB_SLAVE_ASCII_ENABLED > 0 eMBErrorCode eMBASCIIInit( UCHAR slaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ); @@ -49,6 +57,21 @@ BOOL xMBASCIITransmitFSM( void ); BOOL xMBASCIITimerT1SExpired( void ); #endif +#if MB_MASTER_ASCII_ENABLED > 0 +eMBErrorCode eMBMasterASCIIInit( UCHAR ucPort, + ULONG ulBaudRate, eMBParity eParity ); +void eMBMasterASCIIStart( void ); +void eMBMasterASCIIStop( void ); + +eMBErrorCode eMBMasterASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, + USHORT * pusLength ); +eMBErrorCode eMBMasterASCIISend( UCHAR slaveAddress, const UCHAR * pucFrame, + USHORT usLength ); +BOOL xMBMasterASCIIReceiveFSM( void ); +BOOL xMBMasterASCIITransmitFSM( void ); +BOOL xMBMasterASCIITimerT1SExpired( void ); +#endif + #ifdef __cplusplus PR_END_EXTERN_C #endif diff --git a/components/freemodbus/modbus/ascii/mbascii_m.c b/components/freemodbus/modbus/ascii/mbascii_m.c new file mode 100644 index 000000000..0433a3cea --- /dev/null +++ b/components/freemodbus/modbus/ascii/mbascii_m.c @@ -0,0 +1,573 @@ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2006 Christian Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: mbascii.c,v 1.17 2010/06/06 13:47:07 wolti Exp $ + */ + +/* ----------------------- System includes ----------------------------------*/ +#include "stdlib.h" +#include "string.h" + +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +#include "mb_m.h" +#include "mbconfig.h" +#include "mbascii.h" +#include "mbframe.h" + +#include "mbcrc.h" +#include "mbport.h" + +#if MB_MASTER_ASCII_ENABLED > 0 + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_TIMER_TICS_PER_MS 20UL + +/* ----------------------- Type definitions ---------------------------------*/ +typedef enum +{ + STATE_M_RX_INIT, /*!< Receiver is in initial state. */ + STATE_M_RX_IDLE, /*!< Receiver is in idle state. */ + STATE_M_RX_RCV, /*!< Frame is beeing received. */ + STATE_M_RX_WAIT_EOF, /*!< Wait for End of Frame. */ + STATE_M_RX_ERROR, /*!< If the frame is invalid. */ +} eMBMasterAsciiRcvState; + +typedef enum +{ + STATE_M_TX_IDLE, /*!< Transmitter is in idle state. */ + STATE_M_TX_START, /*!< Starting transmission (':' sent). */ + STATE_M_TX_DATA, /*!< Sending of data (Address, Data, LRC). */ + STATE_M_TX_END, /*!< End of transmission. */ + STATE_M_TX_NOTIFY, /*!< Notify sender that the frame has been sent. */ + STATE_M_TX_XFWR, /*!< Transmitter is in transfer finish and wait receive state. */ +} eMBMasterAsciiSndState; + +typedef enum +{ + BYTE_HIGH_NIBBLE, /*!< Character for high nibble of byte. */ + BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */ +} eMBBytePos; + +/* ----------------------- Shared values -----------------------------------*/ +/* These Modbus values are shared in ASCII mode*/ +extern volatile UCHAR ucMasterRcvBuf[]; +extern volatile UCHAR ucMasterSndBuf[]; +extern volatile eMBMasterTimerMode eMasterCurTimerMode; + +/* ----------------------- Static functions ---------------------------------*/ +static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter ); + +static UCHAR prvucMBBIN2CHAR( UCHAR ucByte ); + +static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen ); + +/* ----------------------- Static variables ---------------------------------*/ +static volatile eMBMasterAsciiSndState eSndState; +static volatile eMBMasterAsciiRcvState eRcvState; + +static volatile UCHAR *ucMasterASCIIRcvBuf = ucMasterRcvBuf; +static volatile UCHAR *ucMasterASCIISndBuf = ucMasterSndBuf; + +static volatile USHORT usMasterRcvBufferPos; +static volatile eMBBytePos eBytePos; + +static volatile UCHAR *pucMasterSndBufferCur; +static volatile USHORT usMasterSndBufferCount; + +static volatile UCHAR ucLRC; +static volatile UCHAR ucMBLFCharacter; + +/* ----------------------- Start implementation -----------------------------*/ +eMBErrorCode +eMBMasterASCIIInit( UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) +{ + eMBErrorCode eStatus = MB_ENOERR; + + ENTER_CRITICAL_SECTION( ); + ucMBLFCharacter = MB_ASCII_DEFAULT_LF; + + if( xMBMasterPortSerialInit( ucPort, ulBaudRate, MB_ASCII_BITS_PER_SYMB, eParity ) != TRUE ) + { + eStatus = MB_EPORTERR; + } + else if( xMBMasterPortTimersInit( MB_ASCII_TIMEOUT_MS * MB_TIMER_TICS_PER_MS ) != TRUE ) + { + eStatus = MB_EPORTERR; + } + + EXIT_CRITICAL_SECTION( ); + + return eStatus; +} + +void +eMBMasterASCIIStart( void ) +{ + ENTER_CRITICAL_SECTION( ); + eRcvState = STATE_M_RX_IDLE; + vMBMasterPortSerialEnable( TRUE, FALSE ); + vMBMasterPortTimersT35Enable( ); + EXIT_CRITICAL_SECTION( ); +} + +void +eMBMasterASCIIStop( void ) +{ + ENTER_CRITICAL_SECTION( ); + vMBMasterPortSerialEnable( FALSE, FALSE ); + vMBMasterPortTimersDisable( ); + EXIT_CRITICAL_SECTION( ); +} + +eMBErrorCode +eMBMasterASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) +{ + eMBErrorCode eStatus = MB_ENOERR; + + ENTER_CRITICAL_SECTION( ); + assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX ); + + /* Length and CRC check */ + if( ( usMasterRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN ) + && ( prvucMBLRC( ( UCHAR * ) ucMasterASCIIRcvBuf, usMasterRcvBufferPos ) == 0 ) ) + { + /* Save the address field. All frames are passed to the upper layed + * and the decision if a frame is used is done there. + */ + *pucRcvAddress = ucMasterASCIIRcvBuf[MB_SER_PDU_ADDR_OFF]; + + /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus + * size of address field and CRC checksum. + */ + *pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); + + /* Return the start of the Modbus PDU to the caller. */ + *pucFrame = ( UCHAR * ) & ucMasterASCIIRcvBuf[MB_SER_PDU_PDU_OFF]; + } + else + { + eStatus = MB_EIO; + } + EXIT_CRITICAL_SECTION( ); + return eStatus; +} + +eMBErrorCode +eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) +{ + eMBErrorCode eStatus = MB_ENOERR; + UCHAR usLRC; + + if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL; + + ENTER_CRITICAL_SECTION( ); + /* Check if the receiver is still in idle state. If not we where too + * slow with processing the received frame and the master sent another + * frame on the network. We have to abort sending the frame. + */ + if(eRcvState == STATE_M_RX_IDLE) + { + /* First byte before the Modbus-PDU is the slave address. */ + pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1; + usMasterSndBufferCount = 1; + + /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ + pucMasterSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; + usMasterSndBufferCount += usLength; + + /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */ + usLRC = prvucMBLRC( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount ); + ucMasterASCIISndBuf[usMasterSndBufferCount++] = usLRC; + + /* Activate the transmitter. */ + eSndState = STATE_M_TX_START; + vMBMasterPortSerialEnable( FALSE, TRUE ); + } + else + { + eStatus = MB_EIO; + } + EXIT_CRITICAL_SECTION( ); + return eStatus; +} + +BOOL +xMBMasterASCIIReceiveFSM( void ) +{ + BOOL xNeedPoll = FALSE; + UCHAR ucByte; + UCHAR ucResult; + + assert(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR )); + + /* Always read the character. */ + xNeedPoll = xMBMasterPortSerialGetByte( ( CHAR * ) & ucByte ); + + switch ( eRcvState ) + { + /* If we have received a character in the init state we have to + * wait until the frame is finished. + */ + case STATE_M_RX_INIT: + vMBMasterPortTimersT35Enable( ); + break; + + /* In the error state we wait until all characters in the + * damaged frame are transmitted. + */ + case STATE_M_RX_ERROR: + vMBMasterPortTimersRespondTimeoutEnable( ); + break; + + /* In the idle state we wait for a new character. If a character + * is received the t1.5 and t3.5 timers are started and the + * receiver is in the state STATE_RX_RECEIVE and disable early + * the timer of respond timeout . + */ + case STATE_M_RX_IDLE: + /* Waiting for the start of frame character during respond timeout */ + vMBMasterPortTimersRespondTimeoutEnable( ); + if( ucByte == ':' ) + { + /* Reset the input buffers to store the frame in receive state. */ + usMasterRcvBufferPos = 0; + eBytePos = BYTE_HIGH_NIBBLE; + eRcvState = STATE_M_RX_RCV; + } + eSndState = STATE_M_TX_IDLE; + break; + + /* A new character is received. If the character is a ':' the input + * buffer is cleared. A CR-character signals the end of the data + * block. Other characters are part of the data block and their + * ASCII value is converted back to a binary representation. + */ + case STATE_M_RX_RCV: + /* Enable timer timeout. */ + vMBMasterPortTimersT35Enable( ); + if( ucByte == ':' ) + { + /* Empty receive buffer. */ + eBytePos = BYTE_HIGH_NIBBLE; + usMasterRcvBufferPos = 0; + } + else if( ucByte == MB_ASCII_DEFAULT_CR ) + { + eRcvState = STATE_M_RX_WAIT_EOF; + } + else + { + ucResult = prvucMBCHAR2BIN( ucByte ); + switch ( eBytePos ) + { + /* High nibble of the byte comes first. We check for + * a buffer overflow here. */ + case BYTE_HIGH_NIBBLE: + if( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX ) + { + ucMasterASCIIRcvBuf[usMasterRcvBufferPos] = ( UCHAR )( ucResult << 4 ); + eBytePos = BYTE_LOW_NIBBLE; + break; + } + else + { + /* not handled in Modbus specification but seems + * a resonable implementation. */ + eRcvState = STATE_M_RX_ERROR; + /* Disable previously activated timer because of error state. */ + vMBPortTimersDisable( ); + } + break; + + case BYTE_LOW_NIBBLE: + ucMasterASCIIRcvBuf[usMasterRcvBufferPos] |= ucResult; + usMasterRcvBufferPos++; + eBytePos = BYTE_HIGH_NIBBLE; + break; + } + } + break; + + case STATE_M_RX_WAIT_EOF: + if( ucByte == ucMBLFCharacter ) + { + /* Disable character timeout timer because all characters are + * received. */ + vMBPortTimersDisable( ); + /* Receiver is again in idle state. */ + eRcvState = STATE_M_RX_IDLE; + + /* Notify the caller of eMBMasterASCIIReceive that a new frame + * was received. */ + (void)xMBMasterPortEventPost( EV_MASTER_FRAME_RECEIVED ); + } + else if( ucByte == ':' ) + { + /* Start of frame character received but last message is not completed. + * Empty receive buffer and back to receive state. */ + eBytePos = BYTE_HIGH_NIBBLE; + usMasterRcvBufferPos = 0; + eRcvState = STATE_M_RX_IDLE; + + /* Enable timer for respond timeout and wait for next frame. */ + vMBMasterPortTimersRespondTimeoutEnable( ); + } + else + { + /* Frame is not okay. Delete entire frame. */ + eRcvState = STATE_M_RX_IDLE; + } + break; + } + + return xNeedPoll; +} + +BOOL +xMBMasterASCIITransmitFSM( void ) +{ + BOOL xNeedPoll = TRUE; + UCHAR ucByte; + BOOL xFrameIsBroadcast = FALSE; + + assert( eRcvState == STATE_M_RX_IDLE ); + + switch ( eSndState ) + { + /* We should not get a transmitter event if the transmitter is in + * idle state. */ + case STATE_M_TX_XFWR: + break; + + /* We should not get a transmitter event if the transmitter is in + * idle state. */ + case STATE_M_TX_IDLE: + break; + + /* Start of transmission. The start of a frame is defined by sending + * the character ':'. */ + case STATE_M_TX_START: + ucByte = ':'; + xMBMasterPortSerialPutByte( ( CHAR )ucByte ); + eSndState = STATE_M_TX_DATA; + eBytePos = BYTE_HIGH_NIBBLE; + break; + + /* Send the data block. Each data byte is encoded as a character hex + * stream with the high nibble sent first and the low nibble sent + * last. If all data bytes are exhausted we send a '\r' character + * to end the transmission. */ + case STATE_M_TX_DATA: + if( usMasterSndBufferCount > 0 ) + { + switch ( eBytePos ) + { + case BYTE_HIGH_NIBBLE: + ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucMasterSndBufferCur >> 4 ) ); + xMBMasterPortSerialPutByte( ( CHAR ) ucByte ); + eBytePos = BYTE_LOW_NIBBLE; + break; + + case BYTE_LOW_NIBBLE: + ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucMasterSndBufferCur & 0x0F ) ); + xMBMasterPortSerialPutByte( ( CHAR )ucByte ); + pucMasterSndBufferCur++; + eBytePos = BYTE_HIGH_NIBBLE; + usMasterSndBufferCount--; + break; + } + } + else + { + xMBMasterPortSerialPutByte( MB_ASCII_DEFAULT_CR ); + eSndState = STATE_M_TX_END; + } + break; + + /* Finish the frame by sending a LF character. */ + case STATE_M_TX_END: + xMBMasterPortSerialPutByte( ( CHAR )ucMBLFCharacter ); + /* We need another state to make sure that the CR character has + * been sent. */ + eSndState = STATE_M_TX_NOTIFY; + break; + + /* Notify the task which called eMBMasterASCIISend that the frame has + * been sent. */ + case STATE_M_TX_NOTIFY: + xFrameIsBroadcast = ( ucMasterASCIISndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; + vMBMasterRequestSetType( xFrameIsBroadcast ); + eSndState = STATE_M_TX_XFWR; + /* If the frame is broadcast ,master will enable timer of convert delay, + * else master will enable timer of respond timeout. */ + if ( xFrameIsBroadcast == TRUE ) + { + vMBMasterPortTimersConvertDelayEnable( ); + } + else + { + vMBMasterPortTimersRespondTimeoutEnable( ); + } + xNeedPoll = FALSE; + break; + } + + return xNeedPoll; +} + +BOOL +xMBMasterASCIITimerT1SExpired( void ) +{ + BOOL xNeedPoll = FALSE; + + switch ( eRcvState ) + { + /* Timer t35 expired. Startup phase is finished. */ + case STATE_M_RX_INIT: + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY); + ESP_EARLY_LOGI("xMBMasterASCIITimerT1SExpired", "RX_INIT_EXPIRED"); + break; + + /* Start of message is not received during respond timeout. + * Process error. */ + case STATE_M_RX_IDLE: + eRcvState = STATE_M_RX_ERROR; + break; + + /* A recieve timeout expired and no any new character received. + * Wait for respond time and go to error state to inform listener about error */ + case STATE_M_RX_RCV: + eRcvState = STATE_M_RX_ERROR; + break; + + /* An error occured while receiving the frame. */ + case STATE_M_RX_ERROR: + vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); + xNeedPoll = xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); + break; + + /* If we have a timeout we go back to the idle state and wait for + * the next frame. + */ + case STATE_M_RX_WAIT_EOF: + eRcvState = STATE_M_RX_IDLE; + break; + + default: + assert( 0 ); + break; + } + eRcvState = STATE_M_RX_IDLE; + + switch (eSndState) + { + /* A frame was send finish and convert delay or respond timeout expired. + * If the frame is broadcast,The master will idle,and if the frame is not + * broadcast.*/ + case STATE_M_TX_XFWR: + if ( xMBMasterRequestIsBroadcast( ) == FALSE ) { + vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT); + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS); + } + break; + + /* Function called in an illegal state. */ + default: + assert( ( eSndState == STATE_M_TX_START ) || ( eSndState == STATE_M_TX_IDLE ) + || ( eSndState == STATE_M_TX_DATA ) || ( eSndState == STATE_M_TX_END ) + || ( eSndState == STATE_M_TX_NOTIFY ) ); + break; + } + eSndState = STATE_M_TX_IDLE; + + vMBMasterPortTimersDisable( ); + /* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */ + if (xMBMasterGetCurTimerMode() == MB_TMODE_CONVERT_DELAY) { + xNeedPoll = xMBMasterPortEventPost( EV_MASTER_EXECUTE ); + } + + vMBMasterPortTimersDisable( ); + + /* no context switch required. */ + return xNeedPoll; +} + +static UCHAR +prvucMBCHAR2BIN( UCHAR ucCharacter ) +{ + if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) ) + { + return ( UCHAR )( ucCharacter - '0' ); + } + else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) ) + { + return ( UCHAR )( ucCharacter - 'A' + 0x0A ); + } + else + { + return 0xFF; + } +} + +static UCHAR +prvucMBBIN2CHAR( UCHAR ucByte ) +{ + if( ucByte <= 0x09 ) + { + return ( UCHAR )( '0' + ucByte ); + } + else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) ) + { + return ( UCHAR )( ucByte - 0x0A + 'A' ); + } + else + { + /* Programming error. */ + assert( 0 ); + } + return '0'; +} + +static UCHAR +prvucMBLRC( UCHAR * pucFrame, USHORT usLen ) +{ + UCHAR ucLRC = 0; /* LRC char initialized */ + + while( usLen-- ) + { + ucLRC += *pucFrame++; /* Add buffer byte without carry */ + } + + /* Return twos complement */ + ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) ); + return ucLRC; +} + +#endif diff --git a/components/freemodbus/modbus/functions/mbfunccoils.c b/components/freemodbus/modbus/functions/mbfunccoils.c index 54227b335..eac8ef887 100644 --- a/components/freemodbus/modbus/functions/mbfunccoils.c +++ b/components/freemodbus/modbus/functions/mbfunccoils.c @@ -62,8 +62,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED -#if MB_FUNC_READ_COILS_ENABLED > 0 +#if MB_FUNC_READ_COILS_ENABLED eMBException eMBFuncReadCoils( UCHAR * pucFrame, USHORT * usLen ) @@ -268,3 +269,5 @@ eMBFuncWriteMultipleCoils( UCHAR * pucFrame, USHORT * usLen ) #endif #endif + +#endif diff --git a/components/freemodbus/modbus/functions/mbfunccoils_m.c b/components/freemodbus/modbus/functions/mbfunccoils_m.c index 744806a1e..ba3e28d73 100644 --- a/components/freemodbus/modbus/functions/mbfunccoils_m.c +++ b/components/freemodbus/modbus/functions/mbfunccoils_m.c @@ -36,7 +36,6 @@ #include "port.h" /* ----------------------- Modbus includes ----------------------------------*/ -//#include "mb.h" #include "mb_m.h" #include "mbframe.h" #include "mbproto.h" @@ -72,8 +71,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ -#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0 -#if MB_FUNC_READ_COILS_ENABLED > 0 +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED + +#if MB_FUNC_READ_COILS_ENABLED /** * This function will request read coil. @@ -103,7 +103,7 @@ eMBMasterReqReadCoils( UCHAR ucSndAddr, USHORT usCoilAddr, USHORT usNCoils, LONG ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF ] = usNCoils >> 8; ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF + 1] = usNCoils; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } @@ -210,7 +210,7 @@ eMBMasterReqWriteCoil( UCHAR ucSndAddr, USHORT usCoilAddr, USHORT usCoilData, LO ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF ] = usCoilData >> 8; ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = usCoilData; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; @@ -320,7 +320,7 @@ eMBMasterReqWriteMultipleCoils( UCHAR ucSndAddr, *ucMBFrame++ = pucDataBuffer[usRegIndex++]; } vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + ucByteCount ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; diff --git a/components/freemodbus/modbus/functions/mbfuncdisc.c b/components/freemodbus/modbus/functions/mbfuncdisc.c index fb378441b..ef8b9971a 100644 --- a/components/freemodbus/modbus/functions/mbfuncdisc.c +++ b/components/freemodbus/modbus/functions/mbfuncdisc.c @@ -17,8 +17,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - - /* ----------------------- System includes ----------------------------------*/ #include "stdlib.h" #include "string.h" @@ -42,8 +40,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED -#if MB_FUNC_READ_COILS_ENABLED > 0 +#if MB_FUNC_READ_COILS_ENABLED eMBException eMBFuncReadDiscreteInputs( UCHAR * pucFrame, USHORT * usLen ) @@ -123,3 +122,6 @@ eMBFuncReadDiscreteInputs( UCHAR * pucFrame, USHORT * usLen ) } #endif + +#endif + diff --git a/components/freemodbus/modbus/functions/mbfuncdisc_m.c b/components/freemodbus/modbus/functions/mbfuncdisc_m.c index 34e2d0c94..debc52d38 100644 --- a/components/freemodbus/modbus/functions/mbfuncdisc_m.c +++ b/components/freemodbus/modbus/functions/mbfuncdisc_m.c @@ -55,8 +55,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ -#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0 -#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0 +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED + +#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED /** * This function will request read discrete inputs. @@ -86,7 +87,7 @@ eMBMasterReqReadDiscreteInputs( UCHAR ucSndAddr, USHORT usDiscreteAddr, USHORT u ucMBFrame[MB_PDU_REQ_READ_DISCCNT_OFF ] = usNDiscreteIn >> 8; ucMBFrame[MB_PDU_REQ_READ_DISCCNT_OFF + 1] = usNDiscreteIn; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; diff --git a/components/freemodbus/modbus/functions/mbfuncholding.c b/components/freemodbus/modbus/functions/mbfuncholding.c index e74a0624d..534c7b153 100644 --- a/components/freemodbus/modbus/functions/mbfuncholding.c +++ b/components/freemodbus/modbus/functions/mbfuncholding.c @@ -70,8 +70,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED -#if MB_FUNC_WRITE_HOLDING_ENABLED > 0 +#if MB_FUNC_WRITE_HOLDING_ENABLED eMBException eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) @@ -306,3 +307,5 @@ eMBFuncReadWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) } #endif + +#endif diff --git a/components/freemodbus/modbus/functions/mbfuncholding_m.c b/components/freemodbus/modbus/functions/mbfuncholding_m.c index a0c526d33..1cbd876eb 100644 --- a/components/freemodbus/modbus/functions/mbfuncholding_m.c +++ b/components/freemodbus/modbus/functions/mbfuncholding_m.c @@ -83,8 +83,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ -#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0 -#if MB_FUNC_WRITE_HOLDING_ENABLED > 0 +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED + +#if MB_FUNC_WRITE_HOLDING_ENABLED /** * This function will request write holding register. @@ -114,7 +115,7 @@ eMBMasterReqWriteHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRe ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF] = usRegData >> 8; ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = usRegData ; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; @@ -192,7 +193,7 @@ eMBMasterReqWriteMultipleHoldingRegister( UCHAR ucSndAddr, *ucMBFrame++ = pusDataBuffer[usRegIndex++] ; } vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + 2*usNRegs ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; @@ -278,7 +279,7 @@ eMBMasterReqReadHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRe ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF] = usNRegs >> 8; ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1] = usNRegs; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; @@ -384,7 +385,7 @@ eMBMasterReqReadWriteMultipleHoldingRegister( UCHAR ucSndAddr, *ucMBFrame++ = pusDataBuffer[usRegIndex++] ; } vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READWRITE_SIZE_MIN + 2*usNWriteRegs ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; diff --git a/components/freemodbus/modbus/functions/mbfuncinput.c b/components/freemodbus/modbus/functions/mbfuncinput.c index cceedbafe..a54deaf4f 100644 --- a/components/freemodbus/modbus/functions/mbfuncinput.c +++ b/components/freemodbus/modbus/functions/mbfuncinput.c @@ -53,7 +53,9 @@ eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); /* ----------------------- Start implementation -----------------------------*/ -#if MB_FUNC_READ_INPUT_ENABLED > 0 +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED + +#if MB_FUNC_READ_INPUT_ENABLED eMBException eMBFuncReadInputRegister( UCHAR * pucFrame, USHORT * usLen ) @@ -120,3 +122,5 @@ eMBFuncReadInputRegister( UCHAR * pucFrame, USHORT * usLen ) } #endif + +#endif diff --git a/components/freemodbus/modbus/functions/mbfuncinput_m.c b/components/freemodbus/modbus/functions/mbfuncinput_m.c index 6ae3ab11e..aeb7bc253 100644 --- a/components/freemodbus/modbus/functions/mbfuncinput_m.c +++ b/components/freemodbus/modbus/functions/mbfuncinput_m.c @@ -86,7 +86,7 @@ eMBMasterReqReadInputRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF] = usNRegs >> 8; ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1] = usNRegs; vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE ); - ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_SENT ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT ); eErrStatus = eMBMasterWaitRequestFinish( ); } return eErrStatus; diff --git a/components/freemodbus/modbus/functions/mbfuncother.c b/components/freemodbus/modbus/functions/mbfuncother.c index fcee972bf..2efec46dd 100644 --- a/components/freemodbus/modbus/functions/mbfuncother.c +++ b/components/freemodbus/modbus/functions/mbfuncother.c @@ -41,7 +41,9 @@ #include "mbproto.h" #include "mbconfig.h" -#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0 +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED + +#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED /* ----------------------- Static variables ---------------------------------*/ static UCHAR ucMBSlaveID[MB_FUNC_OTHER_REP_SLAVEID_BUF]; @@ -86,3 +88,5 @@ eMBFuncReportSlaveID( UCHAR * pucFrame, USHORT * usLen ) } #endif + +#endif diff --git a/components/freemodbus/modbus/include/mb_m.h b/components/freemodbus/modbus/include/mb_m.h index f25053282..23c0062b8 100644 --- a/components/freemodbus/modbus/include/mb_m.h +++ b/components/freemodbus/modbus/include/mb_m.h @@ -442,6 +442,8 @@ void vMBMasterSetCBRunInMasterMode( BOOL IsMasterMode ); USHORT usMBMasterGetPDUSndLength( void ); void vMBMasterSetPDUSndLength( USHORT SendPDULength ); void vMBMasterSetCurTimerMode( eMBMasterTimerMode eMBTimerMode ); +void vMBMasterRequestSetType( BOOL xIsBroadcast ); +eMBMasterTimerMode xMBMasterGetCurTimerMode( void ); BOOL xMBMasterRequestIsBroadcast( void ); eMBMasterErrorEventType eMBMasterGetErrorType( void ); void vMBMasterSetErrorType( eMBMasterErrorEventType errorType ); diff --git a/components/freemodbus/modbus/include/mbconfig.h b/components/freemodbus/modbus/include/mbconfig.h index 72971e448..be97b5268 100644 --- a/components/freemodbus/modbus/include/mbconfig.h +++ b/components/freemodbus/modbus/include/mbconfig.h @@ -50,24 +50,39 @@ PR_BEGIN_EXTERN_C * @{ */ /*! \brief If Modbus Master ASCII support is enabled. */ -#define MB_MASTER_ASCII_ENABLED ( 0 ) +#define MB_MASTER_ASCII_ENABLED ( CONFIG_FMB_COMM_MODE_ASCII_EN ) /*! \brief If Modbus Master RTU support is enabled. */ -#define MB_MASTER_RTU_ENABLED ( 1 ) +#define MB_MASTER_RTU_ENABLED ( CONFIG_FMB_COMM_MODE_RTU_EN ) /*! \brief If Modbus Master TCP support is enabled. */ #define MB_MASTER_TCP_ENABLED ( 0 ) /*! \brief If Modbus Slave ASCII support is enabled. */ -#define MB_SLAVE_ASCII_ENABLED ( 1 ) +#define MB_SLAVE_ASCII_ENABLED ( CONFIG_FMB_COMM_MODE_ASCII_EN ) /*! \brief If Modbus Slave RTU support is enabled. */ -#define MB_SLAVE_RTU_ENABLED ( 1 ) +#define MB_SLAVE_RTU_ENABLED ( CONFIG_FMB_COMM_MODE_RTU_EN ) /*! \brief If Modbus Slave TCP support is enabled. */ #define MB_TCP_ENABLED ( 1 ) +#if !CONFIG_FMB_COMM_MODE_ASCII_EN && !CONFIG_FMB_COMM_MODE_RTU_EN +#error "None of Modbus communication mode is enabled. Please enable one of ASCII or RTU mode in Kconfig." +#endif + +/*! \brief This option defines the number of data bits per ASCII character. + * + * A parity bit is added before the stop bit which keeps the actual byte size at 10 bits. + */ +#ifdef CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB +#define MB_ASCII_BITS_PER_SYMB ( CONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB ) +#endif + /*! \brief The character timeout value for Modbus ASCII. * * The character timeout value is not fixed for Modbus ASCII and is therefore * a configuration option. It should be set to the maximum expected delay * time of the network. */ -#define MB_ASCII_TIMEOUT_SEC ( 1 ) +#ifdef CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS +#define MB_ASCII_TIMEOUT_MS ( CONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS ) +#endif + /*! \brief Timeout to wait in ASCII prior to enabling transmitter. * * If defined the function calls vMBPortSerialDelay with the argument diff --git a/components/freemodbus/modbus/include/mbframe.h b/components/freemodbus/modbus/include/mbframe.h index 99d59c613..92953adc7 100644 --- a/components/freemodbus/modbus/include/mbframe.h +++ b/components/freemodbus/modbus/include/mbframe.h @@ -66,6 +66,12 @@ PR_BEGIN_EXTERN_C #define MB_PDU_FUNC_OFF 0 /*!< Offset of function code in PDU. */ #define MB_PDU_DATA_OFF 1 /*!< Offset for response data in PDU. */ +#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus frame. */ +#define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */ +#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ +#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ +#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */ + /* ----------------------- Prototypes 0-------------------------------------*/ typedef void ( *pvMBFrameStart ) ( void ); diff --git a/components/freemodbus/modbus/include/mbport.h b/components/freemodbus/modbus/include/mbport.h index aef79b511..9b591ce12 100644 --- a/components/freemodbus/modbus/include/mbport.h +++ b/components/freemodbus/modbus/include/mbport.h @@ -58,7 +58,8 @@ typedef enum EV_READY = 0x01, /*!< Startup finished. */ EV_FRAME_RECEIVED = 0x02, /*!< Frame received. */ EV_EXECUTE = 0x04, /*!< Execute function. */ - EV_FRAME_SENT = 0x08 /*!< Frame sent. */ + EV_FRAME_SENT = 0x08, /*!< Frame sent. */ + EV_FRAME_TRANSMIT = 0x10 /*!< Frame transmit. */ } eMBEventType; #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED @@ -68,7 +69,7 @@ typedef enum { EV_MASTER_FRAME_RECEIVED = 0x0002, /*!< Frame received. */ EV_MASTER_EXECUTE = 0x0004, /*!< Execute function. */ EV_MASTER_FRAME_SENT = 0x0008, /*!< Frame sent. */ - EV_MASTER_FRAME_TRANSMITTED = 0x0010, /*!< Request execute function error. */ + EV_MASTER_FRAME_TRANSMIT = 0x0010, /*!< Frame transmission. */ EV_MASTER_ERROR_PROCESS = 0x0020, /*!< Frame error process. */ EV_MASTER_PROCESS_SUCCESS = 0x0040, /*!< Request process success. */ EV_MASTER_ERROR_RESPOND_TIMEOUT = 0x0080, /*!< Request respond timeout. */ diff --git a/components/freemodbus/modbus/mb.c b/components/freemodbus/modbus/mb.c index f0d348f7f..d6b75306a 100644 --- a/components/freemodbus/modbus/mb.c +++ b/components/freemodbus/modbus/mb.c @@ -62,6 +62,8 @@ static UCHAR ucMBAddress; static eMBMode eMBCurrentMode; +volatile UCHAR ucMbSlaveBuf[MB_SER_PDU_SIZE_MAX]; + static enum { STATE_ENABLED, @@ -353,9 +355,11 @@ eMBPoll( void ) switch ( eEvent ) { case EV_READY: + ESP_LOGD(MB_PORT_TAG, "%s:EV_READY", __func__); break; case EV_FRAME_RECEIVED: + ESP_LOGD(MB_PORT_TAG, "EV_FRAME_RECEIVED"); eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); if( eStatus == MB_ENOERR ) { @@ -365,9 +369,11 @@ eMBPoll( void ) ( void )xMBPortEventPost( EV_EXECUTE ); } } + ESP_LOG_BUFFER_HEX_LEVEL(MB_PORT_TAG, &ucMBFrame[MB_PDU_FUNC_OFF], usLength, ESP_LOG_DEBUG); break; case EV_EXECUTE: + ESP_LOGD(MB_PORT_TAG, "%s:EV_EXECUTE", __func__); ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; eException = MB_EX_ILLEGAL_FUNCTION; for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) @@ -402,8 +408,13 @@ eMBPoll( void ) eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); } break; + + case EV_FRAME_TRANSMIT: + ESP_LOGD(MB_PORT_TAG, "%s:EV_FRAME_TRANSMIT", __func__); + break; case EV_FRAME_SENT: + ESP_LOGD(MB_PORT_TAG, "%s:EV_FRAME_SENT", __func__); break; } } diff --git a/components/freemodbus/modbus/mb_m.c b/components/freemodbus/modbus/mb_m.c index 04c0efcfb..43b8028b7 100644 --- a/components/freemodbus/modbus/mb_m.c +++ b/components/freemodbus/modbus/mb_m.c @@ -48,7 +48,7 @@ #include "mbrtu.h" #endif #if MB_MASTER_ASCII_ENABLED == 1 -#include "mbascii_m.h" +#include "mbascii.h" #endif #if MB_MASTER_TCP_ENABLED == 1 #include "mbtcp.h" @@ -65,6 +65,14 @@ static UCHAR ucMBMasterDestAddress; static BOOL xMBRunInMasterMode = FALSE; static volatile eMBMasterErrorEventType eMBMasterCurErrorType; +static volatile USHORT usMasterSendPDULength; + +/*------------------------ Shared variables ---------------------------------*/ + +volatile UCHAR ucMasterSndBuf[MB_PDU_SIZE_MAX]; +volatile UCHAR ucMasterRcvBuf[MB_SER_PDU_SIZE_MAX]; +volatile eMBMasterTimerMode eMasterCurTimerMode; +volatile BOOL xFrameIsBroadcast = FALSE; static enum { @@ -157,7 +165,7 @@ eMBMasterInit( eMBMode eMode, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity break; #endif #if MB_MASTER_ASCII_ENABLED > 0 - case MB_ASCII: + case MB_ASCII: pvMBMasterFrameStartCur = eMBMasterASCIIStart; pvMBMasterFrameStopCur = eMBMasterASCIIStop; peMBMasterFrameSendCur = eMBMasterASCIISend; @@ -350,8 +358,8 @@ eMBMasterPoll( void ) vMBMasterRunResRelease( ); } break; - case EV_MASTER_FRAME_SENT: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_SENT", __func__); + case EV_MASTER_FRAME_TRANSMIT: + ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_TRANSMIT", __func__); /* Master is busy now. */ vMBMasterGetPDUSndBuf( &ucMBFrame ); eStatus = peMBMasterFrameSendCur( ucMBMasterGetDestAddress(), ucMBFrame, usMBMasterGetPDUSndLength() ); @@ -360,15 +368,16 @@ eMBMasterPoll( void ) } break; - case EV_MASTER_FRAME_TRANSMITTED: - ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_TRANSMITTED", __func__); + case EV_MASTER_FRAME_SENT: + ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_FRAME_SENT", __func__); break; case EV_MASTER_ERROR_PROCESS: ESP_LOGD(MB_PORT_TAG, "%s:EV_MASTER_ERROR_PROCESS", __func__); /* Execute specified error process callback function. */ errorType = eMBMasterGetErrorType(); vMBMasterGetPDUSndBuf( &ucMBFrame ); - switch (errorType) { + switch (errorType) + { case EV_ERROR_RESPOND_TIMEOUT: vMBMasterErrorCBRespondTimeout(ucMBMasterGetDestAddress(), ucMBFrame, usMBMasterGetPDUSndLength()); @@ -420,7 +429,7 @@ void vMBMasterSetDestAddress( UCHAR Address ) } // Get Modbus Master current error event type. -eMBMasterErrorEventType eMBMasterGetErrorType( void ) +eMBMasterErrorEventType inline eMBMasterGetErrorType( void ) { return eMBMasterCurErrorType; } @@ -431,4 +440,45 @@ void IRAM_ATTR vMBMasterSetErrorType( eMBMasterErrorEventType errorType ) eMBMasterCurErrorType = errorType; } +/* Get Modbus Master send PDU's buffer address pointer.*/ +void vMBMasterGetPDUSndBuf( UCHAR ** pucFrame ) +{ + *pucFrame = ( UCHAR * ) &ucMasterSndBuf[MB_SER_PDU_PDU_OFF]; +} + +/* Set Modbus Master send PDU's buffer length.*/ +void vMBMasterSetPDUSndLength( USHORT SendPDULength ) +{ + usMasterSendPDULength = SendPDULength; +} + +/* Get Modbus Master send PDU's buffer length.*/ +USHORT usMBMasterGetPDUSndLength( void ) +{ + return usMasterSendPDULength; +} + +/* Set Modbus Master current timer mode.*/ +void vMBMasterSetCurTimerMode( eMBMasterTimerMode eMBTimerMode ) +{ + eMasterCurTimerMode = eMBTimerMode; +} + +/* Get Modbus Master current timer mode.*/ +eMBMasterTimerMode xMBMasterGetCurTimerMode( void ) +{ + return eMasterCurTimerMode; +} + +/* The master request is broadcast? */ +BOOL xMBMasterRequestIsBroadcast( void ){ + return xFrameIsBroadcast; +} + +/* The master request is broadcast? */ +void vMBMasterRequestSetType( BOOL xIsBroadcast ){ + xFrameIsBroadcast = xIsBroadcast; +} + + #endif // MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0 diff --git a/components/freemodbus/modbus/rtu/mbcrc.c b/components/freemodbus/modbus/rtu/mbcrc.c index 29b9ea765..1cd0a6ec8 100644 --- a/components/freemodbus/modbus/rtu/mbcrc.c +++ b/components/freemodbus/modbus/rtu/mbcrc.c @@ -30,6 +30,9 @@ /* ----------------------- Platform includes --------------------------------*/ #include "port.h" +#include "mbconfig.h" + +#if MB_MASTER_RTU_ENABLED || MB_SLAVE_RTU_ENABLED static const UCHAR aucCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, @@ -96,3 +99,5 @@ usMBCRC16( UCHAR * pucFrame, USHORT usLen ) } return ( USHORT )( ucCRCHi << 8 | ucCRCLo ); } + +#endif diff --git a/components/freemodbus/modbus/rtu/mbrtu.c b/components/freemodbus/modbus/rtu/mbrtu.c index 0fbebcbe2..434ac6e7a 100644 --- a/components/freemodbus/modbus/rtu/mbrtu.c +++ b/components/freemodbus/modbus/rtu/mbrtu.c @@ -43,12 +43,7 @@ #include "mbcrc.h" #include "mbport.h" -/* ----------------------- Defines ------------------------------------------*/ -#define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */ -#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */ -#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */ -#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ -#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ +#if MB_SLAVE_RTU_ENABLED > 0 /* ----------------------- Type definitions ---------------------------------*/ typedef enum @@ -65,16 +60,18 @@ typedef enum STATE_TX_XMIT /*!< Transmitter is in transfer state. */ } eMBSndState; +/* ----------------------- Shared variables ---------------------------------*/ +extern volatile UCHAR ucMbSlaveBuf[]; + /* ----------------------- Static variables ---------------------------------*/ static volatile eMBSndState eSndState; static volatile eMBRcvState eRcvState; -volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX]; - static volatile UCHAR *pucSndBufferCur; static volatile USHORT usSndBufferCount; static volatile USHORT usRcvBufferPos; +static volatile UCHAR *ucRTUBuf = ucMbSlaveBuf; /* ----------------------- Start implementation -----------------------------*/ eMBErrorCode @@ -223,13 +220,13 @@ eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) BOOL xMBRTUReceiveFSM( void ) { - BOOL xTaskNeedSwitch = FALSE; + BOOL xStatus = FALSE; UCHAR ucByte; assert( eSndState == STATE_TX_IDLE ); /* Always read the character. */ - ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); + xStatus = xMBPortSerialGetByte( ( CHAR * ) & ucByte ); switch ( eRcvState ) { @@ -277,13 +274,14 @@ xMBRTUReceiveFSM( void ) vMBPortTimersEnable( ); break; } - return xTaskNeedSwitch; + + return xStatus; } BOOL xMBRTUTransmitFSM( void ) { - BOOL xNeedPoll = FALSE; + BOOL xNeedPoll = TRUE; assert( eRcvState == STATE_RX_IDLE ); @@ -304,8 +302,8 @@ xMBRTUTransmitFSM( void ) } else { - xMBPortEventPost( EV_FRAME_SENT ); - xNeedPoll = TRUE; + xMBPortEventPost( EV_FRAME_TRANSMIT ); + xNeedPoll = FALSE; eSndState = STATE_TX_IDLE; vMBPortTimersEnable( ); } @@ -318,7 +316,7 @@ xMBRTUTransmitFSM( void ) BOOL MB_PORT_ISR_ATTR xMBRTUTimerT35Expired( void ) { - BOOL xNeedPoll = FALSE; + BOOL xNeedPoll = FALSE; switch ( eRcvState ) { @@ -347,3 +345,4 @@ xMBRTUTimerT35Expired( void ) return xNeedPoll; } +#endif diff --git a/components/freemodbus/modbus/rtu/mbrtu.h b/components/freemodbus/modbus/rtu/mbrtu.h index 223c63547..e2c8e4c45 100644 --- a/components/freemodbus/modbus/rtu/mbrtu.h +++ b/components/freemodbus/modbus/rtu/mbrtu.h @@ -35,7 +35,12 @@ #ifdef __cplusplus PR_BEGIN_EXTERN_C #endif - eMBErrorCode eMBRTUInit( UCHAR slaveAddress, UCHAR ucPort, ULONG ulBaudRate, + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */ + +#if MB_SLAVE_RTU_ENABLED > 0 +eMBErrorCode eMBRTUInit( UCHAR slaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ); void eMBRTUStart( void ); void eMBRTUStop( void ); @@ -45,6 +50,7 @@ BOOL xMBRTUReceiveFSM( void ); BOOL xMBRTUTransmitFSM( void ); BOOL xMBRTUTimerT15Expired( void ); BOOL xMBRTUTimerT35Expired( void ); +#endif #if MB_MASTER_RTU_ENABLED > 0 eMBErrorCode eMBMasterRTUInit( UCHAR ucPort, ULONG ulBaudRate,eMBParity eParity ); diff --git a/components/freemodbus/modbus/rtu/mbrtu_m.c b/components/freemodbus/modbus/rtu/mbrtu_m.c index 129c2d540..f9abd1273 100644 --- a/components/freemodbus/modbus/rtu/mbrtu_m.c +++ b/components/freemodbus/modbus/rtu/mbrtu_m.c @@ -45,13 +45,8 @@ #include "mbcrc.h" #include "mbport.h" -#if MB_MASTER_RTU_ENABLED > 0 /* ----------------------- Defines ------------------------------------------*/ -#define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */ -#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */ -#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */ -#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ -#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ +#define MB_RTU_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */ /* ----------------------- Type definitions ---------------------------------*/ typedef enum @@ -59,7 +54,7 @@ typedef enum STATE_M_RX_INIT, /*!< Receiver is in initial state. */ STATE_M_RX_IDLE, /*!< Receiver is in idle state. */ STATE_M_RX_RCV, /*!< Frame is beeing received. */ - STATE_M_RX_ERROR, /*!< If the frame is invalid. */ + STATE_M_RX_ERROR, /*!< If the frame is invalid. */ } eMBMasterRcvState; typedef enum @@ -69,24 +64,21 @@ typedef enum STATE_M_TX_XFWR, /*!< Transmitter is in transfer finish and wait receive state. */ } eMBMasterSndState; +#if MB_MASTER_RTU_ENABLED > 0 /*------------------------ Shared variables ---------------------------------*/ - -volatile UCHAR ucMasterRTUSndBuf[MB_PDU_SIZE_MAX]; -volatile UCHAR ucMasterRTURcvBuf[MB_SER_PDU_SIZE_MAX]; +extern volatile UCHAR ucMasterRcvBuf[]; +extern volatile UCHAR ucMasterSndBuf[]; /* ----------------------- Static variables ---------------------------------*/ -static volatile eMBMasterSndState eSndState; -static volatile eMBMasterRcvState eRcvState; +static volatile eMBMasterSndState eSndState; +static volatile eMBMasterRcvState eRcvState; -static volatile USHORT usMasterSendPDULength; +static volatile UCHAR *pucMasterSndBufferCur; +static volatile USHORT usMasterSndBufferCount; +static volatile USHORT usMasterRcvBufferPos; -static volatile UCHAR *pucMasterSndBufferCur; -static volatile USHORT usMasterSndBufferCount; - -static volatile USHORT usMasterRcvBufferPos; -static volatile BOOL xFrameIsBroadcast = FALSE; - -static volatile eMBMasterTimerMode eMasterCurTimerMode; +static volatile UCHAR *ucMasterRTURcvBuf = ucMasterRcvBuf; +static volatile UCHAR *ucMasterRTUSndBuf = ucMasterSndBuf; /* ----------------------- Start implementation -----------------------------*/ eMBErrorCode @@ -142,7 +134,7 @@ eMBMasterRTUStart( void ) * to STATE_M_RX_IDLE. This makes sure that we delay startup of the * modbus protocol stack until the bus is free. */ - eRcvState = STATE_M_RX_INIT; + eRcvState = STATE_M_RX_IDLE; //STATE_M_RX_INIT (We start processing immediately) vMBMasterPortSerialEnable( TRUE, FALSE ); vMBMasterPortTimersT35Enable( ); @@ -167,7 +159,7 @@ eMBMasterRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLengt assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX ); /* Length and CRC check */ - if( ( usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) + if( ( usMasterRcvBufferPos >= MB_RTU_SER_PDU_SIZE_MIN ) && ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed @@ -237,13 +229,13 @@ eMBMasterRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength BOOL xMBMasterRTUReceiveFSM( void ) { - BOOL xTaskNeedSwitch = FALSE; + BOOL xStatus = FALSE; UCHAR ucByte; assert(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR )); /* Always read the character. */ - ( void )xMBMasterPortSerialGetByte( ( CHAR * ) & ucByte ); + xStatus = xMBMasterPortSerialGetByte( ( CHAR * ) & ucByte ); switch ( eRcvState ) { @@ -267,11 +259,11 @@ xMBMasterRTUReceiveFSM( void ) * the timer of respond timeout . */ case STATE_M_RX_IDLE: - /* In time of respond timeout,the receiver receive a frame. - * Disable timer of respond timeout and change the transmiter state to idle. - */ - vMBMasterPortTimersDisable( ); - eSndState = STATE_M_TX_IDLE; + /* In time of respond timeout,the receiver receive a frame. + * Disable timer of respond timeout and change the transmiter state to idle. + */ + vMBMasterPortTimersDisable( ); + eSndState = STATE_M_TX_IDLE; usMasterRcvBufferPos = 0; ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte; @@ -298,13 +290,14 @@ xMBMasterRTUReceiveFSM( void ) vMBMasterPortTimersT35Enable( ); break; } - return xTaskNeedSwitch; + return xStatus; } BOOL xMBMasterRTUTransmitFSM( void ) { - BOOL xNeedPoll = FALSE; + BOOL xNeedPoll = TRUE; + BOOL xFrameIsBroadcast = FALSE; assert( eRcvState == STATE_M_RX_IDLE ); @@ -313,7 +306,7 @@ xMBMasterRTUTransmitFSM( void ) /* We should not get a transmitter event if the transmitter is in * idle state. */ case STATE_M_TX_XFWR: - xNeedPoll = TRUE; + xNeedPoll = FALSE; break; case STATE_M_TX_IDLE: @@ -330,16 +323,17 @@ xMBMasterRTUTransmitFSM( void ) else { xFrameIsBroadcast = ( ucMasterRTUSndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE; + vMBMasterRequestSetType( xFrameIsBroadcast ); eSndState = STATE_M_TX_XFWR; /* If the frame is broadcast ,master will enable timer of convert delay, * else master will enable timer of respond timeout. */ if ( xFrameIsBroadcast == TRUE ) { - vMBMasterPortTimersConvertDelayEnable( ); + vMBMasterPortTimersConvertDelayEnable( ); } else { - vMBMasterPortTimersRespondTimeoutEnable( ); + vMBMasterPortTimersRespondTimeoutEnable( ); } } break; @@ -350,95 +344,62 @@ xMBMasterRTUTransmitFSM( void ) BOOL MB_PORT_ISR_ATTR xMBMasterRTUTimerExpired(void) { - BOOL xNeedPoll = FALSE; + BOOL xNeedPoll = FALSE; - switch (eRcvState) - { - /* Timer t35 expired. Startup phase is finished. */ - case STATE_M_RX_INIT: - xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY); - break; + switch (eRcvState) + { + /* Timer t35 expired. Startup phase is finished. */ + case STATE_M_RX_INIT: + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY); + break; + + /* A frame was received and t35 expired. Notify the listener that + * a new frame was received. */ + case STATE_M_RX_RCV: + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED); + break; - /* A frame was received and t35 expired. Notify the listener that - * a new frame was received. */ - case STATE_M_RX_RCV: - xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED); - break; + /* An error occured while receiving the frame. */ + case STATE_M_RX_ERROR: + vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); + xNeedPoll = xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); + break; - /* An error occured while receiving the frame. */ - case STATE_M_RX_ERROR: - vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA); - xNeedPoll = xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS ); - break; + /* Function called in an illegal state. */ + default: + assert(eRcvState == STATE_M_RX_IDLE); + break; + } + eRcvState = STATE_M_RX_IDLE; - /* Function called in an illegal state. */ - default: - assert((eRcvState == STATE_M_RX_INIT) || (eRcvState == STATE_M_RX_RCV) - || (eRcvState == STATE_M_RX_ERROR) || (eRcvState == STATE_M_RX_IDLE)); - break; - } - eRcvState = STATE_M_RX_IDLE; + switch (eSndState) + { + /* A frame was send finish and convert delay or respond timeout expired. + * If the frame is broadcast,The master will idle,and if the frame is not + * broadcast. Notify the listener process error.*/ + case STATE_M_TX_XFWR: + if ( xMBMasterRequestIsBroadcast( ) == FALSE ) { + vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT); + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS); + } + break; + /* Function called in an illegal state. */ + default: + assert( ( eSndState == STATE_M_TX_XMIT ) || ( eSndState == STATE_M_TX_IDLE )); + break; + } + eSndState = STATE_M_TX_IDLE; - switch (eSndState) - { - /* A frame was send finish and convert delay or respond timeout expired. - * If the frame is broadcast,The master will idle,and if the frame is not - * broadcast.Notify the listener process error.*/ - case STATE_M_TX_XFWR: - if ( xFrameIsBroadcast == FALSE ) { - vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT); - xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS); - } - break; - /* Function called in an illegal state. */ - default: - assert(( eSndState == STATE_M_TX_XFWR ) || ( eSndState == STATE_M_TX_IDLE )); - break; - } - eSndState = STATE_M_TX_IDLE; + vMBMasterPortTimersDisable( ); + /* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */ + if (xMBMasterGetCurTimerMode() == MB_TMODE_CONVERT_DELAY) { + xNeedPoll = xMBMasterPortEventPost( EV_MASTER_EXECUTE ); + } - vMBMasterPortTimersDisable( ); - /* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */ - if (eMasterCurTimerMode == MB_TMODE_CONVERT_DELAY) { - xNeedPoll = xMBMasterPortEventPost( EV_MASTER_EXECUTE ); - } - - return xNeedPoll; + return xNeedPoll; } -/* Get Modbus Master send RTU's buffer address pointer.*/ -void vMBMasterGetRTUSndBuf( UCHAR ** pucFrame ) -{ - *pucFrame = ( UCHAR * ) ucMasterRTUSndBuf; -} -/* Get Modbus Master send PDU's buffer address pointer.*/ -void vMBMasterGetPDUSndBuf( UCHAR ** pucFrame ) -{ - *pucFrame = ( UCHAR * ) &ucMasterRTUSndBuf[MB_SER_PDU_PDU_OFF]; -} - -/* Set Modbus Master send PDU's buffer length.*/ -void vMBMasterSetPDUSndLength( USHORT SendPDULength ) -{ - usMasterSendPDULength = SendPDULength; -} - -/* Get Modbus Master send PDU's buffer length.*/ -USHORT usMBMasterGetPDUSndLength( void ) -{ - return usMasterSendPDULength; -} - -/* Set Modbus Master current timer mode.*/ -void vMBMasterSetCurTimerMode( eMBMasterTimerMode eMBTimerMode ) -{ - eMasterCurTimerMode = eMBTimerMode; -} - -/* The master request is broadcast? */ -BOOL xMBMasterRequestIsBroadcast( void ){ - return xFrameIsBroadcast; -} #endif + diff --git a/components/freemodbus/port/port.h b/components/freemodbus/port/port.h index f405898bb..e442874e2 100644 --- a/components/freemodbus/port/port.h +++ b/components/freemodbus/port/port.h @@ -27,6 +27,11 @@ #define MB_PORT_TAG "MB_PORT_COMMON" +// common definitions for serial port implementations +#define MB_SERIAL_TX_TOUT_MS (100) +#define MB_SERIAL_TX_TOUT_TICKS pdMS_TO_TICKS(MB_SERIAL_TX_TOUT_MS) // timeout for transmission +#define MB_SERIAL_RX_TOUT_TICKS pdMS_TO_TICKS(1) // timeout for rx from buffer + #define MB_PORT_CHECK(a, ret_val, str, ...) \ if (!(a)) { \ ESP_LOGE(MB_PORT_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ diff --git a/components/freemodbus/port/portevent.c b/components/freemodbus/port/portevent.c index e9988bb58..ac5f4dfe9 100644 --- a/components/freemodbus/port/portevent.c +++ b/components/freemodbus/port/portevent.c @@ -120,7 +120,7 @@ xMBPortEventGet(eMBEventType * peEvent) xQueueHandle xMBPortEventGetHandle(void) { - if(xQueueHdl != NULL) // + if(xQueueHdl != NULL) { return xQueueHdl; } diff --git a/components/freemodbus/port/portevent_m.c b/components/freemodbus/port/portevent_m.c index a0d44b95b..abb6fd77d 100644 --- a/components/freemodbus/port/portevent_m.c +++ b/components/freemodbus/port/portevent_m.c @@ -53,7 +53,7 @@ EV_MASTER_FRAME_RECEIVED | \ EV_MASTER_EXECUTE | \ EV_MASTER_FRAME_SENT | \ - EV_MASTER_FRAME_TRANSMITTED | \ + EV_MASTER_FRAME_TRANSMIT | \ EV_MASTER_ERROR_PROCESS ) // Event bit mask for eMBMasterWaitRequestFinish() diff --git a/components/freemodbus/port/portserial.c b/components/freemodbus/port/portserial.c index 73e72807f..ce808e816 100644 --- a/components/freemodbus/port/portserial.c +++ b/components/freemodbus/port/portserial.c @@ -85,12 +85,14 @@ static UCHAR ucUartNumber = UART_NUM_MAX - 1; static BOOL bRxStateEnabled = FALSE; // Receiver enabled flag static BOOL bTxStateEnabled = FALSE; // Transmitter enabled flag -static UCHAR ucBuffer[MB_SERIAL_BUF_SIZE]; // Temporary buffer to transfer received data to modbus stack -static USHORT uiRxBufferPos = 0; // position in the receiver buffer - void vMBPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) { // This function can be called from xMBRTUTransmitFSM() of different task + if (bTxEnable) { + bTxStateEnabled = TRUE; + } else { + bTxStateEnabled = FALSE; + } if (bRxEnable) { //uart_enable_rx_intr(ucUartNumber); bRxStateEnabled = TRUE; @@ -99,35 +101,28 @@ void vMBPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) vTaskSuspend(xMbTaskHandle); // Block receiver task bRxStateEnabled = FALSE; } - if (bTxEnable) { - bTxStateEnabled = TRUE; - } else { - bTxStateEnabled = FALSE; - } } static void vMBPortSerialRxPoll(size_t xEventSize) { - USHORT usLength; + BOOL xReadStatus = TRUE; + USHORT usCnt = 0; if (bRxStateEnabled) { if (xEventSize > 0) { xEventSize = (xEventSize > MB_SERIAL_BUF_SIZE) ? MB_SERIAL_BUF_SIZE : xEventSize; - uiRxBufferPos = ((uiRxBufferPos + xEventSize) >= MB_SERIAL_BUF_SIZE) ? 0 : uiRxBufferPos; // Get received packet into Rx buffer - usLength = uart_read_bytes(ucUartNumber, &ucBuffer[uiRxBufferPos], xEventSize, portMAX_DELAY); - for(USHORT usCnt = 0; usCnt < usLength; usCnt++ ) { + for(usCnt = 0; xReadStatus && (usCnt < xEventSize); usCnt++ ) { // Call the Modbus stack callback function and let it fill the buffers. - ( void )pxMBFrameCBByteReceived(); // calls callback xMBRTUReceiveFSM() to execute MB state machine + xReadStatus = pxMBFrameCBByteReceived(); // callback to execute receive FSM state machine } - // The buffer is transferred into Modbus stack and is not needed here any more uart_flush_input(ucUartNumber); // Send event EV_FRAME_RECEIVED to allow stack process packet #ifndef MB_TIMER_PORT_ENABLED // Let the stack know that T3.5 time is expired and data is received (void)pxMBPortCBTimerExpired(); // calls callback xMBRTUTimerT35Expired(); #endif - ESP_LOGD(TAG, "Receive: %d(bytes in buffer)\n", (uint32_t)usLength); + ESP_LOGD(TAG, "RX: %d bytes\n", usCnt); } } } @@ -135,13 +130,13 @@ static void vMBPortSerialRxPoll(size_t xEventSize) BOOL xMBPortSerialTxPoll(void) { USHORT usCount = 0; - BOOL bNeedPoll = FALSE; + BOOL bNeedPoll = TRUE; if( bTxStateEnabled ) { // Continue while all response bytes put in buffer or out of buffer - while((bNeedPoll == FALSE) && (usCount++ < MB_SERIAL_BUF_SIZE)) { + while((bNeedPoll) && (usCount++ < MB_SERIAL_BUF_SIZE)) { // Calls the modbus stack callback function to let it fill the UART transmit buffer. - bNeedPoll = pxMBFrameCBTransmitterEmpty( ); // calls callback xMBRTUTransmitFSM(); + bNeedPoll = pxMBFrameCBTransmitterEmpty( ); // callback to transmit FSM state machine } ESP_LOGD(TAG, "MB_TX_buffer send: (%d) bytes\n", (uint16_t)usCount); // Waits while UART sending the packet @@ -247,17 +242,17 @@ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, // Set UART config xErr = uart_param_config(ucUartNumber, &xUartConfig); MB_PORT_CHECK((xErr == ESP_OK), - FALSE, "mb config failure, uart_param_config() returned (0x%x).", (uint32_t)xErr); + FALSE, "mb config failure, uart_param_config() returned (0x%x).", xErr); // Install UART driver, and get the queue. xErr = uart_driver_install(ucUartNumber, MB_SERIAL_BUF_SIZE, MB_SERIAL_BUF_SIZE, MB_QUEUE_LENGTH, &xMbUartQueue, MB_PORT_SERIAL_ISR_FLAG); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "mb serial driver failure, uart_driver_install() returned (0x%x).", (uint32_t)xErr); + "mb serial driver failure, uart_driver_install() returned (0x%x).", xErr); #ifndef MB_TIMER_PORT_ENABLED // Set timeout for TOUT interrupt (T3.5 modbus time) xErr = uart_set_rx_timeout(ucUartNumber, MB_SERIAL_TOUT); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "mb serial set rx timeout failure, uart_set_rx_timeout() returned (0x%x).", (uint32_t)xErr); + "mb serial set rx timeout failure, uart_set_rx_timeout() returned (0x%x).", xErr); #endif // Create a task to handle UART events BaseType_t xStatus = xTaskCreate(vUartTask, "uart_queue_task", MB_SERIAL_TASK_STACK_SIZE, @@ -267,11 +262,10 @@ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, // Force exit from function with failure MB_PORT_CHECK(FALSE, FALSE, "mb stack serial task creation error. xTaskCreate() returned (0x%x).", - (uint32_t)xStatus); + xStatus); } else { vTaskSuspend(xMbTaskHandle); // Suspend serial task while stack is not started } - uiRxBufferPos = 0; return TRUE; } @@ -294,10 +288,7 @@ BOOL xMBPortSerialPutByte(CHAR ucByte) BOOL xMBPortSerialGetByte(CHAR* pucByte) { assert(pucByte != NULL); - MB_PORT_CHECK((uiRxBufferPos < MB_SERIAL_BUF_SIZE), - FALSE, "mb stack serial get byte failure."); - *pucByte = ucBuffer[uiRxBufferPos]; - uiRxBufferPos++; - return TRUE; + USHORT usLength = uart_read_bytes(ucUartNumber, (uint8_t*)pucByte, 1, MB_SERIAL_RX_TOUT_TICKS); + return (usLength == 1); } diff --git a/components/freemodbus/port/portserial_m.c b/components/freemodbus/port/portserial_m.c index 15f4d9f00..175d2a6b4 100644 --- a/components/freemodbus/port/portserial_m.c +++ b/components/freemodbus/port/portserial_m.c @@ -62,8 +62,6 @@ // Set buffer size for transmission #define MB_SERIAL_BUF_SIZE (CONFIG_FMB_SERIAL_BUF_SIZE) -#define MB_SERIAL_TX_TOUT_MS (100) -#define MB_SERIAL_TX_TOUT_TICKS pdMS_TO_TICKS(MB_SERIAL_TX_TOUT_MS) // timeout for transmission /* ----------------------- Static variables ---------------------------------*/ static const CHAR *TAG = "MB_MASTER_SERIAL"; @@ -78,9 +76,6 @@ static UCHAR ucUartNumber = UART_NUM_MAX - 1; static BOOL bRxStateEnabled = FALSE; // Receiver enabled flag static BOOL bTxStateEnabled = FALSE; // Transmitter enabled flag -static UCHAR ucBuffer[MB_SERIAL_BUF_SIZE]; // Temporary buffer to transfer received data to modbus stack -static USHORT uiRxBufferPos = 0; // position in the receiver buffer - void vMBMasterPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) { // This function can be called from xMBRTUTransmitFSM() of different task @@ -100,42 +95,40 @@ void vMBMasterPortSerialEnable(BOOL bRxEnable, BOOL bTxEnable) static void vMBMasterPortSerialRxPoll(size_t xEventSize) { - USHORT usLength; + BOOL xReadStatus = TRUE; + USHORT usCnt = 0; if (bRxStateEnabled) { if (xEventSize > 0) { xEventSize = (xEventSize > MB_SERIAL_BUF_SIZE) ? MB_SERIAL_BUF_SIZE : xEventSize; // Get received packet into Rx buffer - usLength = uart_read_bytes(ucUartNumber, &ucBuffer[0], xEventSize, portMAX_DELAY); - uiRxBufferPos = 0; - for(USHORT usCnt = 0; usCnt < usLength; usCnt++ ) { + for(usCnt = 0; xReadStatus && (usCnt < xEventSize); usCnt++ ) { // Call the Modbus stack callback function and let it fill the stack buffers. - ( void )pxMBMasterFrameCBByteReceived(); // calls callback xMBRTUReceiveFSM() + xReadStatus = pxMBMasterFrameCBByteReceived(); // calls callback xMBRTUReceiveFSM() } // The buffer is transferred into Modbus stack and is not needed here any more uart_flush_input(ucUartNumber); - ESP_LOGD(TAG, "Receive: %d(bytes in buffer)\n", (uint32_t)usLength); + ESP_LOGD(TAG, "Received data: %d(bytes in buffer)\n", (uint32_t)usCnt); } } else { - ESP_LOGE(TAG, "%s: bRxState disabled but junk data (%d bytes) received. ", __func__, (uint16_t)xEventSize); + ESP_LOGE(TAG, "%s: bRxState disabled but junk data (%d bytes) received. ", __func__, xEventSize); } } BOOL xMBMasterPortSerialTxPoll(void) { USHORT usCount = 0; - BOOL bNeedPoll = FALSE; + BOOL bNeedPoll = TRUE; if( bTxStateEnabled ) { // Continue while all response bytes put in buffer or out of buffer - while((bNeedPoll == FALSE) && (usCount++ < MB_SERIAL_BUF_SIZE)) { + while((bNeedPoll) && (usCount++ < MB_SERIAL_BUF_SIZE)) { // Calls the modbus stack callback function to let it fill the UART transmit buffer. bNeedPoll = pxMBMasterFrameCBTransmitterEmpty( ); // calls callback xMBRTUTransmitFSM(); } ESP_LOGD(TAG, "MB_TX_buffer sent: (%d) bytes.", (uint16_t)(usCount - 1)); // Waits while UART sending the packet esp_err_t xTxStatus = uart_wait_tx_done(ucUartNumber, MB_SERIAL_TX_TOUT_TICKS); - bTxStateEnabled = FALSE; vMBMasterPortSerialEnable( TRUE, FALSE ); MB_PORT_CHECK((xTxStatus == ESP_OK), FALSE, "mb serial sent buffer failure."); return TRUE; @@ -148,7 +141,7 @@ static void vUartTask(void* pvParameters) { uart_event_t xEvent; for(;;) { - if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) { // portMAX_DELAY + if (xQueueReceive(xMbUartQueue, (void*)&xEvent, portMAX_DELAY) == pdTRUE) { ESP_LOGD(TAG, "MB_uart[%d] event:", ucUartNumber); switch(xEvent.type) { //Event of UART receiving data @@ -238,16 +231,16 @@ BOOL xMBMasterPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, // Set UART config xErr = uart_param_config(ucUartNumber, &xUartConfig); MB_PORT_CHECK((xErr == ESP_OK), - FALSE, "mb config failure, uart_param_config() returned (0x%x).", (uint32_t)xErr); + FALSE, "mb config failure, uart_param_config() returned (0x%x).", xErr); // Install UART driver, and get the queue. xErr = uart_driver_install(ucUartNumber, MB_SERIAL_BUF_SIZE, MB_SERIAL_BUF_SIZE, MB_QUEUE_LENGTH, &xMbUartQueue, MB_PORT_SERIAL_ISR_FLAG); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "mb serial driver failure, uart_driver_install() returned (0x%x).", (uint32_t)xErr); + "mb serial driver failure, uart_driver_install() returned (0x%x).", xErr); // Set timeout for TOUT interrupt (T3.5 modbus time) xErr = uart_set_rx_timeout(ucUartNumber, MB_SERIAL_TOUT); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "mb serial set rx timeout failure, uart_set_rx_timeout() returned (0x%x).", (uint32_t)xErr); + "mb serial set rx timeout failure, uart_set_rx_timeout() returned (0x%x).", xErr); // Create a task to handle UART events BaseType_t xStatus = xTaskCreate(vUartTask, "uart_queue_task", MB_SERIAL_TASK_STACK_SIZE, NULL, MB_SERIAL_TASK_PRIO, &xMbTaskHandle); @@ -256,11 +249,10 @@ BOOL xMBMasterPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, // Force exit from function with failure MB_PORT_CHECK(FALSE, FALSE, "mb stack serial task creation error. xTaskCreate() returned (0x%x).", - (uint32_t)xStatus); + xStatus); } else { vTaskSuspend(xMbTaskHandle); // Suspend serial task while stack is not started } - uiRxBufferPos = 0; ESP_LOGD(MB_PORT_TAG,"%s Init serial.", __func__); return TRUE; } @@ -283,9 +275,6 @@ BOOL xMBMasterPortSerialPutByte(CHAR ucByte) BOOL xMBMasterPortSerialGetByte(CHAR* pucByte) { assert(pucByte != NULL); - MB_PORT_CHECK((uiRxBufferPos < MB_SERIAL_BUF_SIZE), - FALSE, "mb stack serial get byte failure."); - *pucByte = ucBuffer[uiRxBufferPos]; - uiRxBufferPos++; - return TRUE; + USHORT usLength = uart_read_bytes(ucUartNumber, (uint8_t*)pucByte, 1, MB_SERIAL_RX_TOUT_TICKS); + return (usLength == 1); } diff --git a/components/freemodbus/port/porttimer.c b/components/freemodbus/port/porttimer.c index 8637e90b9..246ca335d 100644 --- a/components/freemodbus/port/porttimer.c +++ b/components/freemodbus/port/porttimer.c @@ -94,16 +94,15 @@ BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) // Configure timer xErr = timer_init(usTimerGroupIndex, usTimerIndex, &config); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer init failure, timer_init() returned (0x%x).", (uint32_t)xErr); + "timer init failure, timer_init() returned (0x%x).", xErr); // Stop timer counter xErr = timer_pause(usTimerGroupIndex, usTimerIndex); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "stop timer failure, timer_pause() returned (0x%x).", (uint32_t)xErr); + "stop timer failure, timer_pause() returned (0x%x).", xErr); // Reset counter value xErr = timer_set_counter_value(usTimerGroupIndex, usTimerIndex, 0x00000000ULL); MB_PORT_CHECK((xErr == ESP_OK), FALSE, - "timer set value failure, timer_set_counter_value() returned (0x%x).", - (uint32_t)xErr); + "timer set value failure, timer_set_counter_value() returned (0x%x).", xErr); // wait3T5_us = 35 * 11 * 100000 / baud; // the 3.5T symbol time for baudrate // Set alarm value for usTim1Timerout50us * 50uS xErr = timer_set_alarm_value(usTimerGroupIndex, usTimerIndex, (uint32_t)(usTim1Timerout50us)); diff --git a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c b/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c index 6900af59a..74df9c68b 100644 --- a/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c +++ b/components/freemodbus/serial_master/modbus_controller/mbc_serial_master.c @@ -36,17 +36,8 @@ extern BOOL xMBMasterPortSerialTxPoll(void); /*-----------------------Master mode use these variables----------------------*/ -#define MODE_RTU -#ifdef MODE_RTU -#define MB_DT_SIZE(size) ((size << 1) + 8) -#else -#define MB_DT_SIZE(size) ((size << 2) + 20) -#endif +#define MB_RESPONSE_TICS pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND) -// The response time is average processing time + data transmission (higher on lower speeds) -// ~resp_time_ms = min_pcocessing_time_ms + ((2 packets * (header_size + packet_bytes)) * 11 bits in byte * 1000 ms_in_sec) / transmit_speed)) -#define MB_RESPONSE_TIMEOUT(mb_speed, size) pdMS_TO_TICKS( 30 + (2 * MB_DT_SIZE(size) * 11 * 1000 / mb_speed)) -#define MB_RESPONSE_TICS pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND) static mb_master_interface_t* mbm_interface_ptr = NULL; //&default_interface_inst; @@ -71,7 +62,7 @@ static void modbus_master_task(void *pvParameters) BOOL xSentState = xMBMasterPortSerialTxPoll(); if (xSentState) { // Let state machine know that response was transmitted out - (void)xMBMasterPortEventPost(EV_MASTER_FRAME_TRANSMITTED); + (void)xMBMasterPortEventPost(EV_MASTER_FRAME_SENT); } } } @@ -195,11 +186,6 @@ static esp_err_t mbc_serial_master_send_request(mb_param_request_t* request, voi uint8_t mb_command = request->command; uint16_t mb_offset = request->reg_start; uint16_t mb_size = request->reg_size; - uint32_t mb_speed = mbm_opts->mbm_comm.baudrate; - - // Timeout value for last processed packet - static uint32_t timeout = MB_RESPONSE_TICS; - size_t pack_length = 0; // Set the buffer for callback function processing of received data mbm_opts->mbm_reg_buffer_ptr = (uint8_t*)data_ptr; @@ -209,56 +195,44 @@ static esp_err_t mbc_serial_master_send_request(mb_param_request_t* request, voi switch(mb_command) { case MB_FUNC_READ_COILS: - pack_length = (mb_size >= 8) ? (mb_size >> 3) : 1; - timeout = MB_RESPONSE_TIMEOUT(mb_speed, pack_length); mb_error = eMBMasterReqReadCoils((UCHAR)mb_slave_addr, (USHORT)mb_offset, - (USHORT)mb_size , (LONG)timeout ); + (USHORT)mb_size , (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_WRITE_SINGLE_COIL: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, 1); mb_error = eMBMasterReqWriteCoil((UCHAR)mb_slave_addr, (USHORT)mb_offset, - *(USHORT*)data_ptr, (LONG)timeout ); + *(USHORT*)data_ptr, (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_WRITE_MULTIPLE_COILS: - pack_length = (mb_size >= 8) ? (mb_size >> 3) : 1; - timeout = MB_RESPONSE_TIMEOUT(mb_speed, pack_length); mb_error = eMBMasterReqWriteMultipleCoils((UCHAR)mb_slave_addr, (USHORT)mb_offset, - (USHORT)mb_size, (UCHAR*)data_ptr, (LONG)timeout); + (USHORT)mb_size, (UCHAR*)data_ptr, (LONG)MB_RESPONSE_TICS); break; case MB_FUNC_READ_DISCRETE_INPUTS: - pack_length = (mb_size >= 8) ? (mb_size >> 3) : 1; - timeout = MB_RESPONSE_TIMEOUT(mb_speed, pack_length); mb_error = eMBMasterReqReadDiscreteInputs((UCHAR)mb_slave_addr, (USHORT)mb_offset, - (USHORT)mb_size, (LONG)timeout ); + (USHORT)mb_size, (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_READ_HOLDING_REGISTER: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, mb_size); mb_error = eMBMasterReqReadHoldingRegister((UCHAR)mb_slave_addr, (USHORT)mb_offset, - (USHORT)mb_size, (LONG)timeout ); + (USHORT)mb_size, (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_WRITE_REGISTER: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, 1); mb_error = eMBMasterReqWriteHoldingRegister( (UCHAR)mb_slave_addr, (USHORT)mb_offset, - *(USHORT*)data_ptr, (LONG)timeout ); + *(USHORT*)data_ptr, (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_WRITE_MULTIPLE_REGISTERS: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, mb_size); mb_error = eMBMasterReqWriteMultipleHoldingRegister( (UCHAR)mb_slave_addr, (USHORT)mb_offset, (USHORT)mb_size, - (USHORT*)data_ptr, (LONG)timeout ); + (USHORT*)data_ptr, (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_READWRITE_MULTIPLE_REGISTERS: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, mb_size << 1); mb_error = eMBMasterReqReadWriteMultipleHoldingRegister( (UCHAR)mb_slave_addr, (USHORT)mb_offset, (USHORT)mb_size, (USHORT*)data_ptr, (USHORT)mb_offset, (USHORT)mb_size, - (LONG)timeout ); + (LONG)MB_RESPONSE_TICS ); break; case MB_FUNC_READ_INPUT_REGISTER: - timeout = MB_RESPONSE_TIMEOUT(mb_speed, mb_size); mb_error = eMBMasterReqReadInputRegister( (UCHAR)mb_slave_addr, (USHORT)mb_offset, - (USHORT)mb_size, (LONG) timeout ); + (USHORT)mb_size, (LONG) MB_RESPONSE_TICS ); break; default: ESP_LOGE(MB_MASTER_TAG, "%s: Incorrect function in request (%u) ", diff --git a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c b/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c index da34d629b..edbb12a72 100644 --- a/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c +++ b/components/freemodbus/serial_slave/modbus_controller/mbc_serial_slave.c @@ -49,7 +49,7 @@ static void modbus_slave_task(void *pvParameters) if (status & MB_EVENT_STACK_STARTED) { (void)eMBPoll(); // allow stack to process data // Send response buffer - BOOL xSentState = xMBPortSerialTxPoll(); + BOOL xSentState = xMBPortSerialTxPoll(); if (xSentState) { (void)xMBPortEventPost( EV_FRAME_SENT ); } @@ -99,10 +99,6 @@ static esp_err_t mbc_serial_slave_start(void) (eMBParity)mbs_opts->mbs_comm.parity); MB_SLAVE_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE, "mb stack initialization failure, eMBInit() returns (0x%x).", status); -#ifdef CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT - status = eMBSetSlaveID(MB_SLAVE_ID_SHORT, TRUE, (UCHAR*)mb_slave_id, sizeof(mb_slave_id)); - MB_SLAVE_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE, "mb stack set slave ID failure."); -#endif status = eMBEnable(); MB_SLAVE_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE, "mb stack set slave ID failure, eMBEnable() returned (0x%x).", (uint32_t)status); diff --git a/docs/en/api-reference/protocols/modbus.rst b/docs/en/api-reference/protocols/modbus.rst index b9fb8cb99..7e6bc9494 100644 --- a/docs/en/api-reference/protocols/modbus.rst +++ b/docs/en/api-reference/protocols/modbus.rst @@ -101,11 +101,11 @@ Application Example ------------------- -The examples below use the FreeModbus library port for slave and master implementation accordingly. The selection of stack is performed through KConfig menu "Selection of Modbus stack support mode" and related configuration keys. +The examples below use the FreeModbus library port for serial slave and master implementation accordingly. The selection of stack is performed through KConfig menu "Selection of Modbus stack support mode" and related configuration keys. -:example:`protocols/modbus_slave` +:example:`protocols/modbus/serial/mb_slave` -:example:`protocols/modbus_master` +:example:`protocols/modbus/serial/mb_master` Please refer to the specific example README.md for details. diff --git a/examples/protocols/modbus/serial/README.md b/examples/protocols/modbus/serial/README.md new file mode 100644 index 000000000..a164d5f27 --- /dev/null +++ b/examples/protocols/modbus/serial/README.md @@ -0,0 +1,31 @@ +### Modbus Master-Slave Example + +## Overview + +These two projects illustrate the communication between Modbus master and slave device in the segment. +Master initializes Modbus interface driver and then reads parameters from slave device in the segment. +After several successful read attempts slave sets the alarm relay (end of test condition). +Once master reads the alarm it stops communication and destroy driver. See README.md for each project for more information. + +## Wiring + +The master and slave boards should be connected to each other through the RS485 interface line driver. +See the connection schematic in README.md files of each example. + +## Configure the project + +This example test requires communication mode setting for master and slave be the same and slave address set to 1. + +## About common_component in this example + +The folder "common_components" includes definitions of parameter structures for master and slave device (share the same parameters). +However, currently it is for example purpose only. + +## Start example test + +This test requires to set ```TEST_FW_PATH``` environment variable and other required tools and python packages. +1. Build the master and slave examples in appropriate folders as described in README.md file of each example. +2. Set current folder to ```examples/protocols/modbus/serial/``` and start the example test script ```python example_test.py``` +3. The script has to find two ESP32 connected boards and flash master and slave applications, checks its configuration parameters then start test. +4. Once boards flashed it starts applications and captures master and slave boards output through the terminal. +5. The script uses regular expressions to check correctness of initialization and quality of communication between boards over RS485 interface. diff --git a/examples/protocols/modbus/serial/common_components/CMakeLists.txt b/examples/protocols/modbus/serial/common_components/CMakeLists.txt new file mode 100644 index 000000000..a721a1a61 --- /dev/null +++ b/examples/protocols/modbus/serial/common_components/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "modbus_params.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES freemodbus) \ No newline at end of file diff --git a/examples/protocols/modbus/serial/common_components/component.mk b/examples/protocols/modbus/serial/common_components/component.mk new file mode 100644 index 000000000..9b8ec9027 --- /dev/null +++ b/examples/protocols/modbus/serial/common_components/component.mk @@ -0,0 +1,5 @@ +# +# Component Makefile +# +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/examples/protocols/modbus/serial/common_components/include/modbus_params.h b/examples/protocols/modbus/serial/common_components/include/modbus_params.h new file mode 100644 index 000000000..b6fe9b737 --- /dev/null +++ b/examples/protocols/modbus/serial/common_components/include/modbus_params.h @@ -0,0 +1,60 @@ +/*===================================================================================== + * Description: + * The Modbus parameter structures used to define Modbus instances that + * can be addressed by Modbus protocol. Define these structures per your needs in + * your application. Below is just an example of possible parameters. + *====================================================================================*/ +#ifndef _DEVICE_PARAMS +#define _DEVICE_PARAMS + +// This file defines structure of modbus parameters which reflect correspond modbus address space +// for each modbus register type (coils, discreet inputs, holding registers, input registers) +#pragma pack(push, 1) +typedef struct +{ + uint8_t discrete_input0:1; + uint8_t discrete_input1:1; + uint8_t discrete_input2:1; + uint8_t discrete_input3:1; + uint8_t discrete_input4:1; + uint8_t discrete_input5:1; + uint8_t discrete_input6:1; + uint8_t discrete_input7:1; + uint8_t discrete_input_port1:8; +} discrete_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + uint8_t coils_port0; + uint8_t coils_port1; +} coil_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + float input_data0; + float input_data1; + float input_data2; + float input_data3; +} input_reg_params_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct +{ + float holding_data0; + float holding_data1; + float holding_data2; + float holding_data3; +} holding_reg_params_t; +#pragma pack(pop) + +extern holding_reg_params_t holding_reg_params; +extern input_reg_params_t input_reg_params; +extern coil_reg_params_t coil_reg_params; +extern discrete_reg_params_t discrete_reg_params; + +#endif // !defined(_DEVICE_PARAMS) diff --git a/examples/protocols/modbus_slave/main/deviceparams.c b/examples/protocols/modbus/serial/common_components/modbus_params.c similarity index 95% rename from examples/protocols/modbus_slave/main/deviceparams.c rename to examples/protocols/modbus/serial/common_components/modbus_params.c index 1ce7e41c6..5a0bcadb9 100644 --- a/examples/protocols/modbus_slave/main/deviceparams.c +++ b/examples/protocols/modbus/serial/common_components/modbus_params.c @@ -3,7 +3,7 @@ * C file to define parameter storage instances *====================================================================================*/ #include -#include "deviceparams.h" +#include "modbus_params.h" // Here are the user defined instances for device parameters packed by 1 byte // These are keep the values that can be accessed from Modbus master diff --git a/examples/protocols/modbus/serial/example_test.py b/examples/protocols/modbus/serial/example_test.py new file mode 100644 index 000000000..e1ffa9736 --- /dev/null +++ b/examples/protocols/modbus/serial/example_test.py @@ -0,0 +1,297 @@ +# Need Python 3 string formatting functions +from __future__ import print_function + +import os +import sys +import re +import logging +from threading import Thread + +try: + import IDF +except ImportError: + # The test cause is dependent on the Tiny Test Framework. Ensure the + # `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw` + test_fw_path = os.getenv("TEST_FW_PATH") + if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + import IDF + +LOG_LEVEL = logging.DEBUG +LOGGER_NAME = "modbus_test" + +# Allowed parameter reads +TEST_READ_MIN_COUNT = 10 # Minimum number of correct readings +TEST_READ_MAX_ERR_COUNT = 2 # Maximum allowed read errors during initialization + +TEST_THREAD_EXPECT_TIMEOUT = 120 # Test theread expect timeout in seconds +TEST_THREAD_JOIN_TIMEOUT = 180 # Test theread join timeout in seconds + +# Test definitions +TEST_MASTER_RTU = 'master_rtu' +TEST_SLAVE_RTU = 'slave_rtu' + +TEST_MASTER_ASCII = 'master_ascii' +TEST_SLAVE_ASCII = 'slave_ascii' + +# Define tuple of strings to expect for each DUT. +# +master_expect = ("MASTER_TEST: Modbus master stack initialized...", "MASTER_TEST: Start modbus test...", "MASTER_TEST: Destroy master...") +slave_expect = ("SLAVE_TEST: Modbus slave stack initialized.", "SLAVE_TEST: Start modbus test...", "SLAVE_TEST: Modbus controller destroyed.") + +# The dictionary for expected values in listing +expect_dict_master_ok = {"START": (), + "READ_PAR_OK": (), + "ALARM_MSG": (u'7',)} + +expect_dict_master_err = {"READ_PAR_ERR": (u'263', u'ESP_ERR_TIMEOUT'), + "READ_STK_ERR": (u'107', u'ESP_ERR_TIMEOUT')} + +# The dictionary for regular expression patterns to check in listing +pattern_dict_master_ok = {"START": (r'.*I \([0-9]+\) MASTER_TEST: Start modbus test...'), + "READ_PAR_OK": (r'.*I\s\([0-9]+\) MASTER_TEST: Characteristic #[0-9]+ [a-zA-Z0-9_]+' + r'\s\([a-zA-Z\%\/]+\) value = [a-zA-Z0-9\.]+ \(0x[a-zA-Z0-9]+\) read successful.'), + "ALARM_MSG": (r'.*I \([0-9]*\) MASTER_TEST: Alarm triggered by cid #([0-9]+).')} + +pattern_dict_master_err = {"READ_PAR_ERR_TOUT": (r'.*E \([0-9]+\) MASTER_TEST: Characteristic #[0-9]+' + r'\s\([a-zA-Z0-9_]+\) read fail, err = [0-9]+ \([_A-Z]+\).'), + "READ_STK_ERR_TOUT": (r'.*E \([0-9]+\) MB_CONTROLLER_MASTER: [a-zA-Z0-9_]+\([0-9]+\):\s' + r'SERIAL master get parameter failure error=\(0x([a-zA-Z0-9]+)\) \(([_A-Z]+)\).')} + +# The dictionary for expected values in listing +expect_dict_slave_ok = {"START": (), + "READ_PAR_OK": (), + "DESTROY": ()} + +# The dictionary for regular expression patterns to check in listing +pattern_dict_slave_ok = {"START": (r'.*I \([0-9]+\) SLAVE_TEST: Start modbus test...'), + "READ_PAR_OK": (r'.*I\s\([0-9]+\) SLAVE_TEST: [A-Z]+ READ \([a-zA-Z0-9_]+ us\),\s' + r'ADDR:[0-9]+, TYPE:[0-9]+, INST_ADDR:0x[a-zA-Z0-9]+, SIZE:[0-9]+'), + "DESTROY": (r'.*I\s\([0-9]+\) SLAVE_TEST: Modbus controller destroyed.')} + +logger = logging.getLogger(LOGGER_NAME) + + +class DutTestThread(Thread): + def __init__(self, dut=None, name=None, expect=None): + """ Initialize the thread parameters + """ + self.tname = name + self.dut = dut + self.expected = expect + self.result = False + self.data = None + super(DutTestThread, self).__init__() + + def run(self): + """ The function implements thread functionality + """ + # Must reset again as flashing during start_app will reset multiple times, causing unexpected results + self.dut.reset() + + # Capture output from the DUT + self.dut.start_capture_raw_data() + + # Check expected strings in the listing + for string in self.expected: + self.dut.expect(string, TEST_THREAD_EXPECT_TIMEOUT) + + # Check DUT exceptions + dut_exceptions = self.dut.get_exceptions() + if "Guru Meditation Error:" in dut_exceptions: + raise Exception("%s generated an exception: %s\n" % (str(self.dut), dut_exceptions)) + + # Mark thread has run to completion without any exceptions + self.data = self.dut.stop_capture_raw_data() + self.result = True + + +def test_filter_output(data=None, start_pattern=None, end_pattern=None): + """Use patters to filter output + """ + start_index = str(data).find(start_pattern) + end_index = str(data).find(end_pattern) + logger.debug("Listing start index= %d, end=%d" % (start_index, end_index)) + if start_index == -1 or end_index == -1: + return data + return data[start_index:end_index + len(end_pattern)] + + +def test_expect_re(data, pattern): + """ + Check if re pattern is matched in data cache + :param data: data to process + :param pattern: compiled RegEx pattern + :return: match groups if match succeed otherwise None + """ + ret = None + if isinstance(pattern, type(u'')): + pattern = pattern.encode('utf-8') + regex = re.compile(pattern) + if isinstance(data, type(u'')): + data = data.encode('utf-8') + match = regex.search(data) + if match: + ret = tuple(None if x is None else x.decode() for x in match.groups()) + index = match.end() + else: + index = None + return ret, index + + +def test_check_output(data=None, check_dict=None, expect_dict=None): + """ Check output for the test + Check log using regular expressions: + """ + global logger + match_count = 0 + index = 0 + data_lines = data.splitlines() + for key, pattern in check_dict.items(): + if key not in expect_dict: + break + # Check pattern in the each line + for line in data_lines: + group, index = test_expect_re(line, pattern) + if index is not None: + logger.debug("Found key{%s}=%s, line: \n%s" % (key, group, line)) + if expect_dict[key] == group: + logger.debug("The result is correct for the key:%s, expected:%s == returned:%s" % (key, str(expect_dict[key]), str(group))) + match_count += 1 + return match_count + + +def test_check_mode(dut=None, mode_str=None, value=None): + """ Check communication mode for dut + """ + global logger + try: + opt = dut.app.get_sdkconfig()[mode_str] + logger.info("%s {%s} = %s.\n" % (str(dut), mode_str, opt)) + return value == opt + except Exception: + logger.info('ENV_TEST_FAILURE: %s: Cannot find option %s in sdkconfig.' % (str(dut), mode_str)) + return False + + +@IDF.idf_example_test(env_tag='Example_T2_RS485') +def test_modbus_communication(env, comm_mode): + global logger + + # Get device under test. "dut1 - master", "dut2 - slave" must be properly connected through RS485 interface driver + dut_master = env.get_dut("modbus_master", "examples/protocols/modbus/serial/mb_master") + dut_slave = env.get_dut("modbus_slave", "examples/protocols/modbus/serial/mb_slave") + + try: + logger.debug("Environment vars: %s\r\n" % os.environ) + logger.debug("DUT slave sdkconfig: %s\r\n" % dut_slave.app.get_sdkconfig()) + logger.debug("DUT master sdkconfig: %s\r\n" % dut_master.app.get_sdkconfig()) + + # Check Kconfig configuration options for each built example + if test_check_mode(dut_master, "CONFIG_MB_COMM_MODE_ASCII", "y") and test_check_mode(dut_slave, "CONFIG_MB_COMM_MODE_ASCII", "y"): + logger.info("ENV_TEST_INFO: Modbus ASCII test mode selected in the configuration. \n") + slave_name = TEST_SLAVE_ASCII + master_name = TEST_MASTER_ASCII + elif test_check_mode(dut_master, "CONFIG_MB_COMM_MODE_RTU", "y") and test_check_mode(dut_slave, "CONFIG_MB_COMM_MODE_RTU", "y"): + logger.info("ENV_TEST_INFO: Modbus RTU test mode selected in the configuration. \n") + slave_name = TEST_SLAVE_RTU + master_name = TEST_MASTER_RTU + else: + logger.error("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n") + raise Exception("ENV_TEST_FAILURE: Communication mode in master and slave configuration don't match.\n") + # Check if slave address for example application is default one to be able to communicate + if not test_check_mode(dut_slave, "CONFIG_MB_SLAVE_ADDR", "1"): + logger.error("ENV_TEST_FAILURE: Slave address option is incorrect.\n") + raise Exception("ENV_TEST_FAILURE: Slave address option is incorrect.\n") + + # Flash app onto each DUT + dut_master.start_app() + dut_slave.start_app() + + # Create thread for each dut + dut_master_thread = DutTestThread(dut=dut_master, name=master_name, expect=master_expect) + dut_slave_thread = DutTestThread(dut=dut_slave, name=slave_name, expect=slave_expect) + + # Start each thread + dut_slave_thread.start() + dut_master_thread.start() + + # Wait for threads to complete + dut_slave_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT) + dut_master_thread.join(timeout=TEST_THREAD_JOIN_TIMEOUT) + + if dut_slave_thread.isAlive(): + logger.error("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" % + (dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT)) + raise Exception("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" % + (dut_slave_thread.tname, TEST_THREAD_JOIN_TIMEOUT)) + + if dut_master_thread.isAlive(): + logger.error("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" % + (dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT)) + raise Exception("ENV_TEST_FAILURE: The thread %s is not completed successfully after %d seconds.\n" % + (dut_master_thread.tname, TEST_THREAD_JOIN_TIMEOUT)) + finally: + dut_master.close() + dut_slave.close() + + # Check if test threads completed successfully and captured data + if not dut_slave_thread.result or dut_slave_thread.data is None: + logger.error("The thread %s was not run successfully." % dut_slave_thread.tname) + raise Exception("The thread %s was not run successfully." % dut_slave_thread.tname) + + if not dut_master_thread.result or dut_master_thread.data is None: + logger.error("The thread %s was not run successfully." % dut_slave_thread.tname) + raise Exception("The thread %s was not run successfully." % dut_master_thread.tname) + + # Filter output to get test messages + master_output = test_filter_output(dut_master_thread.data, master_expect[0], master_expect[len(master_expect) - 1]) + if master_output is not None: + logger.info("The data for master thread is captured.") + logger.debug(master_output) + + slave_output = test_filter_output(dut_slave_thread.data, slave_expect[0], slave_expect[len(slave_expect) - 1]) + if slave_output is not None: + logger.info("The data for slave thread is captured.") + logger.debug(slave_output) + + # Check if parameters are read correctly by master + match_count = test_check_output(master_output, pattern_dict_master_ok, expect_dict_master_ok) + if match_count < TEST_READ_MIN_COUNT: + logger.error("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count)) + raise Exception("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count)) + logger.info("OK pattern test for %s, match_count=%d." % (dut_master_thread.tname, match_count)) + + # If the test completed successfully (alarm triggered) but there are some errors during reading of parameters + match_count = test_check_output(master_output, pattern_dict_master_err, expect_dict_master_err) + if match_count > TEST_READ_MAX_ERR_COUNT: + logger.error("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count)) + raise Exception("There are errors reading parameters from %s, %d" % (dut_master_thread.tname, match_count)) + logger.info("ERROR pattern test for %s, match_count=%d." % (dut_master_thread.tname, match_count)) + + match_count = test_check_output(slave_output, pattern_dict_slave_ok, expect_dict_slave_ok) + if match_count < TEST_READ_MIN_COUNT: + logger.error("There are errors reading parameters from %s, %d" % (dut_slave_thread.tname, match_count)) + raise Exception("There are errors reading parameters from %s, %d" % (dut_slave_thread.tname, match_count)) + logger.info("OK pattern test for %s, match_count=%d." % (dut_slave_thread.tname, match_count)) + + +if __name__ == '__main__': + logger = logging.getLogger(LOGGER_NAME) + # create file handler which logs even debug messages + fh = logging.FileHandler('modbus_test.log') + fh.setLevel(logging.DEBUG) + logger.setLevel(logging.DEBUG) + # create console handler + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) + # set format of output for both handlers + formatter = logging.Formatter('%(levelname)s:%(message)s') + ch.setFormatter(formatter) + fh.setFormatter(formatter) + logger.addHandler(fh) + logger.addHandler(ch) + logger.info("Start script %s." % os.path.basename(__file__)) + print("Logging file name: %s" % logger.handlers[0].baseFilename) + test_modbus_communication() + logging.shutdown() diff --git a/examples/protocols/modbus/serial/mb_master/CMakeLists.txt b/examples/protocols/modbus/serial/mb_master/CMakeLists.txt new file mode 100644 index 000000000..7e322563d --- /dev/null +++ b/examples/protocols/modbus/serial/mb_master/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(SUPPORTED_TARGETS esp32) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/serial/common_components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_master) diff --git a/examples/protocols/modbus_master/Makefile b/examples/protocols/modbus/serial/mb_master/Makefile similarity index 56% rename from examples/protocols/modbus_master/Makefile rename to examples/protocols/modbus/serial/mb_master/Makefile index 930f4ce5e..8711a84aa 100644 --- a/examples/protocols/modbus_master/Makefile +++ b/examples/protocols/modbus/serial/mb_master/Makefile @@ -3,6 +3,9 @@ # project subdirectory. # -PROJECT_NAME := modbus_master_sense +PROJECT_NAME := modbus_master + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/serial/common_components include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/modbus/serial/mb_master/README.md b/examples/protocols/modbus/serial/mb_master/README.md new file mode 100644 index 000000000..5968f4115 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_master/README.md @@ -0,0 +1,150 @@ +# Modbus Master Example + +This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device. +This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file. +The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment. +The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of holding_data0 parameter exceeded limit. +The instances for the modbus parameters are common for master and slave examples and located in examples\protocols\modbus\serial\common_components folder. + +Example parameters definition: +-------------------------------------------------------------------------------------------------- +| Slave Address | Characteristic ID | Characteristic name | Description | +|---------------------|----------------------|----------------------|----------------------------| +| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 | +| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off | +| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off | +-------------------------------------------------------------------------------------------------- +Note: The Slave Address is the same for all parameters for example test but it can be changed in the ```Example Data (Object) Dictionary``` table of master example to address parameters from other slaves. +The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment. + +Simplified Modbus connection schematic for example test: + ``` + MB_DEVICE_ADDR1 + ------------- ------------- + | | RS485 network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` +Modbus multi slave segment connection schematic: +``` + MB_DEVICE_ADDR1 + ------------- + | | + | Slave 1 |---<>--+ + | | | + ------------- | + MB_DEVICE_ADDR2 | + ------------- | ------------- + | | | | | + | Slave 2 |---<>--+---<>---| Master | + | | | | | + ------------- | ------------- + MB_DEVICE_ADDR3 | + ------------- RS485 network + | | | + | Slave 3 |---<>--+ + | | + ------------- +``` + +## Hardware required : +Option 1: +PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 WROVER-KIT board. + +Option 2: +Several ESP32 WROVER-KIT board flashed with modbus_slave example software to represent slave device with specific slave address (See CONFIG_MB_SLAVE_ADDR). The slave addresses for each board have to be configured as defined in "connection schematic" above. +One ESP32 WROVER-KIT board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below). + +The MAX485 line driver is used as an example below but other similar chips can be used as well. +RS485 example circuit schematic for connection of master and slave devices into segment: +``` + VCC ---------------+ +--------------- VCC + | | + +-------x-------+ +-------x-------+ + RXD <------| RO | DIFFERENTIAL | RO|-----> RXD + | B|---------------|B | + TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD +ESP32 WROVER KIT 1 | | RS-485 side | | External PC (emulator) with USB to serial or + RTS --+--->| DE | / \ | DE|---+ ESP32 WROVER KIT 2 (slave) + | | A|---------------|A | | + +----| /RE | PAIR | /RE|---+-- RTS + +-------x-------+ +-------x-------+ + | | + --- --- + Modbus Master device Modbus Slave device + +``` + +## How to setup and use an example: + +### Configure the application +Start the command below to setup configuration: +``` +idf.py menuconfig +``` +Configure the UART pins used for modbus communication using and table below. +Define the communication mode parameter for master and slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave devices in one segment). +Configure the slave address for each slave in the Modbus segment (the CONFIG_MB_SLAVE_ADDR in Kconfig). + +``` + ------------------------------------------------------------------------------------------------ + | ESP32 Interface | #define | Default ESP32 Pin | External RS485 Pin| + | ----------------------|------------------------------|-------------------|-------------------| + | Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | DI | + | Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | RO | + | Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | ~RE/DE | + | | | | | + | Ground | n/a | GND | GND | + ------------------------------------------------------------------------------------------------ +``` +The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings. +See the help string of parameters for more information. + +### Setup external Modbus slave devices or emulator +Option 1: +Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices. + +Option 2: +Other option is to have the modbus_slave example application flashed into ESP32 WROVER KIT board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above. + +### Build and flash software of master device +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (9035) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9045) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.539999 (0x40b147ac) read successful. +I (9045) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9055) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9065) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9075) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9085) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9095) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful. +I (9605) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9615) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.739999 (0x40b7ae12) read successful. +I (9615) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9625) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9635) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9645) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9655) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9665) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful. +I (10175) MASTER_TEST: Alarm triggered by cid #7. +I (10175) MASTER_TEST: Destroy master... + +``` +The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex). + diff --git a/examples/protocols/modbus/serial/mb_master/main/CMakeLists.txt b/examples/protocols/modbus/serial/mb_master/main/CMakeLists.txt new file mode 100644 index 000000000..9a6cf30e8 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_master/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(PROJECT_NAME "modbus_master") + +idf_component_register(SRCS "master.c" + INCLUDE_DIRS ".") + diff --git a/examples/protocols/modbus/serial/mb_master/main/Kconfig.projbuild b/examples/protocols/modbus/serial/mb_master/main/Kconfig.projbuild new file mode 100644 index 000000000..6adb91d47 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_master/main/Kconfig.projbuild @@ -0,0 +1,57 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 + default 2 + 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 + default 22 + 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 + default 23 + 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 + default 18 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + + 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/examples/protocols/modbus_master/main/component.mk b/examples/protocols/modbus/serial/mb_master/main/component.mk similarity index 100% rename from examples/protocols/modbus_master/main/component.mk rename to examples/protocols/modbus/serial/mb_master/main/component.mk diff --git a/examples/protocols/modbus/serial/mb_master/main/master.c b/examples/protocols/modbus/serial/mb_master/main/master.c new file mode 100644 index 000000000..da9c58c79 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_master/main/master.c @@ -0,0 +1,273 @@ +// Copyright 2016-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "string.h" +#include "esp_log.h" +#include "modbus_params.h" // for modbus parameters structures +#include "mbcontroller.h" +#include "sdkconfig.h" + +#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection +#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART + +// The number of parameters that intended to be used in the particular control process +#define MASTER_MAX_CIDS num_device_parameters + +// Number of reading of parameters from slave +#define MASTER_MAX_RETRY 30 + +// Timeout to update cid over Modbus +#define UPDATE_CIDS_TIMEOUT_MS (500) +#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_RATE_MS) + +// Timeout between polls +#define POLL_TIMEOUT_MS (1) +#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_RATE_MS) + +#define MASTER_TAG "MASTER_TEST" + +#define MASTER_CHECK(a, ret_val, str, ...) \ + if (!(a)) { \ + ESP_LOGE(MASTER_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return (ret_val); \ + } + +// The macro to get offset for parameter in the appropriate structure +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1)) +#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1)) +// Discrete offset macro +#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1)) + +#define STR(fieldname) ((const char*)( fieldname )) +// Options can be used as bit masks or parameter limits +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +// Enumeration of modbus device addresses accessed by master device +enum { + MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here) +}; + +// Enumeration of all supported CIDs for device (used in parameter definition table) +enum { + CID_INP_DATA_0 = 0, + CID_HOLD_DATA_0, + CID_INP_DATA_1, + CID_HOLD_DATA_1, + CID_INP_DATA_2, + CID_HOLD_DATA_2, + CID_RELAY_P1, + CID_RELAY_P2, + CID_COUNT +}; + +// Example Data (Object) Dictionary for Modbus parameters: +// The CID field in the table must be unique. +// Modbus Slave Addr field defines slave address of the device with correspond parameter. +// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such). +// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly. +// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value. +// Data Type, Data Size specify type of the characteristic and its data size. +// Parameter Options field specifies the options that can be used to process parameter value (limits or masks). +// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc). +const mb_parameter_descriptor_t device_parameters[] = { + // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode} + { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, + INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, + HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2, + INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2, + HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2, + INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2, + HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 0, 8, + COIL_OFFSET(coils_port0), PARAM_TYPE_U16, 2, OPTS( BIT1, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 8, 8, + COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER } +}; + +// Calculate number of parameters in the table +const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0])); + +// The function to get pointer to parameter storage (instance) according to parameter description table +static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor) +{ + assert(param_descriptor != NULL); + void* instance_ptr = NULL; + if (param_descriptor->param_offset != 0) { + switch(param_descriptor->mb_param_type) + { + case MB_PARAM_HOLDING: + instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_INPUT: + instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_COIL: + instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_DISCRETE: + instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1); + break; + default: + instance_ptr = NULL; + break; + } + } else { + ESP_LOGE(MASTER_TAG, "Wrong parameter offset for CID #%d", param_descriptor->cid); + assert(instance_ptr != NULL); + } + return instance_ptr; +} + +// User operation function to read slave values and check alarm +static void master_operation_func(void *arg) +{ + esp_err_t err = ESP_OK; + float value = 0; + bool alarm_state = false; + const mb_parameter_descriptor_t* param_descriptor = NULL; + + ESP_LOGI(MASTER_TAG, "Start modbus test..."); + + for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) { + // Read all found characteristics from slave(s) + for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) + { + // Get data from parameters description table + // and use this information to fill the characteristics description table + // and having all required fields in just one table + err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + void* temp_data_ptr = master_get_param_data(param_descriptor); + assert(temp_data_ptr); + uint8_t type = 0; + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)&value, &type); + if (err == ESP_OK) { + *(float*)temp_data_ptr = value; + if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) || + (param_descriptor->mb_param_type == MB_PARAM_INPUT)) { + ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %f (0x%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + value, + *(uint32_t*)temp_data_ptr); + if (((value > param_descriptor->param_opts.max) || + (value < param_descriptor->param_opts.min))) { + alarm_state = true; + break; + } + } else { + uint16_t state = *(uint16_t*)temp_data_ptr; + const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF"; + ESP_LOGI(MASTER_TAG, "Characteristic #%d %s (%s) value = %s (0x%x) read successful.", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (char*)param_descriptor->param_units, + (const char*)rw_str, + *(uint16_t*)temp_data_ptr); + if (state & param_descriptor->param_opts.opt1) { + alarm_state = true; + break; + } + } + } else { + ESP_LOGE(MASTER_TAG, "Characteristic #%d (%s) read fail, err = %d (%s).", + param_descriptor->cid, + (char*)param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls + } + } + vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); // + } + + if (alarm_state) { + ESP_LOGI(MASTER_TAG, "Alarm triggered by cid #%d.", + param_descriptor->cid); + } else { + ESP_LOGE(MASTER_TAG, "Alarm is not triggered after %d retries.", + MASTER_MAX_RETRY); + } + ESP_LOGI(MASTER_TAG, "Destroy master..."); + ESP_ERROR_CHECK(mbc_master_destroy()); +} + +// Modbus master initialization +static esp_err_t master_init(void) +{ + // Initialize and start Modbus controller + mb_communication_info_t comm = { + .port = MB_PORT_NUM, +#if CONFIG_MB_COMM_MODE_ASCII + .mode = MB_MODE_ASCII, +#elif CONFIG_MB_COMM_MODE_RTU + .mode = MB_MODE_RTU, +#endif + .baudrate = MB_DEV_SPEED, + .parity = MB_PARITY_NONE + }; + void* master_handler = NULL; + + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + MASTER_CHECK((master_handler != NULL), ESP_ERR_INVALID_STATE, + "mb controller initialization fail."); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller initialization fail, returns(0x%x).", + (uint32_t)err); + err = mbc_master_setup((void*)&comm); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller setup fail, returns(0x%x).", + (uint32_t)err); + + // Set UART pin numbers + err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD, + CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE); + + err = mbc_master_start(); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller start fail, returns(0x%x).", + (uint32_t)err); + + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (uint32_t)err); + // Set driver mode to Half Duplex + err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (uint32_t)err); + vTaskDelay(5); + err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); + MASTER_CHECK((err == ESP_OK), ESP_ERR_INVALID_STATE, + "mb controller set descriptor fail, returns(0x%x).", + (uint32_t)err); + ESP_LOGI(MASTER_TAG, "Modbus master stack initialized..."); + return err; +} + +void app_main(void) +{ + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(master_init()); + vTaskDelay(10); + + master_operation_func(NULL); +} diff --git a/examples/protocols/modbus_master/sdkconfig.defaults b/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults similarity index 76% rename from examples/protocols/modbus_master/sdkconfig.defaults rename to examples/protocols/modbus/serial/mb_master/sdkconfig.defaults index 8a793342e..7b669af0b 100644 --- a/examples/protocols/modbus_master/sdkconfig.defaults +++ b/examples/protocols/modbus/serial/mb_master/sdkconfig.defaults @@ -1,12 +1,11 @@ # # Modbus configuration # +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_UART_BAUD_RATE=115200 CONFIG_FMB_TIMER_PORT_ENABLED=y CONFIG_FMB_TIMER_GROUP=0 CONFIG_FMB_TIMER_INDEX=0 CONFIG_FMB_TIMER_ISR_IN_IRAM=y CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150 -CONFIG_MB_UART_RXD=22 -CONFIG_MB_UART_TXD=23 -CONFIG_MB_UART_RTS=18 diff --git a/examples/protocols/modbus/serial/mb_slave/CMakeLists.txt b/examples/protocols/modbus/serial/mb_slave/CMakeLists.txt new file mode 100644 index 000000000..8e0af7472 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_slave/CMakeLists.txt @@ -0,0 +1,11 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +set(SUPPORTED_TARGETS esp32) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/modbus/serial/common_components) + +set(SUPPORTED_TARGETS esp32) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_slave) diff --git a/examples/protocols/modbus_slave/Makefile b/examples/protocols/modbus/serial/mb_slave/Makefile similarity index 67% rename from examples/protocols/modbus_slave/Makefile rename to examples/protocols/modbus/serial/mb_slave/Makefile index 7239baca7..be383a5fe 100644 --- a/examples/protocols/modbus_slave/Makefile +++ b/examples/protocols/modbus/serial/mb_slave/Makefile @@ -5,5 +5,7 @@ PROJECT_NAME := modbus_slave +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/protocols/modbus/serial/common_components + include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/modbus_slave/README.md b/examples/protocols/modbus/serial/mb_slave/README.md similarity index 63% rename from examples/protocols/modbus_slave/README.md rename to examples/protocols/modbus/serial/mb_slave/README.md index 6d671781f..8d09f1ad5 100644 --- a/examples/protocols/modbus_slave/README.md +++ b/examples/protocols/modbus/serial/mb_slave/README.md @@ -4,10 +4,17 @@ This example demonstrates using of FreeModbus stack port implementation for ESP3 These are represented in structures holding_reg_params, input_reg_params, coil_reg_params, discrete_reg_params for holding registers, input parameters, coils and discrete inputs accordingly. The app_main application demonstrates how to setup Modbus stack and use notifications about parameters change from host system. The FreeModbus stack located in components\freemodbus\ folder and contain \port folder inside which contains FreeModbus stack port for ESP32. There are some parameters that can be configured in KConfig file to start stack correctly (See description below for more information). +The slave example uses shared parameter structures defined in examples\protocols\modbus\serial\common_components folder. + ## Hardware required : +Option 1: PC + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 WROVER-KIT board. The MAX485 line driver is used as an example below but other similar chips can be used as well. +Option 2: +The modbus_master example application configured as described in its README.md file and flashed into ESP32 WROVER-KIT board. +Note: The ```Example Data (Object) Dictionary``` in the modbus_master example can be edited to address parameters from other slaves connected into Modbus segment. + RS485 example circuit schematic: ``` VCC ---------------+ +--------------- VCC @@ -28,11 +35,12 @@ ESP32 WROVER KIT 1 | | RS-485 side | | Modbus ## How to setup and use an example: ### Configure the application -Configure the UART pins used for modbus communication using command and table below. +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 | External RS485 | @@ -43,12 +51,19 @@ idf.py menuconfig | Ground | n/a | GND | GND | ----------------------------------------------------------------------------------- ``` -The communication parameters below allow to configure freemodbus stack appropriately but usually it is enough to use default settings. -See the help string of parameters for more information. +Define the ```Modbus communiction mode``` for slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave application). +Set ```Modbus slave address``` for the example application (by default for example script is set to 1). +The communication parameters of freemodbus stack (Component config->Modbus configuration) allow to configure it appropriately but usually it is enough to use default settings. +See the help strings of parameters for more information. ### Setup external Modbus master software +Option 1: Configure the external Modbus master software according to port configuration parameters used in application. As an example the Modbus Poll application can be used with this example. +Option 2: +Setup ESP32 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: @@ -63,11 +78,14 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ## Example Output Example output of the application: ``` -INPUT READ: time_stamp(us):565240387, mb_addr:1, type:8, st_address:0x3ffb385c, size:8 - -HOLDING READ/WRITE: time_stamp(us):12104081, mb_addr:1, type:2, st_address:0x3ffb386c, size:8 +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/examples/protocols/modbus/serial/mb_slave/main/CMakeLists.txt b/examples/protocols/modbus/serial/mb_slave/main/CMakeLists.txt new file mode 100644 index 000000000..b4a0874a3 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_slave/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(PROJECT_NAME "modbus_slave") + +idf_component_register(SRCS "slave.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/protocols/modbus/serial/mb_slave/main/Kconfig.projbuild b/examples/protocols/modbus/serial/mb_slave/main/Kconfig.projbuild new file mode 100644 index 000000000..12f0e0169 --- /dev/null +++ b/examples/protocols/modbus/serial/mb_slave/main/Kconfig.projbuild @@ -0,0 +1,65 @@ +menu "Modbus Example Configuration" + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 + default 2 + 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 + default 22 + 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 + default 23 + 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 + default 18 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + + 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/examples/protocols/modbus_slave/main/component.mk b/examples/protocols/modbus/serial/mb_slave/main/component.mk similarity index 61% rename from examples/protocols/modbus_slave/main/component.mk rename to examples/protocols/modbus/serial/mb_slave/main/component.mk index a98f634ea..b4fa72791 100644 --- a/examples/protocols/modbus_slave/main/component.mk +++ b/examples/protocols/modbus/serial/mb_slave/main/component.mk @@ -1,4 +1,4 @@ # -# "main" pseudo-component makefile. +# 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/examples/protocols/modbus_slave/main/freemodbus.c b/examples/protocols/modbus/serial/mb_slave/main/slave.c similarity index 73% rename from examples/protocols/modbus_slave/main/freemodbus.c rename to examples/protocols/modbus/serial/mb_slave/main/slave.c index abca71999..98808cc4f 100644 --- a/examples/protocols/modbus_slave/main/freemodbus.c +++ b/examples/protocols/modbus/serial/mb_slave/main/slave.c @@ -5,15 +5,16 @@ CONDITIONS OF ANY KIND, either express or implied. */ #include +#include #include "esp_err.h" -#include "sdkconfig.h" #include "mbcontroller.h" // for mbcontroller defines and api -#include "deviceparams.h" // for device parameters structures +#include "modbus_params.h" // for modbus parameters structures #include "esp_log.h" // for log_write +#include "sdkconfig.h" -#define MB_PORT_NUM (2) // Number of UART port used for Modbus connection -#define MB_DEV_ADDR (1) // The address of device in Modbus network -#define MB_DEV_SPEED (115200) // The communication speed of the UART +#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 // Defines below are used to define register start address for each type of Modbus registers #define MB_REG_DISCRETE_INPUT_START (0x0000) @@ -22,8 +23,8 @@ #define MB_REG_COILS_START (0x0000) #define MB_PAR_INFO_GET_TOUT (10) // Timeout for get parameter info -#define MB_CHAN_DATA_MAX_VAL (10) -#define MB_CHAN_DATA_OFFSET (0.01f) +#define MB_CHAN_DATA_MAX_VAL (6) +#define MB_CHAN_DATA_OFFSET (0.2f) #define MB_READ_MASK (MB_EVENT_INPUT_REG_RD \ | MB_EVENT_HOLDING_REG_RD \ | MB_EVENT_DISCRETE_RD \ @@ -32,7 +33,9 @@ | MB_EVENT_COILS_WR) #define MB_READ_WRITE_MASK (MB_READ_MASK | MB_WRITE_MASK) -static const char *TAG = "MODBUS_SLAVE_APP"; +static const char *SLAVE_TAG = "SLAVE_TEST"; + +static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED; // Set register values into known state static void setup_reg_data(void) @@ -43,21 +46,18 @@ static void setup_reg_data(void) discrete_reg_params.discrete_input5 = 1; discrete_reg_params.discrete_input7 = 1; - holding_reg_params.data_chan0 = 1.34; - holding_reg_params.data_chan1 = 2.56; - holding_reg_params.data_chan2 = 3.78; - holding_reg_params.data_chan3 = 4.90; + holding_reg_params.holding_data0 = 1.34; + holding_reg_params.holding_data1 = 2.56; + holding_reg_params.holding_data2 = 3.78; + holding_reg_params.holding_data3 = 4.90; - coil_reg_params.coil0 = 1; - coil_reg_params.coil2 = 1; - coil_reg_params.coil4 = 1; - coil_reg_params.coil6 = 1; - coil_reg_params.coil7 = 1; + coil_reg_params.coils_port0 = 0x55; + coil_reg_params.coils_port1 = 0xAA; - input_reg_params.data_chan0 = 1.34; - input_reg_params.data_chan1 = 2.56; - input_reg_params.data_chan2 = 3.78; - input_reg_params.data_chan3 = 4.90; + input_reg_params.input_data0 = 1.12; + input_reg_params.input_data1 = 2.34; + input_reg_params.input_data2 = 3.56; + input_reg_params.input_data3 = 4.78; } // An example application of Modbus slave. It is based on freemodbus stack. @@ -71,19 +71,23 @@ void app_main(void) mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure // Set UART log level - esp_log_level_set(TAG, ESP_LOG_INFO); + esp_log_level_set(SLAVE_TAG, ESP_LOG_INFO); void* mbc_slave_handler = NULL; ESP_ERROR_CHECK(mbc_slave_init(MB_PORT_SERIAL_SLAVE, &mbc_slave_handler)); // Initialization of Modbus controller // Setup communication parameters and start stack - comm_info.mode = MB_MODE_RTU; - comm_info.slave_addr = MB_DEV_ADDR; +#if CONFIG_MB_COMM_MODE_ASCII + comm_info.mode = MB_MODE_ASCII, +#elif CONFIG_MB_COMM_MODE_RTU + comm_info.mode = MB_MODE_RTU, +#endif + comm_info.slave_addr = MB_SLAVE_ADDR; comm_info.port = MB_PORT_NUM; comm_info.baudrate = MB_DEV_SPEED; comm_info.parity = MB_PARITY_NONE; ESP_ERROR_CHECK(mbc_slave_setup((void*)&comm_info)); - + // The code below initializes Modbus register area descriptors // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs // Initialization should be done for each supported Modbus register area according to register map. @@ -122,17 +126,20 @@ void app_main(void) // Starts of modbus controller and stack ESP_ERROR_CHECK(mbc_slave_start()); + // Set UART pin numbers + ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, + CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS, + UART_PIN_NO_CHANGE)); + // Set UART driver mode to Half Duplex ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX)); - - // Set UART pin numbers - ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, - CONFIG_MB_UART_RXD, CONFIG_MB_UART_RTS, - UART_PIN_NO_CHANGE)); + + ESP_LOGI(SLAVE_TAG, "Modbus slave stack initialized."); + ESP_LOGI(SLAVE_TAG, "Start modbus test..."); // The cycle below will be terminated when parameter holdingRegParams.dataChan0 // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value. - for(;holding_reg_params.data_chan0 < MB_CHAN_DATA_MAX_VAL;) { + for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) { // Check for read/write events of Modbus master for certain events mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK); const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE"; @@ -140,20 +147,25 @@ void app_main(void) if(event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) { // Get parameter information from parameter queue ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); - printf("HOLDING %s: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n", + ESP_LOGI(SLAVE_TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", rw_str, (uint32_t)reg_info.time_stamp, (uint32_t)reg_info.mb_offset, (uint32_t)reg_info.type, (uint32_t)reg_info.address, (uint32_t)reg_info.size); - if (reg_info.address == (uint8_t*)&holding_reg_params.data_chan0) + if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0) { - holding_reg_params.data_chan0 += MB_CHAN_DATA_OFFSET; + portENTER_CRITICAL(¶m_lock); + holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET; + if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) { + coil_reg_params.coils_port1 = 0xFF; + } + portEXIT_CRITICAL(¶m_lock); } } else if (event & MB_EVENT_INPUT_REG_RD) { ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); - printf("INPUT READ: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n", + ESP_LOGI(SLAVE_TAG, "INPUT READ (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", (uint32_t)reg_info.time_stamp, (uint32_t)reg_info.mb_offset, (uint32_t)reg_info.type, @@ -161,7 +173,7 @@ void app_main(void) (uint32_t)reg_info.size); } else if (event & MB_EVENT_DISCRETE_RD) { ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); - printf("DISCRETE READ: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n", + ESP_LOGI(SLAVE_TAG, "DISCRETE READ (%u us): ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", (uint32_t)reg_info.time_stamp, (uint32_t)reg_info.mb_offset, (uint32_t)reg_info.type, @@ -169,16 +181,18 @@ void app_main(void) (uint32_t)reg_info.size); } else if (event & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) { ESP_ERROR_CHECK(mbc_slave_get_param_info(®_info, MB_PAR_INFO_GET_TOUT)); - printf("COILS %s: time_stamp(us):%u, mb_addr:%u, type:%u, st_address:0x%.4x, size:%u\r\n", + ESP_LOGI(SLAVE_TAG, "COILS %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u", rw_str, (uint32_t)reg_info.time_stamp, (uint32_t)reg_info.mb_offset, (uint32_t)reg_info.type, (uint32_t)reg_info.address, (uint32_t)reg_info.size); + if (coil_reg_params.coils_port1 == 0xFF) break; } } - // Destroy of Modbus controller once get maximum value of data_chan0 - printf("Modbus controller destroyed."); + // Destroy of Modbus controller on alarm + ESP_LOGI(SLAVE_TAG,"Modbus controller destroyed."); + vTaskDelay(100); ESP_ERROR_CHECK(mbc_slave_destroy()); } diff --git a/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults b/examples/protocols/modbus/serial/mb_slave/sdkconfig.defaults new file mode 100644 index 000000000..334a4213a --- /dev/null +++ b/examples/protocols/modbus/serial/mb_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 diff --git a/examples/protocols/modbus_master/CMakeLists.txt b/examples/protocols/modbus_master/CMakeLists.txt deleted file mode 100644 index 66400e830..000000000 --- a/examples/protocols/modbus_master/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# 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) - -set(SUPPORTED_TARGETS esp32) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(modbus_master) diff --git a/examples/protocols/modbus_master/README.md b/examples/protocols/modbus_master/README.md deleted file mode 100644 index c700f4b29..000000000 --- a/examples/protocols/modbus_master/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# Modbus Master Example - -This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device. -This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary in the files /main/device_params.h/c. -The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment. - -The example implements simple control algorithm and checks humidity and temperature from two sensors and set alarm (relay in third device) when values exceeded limits. - -Device parameters definition: --------------------------------------------------------------------------------------------------- -| Slave address | Characteristic ID | Characteristic name | Description | -| --------------------|----------------------|----------------------|----------------------------| -| MB_DEVICE_ADDR1 | CID_DATA_CHAN_0, | Data_channel_0 | Data channel 1 | -| MB_DEVICE_ADDR1 | CID_HUMIDITY_1, | Humidity_1 | Humidity from sensor 1 | -| | CID_TEMPERATURE_1 | Temperature_1 | Sensor 1 temperature | -| MB_DEVICE_ADDR2 | CID_HUMIDITY_2, | Humidity_2 | Humidity from sensor 2 | -| | CID_TEMPERATURE_2 | Temperature_2 | Sensor 2 temperature | -| MB_DEVICE_ADDR3 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off | --------------------------------------------------------------------------------------------------- - -Modbus segment device connection schematic: -``` - MB_DEVICE_ADDR1 - ------------- - | | - | Slave 1 |---<>--+ - | | | - ------------- | - MB_DEVICE_ADDR2 | - ------------- | ------------- - | | | | | - | Slave 2 |---<>--+---<>---| Master | - | | | | | - ------------- | ------------- - MB_DEVICE_ADDR3 | - ------------- RS485 network - | | | - | Slave 3 |---<>--+ - | | - ------------- -``` - -## Hardware required : -Option 1: -PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 WROVER-KIT board. -Option 2: -Three ESP32 WROVER-KIT board flashed with modbus_slave example software to represent slave device with specific slave address. The slave addresses for each board have to be configured as defined in "Device parameters definition" table above. -One ESP32 WROVER-KIT board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below). - -The MAX485 line driver is used as an example below but other similar chips can be used as well. -RS485 example circuit schematic for connection of master and slave devices into segment: -``` - VCC ---------------+ +--------------- VCC - | | - +-------x-------+ +-------x-------+ - RXD <------| RO | DIFFERENTIAL | RO|-----> RXD - | B|---------------|B | - TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD -ESP32 WROVER KIT 1 | | RS-485 side | | External PC (emulator) with USB to serial or - RTS --+--->| DE | / \ | DE|---+ ESP32 WROVER KIT 2 (slave) - | | A|---------------|A | | - +----| /RE | PAIR | /RE|---+-- RTS - +-------x-------+ +-------x-------+ - | | - --- --- - Modbus Master device Modbus Slave device - -``` - -## How to setup and use an example: - -### Configure the application -Configure the UART pins used for modbus communication using command and table below. -``` -idf.py menuconfig -``` - -``` - ------------------------------------------------------------------------------------------------ - | ESP32 Interface | #define | Default ESP32 Pin | External RS485 Pin| - | ----------------------|------------------------------|-------------------|-------------------| - | Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | DI | - | Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | RO | - | Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | ~RE/DE | - | | | | | - | Ground | n/a | GND | GND | - ------------------------------------------------------------------------------------------------ -``` -The communication parameters below allow to configure Modbus stack appropriately but usually it is enough to use default settings. -See the help string of parameters for more information. - -### Setup external Modbus slave devices or emulator -Option 1: -Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices. -Option 2: -Other option is to have the modbus_slave example flashed into ESP32 WROVER KIT board and connect boards together as showed in "Modbus segment connection schematic above". See the modbus slave API documentation to configure communication parameters and slave addresses as defined in "Device parameters definition" table above. - -### Build and flash software of master device -Build the project and flash it to the board, then run monitor tool to view serial output: -``` -idf.py -p PORT flash monitor -``` - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. - -## Example Output -Example output of the application: -``` -I (51493) SENSE_MAIN: cid: 0, Data_channel_0(Volts) = 6.00 -I (51543) SENSE_MAIN: cid: 1, Humidity_1(%rH) = 22.00 -I (51573) SENSE_MAIN: cid: 2, Temperature_1(C) = 0.00 -I (51603) SENSE_MAIN: cid: 3, Humidity_2(%rH) = 1.00 -I (51633) SENSE_MAIN: cid: 4, Temperature_2(C) = 33.00 -I (51673) SENSE_MAIN: cid: 5, (RelayP1) = OFF -I (61713) SENSE_MAIN: cid: 0, Data_channel_0(Volts) = 50.00 -I (61763) SENSE_MAIN: cid: 1, Humidity_1(%rH) = 22.00 -I (61793) SENSE_MAIN: cid: 2, Temperature_1(C) = 0.00 -I (61823) SENSE_MAIN: cid: 3, Humidity_2(%rH) = 1.00 -I (61853) SENSE_MAIN: cid: 4, Temperature_2(C) = 33.00 -I (61893) SENSE_MAIN: cid: 5, (RelayP1) = OFF -I (62893) SENSE_MAIN: The value exceeds limit, then set relay. -I (71953) SENSE_MAIN: cid: 0, Data_channel_0(Volts) = 50.00 -I (71993) SENSE_MAIN: cid: 1, Humidity_1(%rH) = 22.00 -I (72023) SENSE_MAIN: cid: 2, Temperature_1(C) = 0.00 -I (72063) SENSE_MAIN: cid: 3, Humidity_2(%rH) = 1.00 -I (72093) SENSE_MAIN: cid: 4, Temperature_2(C) = 33.00 -I (73143) SENSE_MAIN: cid: 5, (RelayP1) = ON - -``` -The example refreshes the characteristics from devices every 10 seconds, verifies if they exceeded limits and sets alarm accordingly. The output line describes Timestamp, Cid of characteristic, Characteristic name(Units), Characteristic value. - - diff --git a/examples/protocols/modbus_master/main/CMakeLists.txt b/examples/protocols/modbus_master/main/CMakeLists.txt deleted file mode 100644 index 2555f99e4..000000000 --- a/examples/protocols/modbus_master/main/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -idf_component_register(SRCS "sense_main.c" - "sense_modbus.c" - "device_params.c" - INCLUDE_DIRS "." "include") diff --git a/examples/protocols/modbus_master/main/Kconfig.projbuild b/examples/protocols/modbus_master/main/Kconfig.projbuild deleted file mode 100644 index 544610001..000000000 --- a/examples/protocols/modbus_master/main/Kconfig.projbuild +++ /dev/null @@ -1,27 +0,0 @@ -menu "Modbus Example Configuration" - - config MB_UART_RXD - int "UART RXD pin number" - range 0 34 - default 22 - 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 - default 23 - 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 - default 18 - help - GPIO number for UART RTS pin. This pin is connected to - ~RE/DE pin of RS485 transceiver to switch direction. - -endmenu diff --git a/examples/protocols/modbus_master/main/device_params.c b/examples/protocols/modbus_master/main/device_params.c deleted file mode 100644 index 12ddd6f30..000000000 --- a/examples/protocols/modbus_master/main/device_params.c +++ /dev/null @@ -1,70 +0,0 @@ -/*====================================================================================== - * Description: - * This C file contains user defined parameters for Modbus example and data dictionary - * which describes each value (characteristic) and links it to modbus registers in - * in corresponded slave device. - *=====================================================================================*/ - -#include "mbcontroller.h" -#include "device_params.h" - -// Here are the user instances defined as structures for device parameters packed by 1 byte -// These are keep the values that can be accessed from Modbus master - -holding_reg_params_t holding_reg_params = { 0 }; - -input_reg_params_t input_reg_params = { 0 }; - -coil_reg_params_t coil_reg_params = { 0 }; - -discrete_reg_params_t discrete_reg_params = { 0 }; - -#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) -#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1)) -#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1)) -// Discrete offset macro (options can be used as bit masks) -#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1)) - -#define STR(fieldname) ((const char*)( fieldname )) -#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } - -// This table below defines the characteristics supported by this Modbus master device (Data dictionary). -// These characteristics are linked to Modbus parameters of external slave devices in Modbus network. -// For this example next devices are supported: -// MB_DEVICE_ADDR1 : (CID_HUMIDITY_1, CID_TEMPERATURE_1) : Modbus sensor 1 (Humidity and Temperature) -// MB_DEVICE_ADDR2 : (CID_HUMIDITY_2, CID_TEMPERATURE_2) : Modbus sensor 2 (Humidity and Temperature) -// MB_DEVICE_ADDR3 : (CID_RELAY_P1, CID_RELAY_P2) : Modbus output device (Relay outputs on/off) - -// There are two options to define instance for parameter: -// 1. Define offset (param_offset field) to the field in the parameter's structure (see example below) -// Once set it will be used as to store parameter value. -// 2. Set param_offset field in characteristics table to zero. -// This will allow to allocate space for parameter storage during initialization and use it as a cache. - -// Example Data (Object) Dictionary for Modbus parameters -const mb_parameter_descriptor_t device_parameters[] = { - // { Cid, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode} - // Parameter: Data channel 0 : Data channel 0 = Voltage - { CID_DATA_CHAN_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, - INPUT_OFFSET(data_chan0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - { CID_HUMIDITY_1, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, - HOLD_OFFSET(mb_device1_humidity), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - // Parameter: Temperature_2 : Temperature from device slave address = 1 - { CID_TEMPERATURE_1, STR("Temperature_1"), STR("°C"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2, - HOLD_OFFSET(mb_device1_temperature), PARAM_TYPE_FLOAT, 4, OPTS( -40, 80, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - // Parameter: Humidity_2 : Humidity from device slave address = 2 - { CID_HUMIDITY_2, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 0, 2, - HOLD_OFFSET(mb_device2_humidity), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - // Parameter: Temperature_2 : Temperature from device slave address = 2 - { CID_TEMPERATURE_2, STR("Temperature_2"), STR("°C"), MB_DEVICE_ADDR2, MB_PARAM_HOLDING, 2, 2, - HOLD_OFFSET(mb_device2_temperature), PARAM_TYPE_FLOAT, 4, OPTS( -40, 80, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, - // Parameter: Relay P1 : Alarm on/off channel 1 : Output device 1 - { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR3, MB_PARAM_COIL, 0, 3, - COIL_OFFSET(coils_port1), PARAM_TYPE_U16, 2, OPTS( BIT0 | BIT1 | BIT2, 0, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, - // Parameter: Relay P2 : Alarm on/off channel 2 : Output device 1 - { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR3, MB_PARAM_COIL, 3, 8, - COIL_OFFSET(coils_port2), PARAM_TYPE_U16, 2, OPTS( BIT3, 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])); diff --git a/examples/protocols/modbus_master/main/include/device_params.h b/examples/protocols/modbus_master/main/include/device_params.h deleted file mode 100644 index 64a3447d3..000000000 --- a/examples/protocols/modbus_master/main/include/device_params.h +++ /dev/null @@ -1,140 +0,0 @@ -/*===================================================================================== - * Description: - * C file to define user defined parameters for Modbus example - *====================================================================================*/ - -#ifndef _DEVICEPARAMS_H_ -#define _DEVICEPARAMS_H_ - -#include "mbcontroller.h" // for common Modbus defines - -// Enumeration of modbus device addresses accessed by master device -enum { - MB_DEVICE_ADDR1 = 1, - MB_DEVICE_ADDR2 = 2, - MB_DEVICE_ADDR3 = 3, -}; - -// Enumeration of all supported CIDs for device (used in parameter definition table) -enum { - CID_DATA_CHAN_0 = 0, - CID_HUMIDITY_1, - CID_TEMPERATURE_1, - CID_HUMIDITY_2, - CID_TEMPERATURE_2, - CID_RELAY_P1, - CID_RELAY_P2, - CID_COUNT, -}; - -#define DEVICE_PARAM_MAX_SIZE 24 - -// The structures below define the parameters that will be accessed by Modbus master device. -// These parameters reflect the parameters in the address space of external devices in Modbus network. -// They defined for each modbus register type (coils, discreet inputs, holding registers, input registers). -// These are not required but can be used by user to link characteristic to corresponded field -// See the parameter definition table for more information. -// It is just example and it is responsibility of user to define them as required. -#pragma pack(push, 1) -typedef struct -{ - // Parameter: discrete_input0 - uint8_t discrete_input0:1; - // Parameter: discrete_input1 - uint8_t discrete_input1:1; - // Parameter: discrete_input2 - uint8_t discrete_input2:1; - // Parameter: discrete_input3 - uint8_t discrete_input3:1; - // Parameter: discrete_input4 - uint8_t discrete_input4:1; - // Parameter: discrete_input5 - uint8_t discrete_input5:1; - // Parameter: discrete_input6 - uint8_t discrete_input6:1; - // Parameter: discrete_input7 - uint8_t discrete_input7:1; - uint8_t discrete_input_port1:8; -} discrete_reg_params_t; -#pragma pack(pop) - -// Note: For correct access the coils storage for each addressed parameter -// has to include at least 2 byte (register)! -#pragma pack(push, 1) -typedef union -{ - struct { - // Parameter: Coil 0 : Coil0 - - uint8_t coil0:1; - // Parameter: Coil 1 : Coil1 - uint8_t coil1:1; - // Parameter: Coil 2 : Coil2 - uint8_t coil2:1; - // Parameter: Coil 3 : Coil3 - uint8_t coil3:1; - // Parameter: Coil 4 : Coil4 - uint8_t coil4:1; - // Parameter: Coil 5 : Coil5 - uint8_t coil5:1; - // Parameter: Coil 6 : Coil6 - uint8_t coil6:1; - // Parameter: Coil 7 : Coil7 - uint8_t coil7:1; - uint8_t coil_port2:8; - }; - uint8_t coils_port1; - uint8_t coils_port2; -} coil_reg_params_t; -#pragma pack(pop) - -// Input register structure to keep characteristic's values -#pragma pack(push, 1) -typedef struct -{ - // Parameter: Data channel 0 : data_chan0 - float data_chan0; - // Parameter: Data channel 1 : data_chan1 - float data_chan1; - // Parameter: Data channel 2 : data_chan2 - float data_chan2; - // Parameter: Data channel 3 : data_chan3 - float data_chan3; -} input_reg_params_t; -#pragma pack(pop) - -// Holding register structure to keep characteristic's values -#pragma pack(push, 1) -typedef struct -{ - // Parameter: Data channel 0 : mb_device1_humidity - float mb_device1_humidity; - // Parameter: Data channel 1 : mb_device1_temperature - float mb_device1_temperature; - // Parameter: Data channel 2 : mb_device2_humidity - float mb_device2_humidity; - // Parameter: Data channel 3 : mb_device2_temperature - float mb_device2_temperature; - // Parameter: Protocol version : protocol_version - uint16_t mb_device1_protocol_version; - // Parameter: Hardware version : hardware_version - uint16_t mb_device1_hardware_version; - // Parameter: Software Version : software_version - uint16_t mb_device1_software_version; - // Parameter: Software Revision : software_revision - uint16_t mb_device1_software_revision; - // Parameter: Device Type : deviceType : - uint16_t mb_device1_device_type; - uint8_t mb_device1_string_test[PARAM_SIZE_ASCII24]; -} holding_reg_params_t; -#pragma pack(pop) - -extern holding_reg_params_t holding_reg_params; -extern input_reg_params_t input_reg_params; -extern coil_reg_params_t coil_reg_params; -extern discrete_reg_params_t discrete_reg_params; - -extern const mb_parameter_descriptor_t device_parameters[]; -extern const uint16_t num_device_parameters; - -#endif /* _DEVICEPARAMS_H_ */ diff --git a/examples/protocols/modbus_master/main/include/sense_modbus.h b/examples/protocols/modbus_master/main/include/sense_modbus.h deleted file mode 100644 index 4c13391da..000000000 --- a/examples/protocols/modbus_master/main/include/sense_modbus.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -* ESPRESSIF MIT License -* -* Copyright (c) 2018 -* -* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, -* it is free of charge, to any person obtaining a copy of this software and associated -* documentation files (the "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -* and/or sell copies of the Software, and to permit persons to whom the Software is furnished -* to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*/ - -#ifndef __SENSE_MB_H__ -#define __SENSE_MB_H__ - -#include "mbcontroller.h" -#include "device_params.h" - -#ifdef __cplusplus -extern "C" { -#endif /**< _cplusplus */ - -typedef struct -{ - uint16_t cid; /*!< Characteristic cid */ - const char* param_key; /*!< The key (name) of the parameter */ - const char* param_units; /*!< The units of the parameter */ - mb_parameter_opt_t param_opts; /*!< Parameter options */ - mb_param_perms_t access; /*!< Access permissions based on mode */ - void* instance_ptr; /*!< Data instance for the parameter */ - mb_descr_type_t instance_type; /*!< Type of instance value */ - size_t instance_size; /*!< Size of instance to save data */ - uint8_t change_flag; /*!< Change value flag */ - esp_err_t status; /*!< Status of the value */ - uint64_t timestamp; /*!< Time stamp of last access to parameter */ -} characteristic_descriptor_t; - -esp_err_t sense_modbus_init(void); -esp_err_t sense_modbus_get_characteristics(characteristic_descriptor_t** cid_table, uint16_t* table_size); -esp_err_t sense_modbus_read_value(uint16_t cid, void* value); -esp_err_t sense_modbus_send_value(uint16_t cid, void* value); -esp_err_t sense_modbus_get_cid_data(uint16_t cid, characteristic_descriptor_t* cid_data); - -#ifdef __cplusplus -} -#endif /**< _cplusplus */ - -#endif /**< __SENSE_MB_H__ */ diff --git a/examples/protocols/modbus_master/main/sense_main.c b/examples/protocols/modbus_master/main/sense_main.c deleted file mode 100644 index 13d52a983..000000000 --- a/examples/protocols/modbus_master/main/sense_main.c +++ /dev/null @@ -1,181 +0,0 @@ -/* -* ESPRESSIF MIT License -* -* Copyright (c) 2018 -* -* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, -* it is free of charge, to any person obtaining a copy of this software and associated -* documentation files (the "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -* and/or sell copies of the Software, and to permit persons to whom the Software is furnished -* to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all copies or -* substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*/ - -#include "string.h" -#include "esp_log.h" -#include "sense_modbus.h" - -static const char *TAG = "sense_main"; - -// The number of parameters that intended to be used in the particular control process -#define SENSE_MAX_CIDS 6 - -// Timeout to update cid over Modbus if it is not updated by set/get request from mdf -#define MODBUS_VALUE_UPDATE_TIMEOUT_US (10000000) -#define MODBUS_GET_REQUEST_TIMEOUT (1000) - -#define INIT_DELAY_TICS (100 / portTICK_RATE_MS) -#define TIMEOUT_UPDATE_CIDS_MS (1000) -#define TIMEOUT_UPDATE_CIDS_TICS (TIMEOUT_UPDATE_CIDS_MS / portTICK_RATE_MS) - -#define SENSE_TRIGGER_TASK_STACK_SIZE (1024 * 4) -#define SENSE_TRIGGER_TASK_PRIO (6) - -#define SENSE_MAIN_TAG "SENSE_MAIN" - -#define SENS_MAIN_CHECK(a, ret_val, str, ...) \ - if (!(a)) { \ - ESP_LOGE(SENSE_MAIN_TAG, "%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return (ret_val); \ - } - -// Characteristic information table -static characteristic_descriptor_t* cid_table[SENSE_MAX_CIDS] = { 0 }; -static uint16_t cid_counter = SENSE_MAX_CIDS; - -// User operational task to trigger event when cid value exceeds limit -static void trigger_operation_task(void *arg) -{ - float value; - uint64_t timeout = 0; - uint16_t temp_value = 0; - bool alarm_state = false; - bool relay_state = false; - characteristic_descriptor_t cid_data = { 0 }; - - while (1) { - alarm_state = false; - for (int cid = 0; cid < (CID_RELAY_P2); cid++) - { - // Get cid data - ESP_ERROR_CHECK_WITHOUT_ABORT(sense_modbus_get_cid_data(cid, &cid_data)); - assert(cid_data.param_key != NULL); - assert(cid_data.instance_ptr != NULL); - // If value is not updated during 10 seconds then update it - timeout = esp_timer_get_time(); - value = *(float*)cid_data.instance_ptr; - // Check limits to set alarm if exceeded limit - if (((value > cid_data.param_opts.max) || - (value < cid_data.param_opts.min)) && - (cid != CID_RELAY_P1)) { - alarm_state = true; - } - if (timeout > (cid_data.timestamp + MODBUS_VALUE_UPDATE_TIMEOUT_US)) { - // The value is not updated during timeout then update it - // The actual value is saved in the instance storage - value = 0; - esp_err_t error = sense_modbus_read_value(cid, (void*)&value); - if (error != ESP_OK) { - ESP_LOGE(SENSE_MAIN_TAG, "Update failed for cid: %u, %s(%s) = %d, %s", - (uint16_t)cid, - (char*)cid_data.param_key, - (char*)cid_data.param_units, - *(int*)cid_data.instance_ptr, - (char*)esp_err_to_name(error)); - } else { - // Update state of alarm - if (cid == CID_RELAY_P1) { - // Get actual relay state - relay_state = (*(uint16_t*)(cid_data.instance_ptr) - & (uint16_t)(cid_data.param_opts.opt1)); - const char* relay_state_str = relay_state ? "ON" : "OFF"; - ESP_LOGI(SENSE_MAIN_TAG, "cid: %u, (%s) = %s", (uint16_t)cid, - (char*)cid_data.param_key, - (const char*)relay_state_str); - } else { - // Update read value - ESP_LOGI(SENSE_MAIN_TAG, "cid: %u, %s(%s) = %.2f", (uint16_t)cid, - (char*)cid_data.param_key, - (char*)cid_data.param_units, - (float)value); - } - } - } - } - // Check all characteristics if they exceed limits and set alarm accordingly - if (!alarm_state) { - if (relay_state == true) { - ESP_LOGI(SENSE_MAIN_TAG, "The values within the limit, then release relay."); - temp_value = 0; - // Release the relay in IO slave device - // Do not check an error in this example (IO slave may be not configured) - (void)sense_modbus_send_value(CID_RELAY_P1, &temp_value); - relay_state = false; - } - } else { - if (!relay_state) { - ESP_LOGI(SENSE_MAIN_TAG, "The value exceeds limit, then set relay."); - temp_value = 0x00FF; - (void)sense_modbus_send_value(CID_RELAY_P1, &temp_value); // Set the relay - relay_state = true; - } - } - - vTaskDelay(TIMEOUT_UPDATE_CIDS_TICS); - } - vTaskDelete(NULL); -} - -static void sense_device_init(void) -{ - // Initialize and start Modbus controller - sense_modbus_init(); -} - -void app_main(void) -{ - esp_err_t result = ESP_OK; - - // Initialization of device peripheral and objects - sense_device_init(); - - // Get all supported modbus characteristics from modbus controller - result = sense_modbus_get_characteristics(&cid_table[0], &cid_counter); - assert((result == ESP_OK) && (cid_counter >= 1)); - ESP_LOGI(TAG, "Found (%u) characteristics in the table.", cid_counter); - characteristic_descriptor_t* sid_descr_ptr = { 0 }; - float value = 0; - esp_err_t err = ESP_OK; - int cid_registered = 0; - // Read all found characteristics - for (uint16_t cid = 0; cid < cid_counter; cid++) { - sid_descr_ptr = cid_table[cid]; - // Initially read of Modbus values and check status - err = sense_modbus_read_value(cid, (void*)&value); - if (err == ESP_OK) { - ESP_LOGI(TAG, "Characteristic (%s) data = 0x%.4x read successful.", - (char*)sid_descr_ptr->param_key, (unsigned int)value); - cid_registered++; - } else { - ESP_LOGE(TAG, "Characteristic (%s) read value fail, err = %d (%s).", - (char*)sid_descr_ptr->param_key, - (int)err, - (char*)esp_err_to_name(err)); - } - } - - // Starts operation task to check values and trigger an event - xTaskCreate(trigger_operation_task, "trigger_operation_task", - SENSE_TRIGGER_TASK_STACK_SIZE, NULL, SENSE_TRIGGER_TASK_PRIO, NULL); -} diff --git a/examples/protocols/modbus_master/tool/Mbslav1.mbs b/examples/protocols/modbus_master/tool/Mbslav1.mbs deleted file mode 100644 index c8f8c23dc247cb2cf059deb840c6812dfaf4d84b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102266 zcmeI!&1zFY6bJCr55Yzd3*x#i1O*qZzCcPhbzwK^rW;WVQfO(33F2Gy4SWO9cN3Q# z|2Iv=k8aFGUHCh2W|EtkxhKCn6SBEpvsg(xNG>H;XIHA$(zn*?OV@lCr`@2$YAxSx zXYJZf9oMwRbx@E+fQ*(%j10bvKugu<@AH(_2j4Ie)86DWQMjbKj(hwzs2eKJ!Wk?YgeDt zs9x7`+KqZvkGgT@arxu>C42A6b{*D1y{I<}@4BpVW4EStlyzPuC-ttL*H-rO-MUlb z+O2zi*6uTR{b|nBQJth6=Q~bPl diff --git a/examples/protocols/modbus_master/tool/Mbslav2.mbs b/examples/protocols/modbus_master/tool/Mbslav2.mbs deleted file mode 100644 index 1e33ed3cc12d72236a575b8839ae5e62a346dbd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102266 zcmeIzy=oLu7zW@s>jrd%U>fUK2nrS=YHMPe*leRVHX-0f0*1)Ci)iHy2)PvR!JDwn zJs7c&jNkqwY>`4UsLgZWJLk-q^EVG?X6~+Lnl4q|tLRj8r!C2qDx0Rs_er*p4wq&Z z-E?j=C(YH(9Aunf_A;n^E&VFrE%a9!zpfvB__@BjyRxx(SDgR>0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0Rja6jliAxGD}r9^Yo+2ql$htPTD_plATFqp6W8aij|7b6)!5T|3OsO zW)k0OjVjO5;cSg*-kda-pJtF(IjDRs&+}x`&p0lB{k+uKTi(d)>}4l!FMR51mTOxX zXTRooSux7{Z0BB`|2C+BCHe&e5g+V!kluG;KZ GpZo%;#P*W_ diff --git a/examples/protocols/modbus_master/tool/Mbslav3.mbs b/examples/protocols/modbus_master/tool/Mbslav3.mbs deleted file mode 100644 index 6f4b6823e596eb39a0c75ecf3d9fdab040ca1dc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106050 zcmeI)L2DC16ae6tR57g}c=23s9(vIJgW{T5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zup-dw^;X`oNq_(W0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0Rp=aI9W_u|0S+0 z;%D|hvi0-5%iJ-|{(Ie6#31{lY|pZNYH2TW|GF&?Ew$a{+}Zz%$^0+unp|dO@G-99 zON=ug$3>ji?@9|lTVi=#}Yv-!Vvpo5bwL8kQe2e$-W|K4RYV051;B;4? z!n{==!wTsR@ITxcx3)soqV9B0&re`%7075?752?4NjlY(aS7-5Wxb@DSmV0d&hNxJ zTc?WMw=3KComXbfZPnSeri`-+*7{mTTb<=KX%+nIXIWOVWqq};WSdy!ES>JG#_jDY P_)wUOSM`DP^0D|m?&xk_ diff --git a/examples/protocols/modbus_master/tool/Mbslav4.mbs b/examples/protocols/modbus_master/tool/Mbslav4.mbs deleted file mode 100644 index c47a16512f071d7ac61cd2b9e0d753afc55f839b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102140 zcmeIzu}T9$5CG6gjGz$&8|&Cx_zP;Ijg8n?X%Qq42qH2rMR%{v%GPmA;y!L;6vM28 zILq~6(V`L{K!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D&celcS|A8w3at zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FqgH0{hL0om@*3@43EYK6Mu#KCeYw z)rKbOc0Y4F^D*->bN2_Ct)=E`o{?)C)5RI{T4rVAJce--ldOZdh|}8VJ}!SgFFAYb xhcS*(T*uw7r!K$6z0Gg6>YvT39qL$Rm7Sh~o`UVbd}ChP-p`Brifvx*@c}~9*yR8K diff --git a/examples/protocols/modbus_master/tool/ModbusSlave_workspace.png b/examples/protocols/modbus_master/tool/ModbusSlave_workspace.png deleted file mode 100644 index 495d0522602c8fb0f0e3b80844d3116c45077204..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49614 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 z 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