Project

General

Profile

Statistics
| Branch: | Revision:

root / freemodbus / modbus / ascii / mbascii.c @ 20e5429c

History | View | Annotate | Download (15.3 KB)

1 20e5429c Tom Mullins
/* 
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_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
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
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
143
eMBASCIIStop( void )
144
{
145
    ENTER_CRITICAL_SECTION(  );
146
    vMBPortSerialEnable( FALSE, FALSE );
147
    vMBPortTimersDisable(  );
148
    EXIT_CRITICAL_SECTION(  );
149
}
150
151
eMBErrorCode
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
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 too
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
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
                usRcvBufferPos++;
278
                eBytePos = BYTE_HIGH_NIBBLE;
279
                break;
280
            }
281
        }
282
        break;
283
284
    case STATE_RX_WAIT_EOF:
285
        if( ucByte == ucMBLFCharacter )
286
        {
287
            /* Disable character timeout timer because all characters are
288
             * received. */
289
            vMBPortTimersDisable(  );
290
            /* Receiver is again in idle state. */
291
            eRcvState = STATE_RX_IDLE;
292
293
            /* Notify the caller of eMBASCIIReceive that a new frame
294
             * was received. */
295
            xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
296
        }
297
        else if( ucByte == ':' )
298
        {
299
            /* Empty receive buffer and back to receive state. */
300
            eBytePos = BYTE_HIGH_NIBBLE;
301
            usRcvBufferPos = 0;
302
            eRcvState = STATE_RX_RCV;
303
304
            /* Enable timer for character timeout. */
305
            vMBPortTimersEnable(  );
306
        }
307
        else
308
        {
309
            /* Frame is not okay. Delete entire frame. */
310
            eRcvState = STATE_RX_IDLE;
311
        }
312
        break;
313
314
    case STATE_RX_IDLE:
315
        if( ucByte == ':' )
316
        {
317
            /* Enable timer for character timeout. */
318
            vMBPortTimersEnable(  );
319
            /* Reset the input buffers to store the frame. */
320
            usRcvBufferPos = 0;;
321
            eBytePos = BYTE_HIGH_NIBBLE;
322
            eRcvState = STATE_RX_RCV;
323
        }
324
        break;
325
    }
326
327
    return xNeedPoll;
328
}
329
330
BOOL
331
xMBASCIITransmitFSM( void )
332
{
333
    BOOL            xNeedPoll = FALSE;
334
    UCHAR           ucByte;
335
336
    assert( eRcvState == STATE_RX_IDLE );
337
    switch ( eSndState )
338
    {
339
        /* Start of transmission. The start of a frame is defined by sending
340
         * the character ':'. */
341
    case STATE_TX_START:
342
        ucByte = ':';
343
        xMBPortSerialPutByte( ( CHAR )ucByte );
344
        eSndState = STATE_TX_DATA;
345
        eBytePos = BYTE_HIGH_NIBBLE;
346
        break;
347
348
        /* Send the data block. Each data byte is encoded as a character hex
349
         * stream with the high nibble sent first and the low nibble sent
350
         * last. If all data bytes are exhausted we send a '\r' character
351
         * to end the transmission. */
352
    case STATE_TX_DATA:
353
        if( usSndBufferCount > 0 )
354
        {
355
            switch ( eBytePos )
356
            {
357
            case BYTE_HIGH_NIBBLE:
358
                ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur >> 4 ) );
359
                xMBPortSerialPutByte( ( CHAR ) ucByte );
360
                eBytePos = BYTE_LOW_NIBBLE;
361
                break;
362
363
            case BYTE_LOW_NIBBLE:
364
                ucByte = prvucMBBIN2CHAR( ( UCHAR )( *pucSndBufferCur & 0x0F ) );
365
                xMBPortSerialPutByte( ( CHAR )ucByte );
366
                pucSndBufferCur++;
367
                eBytePos = BYTE_HIGH_NIBBLE;
368
                usSndBufferCount--;
369
                break;
370
            }
371
        }
372
        else
373
        {
374
            xMBPortSerialPutByte( MB_ASCII_DEFAULT_CR );
375
            eSndState = STATE_TX_END;
376
        }
377
        break;
378
379
        /* Finish the frame by sending a LF character. */
380
    case STATE_TX_END:
381
        xMBPortSerialPutByte( ( CHAR )ucMBLFCharacter );
382
        /* We need another state to make sure that the CR character has
383
         * been sent. */
384
        eSndState = STATE_TX_NOTIFY;
385
        break;
386
387
        /* Notify the task which called eMBASCIISend that the frame has
388
         * been sent. */
389
    case STATE_TX_NOTIFY:
390
        eSndState = STATE_TX_IDLE;
391
        xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
392
393
        /* Disable transmitter. This prevents another transmit buffer
394
         * empty interrupt. */
395
        vMBPortSerialEnable( TRUE, FALSE );
396
        eSndState = STATE_TX_IDLE;
397
        break;
398
399
        /* We should not get a transmitter event if the transmitter is in
400
         * idle state.  */
401
    case STATE_TX_IDLE:
402
        /* enable receiver/disable transmitter. */
403
        vMBPortSerialEnable( TRUE, FALSE );
404
        break;
405
    }
406
407
    return xNeedPoll;
408
}
409
410
BOOL
411
xMBASCIITimerT1SExpired( void )
412
{
413
    switch ( eRcvState )
414
    {
415
        /* If we have a timeout we go back to the idle state and wait for
416
         * the next frame.
417
         */
418
    case STATE_RX_RCV:
419
    case STATE_RX_WAIT_EOF:
420
        eRcvState = STATE_RX_IDLE;
421
        break;
422
423
    default:
424
        assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) );
425
        break;
426
    }
427
    vMBPortTimersDisable(  );
428
429
    /* no context switch required. */
430
    return FALSE;
431
}
432
433
434
static          UCHAR
435
prvucMBCHAR2BIN( UCHAR ucCharacter )
436
{
437
    if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) )
438
    {
439
        return ( UCHAR )( ucCharacter - '0' );
440
    }
441
    else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) )
442
    {
443
        return ( UCHAR )( ucCharacter - 'A' + 0x0A );
444
    }
445
    else
446
    {
447
        return 0xFF;
448
    }
449
}
450
451
static          UCHAR
452
prvucMBBIN2CHAR( UCHAR ucByte )
453
{
454
    if( ucByte <= 0x09 )
455
    {
456
        return ( UCHAR )( '0' + ucByte );
457
    }
458
    else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) )
459
    {
460
        return ( UCHAR )( ucByte - 0x0A + 'A' );
461
    }
462
    else
463
    {
464
        /* Programming error. */
465
        assert( 0 );
466
    }
467
    return '0';
468
}
469
470
471
static          UCHAR
472
prvucMBLRC( UCHAR * pucFrame, USHORT usLen )
473
{
474
    UCHAR           ucLRC = 0;  /* LRC char initialized */
475
476
    while( usLen-- )
477
    {
478
        ucLRC += *pucFrame++;   /* Add buffer byte without carry */
479
    }
480
481
    /* Return twos complement */
482
    ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
483
    return ucLRC;
484
}
485
486
#endif