Biblioteka libModbusRTUserialLineSlave

    -- Sebastian Pawlak, 2014.


Przedstawiam moją bibliotekę implementującą protokół MODBUS/RTU dla urządzeń typu slave dla linii szeregowej RS485. Zamieściłem także kod krótkiego programu wykorzystującego bibliotekę, a także implementującego funkcje callback.


Kod źródłowy pliku "libModbusRTUserialLineSlave.h":

/* libModbusSerialLineSlave: Modbus RTU protocol library for slave devices
 *                           on serial line
 *
 * This library was designed to operate with Atmel AVR ATmega128 and RS485
 *
 * Sebastian Pawlak, 2015, v1.3
 */

#ifndef _MODBUS_H_
#define _MODBUS_H_

#ifndef F_CPU
#define F_CPU 16000000UL    /* set your uC frequency */
#endif

/* set proper values */
extern uint8_t modbusSlaveAddress;
extern uint8_t serialLineBaudRate;
extern uint16_t serialLineBaudRateUBRR;
extern uint8_t serialLineParity;
extern uint8_t serialLineStopBits;
extern uint8_t serialLineCharacterSize;

extern volatile uint8_t modbusBusy;     /* set it to 1 while the device
                                         * is busy and cannot process
                                         * incoming Modbus commands
                                         */

/* serial line diagnostic counters */
extern volatile uint16_t returnBusMessageCount;
extern volatile uint16_t returnBusCommunicationErrorCount;
extern volatile uint16_t returnSlaveExceptionErrorCount;
extern volatile uint16_t returnSlaveMessageCount;
extern volatile uint16_t returnSlaveNoResponseCount;
extern volatile uint16_t returnSlaveNAKcount;
extern volatile uint16_t returnSlaveBusyCount;
extern volatile uint16_t returnBusCharacterOverrunCount;

/* own useful counters, not demanded by Modbus */
extern volatile uint16_t busCharacterParityErrorCount;
extern volatile uint16_t busCharacterFrameErrorCount;

/* these variables are extern because of diagnostic purposes in main program */
extern volatile uint8_t crcError;
extern volatile uint16_t crcErrorCntr;
extern volatile uint8_t frameError;
extern volatile uint8_t tooShortFrame;
extern volatile uint8_t wrongAddress;
extern volatile uint8_t modeState;
extern volatile uint8_t listenOnlyMode;
extern volatile uint8_t bufferOverflow;
extern volatile uint8_t charCntr;
extern volatile uint16_t crc, crc1, crc2;
extern volatile uint8_t modbusBuffer[];
extern volatile uint8_t charReceived;
extern volatile uint8_t charSent;

/* set proper values */
#define MODBUS_MEI_VENDOR_NAME "Pawlak"
#define MODBUS_MEI_PRODUCT_CODE SIGNATURE
#define MODBUS_MEI_MAJOR_MINOR_REVISION VERSION

#define NUMBER_OF_DISCRETE_INPUTS 0
#define NUMBER_OF_COILS 26
#define NUMBER_OF_INPUT_REGISTERS 0
#define NUMBER_OF_HOLDING_REGISTERS 603  /* 592 - welding parameters,
                                          *   2 - drop/holding times,
                                          *   9 - max currents
                                          */

/* set proper port and pin */
#define TX_RX_SWITCH_OUTPUT_PORT PORTE
#define TX_RX_SWITCH_OUTPUT_PIN PORTE2
#define TX_RX_SWITCH_DDR DDRE
#define TX_RX_SWITCH_DDR_PIN DDE2

/* set proper registers and flags */
#define SIG_UART_RECV SIG_UART0_RECV
#define SIG_UART_TRANS SIG_UART0_TRANS

#define USART_UBRRH UBRR0H
#define USART_UBRRL UBRR0L
#define USART_UCSRA UCSR0A
#define USART_UCSRB UCSR0B
#define USART_UCSRC UCSR0C
#define USART_TXEN TXEN0
#define USART_RXEN RXEN0
#define USART_UCSZ2 UCSZ02
#define USART_UCSZ1 UCSZ01
#define USART_UCSZ0 UCSZ00
#define USART_UDRE UDRE0
#define USART_UDR UDR0
#define USART_RXCIE RXCIE0
#define USART_TXCIE TXCIE0
#define USART_UPM1 UPM01
#define USART_UPM0 UPM00
#define USART_USBS USBS0
#define USART_UPE UPE0
#define USART_FE FE0
#define USART_DOR DOR0

#define UBRRVAL(BAUD) ((F_CPU + ((BAUD) * 8UL)) / (16UL * (BAUD)) - 1)

enum {
    USART_BAUD_RATE_9600 = 0,
    USART_BAUD_RATE_19200 = 1,
    USART_DEFAULT_BAUD_RATE = USART_BAUD_RATE_19200,

    USART_BAUD_RATE_9600_UBRR = UBRRVAL(9600),
    USART_BAUD_RATE_19200_UBRR = UBRRVAL(19200),
    USART_DEFAULT_BAUD_RATE_UBRR =  USART_BAUD_RATE_19200_UBRR,

