Merge branch 'feature/freemodbus_master_ascii_support' into 'master'

freemodbus: add support for master ascii mode

Closes IDFGH-1639

See merge request espressif/esp-idf!6032
This commit is contained in:
Angus Gratton 2019-11-26 13:16:27 +08:00
commit 2345895302
76 changed files with 2080 additions and 1155 deletions

View file

@ -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"

View file

@ -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 masters program to handle the error.
config FMB_SERIAL_TASK_PRIO
int "Modbus serial task priority"
range 3 10

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,573 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* 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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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 );

View file

@ -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

View file

@ -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 );

View file

@ -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. */

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 );

View file

@ -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

View file

@ -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__); \

View file

@ -120,7 +120,7 @@ xMBPortEventGet(eMBEventType * peEvent)
xQueueHandle
xMBPortEventGetHandle(void)
{
if(xQueueHdl != NULL) //
if(xQueueHdl != NULL)
{
return xQueueHdl;
}

View file

@ -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()

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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));

View file

@ -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) ",

View file

@ -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);

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,3 @@
idf_component_register(SRCS "modbus_params.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES freemodbus)

View file

@ -0,0 +1,5 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .

View file

@ -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)

View file

@ -3,7 +3,7 @@
* C file to define parameter storage instances
*====================================================================================*/
#include <stdint.h>
#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

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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).

View file

@ -0,0 +1,5 @@
set(PROJECT_NAME "modbus_master")
idf_component_register(SRCS "master.c"
INCLUDE_DIRS ".")

View file

@ -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

View file

@ -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, &param_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);
}

View file

@ -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

View file

@ -0,0 +1,11 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(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)

View file

@ -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

View file

@ -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.

View file

@ -0,0 +1,4 @@
set(PROJECT_NAME "modbus_slave")
idf_component_register(SRCS "slave.c"
INCLUDE_DIRS ".")

View file

@ -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

View file

@ -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.)

View file

@ -5,15 +5,16 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#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(&reg_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(&param_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(&param_lock);
}
} else if (event & MB_EVENT_INPUT_REG_RD) {
ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_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(&reg_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(&reg_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());
}

View file

@ -0,0 +1,10 @@
#
# Modbus configuration
#
CONFIG_MB_COMM_MODE_ASCII=y
CONFIG_MB_SLAVE_ADDR=1
CONFIG_MB_UART_BAUD_RATE=115200
CONFIG_FMB_TIMER_PORT_ENABLED=y
CONFIG_FMB_TIMER_GROUP=0
CONFIG_FMB_TIMER_INDEX=0
CONFIG_FMB_TIMER_ISR_IN_IRAM=y

View file

@ -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)

View file

@ -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.

View file

@ -1,4 +0,0 @@
idf_component_register(SRCS "sense_main.c"
"sense_modbus.c"
"device_params.c"
INCLUDE_DIRS "." "include")

View file

@ -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

View file

@ -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]));

View file

@ -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_ */

View file

@ -1,60 +0,0 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* 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__ */

View file

@ -1,181 +0,0 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* 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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View file

@ -1,6 +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)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modbus_slave)

View file

@ -1,3 +0,0 @@
idf_component_register(SRCS "freemodbus.c"
"deviceparams.c"
INCLUDE_DIRS ".")

View file

@ -1,27 +0,0 @@
menu "Modbus Slave 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

View file

