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
eMBRTUInit(UCHAR ucSlaveAddress,UCHAR ucPort,ULONG ulBaudRate,eMBParity eParity)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
eMBRTUStart(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
eMBRTUStop(void)142 eMBRTUStop( void )
143 {
144 ENTER_CRITICAL_SECTION( );
145 vMBPortSerialEnable( FALSE, FALSE );
146 vMBPortTimersDisable( );
147 EXIT_CRITICAL_SECTION( );
148 }
149
150 eMBErrorCode
eMBRTUReceive(UCHAR * pucRcvAddress,UCHAR ** pucFrame,USHORT * pusLength)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
eMBRTUSend(UCHAR ucSlaveAddress,const UCHAR * pucFrame,USHORT usLength)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
xMBRTUReceiveFSM(void)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
xMBRTUTransmitFSM(void)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
xMBRTUTimerT35Expired(void)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