    USART_PARITY_NONE = 0,
    USART_PARITY_EVEN = 1,
    USART_PARITY_ODD = 2,
    USART_DEFAULT_PARITY = USART_PARITY_EVEN,


    USART_CHARACTER_SIZE_8_BITS = 0,
    USART_CHARACTER_SIZE_7_BITS = 1,
    USART_DEFAULT_CHARACTER_SIZE = USART_CHARACTER_SIZE_8_BITS,

    USART_1_STOP_BIT = 0,
    USART_2_STOP_BITS = 1,
    USART_DEFAULT_STOP_BITS = USART_1_STOP_BIT,

    MODBUS_SLAVE_ADDRESS_MIN = 1,
    MODBUS_SLAVE_ADDRESS_MAX = 247,
    MODBUS_BROADCAST_ADDRESS = 0,
};


#define _BV(bit)        (1 << (bit))
#define cbi(sfr, bit)   (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit)   (_SFR_BYTE(sfr) |= _BV(bit))

void initUSART(void);
void stopUSART(void);

uint8_t (*modbusReadCoilsCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);
uint8_t (*modbusReadDiscreteInputsCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);
uint8_t (*modbusReadHoldingRegistersCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);
uint8_t (*modbusReadInputRegistersCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);
uint8_t (*modbusWriteCoilsCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);
uint8_t (*modbusWriteHoldingRegistersCallbackPtr)(const uint16_t addr,
                                   const uint16_t qty, volatile uint8_t *buf);

#endif

Kod źródłowy pliku "libModbusRTUserialLineSlave.c":

/* libModbusSerialLineSlave: Modbus RTU protocol library for slave devices
 *                           on serial line
 *
 * This library was designed to operate with Atmel AVR ATmega128 and RS485
 *
 *
 * Sebastian Pawlak, 2015, v1.3
 *
 * Changelog:
 * 2015-10-04, v1.3: bugfix in SIG_UART_TRANS
 * 2015-03-01, v1.2: bugfix of modbus busy response
 * 2014-10-26, v1.1: new variables added charReceived, charSent;
 *                   exception code no. 1 returned for nonexistent callbacks
 */

#include <stdlib.h>
#include <string.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/io.h>
#include <inttypes.h>
#include "version.h"
#include "libModbusRTUserialLineSlave.h"
#include <util/delay.h>
#include <avr/crc16.h>

#define OCRVAL(BAUD, RATIO) (F_CPU / (1024UL * (BAUD) / (11UL * (RATIO))) - 1)

void sendBuffer(uint8_t *buffer, uint8_t length);

uint8_t modbusSlaveAddress;
uint8_t serialLineBaudRate = USART_DEFAULT_BAUD_RATE;
uint16_t serialLineBaudRateUBRR = USART_DEFAULT_BAUD_RATE_UBRR;
uint8_t serialLineParity = USART_DEFAULT_PARITY;
uint8_t serialLineStopBits = USART_DEFAULT_STOP_BITS;
uint8_t serialLineCharacterSize = USART_DEFAULT_CHARACTER_SIZE;

volatile uint8_t modbusBusy;     /* set it to 1 while the device is busy
                                  * and cannot process incoming Modbus commands
                                  */

/* serial line diagnostic counters */
volatile uint16_t returnBusMessageCount;
volatile uint16_t returnBusCommunicationErrorCount;
volatile uint16_t returnSlaveExceptionErrorCount;
volatile uint16_t returnSlaveMessageCount;
volatile uint16_t returnSlaveNoResponseCount;
volatile uint16_t returnSlaveNAKcount;
volatile uint16_t returnSlaveBusyCount;
volatile uint16_t returnBusCharacterOverrunCount;

/* own useful counters, not demanded by Modbus */
volatile uint16_t busCharacterParityErrorCount;
volatile uint16_t busCharacterFrameErrorCount;

volatile uint16_t diagnosticRegister;

/* OCRt4_5 corresponds to t3,5,
 * OCRt2_5 corresponds to t1,5,
 * OCRt2_0 corresponds to t3,5 - t1,5 = t2,0
 */
uint8_t OCRt4_5;
uint8_t OCRt2_5;
uint8_t OCRt2_0;

volatile uint8_t crcError;
volatile uint16_t crcErrorCntr;
volatile uint8_t frameError;
volatile uint8_t tooShortFrame;
volatile uint8_t wrongAddress;
volatile uint8_t modeState;
volatile uint8_t listenOnlyMode;
volatile uint8_t bufferOverflow;
volatile uint8_t charCntr;
volatile uint16_t crc, crc1, crc2;
volatile uint8_t charReceived;
volatile uint8_t charSent;


enum {
   MODBUS_BUFFER_SIZE = 83,    /* max. 256 */

   MODE_STATE_INITIAL_STATE = 0,
   MODE_STATE_IDLE,
   MODE_STATE_EMISSION,
   MODE_STATE_RECEPTION,
   MODE_STATE_CONTROL_AND_WAITING,
};

volatile uint8_t modbusBuffer[MODBUS_BUFFER_SIZE] = "\0";
volatile uint8_t *bufPtr = modbusBuffer;
volatile uint8_t data1, data2;


/* initUSART: USART initialization
 */
