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