nuttx-apps/modbus/rtu/mbrtu_m.c
2024-02-17 05:01:18 -08:00

529 lines
15 KiB
C

/****************************************************************************
* apps/modbus/rtu/mbrtu_m.c
*
* FreeModbus Library: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2013 China Beijing Armink <armink.ztl@gmail.com>
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "port.h"
#include "modbus/mb.h"
#include "modbus/mb_m.h"
#include "mbrtu_m.h"
#include "modbus/mbframe.h"
#include "mbcrc.h"
#include "modbus/mbport.h"
#if defined(CONFIG_MB_RTU_MASTER)
/****************************************************************************
* Included Files
****************************************************************************/
#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. */
/****************************************************************************
* Private 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 being received. */
STATE_M_RX_ERROR, /* If the frame is invalid. */
} eMBMasterRcvState;
typedef enum
{
STATE_M_TX_IDLE, /* Transmitter is in idle state. */
STATE_M_TX_XMIT, /* Transmitter is in transfer state. */
STATE_M_TX_XFWR, /* Transmitter is in transfer finish and
* wait receive state. */
} eMBMasterSndState;
/****************************************************************************
* Private Data
****************************************************************************/
static volatile eMBMasterSndState eSndState;
static volatile eMBMasterRcvState eRcvState;
static volatile uint8_t ucMasterRTUSndBuf[MB_PDU_SIZE_MAX];
static volatile uint8_t ucMasterRTURcvBuf[MB_SER_PDU_SIZE_MAX];
static volatile uint16_t usMasterSendPDULength;
static volatile uint8_t *pucMasterSndBufferCur;
static volatile uint16_t usMasterSndBufferCount;
static volatile uint16_t usMasterRcvBufferPos;
static volatile bool xFrameIsBroadcast = false;
static volatile eMBMasterTimerMode eMasterCurTimerMode;
/****************************************************************************
* Public Functions
****************************************************************************/
eMBErrorCode eMBMasterRTUInit(uint8_t ucPort, speed_t ulBaudRate,
eMBParity eParity)
{
eMBErrorCode eStatus = MB_ENOERR;
speed_t usTimerT35_50us;
ENTER_CRITICAL_SECTION();
/* Modbus RTU uses 8 Databits. */
if (xMBMasterPortSerialInit(ucPort, ulBaudRate, 8, eParity) != true)
{
eStatus = MB_EPORTERR;
}
else
{
/* If baudrate > 19200 then we should use the fixed timer values t35 =
* 1750us. Otherwise t35 must be 3.5 times the character time.
*/
if (ulBaudRate > 19200)
{
usTimerT35_50us = 35; /* 1800us. */
}
else
{
/* The timer reload value for a character is given by: ChTimeValue =
* Ticks_per_1s / ( Baudrate / 11 ) = 11 * Ticks_per_1s / Baudrate =
* 220000 / Baudrate The reload for t3.5 is 1.5 times this value and
* similarly for t3.5.
*/
usTimerT35_50us = (7UL * 220000UL) / (2UL * ulBaudRate);
}
if (xMBMasterPortTimersInit((uint16_t) usTimerT35_50us) != true)
{
eStatus = MB_EPORTERR;
}
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
void eMBMasterRTUStart(void)
{
ENTER_CRITICAL_SECTION();
/* Initially the receiver is in the state STATE_M_RX_INIT. we start the timer
* and if no character is received within t3.5 we change 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;
vMBMasterPortSerialEnable(true, false);
vMBMasterPortTimersT35Enable();
EXIT_CRITICAL_SECTION();
}
void eMBMasterRTUStop(void)
{
ENTER_CRITICAL_SECTION();
vMBMasterPortSerialEnable(false, false);
vMBMasterPortTimersDisable();
EXIT_CRITICAL_SECTION();
}
eMBErrorCode eMBMasterRTUReceive(uint8_t *pucRcvAddress, uint8_t **pucFrame,
uint16_t *pusLength)
{
eMBErrorCode eStatus = MB_ENOERR;
ENTER_CRITICAL_SECTION();
DEBUGASSERT(usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX);
/* Length and CRC check */
if ((usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN)
&& (usMBCRC16((uint8_t *) ucMasterRTURcvBuf, usMasterRcvBufferPos) == 0))
{
/* Save the address field. All frames are passed to the upper laid and
* the decision if a frame is used is done there.
*/
*pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus size of
* address field and CRC checksum.
*/
*pusLength =
(uint16_t) (usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF -
MB_SER_PDU_SIZE_CRC);
/* Return the start of the Modbus PDU to the caller. */
*pucFrame = (uint8_t *) & ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF];
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
eMBErrorCode eMBMasterRTUSend(uint8_t ucSlaveAddress, const uint8_t *pucFrame,
uint16_t usLength)
{
eMBErrorCode eStatus = MB_ENOERR;
uint16_t usCRC16;
if (ucSlaveAddress > CONFIG_MB_MASTER_TOTAL_SLAVE_NUM)
{
return MB_EINVAL;
}
ENTER_CRITICAL_SECTION();
/* Check if the receiver is still in idle state. If not we where to 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 = (uint8_t *) 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 CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 =
usMBCRC16((uint8_t *) pucMasterSndBufferCur, usMasterSndBufferCount);
ucMasterRTUSndBuf[usMasterSndBufferCount++] = (uint8_t) (usCRC16 & 0xFF);
ucMasterRTUSndBuf[usMasterSndBufferCount++] = (uint8_t) (usCRC16 >> 8);
/* Activate the transmitter. */
eSndState = STATE_M_TX_XMIT;
vMBMasterPortSerialEnable(false, true);
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
bool xMBMasterRTUReceiveFSM(void)
{
bool xTaskNeedSwitch = false;
uint8_t ucByte;
DEBUGASSERT((eSndState == STATE_M_TX_IDLE) ||
(eSndState == STATE_M_TX_XFWR));
/* Always read the character. */
xMBMasterPortSerialGetByte((int8_t *) & 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:
vMBMasterPortTimersT35Enable();
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_RECEIVCE and disable early 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 transmitter state to idle.
*/
vMBMasterPortTimersDisable();
eSndState = STATE_M_TX_IDLE;
usMasterRcvBufferPos = 0;
ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
eRcvState = STATE_M_RX_RCV;
/* Enable t3.5 timers. */
vMBMasterPortTimersT35Enable();
break;
/* We are currently receiving a frame. Reset the timer after every
* character received. If more than the maximum possible number of bytes
* in a modbus frame is received the frame is ignored.
*/
case STATE_M_RX_RCV:
if (usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX)
{
ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
}
else
{
eRcvState = STATE_M_RX_ERROR;
}
vMBMasterPortTimersT35Enable();
break;
}
return xTaskNeedSwitch;
}
bool xMBMasterRTUTransmitFSM(void)
{
bool xNeedPoll = false;
DEBUGASSERT(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_IDLE:
/* enable receiver/disable transmitter. */
vMBMasterPortSerialEnable(true, false);
break;
case STATE_M_TX_XMIT:
/* check if we are finished. */
if (usMasterSndBufferCount != 0)
{
xMBMasterPortSerialPutByte((uint8_t) * pucMasterSndBufferCur);
pucMasterSndBufferCur++; /* next byte in sendbuffer. */
usMasterSndBufferCount--;
}
else
{
xFrameIsBroadcast =
(ucMasterRTUSndBuf[MB_SER_PDU_ADDR_OFF] ==
MB_ADDRESS_BROADCAST) ? true : false;
/* Disable transmitter. This prevents another transmit buffer empty
* interrupt.
*/
vMBMasterPortSerialEnable(true, false);
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();
}
}
break;
case STATE_M_TX_XFWR:
default:
break;
}
return xNeedPoll;
}
bool xMBMasterRTUTimerExpired(void)
{
bool xNeedPoll = false;
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;
/* An error occurred 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:
DEBUGASSERT((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 (xFrameIsBroadcast == false)
{
vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT);
xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
}
break;
/* Function called in an illegal state. */
default:
DEBUGASSERT((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 (eMasterCurTimerMode == MB_TMODE_CONVERT_DELAY)
{
xNeedPoll = xMBMasterPortEventPost(EV_MASTER_EXECUTE);
}
return xNeedPoll;
}
/* Get Modbus Master send RTU's buffer address pointer.*/
void vMBMasterGetRTUSndBuf(uint8_t **pucFrame)
{
*pucFrame = (uint8_t *) ucMasterRTUSndBuf;
}
/* Get Modbus Master send PDU's buffer address pointer.*/
void vMBMasterGetPDUSndBuf(uint8_t **pucFrame)
{
*pucFrame = (uint8_t *) & ucMasterRTUSndBuf[MB_SER_PDU_PDU_OFF];
}
/* Set Modbus Master send PDU's buffer length.*/
void vMBMasterSetPDUSndLength(uint16_t SendPDULength)
{
usMasterSendPDULength = SendPDULength;
}
/* Get Modbus Master send PDU's buffer length.*/
uint16_t 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