void initUSART(void)
{
    cli();

    TX_RX_SWITCH_DDR |= _BV(TX_RX_SWITCH_DDR_PIN);

    /* transmission baud rate */
    USART_UBRRH = (uint8_t)(serialLineBaudRateUBRR >> 8);
    USART_UBRRL = (uint8_t)serialLineBaudRateUBRR;

    USART_UCSRB = _BV(USART_RXCIE) |         /* receiver complete interrupt */
                  _BV(USART_RXEN) | _BV(USART_TXEN);     /* enable receiver */

    /* frame format */
    #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) || \
        defined(__AVR_ATmega32__)
    USART_UCSRC = _BV(URSEL);
    #else
    USART_UCSRC = 0;
    #endif

    if (serialLineParity == USART_PARITY_EVEN)
        USART_UCSRC |= _BV(USART_UPM1);
    else if (serialLineParity == USART_PARITY_ODD)
        USART_UCSRC |= _BV(USART_UPM0) | _BV(USART_UPM1);

    if (serialLineStopBits == USART_2_STOP_BITS)
        USART_UCSRC |= _BV(USART_USBS);

    if (serialLineCharacterSize == USART_CHARACTER_SIZE_7_BITS)
        USART_UCSRC |= _BV(USART_UCSZ1);
    else    /* USART_CHARACTER_SIZE_8_BITS */
        USART_UCSRC |= _BV(USART_UCSZ1) | _BV(USART_UCSZ0);


    TX_RX_SWITCH_OUTPUT_PORT &= ~_BV(TX_RX_SWITCH_OUTPUT_PIN);    /* receive */
    _delay_us(1);

    /* timer initialization */
    TCCR3B = 0;    /* timer stop - prescaler 0 */

    ETIFR |= _BV(OCF3A);
    ETIMSK |= _BV(OCIE3A);

    switch (serialLineBaudRate) {
    case USART_BAUD_RATE_9600:
        OCRt4_5 = OCRVAL(9600, 4.5);
        OCRt2_5 = OCRVAL(9600, 2.5);
        OCRt2_0 = OCRVAL(9600, 2.0);
        break;
    case USART_BAUD_RATE_19200:
        OCRt4_5 = OCRVAL(19200, 4.5);
        OCRt2_5 = OCRVAL(19200, 2.5);
        OCRt2_0 = OCRVAL(19200, 2.0);
        break;
    }

    crcError = 0;
    frameError = 0;
    tooShortFrame = 0;
    wrongAddress = 0;
    crc = 0xffff;

    modeState = MODE_STATE_INITIAL_STATE;

    sei();

    TCCR3B = 0;
    TCNT3 = 0;
    ETIFR |= _BV(TOV3);    /* clear interrupt flag */
    OCR3A = OCRt4_5;
    TCCR3B = _BV(WGM32) |
             _BV(CS32) | _BV(CS30);    /* prescaler 1024 */
}


/* stopUSART: USART stop
 */
void stopUSART(void)
{
    ETIMSK &= ~_BV(OCIE3A);
    USART_UCSRB &= ~(_BV(USART_RXCIE) |
                     _BV(USART_RXEN) | _BV(USART_TXEN));
}


