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_m.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_MASTER_ASCII_ENABLED > 0
48 
49 /* ----------------------- Defines ------------------------------------------*/
50 
51 /* ----------------------- Type definitions ---------------------------------*/
52 typedef enum
53 {
54     STATE_M_RX_INIT,            /*!< Receiver is in initial state. */
55     STATE_M_RX_IDLE,            /*!< Receiver is in idle state. */
56     STATE_M_RX_RCV,             /*!< Frame is beeing received. */
57     STATE_M_RX_WAIT_EOF,        /*!< Wait for End of Frame. */
58     STATE_M_RX_ERROR,           /*!< If the frame is invalid. */
59 } eMBMasterAsciiRcvState;
60 
61 typedef enum
62 {
63     STATE_M_TX_IDLE,            /*!< Transmitter is in idle state. */
64     STATE_M_TX_START,           /*!< Starting transmission (':' sent). */
65     STATE_M_TX_DATA,            /*!< Sending of data (Address, Data, LRC). */
66     STATE_M_TX_END,             /*!< End of transmission. */
67     STATE_M_TX_NOTIFY,          /*!< Notify sender that the frame has been sent. */
68     STATE_M_TX_XFWR,            /*!< Transmitter is in transfer finish and wait receive state. */
69 } eMBMasterAsciiSndState;
70 
71 typedef enum
72 {
73     BYTE_HIGH_NIBBLE,           /*!< Character for high nibble of byte. */
74     BYTE_LOW_NIBBLE             /*!< Character for low nibble of byte. */
75 } eMBBytePos;
76 
77 /* ----------------------- Shared values  -----------------------------------*/
78 /* These Modbus values are shared in ASCII mode*/
79 extern volatile UCHAR   ucMasterRcvBuf[];
80 extern volatile UCHAR   ucMasterSndBuf[];
81 extern volatile eMBMasterTimerMode eMasterCurTimerMode;
82 
83 /* ----------------------- Static functions ---------------------------------*/
84 static UCHAR    prvucMBCHAR2BIN( UCHAR ucCharacter );
85 
86 static UCHAR    prvucMBBIN2CHAR( UCHAR ucByte );
87 
88 static UCHAR    prvucMBLRC( UCHAR * pucFrame, USHORT usLen );
89 
90 /* ----------------------- Static variables ---------------------------------*/
91 static volatile eMBMasterAsciiSndState eSndState;
92 static volatile eMBMasterAsciiRcvState eRcvState;
93 
94 static volatile UCHAR *ucMasterASCIIRcvBuf = ucMasterRcvBuf;
95 static volatile UCHAR *ucMasterASCIISndBuf = ucMasterSndBuf;
96 
97 static volatile USHORT usMasterRcvBufferPos;
98 static volatile eMBBytePos eBytePos;
99 
100 static volatile UCHAR *pucMasterSndBufferCur;
101 static volatile USHORT usMasterSndBufferCount;
102 
103 static volatile UCHAR ucLRC;
104 static volatile UCHAR ucMBLFCharacter;
105 
106 /* ----------------------- Start implementation -----------------------------*/
107 eMBErrorCode
eMBMasterASCIIInit(UCHAR ucPort,ULONG ulBaudRate,eMBParity eParity)108 eMBMasterASCIIInit( UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
109 {
110     eMBErrorCode    eStatus = MB_ENOERR;
111 
112     ENTER_CRITICAL_SECTION(  );
113     ucMBLFCharacter = MB_ASCII_DEFAULT_LF;
114 
115     if( xMBMasterPortSerialInit( ucPort, ulBaudRate, MB_ASCII_BITS_PER_SYMB, eParity ) != TRUE )
116     {
117         eStatus = MB_EPORTERR;
118     }
119     else if( xMBMasterPortTimersInit( MB_ASCII_TIMEOUT_MS * MB_TIMER_TICS_PER_MS ) != TRUE )
120     {
121         eStatus = MB_EPORTERR;
122     }
123 
124     EXIT_CRITICAL_SECTION(  );
125 
126     return eStatus;
127 }
128 
129 void
eMBMasterASCIIStart(void)130 eMBMasterASCIIStart( void )
131 {
132     ENTER_CRITICAL_SECTION(  );
133     eRcvState = STATE_M_RX_IDLE;
134     vMBMasterPortSerialEnable( TRUE, FALSE );
135     vMBMasterPortTimersT35Enable(  );
136     EXIT_CRITICAL_SECTION(  );
137 }
138 
139 void
eMBMasterASCIIStop(void)140 eMBMasterASCIIStop( void )
141 {
142     ENTER_CRITICAL_SECTION(  );
143     vMBMasterPortSerialEnable( FALSE, FALSE );
144     vMBMasterPortTimersDisable(  );
145     EXIT_CRITICAL_SECTION(  );
146 }
147 
148 eMBErrorCode
eMBMasterASCIIReceive(UCHAR * pucRcvAddress,UCHAR ** pucFrame,USHORT * pusLength)149 eMBMasterASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
150 {
151     eMBErrorCode    eStatus = MB_ENOERR;
152 
153     ENTER_CRITICAL_SECTION(  );
154     assert( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX );
155 
156     /* Length and CRC check */
157     if( ( usMasterRcvBufferPos >= MB_ASCII_SER_PDU_SIZE_MIN )
158         && ( prvucMBLRC( ( UCHAR * ) ucMasterASCIIRcvBuf, usMasterRcvBufferPos ) == 0 ) )
159     {
160         /* Save the address field. All frames are passed to the upper layed
161          * and the decision if a frame is used is done there.
162          */
163         *pucRcvAddress = ucMasterASCIIRcvBuf[MB_SER_PDU_ADDR_OFF];
164 
165         /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
166          * size of address field and CRC checksum.
167          */
168         *pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );
169 
170         /* Return the start of the Modbus PDU to the caller. */
171         *pucFrame = ( UCHAR * ) & ucMasterASCIIRcvBuf[MB_SER_PDU_PDU_OFF];
172     }
173     else
174     {
175         eStatus = MB_EIO;
176     }
177     EXIT_CRITICAL_SECTION(  );
178     return eStatus;
179 }
180 
181 eMBErrorCode
eMBMasterASCIISend(UCHAR ucSlaveAddress,const UCHAR * pucFrame,USHORT usLength)182 eMBMasterASCIISend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
183 {
184     eMBErrorCode    eStatus = MB_ENOERR;
185     UCHAR           usLRC;
186 
187     if ( ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM ) return MB_EINVAL;
188 
189     ENTER_CRITICAL_SECTION(  );
190     /* Check if the receiver is still in idle state. If not we where too
191      * slow with processing the received frame and the master sent another
192      * frame on the network. We have to abort sending the frame.
193      */
194     if(eRcvState == STATE_M_RX_IDLE)
195     {
196         /* First byte before the Modbus-PDU is the slave address. */
197         pucMasterSndBufferCur = ( UCHAR * ) pucFrame - 1;
198         usMasterSndBufferCount = 1;
199 
200         /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
201         pucMasterSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
202         usMasterSndBufferCount += usLength;
203 
204         /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */
205         usLRC = prvucMBLRC( ( UCHAR * ) pucMasterSndBufferCur, usMasterSndBufferCount );
206         ucMasterASCIISndBuf[usMasterSndBufferCount++] = usLRC;
207 
208         /* Activate the transmitter. */
209         eSndState = STATE_M_TX_START;
210         vMBMasterPortSerialEnable( FALSE, TRUE );
211     }
212     else
213     {
214         eStatus = MB_EIO;
215     }
216     EXIT_CRITICAL_SECTION(  );
217     return eStatus;
218 }
219 
220 BOOL
xMBMasterASCIIReceiveFSM(void)221 xMBMasterASCIIReceiveFSM( void )
222 {
223     BOOL            xNeedPoll = FALSE;
224     UCHAR           ucByte;
225     UCHAR           ucResult;
226 
227     assert(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR ));
228 
229     /* Always read the character. */
230     xNeedPoll = xMBMasterPortSerialGetByte( ( CHAR * ) & ucByte );
231 
232     switch ( eRcvState )
233     {
234         /* If we have received a character in the init state we have to
235         * wait until the frame is finished.
236         */
237     case STATE_M_RX_INIT:
238         vMBMasterPortTimersT35Enable( );
239         break;
240 
241          /* In the error state we wait until all characters in the
242          * damaged frame are transmitted.
243          */
244     case STATE_M_RX_ERROR:
245         vMBMasterPortTimersRespondTimeoutEnable( );
246         break;
247 
248         /* In the idle state we wait for a new character. If a character
249          * is received the t1.5 and t3.5 timers are started and the
250          * receiver is in the state STATE_RX_RECEIVE and disable early
251          * the timer of respond timeout .
252          */
253     case STATE_M_RX_IDLE:
254         /* Waiting for the start of frame character during respond timeout */
255         vMBMasterPortTimersRespondTimeoutEnable(  );
256         if( ucByte == ':' )
257         {
258             /* Reset the input buffers to store the frame in receive state. */
259             usMasterRcvBufferPos = 0;
260             eBytePos = BYTE_HIGH_NIBBLE;
261             eRcvState = STATE_M_RX_RCV;
262         }
263         eSndState = STATE_M_TX_IDLE;
264         break;
265 
266         /* A new character is received. If the character is a ':' the input
267         * buffer is cleared. A CR-character signals the end of the data
268         * block. Other characters are part of the data block and their
269         * ASCII value is converted back to a binary representation.
270         */
271     case STATE_M_RX_RCV:
272         /* Enable timer timeout. */
273         vMBMasterPortTimersT35Enable(  );
274         if( ucByte == ':' )
275         {
276             /* Empty receive buffer. */
277             eBytePos = BYTE_HIGH_NIBBLE;
278             usMasterRcvBufferPos = 0;
279         }
280         else if( ucByte == MB_ASCII_DEFAULT_CR )
281         {
282             eRcvState = STATE_M_RX_WAIT_EOF;
283         }
284         else
285         {
286             ucResult = prvucMBCHAR2BIN( ucByte );
287             switch ( eBytePos )
288             {
289                 /* High nibble of the byte comes first. We check for
290                  * a buffer overflow here. */
291             case BYTE_HIGH_NIBBLE:
292                 if( usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX )
293                 {
294                     ucMasterASCIIRcvBuf[usMasterRcvBufferPos] = ( UCHAR )( ucResult << 4 );
295                     eBytePos = BYTE_LOW_NIBBLE;
296                     break;
297                 }
298                 else
299                 {
300                     /* not handled in Modbus specification but seems
301                      * a resonable implementation. */
302                     eRcvState = STATE_M_RX_ERROR;
303                     /* Disable previously activated timer because of error state. */
304                     vMBPortTimersDisable(  );
305                 }
306                 break;
307 
308             case BYTE_LOW_NIBBLE:
309                 ucMasterASCIIRcvBuf[usMasterRcvBufferPos] |= ucResult;
310                 usMasterRcvBufferPos++;
311                 eBytePos = BYTE_HIGH_NIBBLE;
312                 break;
313             }
314         }
315         break;
316 
317     case STATE_M_RX_WAIT_EOF:
318         if( ucByte == ucMBLFCharacter )
319         {
320             /* Disable character timeout timer because all characters are
321              * received. */
322             vMBMasterPortTimersDisable(  );
323             /* Receiver is again in idle state. */
324             eRcvState = STATE_M_RX_IDLE;
325 
326             /* Notify the caller of eMBMasterASCIIReceive that a new frame
327              * was received. */
328             (void)xMBMasterPortEventPost( EV_MASTER_FRAME_RECEIVED );
329             xNeedPoll = FALSE;
330         }
331         else if( ucByte == ':' )
332         {
333             /* Start of frame character received but last message is not completed.
334              * Empty receive buffer and back to receive state. */
335             eBytePos = BYTE_HIGH_NIBBLE;
336             usMasterRcvBufferPos = 0;
337             eRcvState = STATE_M_RX_IDLE;
338 
339             /* Enable timer for respond timeout and wait for next frame. */
340             vMBMasterPortTimersRespondTimeoutEnable(  );
341         }
342         else
343         {
344             /* Frame is not okay. Delete entire frame. */
345             eRcvState = STATE_M_RX_IDLE;
346         }
347         break;
348     }
349 
350     return xNeedPoll;
351 }
352 
353 BOOL
xMBMasterASCIITransmitFSM(void)354 xMBMasterASCIITransmitFSM( void )
355 {
356     BOOL            xNeedPoll = TRUE;
357     UCHAR           ucByte;
358     BOOL            xFrameIsBroadcast = FALSE;
359 
360     assert( eRcvState == STATE_M_RX_IDLE );
361 
362     switch ( eSndState )
363     {
364          /* We should not get a transmitter event if the transmitter is in
365           * idle state.  */
366     case STATE_M_TX_XFWR:
367         break;
368 
369          /* We should not get a transmitter event if the transmitter is in
370           * idle state.  */
371     case STATE_M_TX_IDLE:
372         break;
373 
374         /* Start of transmission. The start of a frame is defined by sending
375          * the character ':'. */
376     case STATE_M_TX_START:
377         ucByte = ':';
378         xMBMasterPortSerialPutByte( ( CHAR )ucByte );
379         eSndState = STATE_M_TX_DATA;
380         eBytePos = BYTE_HIGH_NIBBLE;
381         break;
382 
383         /* Send the data block. Each data byte is encoded as a character hex
384          * stream with the high nibble sent first and the low nibble sent
385          * last. If all data bytes are exhausted we send a '\r' character
386          * to end the transmission. */
387     case STATE_M_TX_DATA:
388         if( usMasterSndBufferCount > 0 )
389         {
390             switch ( eBytePos )
391             {
392             case BYTE_HIGH_NIBBLE:
393                 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucMasterSndBufferCur >> 4 ) );
394                 xMBMasterPortSerialPutByte( ( CHAR ) ucByte );
395                 eBytePos = BYTE_LOW_NIBBLE;
396                 break;
397 
398             case BYTE_LOW_NIBBLE:
399                 ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucMasterSndBufferCur & 0x0F ) );
400                 xMBMasterPortSerialPutByte( ( CHAR )ucByte );
401                 pucMasterSndBufferCur++;
402                 eBytePos = BYTE_HIGH_NIBBLE;
403                 usMasterSndBufferCount--;
404                 break;
405             }
406         }
407         else
408         {
409             xMBMasterPortSerialPutByte( MB_ASCII_DEFAULT_CR );
410             eSndState = STATE_M_TX_END;
411         }
412         break;
413 
414         /* Finish the frame by sending a LF character. */
415     case STATE_M_TX_END:
416         xMBMasterPortSerialPutByte( ( CHAR )ucMBLFCharacter );
417         /* We need another state to make sure that the CR character has
418          * been sent. */
419         eSndState = STATE_M_TX_NOTIFY;
420         break;
421 
422         /* Notify the task which called eMBMasterASCIISend that the frame has
423          * been sent. */
424     case STATE_M_TX_NOTIFY:
425         xFrameIsBroadcast = ( ucMasterASCIISndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST ) ? TRUE : FALSE;
426         vMBMasterRequestSetType( xFrameIsBroadcast );
427         eSndState = STATE_M_TX_XFWR;
428         /* If the frame is broadcast ,master will enable timer of convert delay,
429          * else master will enable timer of respond timeout. */
430         if ( xFrameIsBroadcast == TRUE )
431         {
432              vMBMasterPortTimersConvertDelayEnable( );
433         }
434         else
435         {
436             vMBMasterPortTimersRespondTimeoutEnable( );
437         }
438         xNeedPoll = FALSE;
439         break;
440     }
441 
442     return xNeedPoll;
443 }
444 
445 BOOL
xMBMasterASCIITimerT1SExpired(void)446 xMBMasterASCIITimerT1SExpired( void )
447 {
448     BOOL xNeedPoll = FALSE;
449 
450     switch ( eRcvState )
451     {
452         /* Timer t35 expired. Startup phase is finished. */
453     case STATE_M_RX_INIT:
454         xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY);
455         ESP_EARLY_LOGI("xMBMasterASCIITimerT1SExpired", "RX_INIT_EXPIRED");
456         break;
457 
458         /* Start of message is not received during respond timeout.
459          * Process error. */
460     case STATE_M_RX_IDLE:
461         eRcvState = STATE_M_RX_ERROR;
462         break;
463 
464         /* A recieve timeout expired and no any new character received.
465          * Wait for respond time and go to error state to inform listener about error */
466     case STATE_M_RX_RCV:
467         eRcvState = STATE_M_RX_ERROR;
468         break;
469 
470         /* An error occured while receiving the frame. */
471     case STATE_M_RX_ERROR:
472         vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);
473         xNeedPoll = xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS );
474         break;
475 
476         /* If we have a timeout we go back to the idle state and wait for
477          * the next frame.
478          */
479     case STATE_M_RX_WAIT_EOF:
480         eRcvState = STATE_M_RX_IDLE;
481         break;
482 
483     default:
484         assert( 0 );
485         break;
486     }
487     eRcvState = STATE_M_RX_IDLE;
488 
489     switch (eSndState)
490     {
491         /* A frame was send finish and convert delay or respond timeout expired.
492          * If the frame is broadcast,The master will idle,and if the frame is not
493          * broadcast.*/
494     case STATE_M_TX_XFWR:
495         if ( xMBMasterRequestIsBroadcast( ) == FALSE ) {
496             vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT);
497             xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
498         }
499         break;
500 
501         /* Function called in an illegal state. */
502     default:
503         assert( ( eSndState == STATE_M_TX_START ) || ( eSndState == STATE_M_TX_IDLE )
504                 || ( eSndState == STATE_M_TX_DATA ) || ( eSndState == STATE_M_TX_END )
505                 || ( eSndState == STATE_M_TX_NOTIFY ) );
506         break;
507     }
508     eSndState = STATE_M_TX_IDLE;
509 
510     vMBMasterPortTimersDisable( );
511     /* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
512     if (xMBMasterGetCurTimerMode() == MB_TMODE_CONVERT_DELAY) {
513         xNeedPoll = xMBMasterPortEventPost( EV_MASTER_EXECUTE );
514     }
515 
516     vMBMasterPortTimersDisable(  );
517 
518     /* no context switch required. */
519     return xNeedPoll;
520 }
521 
522 static  UCHAR
prvucMBCHAR2BIN(UCHAR ucCharacter)523 prvucMBCHAR2BIN( UCHAR ucCharacter )
524 {
525     if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) )
526     {
527         return ( UCHAR )( ucCharacter - '0' );
528     }
529     else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) )
530     {
531         return ( UCHAR )( ucCharacter - 'A' + 0x0A );
532     }
533     else
534     {
535         return 0xFF;
536     }
537 }
538 
539 static  UCHAR
prvucMBBIN2CHAR(UCHAR ucByte)540 prvucMBBIN2CHAR( UCHAR ucByte )
541 {
542     if( ucByte <= 0x09 )
543     {
544         return ( UCHAR )( '0' + ucByte );
545     }
546     else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) )
547     {
548         return ( UCHAR )( ucByte - 0x0A + 'A' );
549     }
550     else
551     {
552         /* Programming error. */
553         assert( 0 );
554     }
555     return '0';
556 }
557 
558 static  UCHAR
prvucMBLRC(UCHAR * pucFrame,USHORT usLen)559 prvucMBLRC( UCHAR * pucFrame, USHORT usLen )
560 {
561     UCHAR   ucLRC = 0;  /* LRC char initialized */
562 
563     while( usLen-- )
564     {
565         ucLRC += *pucFrame++;   /* Add buffer byte without carry */
566     }
567 
568     /* Return twos complement */
569     ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
570     return ucLRC;
571 }
572 
573 #endif
574