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