xref: /nrf52832-nimble/rt-thread/components/net/freemodbus/modbus/ascii/mbascii.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: mbascii.c,v 1.15 2007/02/18 23:46:48 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 "mbconfig.h"
41 #include "mbascii.h"
42 #include "mbframe.h"
43 
44 #include "mbcrc.h"
45 #include "mbport.h"
46 
47 #if MB_SLAVE_ASCII_ENABLED > 0
48 
49 /* ----------------------- Defines ------------------------------------------*/
50 #define MB_ASCII_DEFAULT_CR     '\r'    /*!< Default CR character for Modbus ASCII. */
51 #define MB_ASCII_DEFAULT_LF     '\n'    /*!< Default LF character for Modbus ASCII. */
52 #define MB_SER_PDU_SIZE_MIN     3       /*!< Minimum size of a Modbus ASCII frame. */
53 #define MB_SER_PDU_SIZE_MAX     256     /*!< Maximum size of a Modbus ASCII frame. */
54 #define MB_SER_PDU_SIZE_LRC     1       /*!< Size of LRC field in PDU. */
55 #define MB_SER_PDU_ADDR_OFF     0       /*!< Offset of slave address in Ser-PDU. */
56 #define MB_SER_PDU_PDU_OFF      1       /*!< Offset of Modbus-PDU in Ser-PDU. */
57 
58 /* ----------------------- Type definitions ---------------------------------*/
59 typedef enum
60 {
61     STATE_RX_IDLE,              /*!< Receiver is in idle state. */
62     STATE_RX_RCV,               /*!< Frame is beeing received. */
63     STATE_RX_WAIT_EOF           /*!< Wait for End of Frame. */
64 } eMBRcvState;
65 
66 typedef enum
67 {
68     STATE_TX_IDLE,              /*!< Transmitter is in idle state. */
69     STATE_TX_START,             /*!< Starting transmission (':' sent). */
70     STATE_TX_DATA,              /*!< Sending of data (Address, Data, LRC). */
71     STATE_TX_END,               /*!< End of transmission. */
72     STATE_TX_NOTIFY             /*!< Notify sender that the frame has been sent. */
73 } eMBSndState;
74 
75 typedef enum
76 {
77     BYTE_HIGH_NIBBLE,           /*!< Character for high nibble of byte. */
78     BYTE_LOW_NIBBLE             /*!< Character for low nibble of byte. */
79 } eMBBytePos;
80 
81 /* ----------------------- Static functions ---------------------------------*/
82 static UCHAR    prvucMBCHAR2BIN( UCHAR ucCharacter );
83 
84 static UCHAR    prvucMBBIN2CHAR( UCHAR ucByte );
85 
86 static UCHAR    prvucMBLRC( UCHAR * pucFrame, USHORT usLen );
87 
88 /* ----------------------- Static variables ---------------------------------*/
89 static volatile eMBSndState eSndState;
90 static volatile eMBRcvState eRcvState;
91 
92 /* We reuse the Modbus RTU buffer because only one buffer is needed and the
93  * RTU buffer is bigger. */
94 extern volatile UCHAR ucRTUBuf[];
95 static volatile UCHAR *ucASCIIBuf = ucRTUBuf;
96 
97 static volatile USHORT usRcvBufferPos;
98 static volatile eMBBytePos eBytePos;
99 
100 static volatile UCHAR *pucSndBufferCur;
101 static volatile USHORT usSndBufferCount;
102 
103 static volatile UCHAR ucLRC;
104 static volatile UCHAR ucMBLFCharacter;
105 
106 /* ----------------------- Start implementation -----------------------------*/
107 eMBErrorCode
eMBASCIIInit(UCHAR ucSlaveAddress,UCHAR ucPort,ULONG ulBaudRate,eMBParity eParity)108 eMBASCIIInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
109 {
110     eMBErrorCode    eStatus = MB_ENOERR;
111     ( void )ucSlaveAddress;
112 
113     ENTER_CRITICAL_SECTION(  );
114     ucMBLFCharacter = MB_ASCII_DEFAULT_LF;
115 
116     if( xMBPortSerialInit( ucPort, ulBaudRate, 7, eParity ) != TRUE )
117     {
118         eStatus = MB_EPORTERR;
119     }
120     else if( xMBPortTimersInit( MB_ASCII_TIMEOUT_SEC * 20000UL ) != TRUE )
121     {
122         eStatus = MB_EPORTERR;
123     }
124 
125     EXIT_CRITICAL_SECTION(  );
126 
127     return eStatus;
128 }
129 
130 void
eMBASCIIStart(void)131 eMBASCIIStart( void )
132 {
133     ENTER_CRITICAL_SECTION(  );
134     vMBPortSerialEnable( TRUE, FALSE );
135     eRcvState = STATE_RX_IDLE;
136     EXIT_CRITICAL_SECTION(  );
137 
138     /* No special startup required for ASCII. */
139     ( void )xMBPortEventPost( EV_READY );
140 }
141 
142 void
eMBASCIIStop(void)143 eMBASCIIStop( void )
144 {
145     ENTER_CRITICAL_SECTION(  );
146     vMBPortSerialEnable( FALSE, FALSE );
147     vMBPortTimersDisable(  );
148     EXIT_CRITICAL_SECTION(  );
149 }
150 
151 eMBErrorCode
eMBASCIIReceive(UCHAR * pucRcvAddress,UCHAR ** pucFrame,USHORT * pusLength)152 eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
153 {
154     eMBErrorCode    eStatus = MB_ENOERR;
155 
156     ENTER_CRITICAL_SECTION(  );
157     assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
158 
159     /* Length and CRC check */
160     if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
161         && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
162     {
163         /* Save the address field. All frames are passed to the upper layed
164          * and the decision if a frame is used is done there.
165          */
166         *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
167 
168         /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
169          * size of address field and CRC checksum.
170          */
171         *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
172 
173         /* Return the start of the Modbus PDU to the caller. */
174         *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
175     }
176     else
177     {
178         eStatus = MB_EIO;
179     }
180     EXIT_CRITICAL_SECTION(  );
181     return eStatus;
182 }
183 
184 eMBErrorCode
eMBASCIISend(UCHAR ucSlaveAddress,const UCHAR * pucFrame,USHORT usLength)185 eMBASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
186 {
187     eMBErrorCode    eStatus = MB_ENOERR;
188     UCHAR           usLRC;
189 
190     ENTER_CRITICAL_SECTION(  );
191     /* Check if the receiver is still in idle state. If not we where to
192      * slow with processing the received frame and the master sent another
193      * frame on the network. We have to abort sending the frame.
194      */
195     if( eRcvState == STATE_RX_IDLE )
196     {
197         /* First byte before the Modbus-PDU is the slave address. */
198         pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
199         usSndBufferCount = 1;
200 
201         /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
202         pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
203         usSndBufferCount += usLength;
204 
205         /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */
206         usLRC = prvucMBLRC( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
207         ucASCIIBuf[usSndBufferCount++] = usLRC;
208 
209         /* Activate the transmitter. */
210         eSndState = STATE_TX_START;
211         vMBPortSerialEnable( FALSE, TRUE );
212     }
213     else
214     {
215         eStatus = MB_EIO;
216     }
217     EXIT_CRITICAL_SECTION(  );
218     return eStatus;
219 }
220 
221 BOOL
xMBASCIIReceiveFSM(void)222 xMBASCIIReceiveFSM( void )
223 {
224     BOOL            xNeedPoll = FALSE;
225     UCHAR           ucByte;
226     UCHAR           ucResult;
227 
228     assert( eSndState == STATE_TX_IDLE );
229 
230     ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
231     switch ( eRcvState )
232     {
233         /* A new character is received. If the character is a ':' the input
234          * buffer is cleared. A CR-character signals the end of the data
235          * block. Other characters are part of the data block and their
236          * ASCII value is converted back to a binary representation.
237          */
238     case STATE_RX_RCV:
239         /* Enable timer for character timeout. */
240         vMBPortTimersEnable(  );
241         if( ucByte == ':' )
242         {
243             /* Empty receive buffer. */
244             eBytePos = BYTE_HIGH_NIBBLE;
245             usRcvBufferPos = 0;
246         }
247         else if( ucByte == MB_ASCII_DEFAULT_CR )
248         {
249             eRcvState = STATE_RX_WAIT_EOF;
250         }
251         else
252         {
253             ucResult = prvucMBCHAR2BIN( ucByte );
254             switch ( eBytePos )
255             {
256                 /* High nibble of the byte comes first. We check for
257                  * a buffer overflow here. */
258             case BYTE_HIGH_NIBBLE:
259                 if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
260                 {
261                     ucASCIIBuf[usRcvBufferPos] = ( UCHAR )( ucResult << 4 );
262                     eBytePos = BYTE_LOW_NIBBLE;
263                     break;
264                 }
265                 else
266                 {
267                     /* not handled in Modbus specification but seems
268                      * a resonable implementation. */
269                     eRcvState = STATE_RX_IDLE;
270                     /* Disable previously activated timer because of error state. */
271                     vMBPortTimersDisable(  );
272                 }
273                 break;
274 
275             case BYTE_LOW_NIBBLE:
276                 ucASCIIBuf[usRcvBufferPos++] |= ucResult;
277                 eBytePos = BYTE_HIGH_NIBBLE;
278                 break;
279             }
280         }
281         break;
282 
283     case STATE_RX_WAIT_EOF:
284         if( ucByte == ucMBLFCharacter )
285         {
286             /* Disable character timeout timer because all characters are
287              * received. */
288             vMBPortTimersDisable(  );
289             /* Receiver is again in idle state. */
290             eRcvState = STATE_RX_IDLE;
291 
292             /* Notify the caller of eMBASCIIReceive that a new frame
293              * was received. */
294             xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
295         }
296         else if( ucByte == ':' )
297         {
298             /* Empty receive buffer and back to receive state. */
299             eBytePos = BYTE_HIGH_NIBBLE;
300             usRcvBufferPos = 0;
301             eRcvState = STATE_RX_RCV;
302 
303             /* Enable timer for character timeout. */
304             vMBPortTimersEnable(  );
305         }
306         else
307         {
308             /* Frame is not okay. Delete entire frame. */
309             eRcvState = STATE_RX_IDLE;
310         }
311         break;
312 
313     case STATE_RX_IDLE:
314         if( ucByte == ':' )
315         {
316             /* Enable timer for character timeout. */
317             vMBPortTimersEnable(  );
318             /* Reset the input buffers to store the frame. */
319             usRcvBufferPos = 0;;
320             eBytePos = BYTE_HIGH_NIBBLE;
321             eRcvState = STATE_RX_RCV;
322         }
323         break;
324     }
325 
326     return xNeedPoll;
327 }
328 
329 BOOL
xMBASCIITransmitFSM(void)330 xMBASCIITransmitFSM( void )
331 {
332     BOOL            xNeedPoll = FALSE;
333     UCHAR           ucByte;
334 
335     assert( eRcvState == STATE_RX_IDLE );
336     switch ( eSndState )
337     {
338         /* Start of transmission. The start of a frame is defined by sending
339          * the character ':'. */
340     case STATE_TX_START:
341         ucByte = ':';
342         xMBPortSerialPutByte( ( CHAR )ucByte );
343         eSndState = STATE_TX_DATA;
344         eBytePos = BYTE_HIGH_NIBBLE;
345         break;
346 
347         /* Send the data block. Each data byte is encoded as a character hex
348          * stream with the high nibble sent first and the low nibble sent
349          * last. If all data bytes are exhausted we send a '\r' character
350          * to end the transmission. */
351     case STATE_TX_DATA:
352         if( usSndBufferCount > 0 )
353         {
354             switch ( eBytePos )
355             {
356             case BYTE_HIGH_NIBBLE:
357                 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) );
358                 xMBPortSerialPutByte( ( CHAR ) ucByte );
359                 eBytePos = BYTE_LOW_NIBBLE;
360                 break;
361 
362             case BYTE_LOW_NIBBLE:
363                 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) );
364                 xMBPortSerialPutByte( ( CHAR )ucByte );
365                 pucSndBufferCur++;
366                 eBytePos = BYTE_HIGH_NIBBLE;
367                 usSndBufferCount--;
368                 break;
369             }
370         }
371         else
372         {
373             xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR );
374             eSndState = STATE_TX_END;
375         }
376         break;
377 
378         /* Finish the frame by sending a LF character. */
379     case STATE_TX_END:
380         xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter );
381         /* We need another state to make sure that the CR character has
382          * been sent. */
383         eSndState = STATE_TX_NOTIFY;
384         break;
385 
386         /* Notify the task which called eMBASCIISend that the frame has
387          * been sent. */
388     case STATE_TX_NOTIFY:
389         eSndState = STATE_TX_IDLE;
390         xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
391 
392         /* Disable transmitter. This prevents another transmit buffer
393          * empty interrupt. */
394         vMBPortSerialEnable( TRUE, FALSE );
395         eSndState = STATE_TX_IDLE;
396         break;
397 
398         /* We should not get a transmitter event if the transmitter is in
399          * idle state.  */
400     case STATE_TX_IDLE:
401         /* enable receiver/disable transmitter. */
402         vMBPortSerialEnable( TRUE, FALSE );
403         break;
404     }
405 
406     return xNeedPoll;
407 }
408 
409 BOOL
xMBASCIITimerT1SExpired(void)410 xMBASCIITimerT1SExpired( void )
411 {
412     switch ( eRcvState )
413     {
414         /* If we have a timeout we go back to the idle state and wait for
415          * the next frame.
416          */
417     case STATE_RX_RCV:
418     case STATE_RX_WAIT_EOF:
419         eRcvState = STATE_RX_IDLE;
420         break;
421 
422     default:
423         assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) );
424         break;
425     }
426     vMBPortTimersDisable(  );
427 
428     /* no context switch required. */
429     return FALSE;
430 }
431 
432 
433 static          UCHAR
prvucMBCHAR2BIN(UCHAR ucCharacter)434 prvucMBCHAR2BIN( UCHAR ucCharacter )
435 {
436     if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) )
437     {
438         return ( UCHAR )( ucCharacter - '0' );
439     }
440     else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) )
441     {
442         return ( UCHAR )( ucCharacter - 'A' + 0x0A );
443     }
444     else
445     {
446         return 0xFF;
447     }
448 }
449 
450 static          UCHAR
prvucMBBIN2CHAR(UCHAR ucByte)451 prvucMBBIN2CHAR( UCHAR ucByte )
452 {
453     if( ucByte <= 0x09 )
454     {
455         return ( UCHAR )( '0' + ucByte );
456     }
457     else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) )
458     {
459         return ( UCHAR )( ucByte - 0x0A + 'A' );
460     }
461     else
462     {
463         /* Programming error. */
464         assert( 0 );
465     }
466     return '0';
467 }
468 
469 
470 static          UCHAR
prvucMBLRC(UCHAR * pucFrame,USHORT usLen)471 prvucMBLRC( UCHAR * pucFrame, USHORT usLen )
472 {
473     UCHAR           ucLRC = 0;  /* LRC char initialized */
474 
475     while( usLen-- )
476     {
477         ucLRC += *pucFrame++;   /* Add buffer byte without carry */
478     }
479 
480     /* Return twos complement */
481     ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
482     return ucLRC;
483 }
484 
485 #endif
486