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