Adding Modbus TCP Server to an iMXRT 1064 program

取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 

Adding Modbus TCP Server to an iMXRT 1064 program

2,811 次查看
DFenton
Contributor I

I need to add Modbus TCP server functionality to an existing bare metal program. I currently use the lwip library for ethernet communication. I also use the same ethernet hardware found on the MIMXRT1064 EVK evaluation board. Any suggestions for libraries or guidance regarding how to do this will be much appreciated. I may be able to incorporate Free RTOS if needed. Thank you for your consideration. Dave F

标签 (1)
标记 (1)
0 项奖励
回复
1 回复

2,804 次查看
mjbcswitzerland
Specialist V

Hi

There is a complete Modbus (master/slave/gateway - TCP, UART ASCII/RTU) for the i.MX RT 1064 here:
https://www.utasker.com/iMX/RT1064.html#modbus1

Modbus user's guide: https://www.utasker.com/docs/MODBUS/uTasker_MODBUS.PDF

I have attached the user interface code at the bottom showing how the reference defines a slave on UART and TCP (on multiple sockets with configurable port numbers), generates Modbus content based on which registers are read, reacts to changes (such as writing to coils to control GPIOs) and also gateways certain slave addresses from a TCP connecting to a Modbus UART connection.
The Modbus package has, for example, been used in industrial projects controlling Mega Watt wind turbines and also for systems used as part of the controls for the 52 ton transformable video screens used by U2 on their 360° world tour.

Regards

