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