Modbus RTU/ASCII/TCP with lwip TCP working partial, but with errors (retransmitions)
Dependencies: EthernetNetIf mbed
mbascii.cpp
00001 /* 00002 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. 00003 * Copyright (c) 2006 Christian Walter <wolti@sil.at> 00004 * All rights reserved. 00005 * 00006 * Redistribution and use in source and binary forms, with or without 00007 * modification, are permitted provided that the following conditions 00008 * are met: 00009 * 1. Redistributions of source code must retain the above copyright 00010 * notice, this list of conditions and the following disclaimer. 00011 * 2. Redistributions in binary form must reproduce the above copyright 00012 * notice, this list of conditions and the following disclaimer in the 00013 * documentation and/or other materials provided with the distribution. 00014 * 3. The name of the author may not be used to endorse or promote products 00015 * derived from this software without specific prior written permission. 00016 * 00017 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 00018 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00019 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 00020 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 00021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 00022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 00023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 00024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 00026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00027 * 00028 * File: $Id: mbascii.c,v 1.17 2010/06/06 13:47:07 wolti Exp $ 00029 */ 00030 00031 /* ----------------------- System includes ----------------------------------*/ 00032 #include "stdlib.h" 00033 #include "string.h" 00034 00035 /* ----------------------- Platform includes --------------------------------*/ 00036 #include "port.h" 00037 00038 /* ----------------------- Modbus includes ----------------------------------*/ 00039 #include "mb.h" 00040 #include "mbconfig.h" 00041 #include "mbascii.h" 00042 #include "mbframe.h" 00043 00044 #include "mbcrc.h" 00045 #include "mbport.h" 00046 00047 #if MB_ASCII_ENABLED > 0 00048 00049 /* ----------------------- Defines ------------------------------------------*/ 00050 #define MB_ASCII_DEFAULT_CR '\r' /*!< Default CR character for Modbus ASCII. */ 00051 #define MB_ASCII_DEFAULT_LF '\n' /*!< Default LF character for Modbus ASCII. */ 00052 #define MB_SER_PDU_SIZE_MIN 3 /*!< Minimum size of a Modbus ASCII frame. */ 00053 #define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus ASCII frame. */ 00054 #define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */ 00055 #define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ 00056 #define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ 00057 00058 /* ----------------------- Type definitions ---------------------------------*/ 00059 typedef enum 00060 { 00061 STATE_RX_IDLE, /*!< Receiver is in idle state. */ 00062 STATE_RX_RCV, /*!< Frame is beeing received. */ 00063 STATE_RX_WAIT_EOF /*!< Wait for End of Frame. */ 00064 } eMBRcvState; 00065 00066 typedef enum 00067 { 00068 STATE_TX_IDLE, /*!< Transmitter is in idle state. */ 00069 STATE_TX_START, /*!< Starting transmission (':' sent). */ 00070 STATE_TX_DATA, /*!< Sending of data (Address, Data, LRC). */ 00071 STATE_TX_END, /*!< End of transmission. */ 00072 STATE_TX_NOTIFY /*!< Notify sender that the frame has been sent. */ 00073 } eMBSndState; 00074 00075 typedef enum 00076 { 00077 BYTE_HIGH_NIBBLE, /*!< Character for high nibble of byte. */ 00078 BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */ 00079 } eMBBytePos; 00080 00081 /* ----------------------- Static functions ---------------------------------*/ 00082 static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter ); 00083 00084 static UCHAR prvucMBBIN2CHAR( UCHAR ucByte ); 00085 00086 static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen ); 00087 00088 /* ----------------------- Static variables ---------------------------------*/ 00089 static volatile eMBSndState eSndState; 00090 static volatile eMBRcvState eRcvState; 00091 00092 /* We reuse the Modbus RTU buffer because only one buffer is needed and the 00093 * RTU buffer is bigger. */ 00094 extern volatile UCHAR ucRTUBuf[]; 00095 static volatile UCHAR *ucASCIIBuf = ucRTUBuf; 00096 00097 static volatile USHORT usRcvBufferPos; 00098 static volatile eMBBytePos eBytePos; 00099 00100 static volatile UCHAR *pucSndBufferCur; 00101 static volatile USHORT usSndBufferCount; 00102 00103 static volatile UCHAR ucLRC; 00104 static volatile UCHAR ucMBLFCharacter; 00105 00106 /* ----------------------- Start implementation -----------------------------*/ 00107 eMBErrorCode 00108 eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 00109 { 00110 eMBErrorCode eStatus = MB_ENOERR ; 00111 ( void )ucSlaveAddress; 00112 00113 ENTER_CRITICAL_SECTION( ); 00114 ucMBLFCharacter = MB_ASCII_DEFAULT_LF; 00115 00116 if( xMBPortSerialInit( ucPort, ulBaudRate, 7, eParity ) != TRUE ) 00117 { 00118 eStatus = MB_EPORTERR ; 00119 } 00120 else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE ) 00121 { 00122 eStatus = MB_EPORTERR ; 00123 } 00124 00125 EXIT_CRITICAL_SECTION( ); 00126 00127 return eStatus; 00128 } 00129 00130 void 00131 eMBASCIIStart( void ) 00132 { 00133 ENTER_CRITICAL_SECTION( ); 00134 vMBPortSerialEnable( TRUE, FALSE ); 00135 eRcvState = STATE_RX_IDLE; 00136 EXIT_CRITICAL_SECTION( ); 00137 00138 /* No special startup required for ASCII. */ 00139 ( void )xMBPortEventPost( EV_READY ); 00140 } 00141 00142 void 00143 eMBASCIIStop( void ) 00144 { 00145 ENTER_CRITICAL_SECTION( ); 00146 vMBPortSerialEnable( FALSE, FALSE ); 00147 vMBPortTimersDisable( ); 00148 EXIT_CRITICAL_SECTION( ); 00149 } 00150 00151 eMBErrorCode 00152 eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) 00153 { 00154 eMBErrorCode eStatus = MB_ENOERR ; 00155 00156 ENTER_CRITICAL_SECTION( ); 00157 assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); 00158 00159 /* Length and CRC check */ 00160 if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) 00161 && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) ) 00162 { 00163 /* Save the address field. All frames are passed to the upper layed 00164 * and the decision if a frame is used is done there. 00165 */ 00166 *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF]; 00167 00168 /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus 00169 * size of address field and CRC checksum. 00170 */ 00171 *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); 00172 00173 /* Return the start of the Modbus PDU to the caller. */ 00174 *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF]; 00175 } 00176 else 00177 { 00178 eStatus = MB_EIO ; 00179 } 00180 EXIT_CRITICAL_SECTION( ); 00181 return eStatus; 00182 } 00183 00184 eMBErrorCode 00185 eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) 00186 { 00187 eMBErrorCode eStatus = MB_ENOERR ; 00188 UCHAR usLRC; 00189 00190 ENTER_CRITICAL_SECTION( ); 00191 /* Check if the receiver is still in idle state. If not we where too 00192 * slow with processing the received frame and the master sent another 00193 * frame on the network. We have to abort sending the frame. 00194 */ 00195 if( eRcvState == STATE_RX_IDLE ) 00196 { 00197 /* First byte before the Modbus-PDU is the slave address. */ 00198 pucSndBufferCur = ( UCHAR * ) pucFrame - 1; 00199 usSndBufferCount = 1; 00200 00201 /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ 00202 pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; 00203 usSndBufferCount += usLength; 00204 00205 /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */ 00206 usLRC = prvucMBLRC( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); 00207 ucASCIIBuf[usSndBufferCount++] = usLRC; 00208 00209 /* Activate the transmitter. */ 00210 eSndState = STATE_TX_START; 00211 vMBPortSerialEnable( FALSE, TRUE ); 00212 } 00213 else 00214 { 00215 eStatus = MB_EIO ; 00216 } 00217 EXIT_CRITICAL_SECTION( ); 00218 return eStatus; 00219 } 00220 00221 BOOL 00222 xMBASCIIReceiveFSM( void ) 00223 { 00224 BOOL xNeedPoll = FALSE; 00225 UCHAR ucByte; 00226 UCHAR ucResult; 00227 00228 assert( eSndState == STATE_TX_IDLE ); 00229 00230 ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); 00231 switch ( eRcvState ) 00232 { 00233 /* A new character is received. If the character is a ':' the input 00234 * buffer is cleared. A CR-character signals the end of the data 00235 * block. Other characters are part of the data block and their 00236 * ASCII value is converted back to a binary representation. 00237 */ 00238 case STATE_RX_RCV: 00239 /* Enable timer for character timeout. */ 00240 vMBPortTimersEnable( ); 00241 if( ucByte == ':' ) 00242 { 00243 /* Empty receive buffer. */ 00244 eBytePos = BYTE_HIGH_NIBBLE; 00245 usRcvBufferPos = 0; 00246 } 00247 else if( ucByte == MB_ASCII_DEFAULT_CR ) 00248 { 00249 eRcvState = STATE_RX_WAIT_EOF; 00250 } 00251 else 00252 { 00253 ucResult = prvucMBCHAR2BIN( ucByte ); 00254 switch ( eBytePos ) 00255 { 00256 /* High nibble of the byte comes first. We check for 00257 * a buffer overflow here. */ 00258 case BYTE_HIGH_NIBBLE: 00259 if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) 00260 { 00261 ucASCIIBuf[usRcvBufferPos] = ( UCHAR )( ucResult << 4 ); 00262 eBytePos = BYTE_LOW_NIBBLE; 00263 break; 00264 } 00265 else 00266 { 00267 /* not handled in Modbus specification but seems 00268 * a resonable implementation. */ 00269 eRcvState = STATE_RX_IDLE; 00270 /* Disable previously activated timer because of error state. */ 00271 vMBPortTimersDisable( ); 00272 } 00273 break; 00274 00275 case BYTE_LOW_NIBBLE: 00276 ucASCIIBuf[usRcvBufferPos] |= ucResult; 00277 usRcvBufferPos++; 00278 eBytePos = BYTE_HIGH_NIBBLE; 00279 break; 00280 } 00281 } 00282 break; 00283 00284 case STATE_RX_WAIT_EOF: 00285 if( ucByte == ucMBLFCharacter ) 00286 { 00287 /* Disable character timeout timer because all characters are 00288 * received. */ 00289 vMBPortTimersDisable( ); 00290 /* Receiver is again in idle state. */ 00291 eRcvState = STATE_RX_IDLE; 00292 00293 /* Notify the caller of eMBASCIIReceive that a new frame 00294 * was received. */ 00295 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); 00296 } 00297 else if( ucByte == ':' ) 00298 { 00299 /* Empty receive buffer and back to receive state. */ 00300 eBytePos = BYTE_HIGH_NIBBLE; 00301 usRcvBufferPos = 0; 00302 eRcvState = STATE_RX_RCV; 00303 00304 /* Enable timer for character timeout. */ 00305 vMBPortTimersEnable( ); 00306 } 00307 else 00308 { 00309 /* Frame is not okay. Delete entire frame. */ 00310 eRcvState = STATE_RX_IDLE; 00311 } 00312 break; 00313 00314 case STATE_RX_IDLE: 00315 if( ucByte == ':' ) 00316 { 00317 /* Enable timer for character timeout. */ 00318 vMBPortTimersEnable( ); 00319 /* Reset the input buffers to store the frame. */ 00320 usRcvBufferPos = 0;; 00321 eBytePos = BYTE_HIGH_NIBBLE; 00322 eRcvState = STATE_RX_RCV; 00323 } 00324 break; 00325 } 00326 00327 return xNeedPoll; 00328 } 00329 00330 BOOL 00331 xMBASCIITransmitFSM( void ) 00332 { 00333 BOOL xNeedPoll = FALSE; 00334 UCHAR ucByte; 00335 00336 assert( eRcvState == STATE_RX_IDLE ); 00337 switch ( eSndState ) 00338 { 00339 /* Start of transmission. The start of a frame is defined by sending 00340 * the character ':'. */ 00341 case STATE_TX_START: 00342 ucByte = ':'; 00343 xMBPortSerialPutByte( ( CHAR )ucByte ); 00344 eSndState = STATE_TX_DATA; 00345 eBytePos = BYTE_HIGH_NIBBLE; 00346 break; 00347 00348 /* Send the data block. Each data byte is encoded as a character hex 00349 * stream with the high nibble sent first and the low nibble sent 00350 * last. If all data bytes are exhausted we send a '\r' character 00351 * to end the transmission. */ 00352 case STATE_TX_DATA: 00353 if( usSndBufferCount > 0 ) 00354 { 00355 switch ( eBytePos ) 00356 { 00357 case BYTE_HIGH_NIBBLE: 00358 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) ); 00359 xMBPortSerialPutByte( ( CHAR ) ucByte ); 00360 eBytePos = BYTE_LOW_NIBBLE; 00361 break; 00362 00363 case BYTE_LOW_NIBBLE: 00364 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) ); 00365 xMBPortSerialPutByte( ( CHAR )ucByte ); 00366 pucSndBufferCur++; 00367 eBytePos = BYTE_HIGH_NIBBLE; 00368 usSndBufferCount--; 00369 break; 00370 } 00371 } 00372 else 00373 { 00374 xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR ); 00375 eSndState = STATE_TX_END; 00376 } 00377 break; 00378 00379 /* Finish the frame by sending a LF character. */ 00380 case STATE_TX_END: 00381 xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter ); 00382 /* We need another state to make sure that the CR character has 00383 * been sent. */ 00384 eSndState = STATE_TX_NOTIFY; 00385 break; 00386 00387 /* Notify the task which called eMBASCIISend that the frame has 00388 * been sent. */ 00389 case STATE_TX_NOTIFY: 00390 eSndState = STATE_TX_IDLE; 00391 xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); 00392 00393 /* Disable transmitter. This prevents another transmit buffer 00394 * empty interrupt. */ 00395 vMBPortSerialEnable( TRUE, FALSE ); 00396 eSndState = STATE_TX_IDLE; 00397 break; 00398 00399 /* We should not get a transmitter event if the transmitter is in 00400 * idle state. */ 00401 case STATE_TX_IDLE: 00402 /* enable receiver/disable transmitter. */ 00403 vMBPortSerialEnable( TRUE, FALSE ); 00404 break; 00405 } 00406 00407 return xNeedPoll; 00408 } 00409 00410 BOOL 00411 xMBASCIITimerT1SExpired( void ) 00412 { 00413 switch ( eRcvState ) 00414 { 00415 /* If we have a timeout we go back to the idle state and wait for 00416 * the next frame. 00417 */ 00418 case STATE_RX_RCV: 00419 case STATE_RX_WAIT_EOF: 00420 eRcvState = STATE_RX_IDLE; 00421 break; 00422 00423 default: 00424 assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) ); 00425 break; 00426 } 00427 vMBPortTimersDisable( ); 00428 00429 /* no context switch required. */ 00430 return FALSE; 00431 } 00432 00433 00434 static UCHAR 00435 prvucMBCHAR2BIN( UCHAR ucCharacter ) 00436 { 00437 if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) ) 00438 { 00439 return ( UCHAR )( ucCharacter - '0' ); 00440 } 00441 else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) ) 00442 { 00443 return ( UCHAR )( ucCharacter - 'A' + 0x0A ); 00444 } 00445 else 00446 { 00447 return 0xFF; 00448 } 00449 } 00450 00451 static UCHAR 00452 prvucMBBIN2CHAR( UCHAR ucByte ) 00453 { 00454 if( ucByte <= 0x09 ) 00455 { 00456 return ( UCHAR )( '0' + ucByte ); 00457 } 00458 else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) ) 00459 { 00460 return ( UCHAR )( ucByte - 0x0A + 'A' ); 00461 } 00462 else 00463 { 00464 /* Programming error. */ 00465 assert( 0 ); 00466 } 00467 return '0'; 00468 } 00469 00470 00471 static UCHAR 00472 prvucMBLRC( UCHAR * pucFrame, USHORT usLen ) 00473 { 00474 UCHAR ucLRC = 0; /* LRC char initialized */ 00475 00476 while( usLen-- ) 00477 { 00478 ucLRC += *pucFrame++; /* Add buffer byte without carry */ 00479 } 00480 00481 /* Return twos complement */ 00482 ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) ); 00483 return ucLRC; 00484 } 00485 00486 #endif
Generated on Tue Jul 12 2022 21:29:48 by 1.7.2