SIGNAL (SIG_OUTPUT_COMPARE3A)
{
    uint16_t addr, qty;
    uint8_t m;

    if (modeState == MODE_STATE_RECEPTION) {

        modeState = MODE_STATE_CONTROL_AND_WAITING;

        if (charCntr >= 3) {
            if ((data2 != (uint8_t)crc2) || (data1 != (uint8_t)(crc2 >> 8)))
                crcError = 1, crcErrorCntr++;
        } else
            tooShortFrame = 1;

        if ((frameError == 0) && (tooShortFrame == 0) && (crcError == 0)) {
            returnBusMessageCount++;

            if (wrongAddress == 0) {
                returnSlaveMessageCount++;

                if (modbusBuffer[0] == 0)
                    returnSlaveNoResponseCount++;
            }

        } else
            returnBusCommunicationErrorCount++;

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */
        OCR3A = OCRt2_0;
        TCCR3B = _BV(WGM32) |
                 _BV(CS32) | _BV(CS30);    /* prescaler 1024 */

    } else if (modeState == MODE_STATE_CONTROL_AND_WAITING) {

        modeState = MODE_STATE_IDLE;

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */
	
        if ((wrongAddress == 0) && (tooShortFrame == 0) && (frameError == 0) &&
            (crcError == 0) && (bufferOverflow == 0) &&
            ((listenOnlyMode == 0) ||
             ((modbusBuffer[1] == 8) && (modbusBuffer[2] == 0) &&
              (modbusBuffer[3] == 1)))) {

            uint8_t exceptionCode = 0;

            if (modbusBusy == 1) {
                returnSlaveBusyCount++;
                exceptionCode = 6;
                goto exception;
     	    }

            switch (modbusBuffer[1]) {
            case 1:    /* 01 (0x01) Read Coils */
                if (modbusReadCoilsCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07D0)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_COILS) {

                        m = (qty >> 3) + ((qty & 0x07) != 0);
                        if (3 + 2 + m <= MODBUS_BUFFER_SIZE) {
                            if ((exceptionCode =
                                 modbusReadCoilsCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[3])) == 0) {
                                modbusBuffer[2] = m;
                                sendBuffer((uint8_t *)modbusBuffer,
                                           3 + modbusBuffer[2]);
                            }
                        } else
                            exceptionCode = 4;

                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 2:    /* 02 (0x02) Read Discrete Inputs */
                if (modbusReadDiscreteInputsCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07D0)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_DISCRETE_INPUTS) {

                        m = (qty >> 3) + ((qty & 0x07) != 0);
                        if (3 + 2 + m <= MODBUS_BUFFER_SIZE) {
                            if ((exceptionCode =
                                modbusReadDiscreteInputsCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[3])) == 0) {
                                modbusBuffer[2] = m;
                                sendBuffer((uint8_t *)modbusBuffer,
                                           3 + modbusBuffer[2]);
                            }
                        } else
                            exceptionCode = 4;

                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 3:    /* 03 (0x03) Read Holding Registers */
                if (modbusReadHoldingRegistersCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07D)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_HOLDING_REGISTERS) {

                        m = qty << 1;
                        if (3 + 2 + m <= MODBUS_BUFFER_SIZE) {
                            if ((exceptionCode =
                                modbusReadHoldingRegistersCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[3])) == 0) {
                                modbusBuffer[2] = m;
                                sendBuffer((uint8_t *)modbusBuffer,
                                           3 + modbusBuffer[2]);
                            }
                        } else
                            exceptionCode = 4;

                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 4:    /* 04 (0x04) Read Input Registers */
                if (modbusReadInputRegistersCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07D)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_INPUT_REGISTERS) {

                        m = qty << 1;
                        if (3 + 2 + m <= MODBUS_BUFFER_SIZE) {
                            if ((exceptionCode =
                                modbusReadInputRegistersCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[3])) == 0) {
                                modbusBuffer[2] = m;
                                sendBuffer((uint8_t *)modbusBuffer,
                                           3 + modbusBuffer[2]);
                            }
                        } else
                            exceptionCode = 4;

                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 5:    /* 05 (0x05) Write Single Coil */
                if (modbusWriteCoilsCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                if ((modbusBuffer[5] != 0) ||
                    ((modbusBuffer[4] != 0) && (modbusBuffer[4] != 0xff))) {
                    exceptionCode = 3;
                    break;
                }

                addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                if (addr < NUMBER_OF_COILS) {
                    if ((exceptionCode =
                        modbusWriteCoilsCallbackPtr(addr, 1,
                                        (uint8_t *)&modbusBuffer[4])) == 0) {
                        sendBuffer((uint8_t *)modbusBuffer, 6);
                    }
                } else
                    exceptionCode = 2;
                break;

            case 6:    /* 06 (0x06) Write Single Register */
                if (modbusWriteHoldingRegistersCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                if (addr < NUMBER_OF_HOLDING_REGISTERS) {
                    if ((exceptionCode =
                        modbusWriteHoldingRegistersCallbackPtr(addr, 1,
                                        (uint8_t *)&modbusBuffer[4])) == 0) {
                        sendBuffer((uint8_t *)modbusBuffer, 6);
                    }
                } else
                    exceptionCode = 2;
                break;

            case 8:    /* 08 (0x08) Diagnostics (Serial Line only) */
                if (modbusBuffer[2] != 0) {
                    exceptionCode = 1;
                    break;
                }

                switch (modbusBuffer[3]) {
                case 0:    /* Return Query Data */
                    sendBuffer((uint8_t *)modbusBuffer, charCntr - 2);
                    break;

                case 1:    /* Restart Communications Option */
                    if ((modbusBuffer[5] != 0) ||
                        ((modbusBuffer[4] != 0) && (modbusBuffer[4] != 0xff))) {
                        exceptionCode = 3;
                        break;
                    }

                    returnBusMessageCount = returnBusCommunicationErrorCount =
                    returnSlaveExceptionErrorCount = returnSlaveMessageCount =
                    returnSlaveNoResponseCount =
                    returnBusCharacterOverrunCount = 0;

                    busCharacterParityErrorCount =
                    busCharacterFrameErrorCount = 0;

                    sendBuffer((uint8_t *)modbusBuffer, charCntr - 2);

                    listenOnlyMode = 0;
                    break;

                case 2:    /* Return Diagnostic Register */
                    if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) {
                        exceptionCode = 3;
                        break;
                    }

                    modbusBuffer[4] = diagnosticRegister >> 8;
                    modbusBuffer[5] = diagnosticRegister;

                    sendBuffer((uint8_t *)modbusBuffer, 6);
                    break;

                case 4:    /* Force Listen Only Mode */
                    if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) {
                        exceptionCode = 3;
                        break;
                    }

                    listenOnlyMode = 1;
                    break;

                case 10:    /* Clear Counters and Diagnostic Register */
                    if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) {
                        exceptionCode = 3;
                        break;
                    }

                    returnBusMessageCount = returnBusCommunicationErrorCount =
                    returnSlaveExceptionErrorCount = returnSlaveMessageCount =
                    returnSlaveNoResponseCount = returnSlaveNAKcount =
                    returnSlaveBusyCount = returnBusCharacterOverrunCount =
                    diagnosticRegister = 0;

                    busCharacterParityErrorCount =
                    busCharacterFrameErrorCount = 0;

                    crcErrorCntr = 0;

                    sendBuffer((uint8_t *)modbusBuffer, charCntr - 2);
                    break;

                case 11: case 12: case 13: case 14: case 15: case 16:
                case 17: case 18:    /* diagnostic counters */
                    if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) {
                        exceptionCode = 3;
                        break;
                    }

                    uint16_t w = 0;

                    switch (modbusBuffer[3]) {
                    case 11: w = returnBusMessageCount; break;
                    case 12: w = returnBusCommunicationErrorCount; break;
                    case 13: w = returnSlaveExceptionErrorCount; break;
                    case 14: w = returnSlaveMessageCount; break;
                    case 15: w = returnSlaveNoResponseCount; break;
                    case 16: w = returnSlaveNAKcount; break;
                    case 17: w = returnSlaveBusyCount; break;
                    case 18: w = returnBusCharacterOverrunCount; break;
                    }

                    modbusBuffer[4] = w >> 8;
                    modbusBuffer[5] = w;

                    sendBuffer((uint8_t *)modbusBuffer, 6);
                    break;

                case 20:    /* Clear Overrun Counter and Flag */
                    if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) {
                        exceptionCode = 3;
                        break;
                    }

                    returnBusCharacterOverrunCount = 0;

                    sendBuffer((uint8_t *)modbusBuffer, charCntr - 2);
                    break;

                default:
                    exceptionCode = 1;
                    break;
                }
                break;

            case 15:    /* 15 (0x0F) Write Multiple Coils */
                if (modbusWriteCoilsCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07B0) &&
                    (modbusBuffer[6] == charCntr - 9)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_COILS) {
                        if ((exceptionCode =
                                 modbusWriteCoilsCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[7])) == 0) {
                            sendBuffer((uint8_t *)modbusBuffer, 6);
                        }
                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 16:    /* 16 (0x10) Write Multiple Registers */
                if (modbusWriteHoldingRegistersCallbackPtr == NULL) {
                    exceptionCode = 1;
                    break;
                }

                qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5];
                if ((qty >= 1) && (qty <= 0x07B) &&
                    (modbusBuffer[6] == charCntr - 9)) {

                    addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3];
                    if (addr + qty <= NUMBER_OF_HOLDING_REGISTERS) {
                        if ((exceptionCode =
                            modbusWriteHoldingRegistersCallbackPtr(addr, qty,
                                          (uint8_t *)&modbusBuffer[7])) == 0) {
                            sendBuffer((uint8_t *)modbusBuffer, 6);
                        }
                    } else
                        exceptionCode = 2;

                } else
                    exceptionCode = 3;
                break;

            case 43:    /* 43/14 (0x2B/0x0E) Read Device Identification */
                if (modbusBuffer[2] != 14) {
                    exceptionCode = 1;
                    break;
                }

                /* Read Device ID code: basic, regular, extended, specific */
                if ((modbusBuffer[3] < 1) || (modbusBuffer[3] > 4)) {
                    exceptionCode = 3;
                    break;
                }

                uint8_t d = modbusBuffer[4], i = 8, l = 0;

                modbusBuffer[5] = 0;    /* More Follows */
                modbusBuffer[6] = 0;    /* Next Object Id */

                if (modbusBuffer[3] <= 3) {    /* stream access */
                    modbusBuffer[4] = 1;       /* Conformity Level */
                    modbusBuffer[7] = 3 - d;   /* Number Of Objects */

                    switch (d) {    /* Object Id */
                    case 0: 
                    default:
                        modbusBuffer[i++] = 0;    /* Object Id */
                        modbusBuffer[i++] = l = strlen(MODBUS_MEI_VENDOR_NAME);
                        strcpy((uint8_t *)&modbusBuffer[i],
                               MODBUS_MEI_VENDOR_NAME);
                        i += l;
                        /* do not use break; */

                    case 1:
                        modbusBuffer[i++] = 1;    /* Object Id */
                        modbusBuffer[i++] = l = strlen(MODBUS_MEI_PRODUCT_CODE);
                        strcpy((uint8_t *)&modbusBuffer[i],
                               MODBUS_MEI_PRODUCT_CODE);
                        i += l;
                        /* do not use break; */

                    case 2:
                        modbusBuffer[i++] = 2;    /* Object Id */
                        modbusBuffer[i++] = l =
                            strlen(MODBUS_MEI_MAJOR_MINOR_REVISION);
                        strcpy((uint8_t *)&modbusBuffer[i],
                               MODBUS_MEI_MAJOR_MINOR_REVISION);
                        i += l;
                        break;
                    }

                } else {    /* individual access */
                    modbusBuffer[4] = 0x81;    /* Conformity Level */
                    modbusBuffer[7] = 1;       /* Number Of Objects */
                    modbusBuffer[8] = d;       /* Object Id */

                    if (d > 2) {
                        exceptionCode = 2;
                        break;
                    }

                    switch (d) {    /* Object Id */
                    case 0: 
                        modbusBuffer[9] = l = strlen(MODBUS_MEI_VENDOR_NAME);
                        strcpy((uint8_t *)&modbusBuffer[10],
                               MODBUS_MEI_VENDOR_NAME);
                        break;

                    case 1:
                        modbusBuffer[9] = l = strlen(MODBUS_MEI_PRODUCT_CODE);
                        strcpy((uint8_t *)&modbusBuffer[10],
                               MODBUS_MEI_PRODUCT_CODE);
                        break;

                    case 2:
                        modbusBuffer[9] = l =
                            strlen(MODBUS_MEI_MAJOR_MINOR_REVISION);
                        strcpy((uint8_t *)&modbusBuffer[10],
                               MODBUS_MEI_MAJOR_MINOR_REVISION);
                        break;
                    }

                    i = 10 + l;
                }

                sendBuffer((uint8_t *)modbusBuffer, i);
                break;

            default:
                exceptionCode = 1;
                break;
            }

