EV-Embedded-Control-System/middlewares/FreeModbus/modbus/rtu/mbrtu.c

314 lines
10 KiB
C
Raw Normal View History

/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.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: mbrtu.c,v 1.18 2007/09/12 10:15:56 wolti Exp $
*/
/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbframe.h"
#include "mbrtu.h"
#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. */
/* ----------------------- Type definitions ---------------------------------*/
typedef enum {
STATE_RX_INIT, /*!< Receiver is in initial state. */
STATE_RX_IDLE, /*!< Receiver is in idle state. */
STATE_RX_RCV, /*!< Frame is beeing received. */
STATE_RX_ERROR /*!< If the frame is invalid. */
} eMBRcvState;
typedef enum {
STATE_TX_IDLE, /*!< Transmitter is in idle state. */
STATE_TX_XMIT /*!< Transmitter is in transfer state. */
} eMBSndState;
/* ----------------------- 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;
/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode eMBRTUInit(UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate,
eMBParity eParity) {
eMBErrorCode eStatus = MB_ENOERR;
ULONG usTimerT35_50us;
(void)ucSlaveAddress;
ENTER_CRITICAL_SECTION();
/* Modbus RTU uses 8 Databits. */
if (xMBPortSerialInit(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 similary
* for t3.5.
*/
usTimerT35_50us = (7UL * 220000UL) / (2UL * ulBaudRate);
}
if (xMBPortTimersInit((USHORT)usTimerT35_50us) != TRUE) {
eStatus = MB_EPORTERR;
}
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
void eMBRTUStart(void) {
ENTER_CRITICAL_SECTION();
/* Initially the receiver is in the state STATE_RX_INIT. we start
* the timer and if no character is received within t3.5 we change
* to STATE_RX_IDLE. This makes sure that we delay startup of the
* modbus protocol stack until the bus is free.
*/
eRcvState = STATE_RX_INIT;
vMBPortSerialEnable(TRUE, FALSE);
vMBPortTimersEnable();
EXIT_CRITICAL_SECTION();
}
void eMBRTUStop(void) {
ENTER_CRITICAL_SECTION();
vMBPortSerialEnable(FALSE, FALSE);
vMBPortTimersDisable();
EXIT_CRITICAL_SECTION();
}
eMBErrorCode eMBRTUReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame,
USHORT *pusLength) {
eMBErrorCode eStatus = MB_ENOERR;
ENTER_CRITICAL_SECTION();
assert_param(usRcvBufferPos < MB_SER_PDU_SIZE_MAX);
/* Length and CRC check */
if ((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) &&
(usMBCRC16((UCHAR *)ucRTUBuf, usRcvBufferPos) == 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 = ucRTUBuf[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)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);
/* Return the start of the Modbus PDU to the caller. */
*pucFrame = (UCHAR *)&ucRTUBuf[MB_SER_PDU_PDU_OFF];
} else {
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
eMBErrorCode eMBRTUSend(UCHAR ucSlaveAddress, const UCHAR *pucFrame,
USHORT usLength) {
eMBErrorCode eStatus = MB_ENOERR;
USHORT usCRC16;
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_RX_IDLE) {
/* First byte before the Modbus-PDU is the slave address. */
pucSndBufferCur = (UCHAR *)pucFrame - 1;
usSndBufferCount = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
usSndBufferCount += usLength;
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 = usMBCRC16((UCHAR *)pucSndBufferCur, usSndBufferCount);
ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 & 0xFF);
ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 >> 8);
/* Activate the transmitter. */
eSndState = STATE_TX_XMIT;
vMBPortSerialEnable(FALSE, TRUE);
} else {
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
BOOL xMBRTUReceiveFSM(void) {
BOOL xTaskNeedSwitch = FALSE;
UCHAR ucByte;
assert_param(eSndState == STATE_TX_IDLE);
/* Always read the character. */
(void)xMBPortSerialGetByte((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_RX_INIT:
vMBPortTimersEnable();
break;
/* In the error state we wait until all characters in the
* damaged frame are transmitted.
*/
case STATE_RX_ERROR:
vMBPortTimersEnable();
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.
*/
case STATE_RX_IDLE:
usRcvBufferPos = 0;
ucRTUBuf[usRcvBufferPos++] = ucByte;
eRcvState = STATE_RX_RCV;
/* Enable t3.5 timers. */
vMBPortTimersEnable();
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_RX_RCV:
if (usRcvBufferPos < MB_SER_PDU_SIZE_MAX) {
ucRTUBuf[usRcvBufferPos++] = ucByte;
} else {
eRcvState = STATE_RX_ERROR;
}
vMBPortTimersEnable();
break;
}
return xTaskNeedSwitch;
}
BOOL xMBRTUTransmitFSM(void) {
BOOL xNeedPoll = FALSE;
assert_param(eRcvState == STATE_RX_IDLE);
switch (eSndState) {
/* We should not get a transmitter event if the transmitter is in
* idle state. */
case STATE_TX_IDLE:
/* enable receiver/disable transmitter. */
vMBPortSerialEnable(TRUE, FALSE);
break;
case STATE_TX_XMIT:
/* check if we are finished. */
if (usSndBufferCount != 0) {
xMBPortSerialPutByte((CHAR)*pucSndBufferCur);
pucSndBufferCur++; /* next byte in sendbuffer. */
usSndBufferCount--;
} else {
xNeedPoll = xMBPortEventPost(EV_FRAME_SENT);
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
vMBPortSerialEnable(TRUE, FALSE);
eSndState = STATE_TX_IDLE;
}
break;
}
return xNeedPoll;
}
BOOL xMBRTUTimerT35Expired(void) {
BOOL xNeedPoll = FALSE;
switch (eRcvState) {
/* Timer t35 expired. Startup phase is finished. */
case STATE_RX_INIT:
xNeedPoll = xMBPortEventPost(EV_READY);
break;
/* A frame was received and t35 expired. Notify the listener that
* a new frame was received. */
case STATE_RX_RCV:
xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED);
break;
/* An error occured while receiving the frame. */
case STATE_RX_ERROR:
break;
/* Function called in an illegal state. */
default:
assert_param((eRcvState == STATE_RX_INIT) || (eRcvState == STATE_RX_RCV) ||
(eRcvState == STATE_RX_ERROR));
break;
}
vMBPortTimersDisable();
eRcvState = STATE_RX_IDLE;
return xNeedPoll;
}