@ -1,131 +0,0 @@
/*=====================================================================================
* 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
#define A24_ARR_SIZE 24
// 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
{
// 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)
#pragma pack(push, 1)
typedef 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;
// Coils port 1
uint8_t coil_port1:8;
} coil_reg_params_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct
{
// Parameter: Data channel 0 : data_chan0 : NV Address: 0
float data_chan0;
// Parameter: Data channel 1 : data_chan1 : NV Address: 0
float data_chan1;
// Parameter: Data channel 2 : data_chan2 : NV Address: 0
float data_chan2;
// Parameter: Data channel 3 : data_chan3 : NV Address: 0
float data_chan3;
} input_reg_params_t;
#pragma pack(pop)
//See register map for more information.
#pragma pack(push, 1)
typedef struct
{
// Parameter: Data channel 0 : DataChan0
float data_chan0;
// Parameter: Data channel 1 : DataChan1
float data_chan1;
// Parameter: Data channel 2 : DataChan2
float data_chan2;
// Parameter: Data channel 3 : DataChan3
float data_chan3;
// Parameter: Protocol version : protocol_version
uint16_t protocol_version;
// Parameter: Hardware version : hardware_version
uint16_t hardware_version;
// Parameter: Software Version : software_version
uint16_t software_version;
// Parameter: Software Revision : software_revision
uint16_t software_revision;
// Parameter: Device Type : deviceType :
uint16_t deviceType;
// Parameter: Modbus Network Address : modbus_address
uint16_t modbus_address;
// Parameter: Modbus Baudrate : modbus_baud
uint16_t modbus_baud;
// Parameter: Modbus parity : modbus_parity
uint16_t modbus_parity;
// Parameter: Modbus stopbit : modbus_stop_bits
uint16_t modbus_stop_bits;
// Parameter: Brace control : modbus_brace_ctrl
uint16_t modbus_brace_ctrl;
// Parameter: Serial number : serial_number
uint32_t serial_number;
// Parameter: Up time : up_time
uint32_t up_time;
// Parameter: Device state : device_state
uint16_t device_state;
// Parameter: Test Float0 : test_float0
float test_float0;
// Parameter: Test Float1 : test_float1
float test_float1;
// Parameter: Test Float2 : test_float2
float test_float2;
// Parameter: Test Float3 : test_float3
float test_float3;
// Parameter: Test String : string_test
uint8_t string_test[A24_ARR_SIZE];
} 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)

View file

@ -49,6 +49,47 @@
- cd $TEST_FW_PATH
# run test
- python Runner.py $TEST_CASE_PATH -c $CONFIG_FILE -e $ENV_FILE
.example_debug_template:
stage: target_test
when: on_success
only:
refs:
- master
- /^release\/v/
- /^v\d+\.\d+(\.\d+)?($|-)/
- triggers
- schedules
variables:
- $BOT_TRIGGER_WITH_LABEL == null
- $BOT_LABEL_EXAMPLE_TEST
dependencies:
- assign_test
- build_examples_make
- build_examples_cmake_esp32
artifacts:
when: always
paths:
- $LOG_PATH
expire_in: 1 week
reports:
junit: $LOG_PATH/*/XUNIT_RESULT.xml
variables:
TEST_FW_PATH: "$CI_PROJECT_DIR/tools/tiny-test-fw"
TEST_CASE_PATH: "$CI_PROJECT_DIR/examples"
CONFIG_FILE_PATH: "${CI_PROJECT_DIR}/examples/test_configs"
LOG_PATH: "$CI_PROJECT_DIR/TEST_LOGS"
ENV_FILE: "$CI_PROJECT_DIR/ci-test-runner-configs/$CI_RUNNER_DESCRIPTION/EnvConfig.yml"
script:
- *define_config_file_name
# first test if config file exists, if not exist, exit 0
- test -e $CONFIG_FILE || exit 0
# clone test env configs
- git clone $TEST_ENV_CONFIG_REPOSITORY
- python $CHECKOUT_REF_SCRIPT ci-test-runner-configs ci-test-runner-configs
- cd $TEST_FW_PATH
# run test
- python Runner.py $TEST_CASE_PATH -c $CONFIG_FILE -e $ENV_FILE
.unit_test_template:
extends: .example_test_template
@ -243,6 +284,21 @@ example_test_010:
- ESP32
- Example_ExtFlash
example_test_011:
extends: .example_debug_template
parallel: 4
tags:
- ESP32
- Example_T2_RS485
artifacts:
when: always
expire_in: 1 week
paths:
- $CI_PROJECT_DIR/examples/protocols/modbus/serial/*.log
- $LOG_PATH
variables:
SETUP_TOOLS: "1"
UT_001:
extends: .unit_test_template
parallel: 28