exception:
            if (exceptionCode != 0) {
                returnSlaveExceptionErrorCount++;
                modbusBuffer[1] |= 0x80;
                modbusBuffer[2] = exceptionCode;
                sendBuffer((uint8_t *)modbusBuffer, 3);
            }
        }

        crcError = 0;
        frameError = 0;
        tooShortFrame = 0;
        wrongAddress = 0;
        crc = 0xffff;

    } else if (modeState == MODE_STATE_INITIAL_STATE) {

        modeState = MODE_STATE_IDLE;

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */

        crcError = 0;
        frameError = 0;
        tooShortFrame = 0;
        wrongAddress = 0;
        crc = 0xffff;
    }
}


/* SIG_UART_RECV: receiver
 */
SIGNAL (SIG_UART_RECV)
{
    uint8_t status = USART_UCSRA;
    uint8_t data = USART_UDR;
    uint8_t n;

    charReceived++;

    /* Parity Error */
    if ((USART_UCSRC & _BV(USART_UPM1)) && (status & _BV(USART_UPE))) {
        busCharacterParityErrorCount++;
        frameError = 1;
    }

    /* Frame Error - stop bit was incorrectly read which may mean out-of-sync
     *               condition
     */
    if (status & _BV(USART_FE)) {
        busCharacterFrameErrorCount++;
        frameError = 1;
    }

    /* Data OverRun - data loss due to a receiver buffer full condition */
    if (status & _BV(USART_DOR)) {
        returnBusCharacterOverrunCount++;
        frameError = 1;
    }

    if (modeState == MODE_STATE_IDLE) {

        modeState = MODE_STATE_RECEPTION;

        bufPtr = modbusBuffer;
        charCntr = 1;
        bufferOverflow = 0;

        if ((data == modbusSlaveAddress) || (data == 0))
            *bufPtr++ = data;
        else
            wrongAddress = 1;

        data2 = data1, data1 = data;
        crc2 = crc1, crc1 = crc;

        crc ^= data;
        for(n = 0; n < 8; n++)
            crc = (crc & 1) ?  ((crc >> 1) ^ 0xa001) : (crc >> 1);

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */
        OCR3A = OCRt2_5;
        TCCR3B = _BV(WGM32) |
                 _BV(CS32) | _BV(CS30);    /* prescaler 1024 */

    } else if (modeState == MODE_STATE_RECEPTION) {

        if (charCntr < MODBUS_BUFFER_SIZE) {
            charCntr++;

            if (wrongAddress == 0)
                *bufPtr++ = data;
        } else
            bufferOverflow = 1;

        data2 = data1, data1 = data;
        crc2 = crc1, crc1 = crc;

        crc ^= data;
        for(n = 0; n < 8; n++)
            crc = (crc & 1) ? ((crc >> 1) ^ 0xa001) : (crc >> 1);

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */
        OCR3A = OCRt2_5;
        TCCR3B = _BV(WGM32) |
                 _BV(CS32) | _BV(CS30);    /* prescaler 1024 */

    } else if (modeState == MODE_STATE_CONTROL_AND_WAITING) {

        frameError = 1;

    } else if (modeState == MODE_STATE_INITIAL_STATE) {

        TCCR3B = 0;
        TCNT3 = 0;
        ETIFR |= _BV(TOV3);    /* clear interrupt flag */
        OCR3A = OCRt4_5;
        TCCR3B = _BV(WGM32) |
                 _BV(CS32) | _BV(CS30);    /* prescaler 1024 */
    }
}


