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: mbrtu.c,v 1.18 2007/09/12 10:15:56 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 "mbrtu.h" 41 #include "mbframe.h" 42 43 #include "mbcrc.h" 44 #include "mbport.h" 45 46 /* ----------------------- Defines ------------------------------------------*/ 47 #define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */ 48 #define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */ 49 #define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */ 50 #define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */ 51 #define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */ 52 53 /* ----------------------- Type definitions ---------------------------------*/ 54 typedef enum 55 { 56 STATE_RX_INIT, /*!< Receiver is in initial state. */ 57 STATE_RX_IDLE, /*!< Receiver is in idle state. */ 58 STATE_RX_RCV, /*!< Frame is beeing received. */ 59 STATE_RX_ERROR /*!< If the frame is invalid. */ 60 } eMBRcvState; 61 62 typedef enum 63 { 64 STATE_TX_IDLE, /*!< Transmitter is in idle state. */ 65 STATE_TX_XMIT /*!< Transmitter is in transfer state. */ 66 } eMBSndState; 67 68 /* ----------------------- Static variables ---------------------------------*/ 69 static volatile eMBSndState eSndState; 70 static volatile eMBRcvState eRcvState; 71 72 volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX]; 73 74 static volatile UCHAR *pucSndBufferCur; 75 static volatile USHORT usSndBufferCount; 76 77 static volatile USHORT usRcvBufferPos; 78 79 /* ----------------------- Start implementation -----------------------------*/ 80 eMBErrorCode 81 eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 82 { 83 eMBErrorCode eStatus = MB_ENOERR; 84 ULONG usTimerT35_50us; 85 86 ( void )ucSlaveAddress; 87 ENTER_CRITICAL_SECTION( ); 88 89 /* Modbus RTU uses 8 Databits. */ 90 if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE ) 91 { 92 eStatus = MB_EPORTERR; 93 } 94 else 95 { 96 /* If baudrate > 19200 then we should use the fixed timer values 97 * t35 = 1750us. Otherwise t35 must be 3.5 times the character time. 98 */ 99 if( ulBaudRate > 19200 ) 100 { 101 usTimerT35_50us = 35; /* 1800us. */ 102 } 103 else 104 { 105 /* The timer reload value for a character is given by: 106 * 107 * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 ) 108 * = 11 * Ticks_per_1s / Baudrate 109 * = 220000 / Baudrate 110 * The reload for t3.5 is 1.5 times this value and similary 111 * for t3.5. 112 */ 113 usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate ); 114 } 115 if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE ) 116 { 117 eStatus = MB_EPORTERR; 118 } 119 } 120 EXIT_CRITICAL_SECTION( ); 121 122 return eStatus; 123 } 124 125 void 126 eMBRTUStart( void ) 127 { 128 ENTER_CRITICAL_SECTION( ); 129 /* Initially the receiver is in the state STATE_RX_INIT. we start 130 * the timer and if no character is received within t3.5 we change 131 * to STATE_RX_IDLE. This makes sure that we delay startup of the 132 * modbus protocol stack until the bus is free. 133 */ 134 eRcvState = STATE_RX_INIT; 135 vMBPortSerialEnable( TRUE, FALSE ); 136 vMBPortTimersEnable( ); 137 138 EXIT_CRITICAL_SECTION( ); 139 } 140 141 void 142 eMBRTUStop( void ) 143 { 144 ENTER_CRITICAL_SECTION( ); 145 vMBPortSerialEnable( FALSE, FALSE ); 146 vMBPortTimersDisable( ); 147 EXIT_CRITICAL_SECTION( ); 148 } 149 150 eMBErrorCode 151 eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) 152 { 153 eMBErrorCode eStatus = MB_ENOERR; 154 155 ENTER_CRITICAL_SECTION( ); 156 RT_ASSERT( usRcvBufferPos <= MB_SER_PDU_SIZE_MAX ); 157 158 /* Length and CRC check */ 159 if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) 160 && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) 161 { 162 /* Save the address field. All frames are passed to the upper layed 163 * and the decision if a frame is used is done there. 164 */ 165 *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; 166 167 /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus 168 * size of address field and CRC checksum. 169 */ 170 *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); 171 172 /* Return the start of the Modbus PDU to the caller. */ 173 *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; 174 } 175 else 176 { 177 eStatus = MB_EIO; 178 } 179 180 EXIT_CRITICAL_SECTION( ); 181 return eStatus; 182 } 183 184 eMBErrorCode 185 eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) 186 { 187 eMBErrorCode eStatus = MB_ENOERR; 188 USHORT usCRC16; 189 190 ENTER_CRITICAL_SECTION( ); 191 192 /* Check if the receiver is still in idle state. If not we where to 193 * slow with processing the received frame and the master sent another 194 * frame on the network. We have to abort sending the frame. 195 */ 196 if( eRcvState == STATE_RX_IDLE ) 197 { 198 /* First byte before the Modbus-PDU is the slave address. */ 199 pucSndBufferCur = ( UCHAR * ) pucFrame - 1; 200 usSndBufferCount = 1; 201 202 /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ 203 pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; 204 usSndBufferCount += usLength; 205 206 /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ 207 usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); 208 ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); 209 ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); 210 211 /* Activate the transmitter. */ 212 eSndState = STATE_TX_XMIT; 213 vMBPortSerialEnable( FALSE, TRUE ); 214 } 215 else 216 { 217 eStatus = MB_EIO; 218 } 219 EXIT_CRITICAL_SECTION( ); 220 return eStatus; 221 } 222 223 BOOL 224 xMBRTUReceiveFSM( void ) 225 { 226 BOOL xTaskNeedSwitch = FALSE; 227 UCHAR ucByte; 228 229 RT_ASSERT( eSndState == STATE_TX_IDLE ); 230 231 /* Always read the character. */ 232 ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); 233 234 switch ( eRcvState ) 235 { 236 /* If we have received a character in the init state we have to 237 * wait until the frame is finished. 238 */ 239 case STATE_RX_INIT: 240 vMBPortTimersEnable( ); 241 break; 242 243 /* In the error state we wait until all characters in the 244 * damaged frame are transmitted. 245 */ 246 case STATE_RX_ERROR: 247 vMBPortTimersEnable( ); 248 break; 249 250 /* In the idle state we wait for a new character. If a character 251 * is received the t1.5 and t3.5 timers are started and the 252 * receiver is in the state STATE_RX_RECEIVCE. 253 */ 254 case STATE_RX_IDLE: 255 usRcvBufferPos = 0; 256 ucRTUBuf[usRcvBufferPos++] = ucByte; 257 eRcvState = STATE_RX_RCV; 258 259 /* Enable t3.5 timers. */ 260 vMBPortTimersEnable( ); 261 break; 262 263 /* We are currently receiving a frame. Reset the timer after 264 * every character received. If more than the maximum possible 265 * number of bytes in a modbus frame is received the frame is 266 * ignored. 267 */ 268 case STATE_RX_RCV: 269 if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) 270 { 271 ucRTUBuf[usRcvBufferPos++] = ucByte; 272 } 273 else 274 { 275 eRcvState = STATE_RX_ERROR; 276 } 277 vMBPortTimersEnable(); 278 break; 279 } 280 return xTaskNeedSwitch; 281 } 282 283 BOOL 284 xMBRTUTransmitFSM( void ) 285 { 286 BOOL xNeedPoll = FALSE; 287 288 RT_ASSERT( eRcvState == STATE_RX_IDLE ); 289 290 switch ( eSndState ) 291 { 292 /* We should not get a transmitter event if the transmitter is in 293 * idle state. */ 294 case STATE_TX_IDLE: 295 /* enable receiver/disable transmitter. */ 296 vMBPortSerialEnable( TRUE, FALSE ); 297 break; 298 299 case STATE_TX_XMIT: 300 /* check if we are finished. */ 301 if( usSndBufferCount != 0 ) 302 { 303 xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); 304 pucSndBufferCur++; /* next byte in sendbuffer. */ 305 usSndBufferCount--; 306 } 307 else 308 { 309 xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); 310 /* Disable transmitter. This prevents another transmit buffer 311 * empty interrupt. */ 312 vMBPortSerialEnable( TRUE, FALSE ); 313 eSndState = STATE_TX_IDLE; 314 } 315 break; 316 } 317 318 return xNeedPoll; 319 } 320 321 BOOL 322 xMBRTUTimerT35Expired( void ) 323 { 324 BOOL xNeedPoll = FALSE; 325 326 switch ( eRcvState ) 327 { 328 /* Timer t35 expired. Startup phase is finished. */ 329 case STATE_RX_INIT: 330 xNeedPoll = xMBPortEventPost( EV_READY ); 331 break; 332 333 /* A frame was received and t35 expired. Notify the listener that 334 * a new frame was received. */ 335 case STATE_RX_RCV: 336 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); 337 break; 338 339 /* An error occured while receiving the frame. */ 340 case STATE_RX_ERROR: 341 break; 342 343 /* Function called in an illegal state. */ 344 default: 345 RT_ASSERT( ( eRcvState == STATE_RX_INIT ) || 346 ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); 347 break; 348 } 349 350 vMBPortTimersDisable( ); 351 eRcvState = STATE_RX_IDLE; 352 353 return xNeedPoll; 354 } 355