summaryrefslogtreecommitdiff
path: root/apps/modbus/ascii/mbascii.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/modbus/ascii/mbascii.c')
-rw-r--r--apps/modbus/ascii/mbascii.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/apps/modbus/ascii/mbascii.c b/apps/modbus/ascii/mbascii.c
new file mode 100644
index 000000000..c513457ea
--- /dev/null
+++ b/apps/modbus/ascii/mbascii.c
@@ -0,0 +1,486 @@
+/*
+ * 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.h"
+#include "mbconfig.h"
+#include "mbascii.h"
+#include "mbframe.h"
+
+#include "mbcrc.h"
+#include "mbport.h"
+
+#if MB_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
+{
+ STATE_RX_IDLE, /*!< Receiver is in idle state. */
+ STATE_RX_RCV, /*!< Frame is beeing received. */
+ STATE_RX_WAIT_EOF /*!< Wait for End of Frame. */
+} eMBRcvState;
+
+typedef enum
+{
+ STATE_TX_IDLE, /*!< Transmitter is in idle state. */
+ STATE_TX_START, /*!< Starting transmission (':' sent). */
+ STATE_TX_DATA, /*!< Sending of data (Address, Data, LRC). */
+ STATE_TX_END, /*!< End of transmission. */
+ STATE_TX_NOTIFY /*!< Notify sender that the frame has been sent. */
+} eMBSndState;
+
+typedef enum
+{
+ BYTE_HIGH_NIBBLE, /*!< Character for high nibble of byte. */
+ BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */
+} eMBBytePos;
+
+/* ----------------------- Static functions ---------------------------------*/
+static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter );
+
+static UCHAR prvucMBBIN2CHAR( UCHAR ucByte );
+
+static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen );
+
+/* ----------------------- Static variables ---------------------------------*/
+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 USHORT usRcvBufferPos;
+static volatile eMBBytePos eBytePos;
+
+static volatile UCHAR *pucSndBufferCur;
+static volatile USHORT usSndBufferCount;
+
+static volatile UCHAR ucLRC;
+static volatile UCHAR ucMBLFCharacter;
+
+/* ----------------------- Start implementation -----------------------------*/
+eMBErrorCode
+eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
+{
+ eMBErrorCode eStatus = MB_ENOERR;
+ ( void )ucSlaveAddress;
+
+ ENTER_CRITICAL_SECTION( );
+ ucMBLFCharacter = MB_ASCII_DEFAULT_LF;
+
+ if( xMBPortSerialInit( ucPort, ulBaudRate, 7, eParity ) != TRUE )
+ {
+ eStatus = MB_EPORTERR;
+ }
+ else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE )
+ {
+ eStatus = MB_EPORTERR;
+ }
+
+ EXIT_CRITICAL_SECTION( );
+
+ return eStatus;
+}
+
+void
+eMBASCIIStart( void )
+{
+ ENTER_CRITICAL_SECTION( );
+ vMBPortSerialEnable( TRUE, FALSE );
+ eRcvState = STATE_RX_IDLE;
+ EXIT_CRITICAL_SECTION( );
+
+ /* No special startup required for ASCII. */
+ ( void )xMBPortEventPost( EV_READY );
+}
+
+void
+eMBASCIIStop( void )
+{
+ ENTER_CRITICAL_SECTION( );
+ vMBPortSerialEnable( FALSE, FALSE );
+ vMBPortTimersDisable( );
+ EXIT_CRITICAL_SECTION( );
+}
+
+eMBErrorCode
+eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
+{
+ eMBErrorCode eStatus = MB_ENOERR;
+
+ ENTER_CRITICAL_SECTION( );
+ assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
+
+ /* Length and CRC check */
+ if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
+ && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, 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 = ucASCIIBuf[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_LRC );
+
+ /* Return the start of the Modbus PDU to the caller. */
+ *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
+ }
+ else
+ {
+ eStatus = MB_EIO;
+ }
+ EXIT_CRITICAL_SECTION( );
+ return eStatus;
+}
+
+eMBErrorCode
+eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
+{
+ eMBErrorCode eStatus = MB_ENOERR;
+ UCHAR usLRC;
+
+ 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_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 LRC checksum for Modbus-Serial-Line-PDU. */
+ usLRC = prvucMBLRC( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
+ ucASCIIBuf[usSndBufferCount++] = usLRC;
+
+ /* Activate the transmitter. */
+ eSndState = STATE_TX_START;
+ vMBPortSerialEnable( FALSE, TRUE );
+ }
+ else
+ {
+ eStatus = MB_EIO;
+ }
+ EXIT_CRITICAL_SECTION( );
+ return eStatus;
+}
+
+BOOL
+xMBASCIIReceiveFSM( void )
+{
+ BOOL xNeedPoll = FALSE;
+ UCHAR ucByte;
+ UCHAR ucResult;
+
+ assert( eSndState == STATE_TX_IDLE );
+
+ ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
+ switch ( eRcvState )
+ {
+ /* 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_RX_RCV:
+ /* Enable timer for character timeout. */
+ vMBPortTimersEnable( );
+ if( ucByte == ':' )
+ {
+ /* Empty receive buffer. */
+ eBytePos = BYTE_HIGH_NIBBLE;
+ usRcvBufferPos = 0;
+ }
+ else if( ucByte == MB_ASCII_DEFAULT_CR )
+ {
+ eRcvState = STATE_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( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
+ {
+ ucASCIIBuf[usRcvBufferPos] = ( UCHAR )( ucResult << 4 );
+ eBytePos = BYTE_LOW_NIBBLE;
+ break;
+ }
+ else
+ {
+ /* not handled in Modbus specification but seems
+ * a resonable implementation. */
+ eRcvState = STATE_RX_IDLE;
+ /* Disable previously activated timer because of error state. */
+ vMBPortTimersDisable( );
+ }
+ break;
+
+ case BYTE_LOW_NIBBLE:
+ ucASCIIBuf[usRcvBufferPos] |= ucResult;
+ usRcvBufferPos++;
+ eBytePos = BYTE_HIGH_NIBBLE;
+ break;
+ }
+ }
+ break;
+
+ case STATE_RX_WAIT_EOF:
+ if( ucByte == ucMBLFCharacter )
+ {
+ /* Disable character timeout timer because all characters are
+ * received. */
+ vMBPortTimersDisable( );
+ /* Receiver is again in idle state. */
+ eRcvState = STATE_RX_IDLE;
+
+ /* Notify the caller of eMBASCIIReceive that a new frame
+ * was received. */
+ xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
+ }
+ else if( ucByte == ':' )
+ {
+ /* Empty receive buffer and back to receive state. */
+ eBytePos = BYTE_HIGH_NIBBLE;
+ usRcvBufferPos = 0;
+ eRcvState = STATE_RX_RCV;
+
+ /* Enable timer for character timeout. */
+ vMBPortTimersEnable( );
+ }
+ else
+ {
+ /* Frame is not okay. Delete entire frame. */
+ eRcvState = STATE_RX_IDLE;
+ }
+ break;
+
+ case STATE_RX_IDLE:
+ if( ucByte == ':' )
+ {
+ /* Enable timer for character timeout. */
+ vMBPortTimersEnable( );
+ /* Reset the input buffers to store the frame. */
+ usRcvBufferPos = 0;;
+ eBytePos = BYTE_HIGH_NIBBLE;
+ eRcvState = STATE_RX_RCV;
+ }
+ break;
+ }
+
+ return xNeedPoll;
+}
+
+BOOL
+xMBASCIITransmitFSM( void )
+{
+ BOOL xNeedPoll = FALSE;
+ UCHAR ucByte;
+
+ assert( eRcvState == STATE_RX_IDLE );
+ switch ( eSndState )
+ {
+ /* Start of transmission. The start of a frame is defined by sending
+ * the character ':'. */
+ case STATE_TX_START:
+ ucByte = ':';
+ xMBPortSerialPutByte( ( CHAR )ucByte );
+ eSndState = STATE_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_TX_DATA:
+ if( usSndBufferCount > 0 )
+ {
+ switch ( eBytePos )
+ {
+ case BYTE_HIGH_NIBBLE:
+ ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) );
+ xMBPortSerialPutByte( ( CHAR ) ucByte );
+ eBytePos = BYTE_LOW_NIBBLE;
+ break;
+
+ case BYTE_LOW_NIBBLE:
+ ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) );
+ xMBPortSerialPutByte( ( CHAR )ucByte );
+ pucSndBufferCur++;
+ eBytePos = BYTE_HIGH_NIBBLE;
+ usSndBufferCount--;
+ break;
+ }
+ }
+ else
+ {
+ xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR );
+ eSndState = STATE_TX_END;
+ }
+ break;
+
+ /* Finish the frame by sending a LF character. */
+ case STATE_TX_END:
+ xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter );
+ /* We need another state to make sure that the CR character has
+ * been sent. */
+ eSndState = STATE_TX_NOTIFY;
+ break;
+
+ /* Notify the task which called eMBASCIISend that the frame has
+ * been sent. */
+ case STATE_TX_NOTIFY:
+ eSndState = STATE_TX_IDLE;
+ xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
+
+ /* Disable transmitter. This prevents another transmit buffer
+ * empty interrupt. */
+ vMBPortSerialEnable( TRUE, FALSE );
+ eSndState = STATE_TX_IDLE;
+ break;
+
+ /* 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;
+ }
+
+ return xNeedPoll;
+}
+
+BOOL
+xMBASCIITimerT1SExpired( void )
+{
+ switch ( eRcvState )
+ {
+ /* If we have a timeout we go back to the idle state and wait for
+ * the next frame.
+ */
+ case STATE_RX_RCV:
+ case STATE_RX_WAIT_EOF:
+ eRcvState = STATE_RX_IDLE;
+ break;
+
+ default:
+ assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) );
+ break;
+ }
+ vMBPortTimersDisable( );
+
+ /* no context switch required. */
+ return FALSE;
+}
+
+
+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