volatile uint8_t *sendBufPtr = NULL;
volatile uint8_t frameLength;

/* SIG_UART_TRANS: transmiter
 */
SIGNAL (SIG_UART_TRANS)
{
    if (--frameLength == 0) {
        USART_UCSRB &= ~_BV(USART_TXCIE);  /* stop transmiter interrupt */

        TX_RX_SWITCH_OUTPUT_PORT &= ~_BV(TX_RX_SWITCH_OUTPUT_PIN); /* receive */
    } else {
        USART_UDR = *sendBufPtr++;

        charSent++;
    }
}


/* sendBuffer: sends data
 */
void sendBuffer(uint8_t *buffer, uint8_t length)
{
    uint16_t crc = 0xffff;
    uint8_t i;

    if ((length == 0) || (length > MODBUS_BUFFER_SIZE - 2) ||
        (listenOnlyMode != 0) || (buffer[0] == 0))
        return;

    TX_RX_SWITCH_OUTPUT_PORT |= _BV(TX_RX_SWITCH_OUTPUT_PIN);    /* transmit */

    frameLength = length;

    for (i = 0; i < length; i++)
        crc = _crc16_update(crc, buffer[i]);

    buffer[frameLength++] = crc;
    buffer[frameLength++] = crc >> 8;

    while (!(USART_UCSRA & _BV(USART_UDRE)))
        ;

    sendBufPtr = (uint8_t *)buffer;

    USART_UCSRB |= _BV(USART_TXCIE);         /* start transmiter interrupt */
    USART_UDR = (uint8_t)*sendBufPtr++;     /* send first byte of data */
}