Mark
[uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training, solutions to problems or rapid product development requirements

For professionals searching for faster, problem-free Kinetis and i.MX RT 10xx developments the uTasker project holds the key: https://www.utasker.com/iMX/RT1064.html

 

 

 

/***********************************************************************
    Mark Butcher    Bsc (Hons) MPhil MIET

    M.J.Butcher Consulting
    Birchstrasse 20f,    CH-5406, Rütihof
    Switzerland

    www.uTasker.com    Skype: M_J_Butcher

    ---------------------------------------------------------------------
    File:      modbus_app.c
    Project:   uTasker project
    ---------------------------------------------------------------------
    Copyright (C) M.J.Butcher Consulting 2004..2021
    *********************************************************************
    02.09.2009 Adjust gateway parameter to respect present use           // {1}
    07.11.2011 Don't use fnMODBUS_Master_send() when there is no master functionality {2}

    This example of a MODBUS application is based on a configuration with
    two UARTs (one master and one slave) plus two TCP slaves and two TCP
    masters.

    Various public functions are tested by either the serial or a TCP master
    by sending request and then waiting of a response. On a response, exception
    or timeout the next function will be sent until all test functions have
    been completed and the test restarts.

    By activating various test defines special conditions can be tested.

    The serial test runs in the uTasker simulator when the serial master
    and slave are connected (eg. using a com0com loop-back).

    TCP -> serial gateway tests are possible to include modbus TCP in the
    process, whereby the simulator can also be used to achieve full tests
    (activate PSEUDO_LOOPBACK in config.h to allow a TCP master to transmit
    a TCP slave on its own IP address). The test path is then -
    Request: TCP master -> TCP slave gateway -> serial master -> serial slave
    Response: serial slave -> serial master ->TCP slave gateway -> TCP master

    The MODBUS traffic can simply be monitored using wireshark, where the
    MODBUS content is conveniently interpreted.

*/


/* =================================================================== */
/*                           include files                             */
/* =================================================================== */

#include "config.h"


#if defined USE_MODBUS
/* =================================================================== */
/*                          local definitions                          */
/* =================================================================== */

/* =================================================================== */
/*                      local structure definitions                    */
/* =================================================================== */

/* =================================================================== */
/*                 local function prototype declarations               */
/* =================================================================== */

static void          fnGetMODBUS_parameters(void);
static int           fnMODBUSPreFunction (int iType, MODBUS_RX_FUNCTION *modbus_rx_function);
static int           fnMODBUSPostFunction(int iType, MODBUS_RX_FUNCTION *modbus_rx_function);
static int           fnMODBUSuserFunction(int iType, MODBUS_RX_FUNCTION *modbus_rx_function);
#if defined USE_MODBUS_MASTER
    static void      fnModbusTest(unsigned char);
    static int       fnMODBUSmaster(int iType, MODBUS_RX_FUNCTION *modbus_rx_function);
#else
    #define fnMODBUSmaster 0
#endif

/* =================================================================== */
/*                             constants                               */
/* =================================================================== */


static const MODBUS_PARS cMODBUS_default = {
    MODBUS_PAR_BLOCK_VERSION,
#if defined MODBUS_SERIAL_INTERFACES && defined SERIAL_INTERFACE
    {
    #if MODBUS_SERIAL_INTERFACES > 1
        (0),                                                             // master address not relevant - serial port 0
    #endif
        (33),                                                            // slave address - serial port 1
    },
    {
    #if MODBUS_SERIAL_INTERFACES > 1
        (CHAR_8 | NO_PARITY | ONE_STOP | CHAR_MODE),                     // serial interface settings (ASCII mode uses 7 bits) - serial port 0
    #endif
        (CHAR_8 | NO_PARITY | ONE_STOP | CHAR_MODE/* | UART_SINGLE_WIRE_MODE*/), // serial interface settings (ASCII mode uses 7 bits) - serial port 1
    },
    {
    #if MODBUS_SERIAL_INTERFACES > 1
        SERIAL_BAUD_115200,                                              // baud rate of serial interface - serial port 0
    #endif
        SERIAL_BAUD_115200,                                              // baud rate of serial interface - serial port 1
    },
    {
    #if MODBUS_SERIAL_INTERFACES > 1
        (MODBUS_MODE_ASCII | MODBUS_SERIAL_MASTER | MODBUS_RS485_POSITIVE /*MODBUS_RS485_NEGATIVE*/),// default to RTU mode as master - serial port 0
    #endif
        (MODBUS_MODE_RTU | MODBUS_SERIAL_SLAVE | /*MODBUS_SERIAL_GATEWAY | */MODBUS_RS485_POSITIVE), // default to RTU mode as slave - serial port 1
    },
    #if defined MODBUS_GATE_WAY_QUEUE
    {
        #if MODBUS_SERIAL_INTERFACES > 1
        1024,                                                            // buffer size for queuing waiting messages on MODBUS serial port 0
        #endif
        0,                                                               // slave doesn't need queue MODBUS serial port 1
    },
    #endif
    #if defined MODBUS_ASCII
    {
        #if MODBUS_SERIAL_INTERFACES > 1
        (DELAY_LIMIT)(1 * SEC),                                          // inter-character delays greater than 1s are considered errors in ASCII mode - serial port 0
        #endif
        (DELAY_LIMIT)(2 * SEC),                                          // inter-character delays greater than 2s are considered errors in ASCII mode - serial port 1
    },
    {
        #if MODBUS_SERIAL_INTERFACES > 1
        0x0a,                                                            // ASCII mode line feed character - serial port 0
        #endif
        0x0a,                                                            // ASCII mode line feed character - serial port 1
    },
    #endif
    #if defined MODBUS_SUPPORT_SERIAL_LINE_FUNCTIONS && !defined NO_SLAVE_MODBUS_REPORT_SLAVE_ID
    { 'u', 'T', 'a', 's', 'k', 'e', 'r', '-', 'M', 'O', 'D', 'B', 'U', 'S', '-', 's', 'l','a','v','e' },
    #endif
#endif
#if defined MODBUS_TCP
    #if defined MODBUS_TCP_SERVERS                                       // slaves
    {
        (/*MODBUS_TCP_SERIAL_GATEWAY | */MODBUS_TCP_SLAVE_PORT),         // MODBUS listener mode - TCP slave port 2
        MODBUS_TCP_SLAVE_PORT,                                           // MODBUS listener mode - TCP slave port 3
    },
    {
        MODBUS_TCP_PORT,                                                 // MODBUS listener port number - standard - TCP slave port 2
        4321,                                                            // MODBUS listener port number - user defined - TCP slave port 3
    },
    {
        (2 * 60),                                                        // MODBUS listener port number - 2 minute TCP idle timeout - TCP slave port 2
        INFINITE_TIMEOUT,                                                // MODBUS listener port number - no TCP connection timeout - TCP slave port 3
    },
    #endif
#endif
};


#define DISCRETES_START       15                                         // start address
#define DISCRETES_END         43                                         // end address
#define DISCRETES_QUANTITY    ((DISCRETES_END - DISCRETES_START) + 1)    // quantity
static MODBUS_BITS_ELEMENT discretes[_MODBUS_BITS_ELEMENT_SIZE(DISCRETES_QUANTITY)] = {0};
static MODBUS_BITS test_discretes = { discretes, {DISCRETES_START, DISCRETES_END}};

#define COILS_START           10                                         // start address
#define COILS_END             39                                         // end address
#define COILS_QUANTITY        ((COILS_END - COILS_START) + 1)            // quantity
static MODBUS_BITS_ELEMENT coils[_MODBUS_BITS_ELEMENT_SIZE(COILS_QUANTITY)] = {0};
static MODBUS_BITS test_coils = { coils, {COILS_START, COILS_END}};

#define INPUT_REGS_START      24                                         // start address
#define INPUT_REGS_END        29                                         // end address
#define INPUT_REGS_QUANTITY   ((INPUT_REGS_END - INPUT_REGS_START) + 1)  // quantity
static unsigned short input_regs[INPUT_REGS_QUANTITY] = {0};
static MODBUS_REGISTERS test_input_regs = { input_regs, {INPUT_REGS_START, INPUT_REGS_END}};

#define HOLDING_REGS_START    2                                          // start address
#define HOLDING_REGS_END      6                                          // end address
#define HOLDING_REGS_QUANTITY ((HOLDING_REGS_END - HOLDING_REGS_START) + 1) // quantity
static unsigned short holding_regs[HOLDING_REGS_QUANTITY] = {0};
static MODBUS_REGISTERS test_holding_regs = { holding_regs, {HOLDING_REGS_START, HOLDING_REGS_END}};

static const MODBUS_CONFIG modbus_configuration = {
    &test_discretes,                                                     // read-only discrete input configuration
    &test_coils,                                                         // read/write coil configuration
    &test_input_regs,                                                    // read-only input registers
    &test_holding_regs,                                                  // read/write input registers
};

static const MODBUS_CALLBACKS modbus_slave_callbacks = {
    fnMODBUSPreFunction,
    fnMODBUSPostFunction,
    fnMODBUSuserFunction,
};


/* =================================================================== */
/*                     global variable definitions                     */
/* =================================================================== */

MODBUS_PARS *ptrMODBUS_pars = 0;                                         // these parameters need to be supplied by the MODBUS application


/* =================================================================== */
/*                      local variable definitions                     */
/* =================================================================== */




// Load either a temporary configuration, or committed configuration. If none found, load default settings
//
static void fnGetMODBUS_parameters(void)
{
    if (ptrMODBUS_pars == 0) {
        ptrMODBUS_pars = uMalloc(sizeof(MODBUS_PARS));
    }
#if defined USE_PARAMETER_BLOCK
    if (fnGetPar((PAR_MODBUS | TEMPORARY_PARAM_SET), (unsigned char *)ptrMODBUS_pars, sizeof(MODBUS_PARS)) < 0) {
        if ((fnGetPar(PAR_MODBUS, (unsigned char *)ptrMODBUS_pars, sizeof(MODBUS_PARS)) < 0) || (MODBUS_PAR_BLOCK_VERSION != ptrMODBUS_pars->ucModbusParVersion)) {
#endif
            uMemcpy(ptrMODBUS_pars, (unsigned char *)&cMODBUS_default, sizeof(MODBUS_PARS)); // no valid parameters available - set defaults
#if defined USE_PARAMETER_BLOCK
        }
    }
#endif
    uMemcpy(&temp_pars->modbus_parameters, ptrMODBUS_pars, sizeof(MODBUS_PARS)); // copy the working parameters to a temporary set
}

// This routine is called by the MODBUS interface prior to a read
// It can be used to update the MODBUS tables if required
//
static int fnMODBUSPreFunction(int iType, MODBUS_RX_FUNCTION *modbus_rx_function)
{
    unsigned short usAddress = modbus_rx_function->usElementAddress;
    switch (iType) {
    case PREPARE_COILS:                                                  // coils are being read - update table values if necessary
        usAddress -= COILS_START;                                        // reference to start of coil region
        coils[0] ^= 0xf0;                                                // toggle some bits on each request
        if (modbus_rx_function->ucSourceAddress != 33) {
            modbus_rx_function->ucMappedAddress = 33;                    // map the receive address to a new destination address
            return (MODBUS_APP_GATEWAY_FUNCTION - 0);                    // decide to gateway this to a different port - MODBUS port 0 in this case
        }
        break;
    case PREPARE_DISCRETE_INPUTS:                                        // discrete inputs are being read - update table values if necessary
        discretes[0] ^= 0xa3;                                            // toggle test discretes
        break;
    case PREPARE_HOLDING_REGISTERS:                                      // holding registers are being read - update table values if necessary
        holding_regs[0] += 0x0010;
        holding_regs[1] -= 0x0010;
        holding_regs[2] += 0x0100;
        holding_regs[3] -= 0x0100;
        break;
    case PREPARE_INPUT_REGISTERS:                                        // input registers are being read - update table values if necessary
        input_regs[0] += 1;
        input_regs[1] -= 1;
        input_regs[2] ^= 0xffff;
        input_regs[3] ^= 0xff00;
        input_regs[4] ^= 0x00ff;
        break;
    case PREPARE_FIFO_READ:                                              // return the FIFO queue length (maximum 31)
        return 2;                                                        // test 2 byte FIFO queue (return 0 if not supported)
    case DO_FIFO_READ:                                                   // return FIFO content - incrementing FIFO read position on each call
        {
            static unsigned short usFifoContent = 0;
            return usFifoContent++;
        }
#if defined MODBUS_SUPPORT_SERIAL_LINE_FUNCTIONS                         // serial line function support
    case GET_OPERATING_STATE:
        return MODBUS_DEVICE_RUNNING;                                    // always signal running state
    case GET_EXCEPTION_STATUS:                                           // return 8 bits of status information if desired
        return 0x53;
    case GET_STATUS_WORD:                                                // if a previous program command is still being processed this can be returned, for example, as 0xffff
        return 0x1234;                                                   // return a status short word
#endif
    }
    return 0;
}

// Handle special coil controls
//
static void fnSetCoil(unsigned short usAddress, unsigned char ucState)
{
    switch (usAddress) {
    case 14:                                                             // map this coil to a relay output
        if (ucState != 0) {
            SET_TEST_OUTPUT();
        }
        else {
            CLEAR_TEST_OUTPUT();
        }
        break;
    }
}

// This routine is called by the MODBUS interface after a write to the MODBUS table
// It can be used to respond to table changes
//
static int fnMODBUSPostFunction(int iType, MODBUS_RX_FUNCTION *modbus_rx_function)
{
    unsigned short usAddress = modbus_rx_function->usElementAddress;
    unsigned short usLength = modbus_rx_function->usElementLength;
    switch (iType) {
    case UPDATE_COILS:                                                   // coils have been altered - react to changes if necessary
        {
            MODBUS_BITS_ELEMENT coil_bit;
            MODBUS_BITS_ELEMENT coil_element;
            unsigned short usTableAddress = (usAddress - COILS_START);   // referenced to the coil table
            coil_bit = (0x01 << usTableAddress%MODBUS_BITS_ELEMENT_WIDTH); // the first coil bit
            coil_element = usTableAddress/MODBUS_BITS_ELEMENT_WIDTH;     // the first coil element location
            while (usLength != 0) {
                fnSetCoil(usAddress, (unsigned char)((coils[coil_element] & coil_bit) != 0));
                if (coil_bit & (0x1 << (MODBUS_BITS_ELEMENT_WIDTH - 1))) {
                    coil_bit = 0x01;
                    coil_element++;
                }
                else {
                    coil_bit <<= 1;
                }
                usAddress++;
                usLength--;
            }
        }
        break;
    case UPDATE__HOLDING_REGISTERS:                                      // holding registers have been altered - react to changes if necessary
        break;
    }
    return 0;
}

// The user will receive functions which are defined in the range START_USER_CODE_BLOCK to END_USER_CODE_BLOCK to handled completely here
// Return 0 if no error, or else the appropriate exception value
// Any responses must be sent here using the call fnMODBUS_transmit(ucBuff, usLength, MODBUS_MODE_RTU, 0);
//
static int fnMODBUSuserFunction(int iType, MODBUS_RX_FUNCTION *modbus_rx_function)
{
    switch (iType) {
    case USER_RECEIVING_ALL_MODBUS_DATA:
        if (modbus_rx_function->ucSourceAddress != 33) {
            modbus_rx_function->ucMappedAddress = 33;                    // map the receive address to a new destination address
            return (MODBUS_APP_GATEWAY_FUNCTION - 0);                    // decide to gateway this to a different port - MODBUS port 0 in this case
        }
        break;
    case USER_RECEIVING_ALL_MODBUS_TYPE:
        break;
    case USER_RECEIVING_MISSED_RANGE:
        break;
    case USER_RECEIVING_MODBUS_UNDEFINED_FUNCTION:
        break;
    }
    if (modbus_rx_function->ucFunctionCode == MODBUS_READ_COILS) {
        unsigned char ucTest[6];                                         // the message to be sent must have the address at the start and 2 additional bytes space at the end for a check sum to be added. Do not use const data!
        ucTest[0] = modbus_rx_function->ucSourceAddress;                 // our address
        ucTest[1] = MODBUS_READ_COILS;
        ucTest[2] = 1;                                                   // byte count
        ucTest[3] = 3;                                                   // value
        // Two byte required for CRC but are not set here - transmitted length includes CRC
        //
        fnMODBUS_transmit(modbus_rx_function, ucTest, (sizeof(ucTest))); // answer with pre-defined response
        return 0;
    }
    return MODBUS_EXCEPTION_ILLEGAL_FUNCTION;                            // unsupported function
}

#if defined MODBUS_TCP
// Gateway routing call-back
//
static int fnGateway(int iType, MODBUS_RX_FUNCTION *modbus_rx_function)
{
    if (iType == TCP_ROUTE_FROM_SLAVE) {                                 // TCP frame from MODBUS TCP slave has been received. We should route it to a TCP master
    return 0;
}
#endif

extern void fnInitModbus(void)
{
    unsigned char usModbusPort = 0;
    ptrMODBUS_pars = uMalloc(sizeof(MODBUS_PARS));                       // get memory for MODBUS parameters
    fnGetMODBUS_parameters();                                            // fill the working parameters from configuration settings


    fnInitialiseMODBUS_port(usModbusPort, &modbus_configuration, &modbus_slave_callbacks, fnMODBUSmaster); // port 1 - both serial interfaces use the same application tables, but could use separate ones if required

#if defined MODBUS_TCP
    fnInitialiseMODBUS_port(++usModbusPort, &modbus_configuration, &modbus_slave_callbacks, fnGateway); // initialise MODBUS tcp interface on first TCP port
    fnInitialiseMODBUS_port(++usModbusPort, 0, &modbus_slave_callbacks, 0); // initialise MODBUS tcp interface on second TCP port
#endif
}
#endif

 

0 项奖励
回复