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