Kod źródłowy pliku "main.c":

/* Sample usage of libModbusSerialLineSlave
 *
 * Sebastian Pawlak, 2014
 */

#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include "libModbusRTUserialLineSlave.h"

uint8_t isFanRunning = 1;
uint8_t alarmNumber[4] = { 0, 1, 1, 0 };  /* 0000b - no alarm, 0001b - alarm #1,
                                           * 0010b - alarm #2, 0011b - alarm #3
                                           */
uint8_t coils[5] = { 0, 1, 0, 1, 0 };

uint16_t registerA = 77, registerX = 4444;
uint16_t registers[10] = { 65, 7778, 3234, 2151, 0, 7, 55, 640, 223, 224 };


/* modbusReadCoilsCallback: used by Modbus function
 *                          01 (0x01) Read Coils
 */
uint8_t modbusReadCoilsCallback(const uint16_t addr, const uint16_t qty,
                                volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty;
    uint8_t j = 0, k = 0, b = 0, mask = 1;

    for (i = addr; i < m; i++) {
        switch (i) {
        case 0:
            b |= isFanRunning ? mask : 0;
            break;

        case 1: case 2: case 3: case 4:
            b |= alarmNumber[i - 1] ? mask : 0;
            break;

        case 5:
            b |= (PORTA & _BV(PORTA3)) ? mask : 0;
            break;

        case 6: case 7: case 8: case 9: case 10:
            b |= coils[i - 6] ? mask : 0;
            break;
        }

        if (++j == 8)
            buf[k++] = b, b = j = 0, mask = 1;
        else
            mask <<= 1;
    }

    if (j != 0)
        buf[k] = b;

    return 0;
}


/* modbusWriteCoilsCallback: used by Modbus function
 *                           05 (0x05) Write Single Coil
 *                           15 (0x0F) Write Multiple Coils
 */
uint8_t modbusWriteCoilsCallback(const uint16_t addr, const uint16_t qty,
                                 volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty;
    uint8_t j = 0, k = 1, b = buf[0], mask = 1;

    for (i = addr; i < m; i++) {
        switch (i) {
        case 0:
            isFanRunning = ((b & mask) != 0);
            break;

        case 1: case 2: case 3: case 4:
            alarmNumber[i - 1] = ((b & mask) != 0);
            break;

        case 5:
            if ((b & mask) != 0)
                PORTA |= _BV(PORTA3);
            else
                PORTA &= ~_BV(PORTA3);
            break;

        case 6: case 7: case 8: case 9: case 10:
            coils[i - 6] = ((b & mask) != 0);
            break;
        }

        if (++j == 8)
            b = buf[k++], j = 0, mask = 1;
        else
            mask <<= 1;
    }

    return 0;
}


/* modbusReadDiscreteInputsCallback: used by Modbus function
 *                                   02 (0x02) Read Discrete Inputs
 */
uint8_t modbusReadDiscreteInputsCallback(const uint16_t addr,
                                         const uint16_t qty,
                                         volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty;
    uint8_t j = 0, k = 0, b = 0, mask = 1;

    for (i = addr; i < m; i++) {
        if (i < 8)
            b |= (PING & _BV(i)) ? mask : 0;
        else if (i < 16)
            b |= (PIND & _BV(i - 8)) ? mask : 0;

        if (++j == 8)
            buf[k++] = b, b = j = 0, mask = 1;
        else
            mask <<= 1;
    }

    if (j != 0)
        buf[k] = b;

    return 0;
}


/*  modbusReadHoldingRegistersCallback: used by Modbus function
 *                                      03 (0x03) Read Holding Registers
 */
uint8_t modbusReadHoldingRegistersCallback(const uint16_t addr,
                                           const uint16_t qty,
                                           volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty;
    uint8_t k = 0;

    for (i = addr; i < m; i++) {
        if (i == 0)
            buf[k++] = registerA >> 8, buf[k++] = registerA;
        else if (i == 1)
            buf[k++] = registerX >> 8, buf[k++] = registerX;
        else if (i < 12)
            buf[k++] = registers[i - 2] >> 8, buf[k++] = registers[i - 2];
    }

    return 0;
}


/*  modbusWriteHoldingRegistersCallback: used by Modbus function
 *                                       06 (0x06) Write Single Register
 *                                       16 (0x10) Write Multiple Registers
 */
