1 /* 2 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. 3 * Copyright (c) 2006 Christian Walter <[email protected]> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * File: $Id: mbascii.c,v 1.15 2007/02/18 23:46:48 wolti Exp $ 29 */ 30 31 /* ----------------------- System includes ----------------------------------*/ 32 #include "stdlib.h" 33 #include "string.h" 34 35 /* ----------------------- Platform includes --------------------------------*/ 36 #include "port.h" 37 38 /* ----------------------- Modbus includes ----------------------------------*/ 39 #include "mb.h" 40 #include "mbconfig.h" 41 #include "mbascii.h" 42 #include "mbframe.h" 43 44 #include "mbcrc.h" 45 #include "mbport.h" 46 47 #if MB_SLAVE_ASCII_ENABLED > 0 48 49 /* ----------------------- Defines ------------------------------------------*/ 50 #define MB_ASCII_DEFAULT_CR '\r' /*!< Default CR character for Modbus ASCII. */ 51 #define MB_ASCII_DEFAULT_LF '\n' /*!< Default LF character for Modbus ASCII. */ 52 #define MB_SER_PDU_SIZE_MIN 3 /*!< Minimum size of a Modbus ASCII frame. */ 53 #define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus ASCII frame. */ 54 #define MB_SER_PDU_SIZE_LRC 1 /*!< Size of LRC field in PDU. */ 55 #define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ 56 #define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ 57 58 /* ----------------------- Type definitions ---------------------------------*/ 59 typedef enum 60 { 61 STATE_RX_IDLE, /*!< Receiver is in idle state. */ 62 STATE_RX_RCV, /*!< Frame is beeing received. */ 63 STATE_RX_WAIT_EOF /*!< Wait for End of Frame. */ 64 } eMBRcvState; 65 66 typedef enum 67 { 68 STATE_TX_IDLE, /*!< Transmitter is in idle state. */ 69 STATE_TX_START, /*!< Starting transmission (':' sent). */ 70 STATE_TX_DATA, /*!< Sending of data (Address, Data, LRC). */ 71 STATE_TX_END, /*!< End of transmission. */ 72 STATE_TX_NOTIFY /*!< Notify sender that the frame has been sent. */ 73 } eMBSndState; 74 75 typedef enum 76 { 77 BYTE_HIGH_NIBBLE, /*!< Character for high nibble of byte. */ 78 BYTE_LOW_NIBBLE /*!< Character for low nibble of byte. */ 79 } eMBBytePos; 80 81 /* ----------------------- Static functions ---------------------------------*/ 82 static UCHAR prvucMBCHAR2BIN( UCHAR ucCharacter ); 83 84 static UCHAR prvucMBBIN2CHAR( UCHAR ucByte ); 85 86 static UCHAR prvucMBLRC( UCHAR * pucFrame, USHORT usLen ); 87 88 /* ----------------------- Static variables ---------------------------------*/ 89 static volatile eMBSndState eSndState; 90 static volatile eMBRcvState eRcvState; 91 92 /* We reuse the Modbus RTU buffer because only one buffer is needed and the 93 * RTU buffer is bigger. */ 94 extern volatile UCHAR ucRTUBuf[]; 95 static volatile UCHAR *ucASCIIBuf = ucRTUBuf; 96 97 static volatile USHORT usRcvBufferPos; 98 static volatile eMBBytePos eBytePos; 99 100 static volatile UCHAR *pucSndBufferCur; 101 static volatile USHORT usSndBufferCount; 102 103 static volatile UCHAR ucLRC; 104 static volatile UCHAR ucMBLFCharacter; 105 106 /* ----------------------- Start implementation -----------------------------*/ 107 eMBErrorCode 108 eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 109 { 110 eMBErrorCode eStatus = MB_ENOERR; 111 ( void )ucSlaveAddress; 112 113 ENTER_CRITICAL_SECTION( ); 114 ucMBLFCharacter = MB_ASCII_DEFAULT_LF; 115 116 if( xMBPortSerialInit( ucPort, ulBaudRate, 7, eParity ) != TRUE ) 117 { 118 eStatus = MB_EPORTERR; 119 } 120 else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE ) 121 { 122 eStatus = MB_EPORTERR; 123 } 124 125 EXIT_CRITICAL_SECTION( ); 126 127 return eStatus; 128 } 129 130 void 131 eMBASCIIStart( void ) 132 { 133 ENTER_CRITICAL_SECTION( ); 134 vMBPortSerialEnable( TRUE, FALSE ); 135 eRcvState = STATE_RX_IDLE; 136 EXIT_CRITICAL_SECTION( ); 137 138 /* No special startup required for ASCII. */ 139 ( void )xMBPortEventPost( EV_READY ); 140 } 141 142 void 143 eMBASCIIStop( void ) 144 { 145 ENTER_CRITICAL_SECTION( ); 146 vMBPortSerialEnable( FALSE, FALSE ); 147 vMBPortTimersDisable( ); 148 EXIT_CRITICAL_SECTION( ); 149 } 150 151 eMBErrorCode 152 eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) 153 { 154 eMBErrorCode eStatus = MB_ENOERR; 155 156 ENTER_CRITICAL_SECTION( ); 157 assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); 158 159 /* Length and CRC check */ 160 if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) 161 && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) ) 162 { 163 /* Save the address field. All frames are passed to the upper layed 164 * and the decision if a frame is used is done there. 165 */ 166 *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF]; 167 168 /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus 169 * size of address field and CRC checksum. 170 */ 171 *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC ); 172 173 /* Return the start of the Modbus PDU to the caller. */ 174 *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF]; 175 } 176 else 177 { 178 eStatus = MB_EIO; 179 } 180 EXIT_CRITICAL_SECTION( ); 181 return eStatus; 182 } 183 184 eMBErrorCode 185 eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) 186 { 187 eMBErrorCode eStatus = MB_ENOERR; 188 UCHAR usLRC; 189 190 ENTER_CRITICAL_SECTION( ); 191 /* Check if the receiver is still in idle state. If not we where to 192 * slow with processing the received frame and the master sent another 193 * frame on the network. We have to abort sending the frame. 194 */ 195 if( eRcvState == STATE_RX_IDLE ) 196 { 197 /* First byte before the Modbus-PDU is the slave address. */ 198 pucSndBufferCur = ( UCHAR * ) pucFrame - 1; 199 usSndBufferCount = 1; 200 201 /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ 202 pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; 203 usSndBufferCount += usLength; 204 205 /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */ 206 usLRC = prvucMBLRC( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); 207 ucASCIIBuf[usSndBufferCount++] = usLRC; 208 209 /* Activate the transmitter. */ 210 eSndState = STATE_TX_START; 211 vMBPortSerialEnable( FALSE, TRUE ); 212 } 213 else 214 { 215 eStatus = MB_EIO; 216 } 217 EXIT_CRITICAL_SECTION( ); 218 return eStatus; 219 } 220 221 BOOL 222 xMBASCIIReceiveFSM( void ) 223 { 224 BOOL xNeedPoll = FALSE; 225 UCHAR ucByte; 226 UCHAR ucResult; 227 228 assert( eSndState == STATE_TX_IDLE ); 229 230 ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); 231 switch ( eRcvState ) 232 { 233 /* A new character is received. If the character is a ':' the input 234 * buffer is cleared. A CR-character signals the end of the data 235 * block. Other characters are part of the data block and their 236 * ASCII value is converted back to a binary representation. 237 */ 238 case STATE_RX_RCV: 239 /* Enable timer for character timeout. */ 240 vMBPortTimersEnable( ); 241 if( ucByte == ':' ) 242 { 243 /* Empty receive buffer. */ 244 eBytePos = BYTE_HIGH_NIBBLE; 245 usRcvBufferPos = 0; 246 } 247 else if( ucByte == MB_ASCII_DEFAULT_CR ) 248 { 249 eRcvState = STATE_RX_WAIT_EOF; 250 } 251 else 252 { 253 ucResult = prvucMBCHAR2BIN( ucByte ); 254 switch ( eBytePos ) 255 { 256 /* High nibble of the byte comes first. We check for 257 * a buffer overflow here. */ 258 case BYTE_HIGH_NIBBLE: 259 if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) 260 { 261 ucASCIIBuf[usRcvBufferPos] = ( UCHAR )( ucResult << 4 ); 262 eBytePos = BYTE_LOW_NIBBLE; 263 break; 264 } 265 else 266 { 267 /* not handled in Modbus specification but seems 268 * a resonable implementation. */ 269 eRcvState = STATE_RX_IDLE; 270 /* Disable previously activated timer because of error state. */ 271 vMBPortTimersDisable( ); 272 } 273 break; 274 275 case BYTE_LOW_NIBBLE: 276 ucASCIIBuf[usRcvBufferPos++] |= ucResult; 277 eBytePos = BYTE_HIGH_NIBBLE; 278 break; 279 } 280 } 281 break; 282 283 case STATE_RX_WAIT_EOF: 284 if( ucByte == ucMBLFCharacter ) 285 { 286 /* Disable character timeout timer because all characters are 287 * received. */ 288 vMBPortTimersDisable( ); 289 /* Receiver is again in idle state. */ 290 eRcvState = STATE_RX_IDLE; 291 292 /* Notify the caller of eMBASCIIReceive that a new frame 293 * was received. */ 294 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); 295 } 296 else if( ucByte == ':' ) 297 { 298 /* Empty receive buffer and back to receive state. */ 299 eBytePos = BYTE_HIGH_NIBBLE; 300 usRcvBufferPos = 0; 301 eRcvState = STATE_RX_RCV; 302 303 /* Enable timer for character timeout. */ 304 vMBPortTimersEnable( ); 305 } 306 else 307 { 308 /* Frame is not okay. Delete entire frame. */ 309 eRcvState = STATE_RX_IDLE; 310 } 311 break; 312 313 case STATE_RX_IDLE: 314 if( ucByte == ':' ) 315 { 316 /* Enable timer for character timeout. */ 317 vMBPortTimersEnable( ); 318 /* Reset the input buffers to store the frame. */ 319 usRcvBufferPos = 0;; 320 eBytePos = BYTE_HIGH_NIBBLE; 321 eRcvState = STATE_RX_RCV; 322 } 323 break; 324 } 325 326 return xNeedPoll; 327 } 328 329 BOOL 330 xMBASCIITransmitFSM( void ) 331 { 332 BOOL xNeedPoll = FALSE; 333 UCHAR ucByte; 334 335 assert( eRcvState == STATE_RX_IDLE ); 336 switch ( eSndState ) 337 { 338 /* Start of transmission. The start of a frame is defined by sending 339 * the character ':'. */ 340 case STATE_TX_START: 341 ucByte = ':'; 342 xMBPortSerialPutByte( ( CHAR )ucByte ); 343 eSndState = STATE_TX_DATA; 344 eBytePos = BYTE_HIGH_NIBBLE; 345 break; 346 347 /* Send the data block. Each data byte is encoded as a character hex 348 * stream with the high nibble sent first and the low nibble sent 349 * last. If all data bytes are exhausted we send a '\r' character 350 * to end the transmission. */ 351 case STATE_TX_DATA: 352 if( usSndBufferCount > 0 ) 353 { 354 switch ( eBytePos ) 355 { 356 case BYTE_HIGH_NIBBLE: 357 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) ); 358 xMBPortSerialPutByte( ( CHAR ) ucByte ); 359 eBytePos = BYTE_LOW_NIBBLE; 360 break; 361 362 case BYTE_LOW_NIBBLE: 363 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) ); 364 xMBPortSerialPutByte( ( CHAR )ucByte ); 365 pucSndBufferCur++; 366 eBytePos = BYTE_HIGH_NIBBLE; 367 usSndBufferCount--; 368 break; 369 } 370 } 371 else 372 { 373 xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR ); 374 eSndState = STATE_TX_END; 375 } 376 break; 377 378 /* Finish the frame by sending a LF character. */ 379 case STATE_TX_END: 380 xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter ); 381 /* We need another state to make sure that the CR character has 382 * been sent. */ 383 eSndState = STATE_TX_NOTIFY; 384 break; 385 386 /* Notify the task which called eMBASCIISend that the frame has 387 * been sent. */ 388 case STATE_TX_NOTIFY: 389 eSndState = STATE_TX_IDLE; 390 xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); 391 392 /* Disable transmitter. This prevents another transmit buffer 393 * empty interrupt. */ 394 vMBPortSerialEnable( TRUE, FALSE ); 395 eSndState = STATE_TX_IDLE; 396 break; 397 398 /* We should not get a transmitter event if the transmitter is in 399 * idle state. */ 400 case STATE_TX_IDLE: 401 /* enable receiver/disable transmitter. */ 402 vMBPortSerialEnable( TRUE, FALSE ); 403 break; 404 } 405 406 return xNeedPoll; 407 } 408 409 BOOL 410 xMBASCIITimerT1SExpired( void ) 411 { 412 switch ( eRcvState ) 413 { 414 /* If we have a timeout we go back to the idle state and wait for 415 * the next frame. 416 */ 417 case STATE_RX_RCV: 418 case STATE_RX_WAIT_EOF: 419 eRcvState = STATE_RX_IDLE; 420 break; 421 422 default: 423 assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) ); 424 break; 425 } 426 vMBPortTimersDisable( ); 427 428 /* no context switch required. */ 429 return FALSE; 430 } 431 432 433 static UCHAR 434 prvucMBCHAR2BIN( UCHAR ucCharacter ) 435 { 436 if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) ) 437 { 438 return ( UCHAR )( ucCharacter - '0' ); 439 } 440 else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) ) 441 { 442 return ( UCHAR )( ucCharacter - 'A' + 0x0A ); 443 } 444 else 445 { 446 return 0xFF; 447 } 448 } 449 450 static UCHAR 451 prvucMBBIN2CHAR( UCHAR ucByte ) 452 { 453 if( ucByte <= 0x09 ) 454 { 455 return ( UCHAR )( '0' + ucByte ); 456 } 457 else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) ) 458 { 459 return ( UCHAR )( ucByte - 0x0A + 'A' ); 460 } 461 else 462 { 463 /* Programming error. */ 464 assert( 0 ); 465 } 466 return '0'; 467 } 468 469 470 static UCHAR 471 prvucMBLRC( UCHAR * pucFrame, USHORT usLen ) 472 { 473 UCHAR ucLRC = 0; /* LRC char initialized */ 474 475 while( usLen-- ) 476 { 477 ucLRC += *pucFrame++; /* Add buffer byte without carry */ 478 } 479 480 /* Return twos complement */ 481 ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) ); 482 return ucLRC; 483 } 484 485 #endif 486