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
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