uint8_t modbusWriteHoldingRegistersCallback(const uint16_t addr,
                                            const uint16_t qty,
                                            volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty;
    uint8_t k = 0;

    for (i = addr; i < m; i++) {
        if (i == 0)
            registerA = (buf[k++] << 8), registerA |= buf[k++];
        else if (i == 1)
            registerX = (buf[k++] << 8), registerX |= buf[k++];
        else if (i < 12)
            registers[i - 2] = (buf[k++] << 8), registers[i - 2] |= buf[k++];
    }

    return 0;
}


/*  modbusReadInputRegistersCallback: used by Modbus function
 *                                    04 (0x04) Read Input Registers
 */
uint8_t modbusReadInputRegistersCallback(const uint16_t addr,
                                         const uint16_t qty,
                                         volatile uint8_t *buf)
{
    uint16_t i, m = addr + qty, w;
    uint8_t k = 0;

    for (i = addr; i < m; i++) {
        if (i == 0) {
            w = PING, buf[k++] = w >> 8, buf[k++] = w;
        } else if (i == 1) {
            w = PIND, buf[k++] = w >> 8, buf[k++] = w;
        }
    }

    return 0;
}


int main(void)
{
    DDRG = 0xff;
    PORTG = 0xff;
    DDRD = 0xff;
    PORTD = 0xff;

    modbusReadCoilsCallbackPtr = &modbusReadCoilsCallback;
    modbusReadDiscreteInputsCallbackPtr =  &modbusReadDiscreteInputsCallback;
    modbusReadHoldingRegistersCallbackPtr = &modbusReadHoldingRegistersCallback;
    modbusReadInputRegistersCallbackPtr = &modbusReadInputRegistersCallback;
    modbusWriteCoilsCallbackPtr = &modbusWriteCoilsCallback;
    modbusWriteHoldingRegistersCallbackPtr =
            &modbusWriteHoldingRegistersCallback;

    modbusSlaveAddress = 10;
     
    initUSART();

    while (1)
        ;

    return 0;
}



Poniżej przedstawiono typy, adresy oraz wartości początkowe danych obsługiwanych przez program przykładowy korzystający z zaprezentowanej biblioteki.

Cewki (ang. coils)
adres MODBUS PDU    wartość        zmienna w programie
      0                1              isFanRunning
      1                0              alarmNumber[0]
      2                1              alarmNumber[1]
      3                1              alarmNumber[2]
      4                0              alarmNumber[3]
      5                x              PORTA3
      6                0              coils[0]
      7                1              coils[1]
      8                0              coils[2]
      9                1              coils[3]
      10               0              coils[4]


Wejścia dyskretne (ang. discrete inputs)
adres MODBUS PDU    wartość        zmienna w programie
      0 - 7            x              PINGn
      8 - 15           x              PINDn


Rejestry typu Holding Register
adres MODBUS PDU    wartość        zmienna w programie
      0                77             registerA
      1                4444           registerB
      2                65             registers[0]
      3                7778           registers[1]
      4                3234           registers[2]
      5                2151           registers[3]
      6                0              registers[4]
      7                7              registers[5]
      8                55             registers[6]
      9                650            registers[7]
      10               223            registers[8]
      11               224            registers[9]


Rejestry typu Input Register
adres MODBUS PDU    wartość        zmienna w programie
      0                x              PING
      1                x              PIND



Przykładowa sesja:
> 0A 22 87 09
< 0A A2 01 E9 62

> 0A 01 00 00 FF FF 3C C1
< 0A 81 03 71 93

> 0A 01 00 00 00 BB 7D 02
< 0A 81 02 B0 53

> 0A 01 00 00 00 0B 7C B6
< 0A 01 02 8D 02 F8 AC

> 0A 05 00 02 00 00 6D 71
< 0A 05 00 02 00 00 6D 71

> 0A 01 00 00 00 0B 7C B6
< 0A 01 02 89 02 FA 6C

> 0A 05 00 02 FF 00 2C 81
< 0A 05 00 02 FF 00 2C 81

> 0A 01 00 00 00 0B 7C B6
< 0A 01 02 8D 02 F8 AC

> 0A 0F 00 00 00 0B 02 FF 07 97 C6
< 0A 0F 00 00 00 0B 15 77

> 0A 01 00 00 00 0B 7C B6
< 0A 01 02 DF 07 05 CF

> 0A 03 00 02 00 02 04 B0 28
< 0A 03 04 00 41 1E 62 98 AE

> 0A 10 00 02 00 01 02 FF FF D5 32
< 0A 10 00 02 00 01 A1 72

> 0A 03 00 02 00 02 04 B0 28
< 0A 03 04 FF FF 1E 62 C8 9E

> 0A 08 00 0B 00 00 90 B2
< 0A 08 00 0B 00 0E 11 76

> 0A 2B 0E 01 00 D5 B6
< 0A 2B 0E 01 01 00 00 03 00 06 50 61 77 6C 61 6B 01 08 4D 52 4D 69 43 5A 33 67 02 03 31 2E 30 D7 C2
w3cw3c
automatyka przemysłowa