xref: /nrf52832-nimble/rt-thread/components/net/freemodbus/modbus/rtu/mbrtu.c (revision 104654410c56c573564690304ae786df310c91fc)
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