liblightmodbus 3.0
A lightweight, header-only, hardware-agnostic Modbus RTU/TCP library
Loading...
Searching...
No Matches
Slave device

In order to use slave side functions, make sure to define LIGHTMODBUS_SLAVE macro and enable necessary functions (e.g. LIGHTMODBUS_F03S) before including the library. Please also see Building and integrating liblightmodbus for more information.

Slave initialization

Slave device state is represented by ModbusSlave structure. It must be initialized with modbusSlaveInit() before it is used:

&slave,
myRegisterCallback, // Callback for register operations
myExceptionCallback, // Callback for handling slave exceptions (optional)
modbusDefaultAllocator, // Memory allocator for allocating responses
modbusSlaveDefaultFunctions, // Set of supported functions
modbusSlaveDefaultFunctionCount // Number of supported functions
);
// Check for errors
assert(modbusIsOk(err) && "modbusSlaveInit() failed");
static uint8_t modbusIsOk(ModbusErrorInfo err)
Checks if ModbusErrorInfo contains an error.
Definition base.h:442
ModbusError modbusDefaultAllocator(ModbusBuffer *buffer, uint16_t size, void *context)
The default memory allocator based on realloc()
Definition base.impl.h:21
ModbusSlaveFunctionHandler modbusSlaveDefaultFunctions[]
Associates function IDs with pointers to functions responsible for parsing. Length of this array is s...
Definition slave.impl.h:18
const uint8_t modbusSlaveDefaultFunctionCount
Stores length of modbusSlaveDefaultFunctions.
Definition slave.impl.h:63
ModbusErrorInfo modbusSlaveInit(ModbusSlave *status, ModbusRegisterCallback registerCallback, ModbusSlaveExceptionCallback exceptionCallback, ModbusAllocator allocator, const ModbusSlaveFunctionHandler *functions, uint8_t functionCount)
Initializes slave device.
Definition slave.impl.h:79
Richer error represenation - source and type of error.
Definition base.h:126
Slave device status.
Definition slave.h:90

ModbusSlave contains an internal ModbusBuffer for the response data. This buffer relies on the allocator passed to modbusSlaveInit() as an argument. modbusDefaultAllocator is a default allocator that uses malloc() and free() functions. You may write your own more sophisticated allocators if you wish - please refer to Custom allocators for more details.

In this code myRegisterCallback is a name of a callback function that will be called for every register operation. In previous this versions of the library this functionality was optionally available as LIGHTMODBUS_REGISTER_CALLBACK. Now, all register operations are performed using callbacks. If you're sure that none of the used parsing functions use the callback, you can pass NULL as the argument. Importantly, all default parsing functions in the library require the callback to be set.

myExceptionCallback is a name of a callback function that will be called every time slave reports an exception to the master. To disable this functionality, pass NULL as the callback.

The set of functions supported by the slave is determined by the 5th function argument - in this case modbusSlaveDefaultFunctions. This argument is a pointer to an array of ModbusSlaveFunctionHandler structures which associate function codes with callbacks. Lifetime of this array must not be shorter than the lifetime of the slave. The length of this array should be provided as the last argument.

Register callback

The register callback is a function matching ModbusRegisterCallback used by the library to operate on register values. The register callback must respond to four types of register queries (ModbusRegisterQuery) described further in this section.

The library guarantees that before each register is accessed a suitable access query will be made for it beforehand. This mechanism can guarantee that registers outside of valid range will never be accessed and allows the user to implement read/write protection for registers. However, all access queries are performed before the first R/W query, so creating cross-dependencies between registers (i.e. access rights to one register depend on the value of other register) will cause problems. It's best to disable "write multiple X" functions on such registers.

Note
Tip: You can use the user pointer to access your register values from the callback without making them global. Use modbusSlaveGetUserPointer() and modbusSlaveSetUserPointer() to pass a pointer to your data to the callback.

R/W access check queries

ModbusRegisterCallbackArgs::query value of either MODBUS_REGQ_R_CHECK or MODBUS_REGQ_W_CHECK indicates that an access query is being made to the callback function. When ModbusRegisterCallbackArgs::query is MODBUS_REGQ_R_CHECK, read access is requested. MODBUS_REGQ_W_CHECK means that write access is requested.

The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type. The index of the register is stored in ModbusRegisterCallbackArgs::index and the ID of the function requesting access is available in ModbusRegisterCallbackArgs::function.

The value stored in ModbusRegisterCallbackArgs::value during a write access check contains the value of the register that will be written in a subsequent write request. In case of read requests the value stored in this variable may not be used.

When responding to an access query, the function must return a value via ModbusRegisterCallbackResult::exceptionCode in the result output argument. This value is treated as the exception code that the slave will report to the master. In order to grant access to a certain register, MODBUS_EXCEP_NONE must be returned.

The return value of the register callback is checked by the library during the access check phase. Returning a value other than MODBUS_OK results in the slave reporting a MODBUS_EXCEP_SLAVE_FAILURE exception to the master.

Note
This functionality should not be relied upon and may be subject to change in future versions of the library. For your own safety, please always return MODBUS_OK from the callback and use the result->exceptionCode to report exceptions.

Read queries

Read query is a request to read contents of some register. During such query ModbusRegisterCallbackArgs::query is set to MODBUS_REGQ_R. Read query is always preceded with a MODBUS_REGQ_R_CHECK query.

The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type. The index of the register is stored in ModbusRegisterCallbackArgs::index and the ID of the function reading the register is available in ModbusRegisterCallbackArgs::function. ModbusRegisterCallbackArgs::value is unused.

The callback function must return the contents of the requested register via ModbusRegisterCallbackResult::value in the result output argument.

The return value from the callback is ignored.

Note
This functionality should not be relied upon and may be subject to change in future versions of the library. For your own safety, please always return MODBUS_OK from the callback.

Write queries

Write query is a request to change contents of some register to given value. During such query ModbusRegisterCallbackArgs::query is set to MODBUS_REGQ_W. Write query is always preceded with a MODBUS_REGQ_W_CHECK query.

The type of the accessed register is provided to the function in ModbusRegisterCallbackArgs::type. The index of the register is stored in ModbusRegisterCallbackArgs::index and the ID of the function writing the register is available in ModbusRegisterCallbackArgs::function. The new value for the register is stored in ModbusRegisterCallbackArgs::value.

Both value returned through the result argument and the return value from the function are ignored.

Note
This functionality should not be relied upon and may be subject to change in future versions of the library. For your own safety, please always return MODBUS_OK from the callback and do not modify the result output argument during write queries.

Summary

The table below represents a brief summary of different queries should be handled.

Query type args->value result->exceptionCode result->value Return value
MODBUS_REGQ_R_CHECK Undefined Must be set to exception code to be reported. MODBUS_EXCEP_NONE otherwise. Ignored Returning value different than MODBUS_OK results in MODBUS_EXCEP_SLAVE_FAILURE being reported by the slave. Do not rely on this.
MODBUS_REGQ_W_CHECK Value that will be written in the subsequent write request Must be set to exception code to be reported. MODBUS_EXCEP_NONE otherwise. Ignored Returning value different than MODBUS_OK results in MODBUS_EXCEP_SLAVE_FAILURE being reported by the slave. Do not rely on this.
MODBUS_REGQ_R Undefined Ignored Must be set to the value of the register Ignored
MODBUS_REGQ_W New value for the register Ignored Ignored Ignored

Example

An example implementation of a register callback operating on registers stored in an array:

#define REG_COUNT 16
static uint16_t registers[REG_COUNT];
static uint16_t inputRegisters[REG_COUNT];
static uint8_t coils[REG_COUNT / 8]; // Each coil corresponds to one bit
static uint8_t discreteInputs[REG_COUNT / 8]; // Each input corresponds to one bit
ModbusError myRegisterCallback(
const ModbusSlave *status,
{
switch (args->query)
{
// R/W access check
// If result->exceptionCode of a read/write access query is not MODBUS_EXCEP_NONE,
// an exception is reported by the slave. If result->exceptionCode is not set,
// the behavior is undefined.
result->exceptionCode = args->id < REG_COUNT ? MODBUS_EXCEP_NONE : MODBUS_EXCEP_ILLEGAL_ADDRESS;
break;
// Read register
switch (args->type)
{
case MODBUS_HOLDING_REGISTER: result->value = registers[args->id]; break;
case MODBUS_INPUT_REGISTER: result->value = inputRegsiters[args->id]; break;
case MODBUS_COIL: result->value = modbusMaskRead(coils, args->id); break;
case MODBUS_DISCRETE_INPUT: result->value = modbusMaskRead(discreteInputs, args->id); break;
}
break;
// Write register
switch (args->type)
{
case MODBUS_HOLDING_REGISTER: registers[args->id] = args->value; break;
case MODBUS_COIL: modbusMaskWrite(coils, args->id, args->value); break;
default: break;
}
break;
}
// Always return MODBUS_OK
return MODBUS_OK;
}
@ MODBUS_EXCEP_ILLEGAL_ADDRESS
Illegal data address.
Definition base.h:233
@ MODBUS_EXCEP_NONE
Definition base.h:231
@ MODBUS_DISCRETE_INPUT
Discrete input.
Definition base.h:248
@ MODBUS_HOLDING_REGISTER
Holding register.
Definition base.h:245
@ MODBUS_INPUT_REGISTER
Input register.
Definition base.h:246
@ MODBUS_COIL
Coil.
Definition base.h:247
ModbusError
Represtents different kinds of errors.
Definition base.h:137
@ MODBUS_OK
No error.
Definition base.h:143
static void modbusMaskWrite(uint8_t *mask, uint16_t n, uint8_t value)
Writes n-th bit in an array.
Definition base.h:344
static uint8_t modbusMaskRead(const uint8_t *mask, uint16_t n)
Reads n-th bit from an array.
Definition base.h:333
@ MODBUS_REGQ_W_CHECK
Request for write access.
Definition slave.h:39
@ MODBUS_REGQ_W
Write request.
Definition slave.h:41
@ MODBUS_REGQ_R_CHECK
Request for read access.
Definition slave.h:38
@ MODBUS_REGQ_R
Read request.
Definition slave.h:40
Contains arguments for the register callback function.
Definition slave.h:48
uint16_t value
Value of the register.
Definition slave.h:52
ModbusDataType type
Type of accessed data.
Definition slave.h:49
ModbusRegisterQuery query
Type of request made to the register.
Definition slave.h:50
Contains values returned by the slave register callback.
Definition slave.h:60
uint16_t value
Register/coil value.
Definition slave.h:62
ModbusExceptionCode exceptionCode
Exception to be reported.
Definition slave.h:61

Slave exception callback

Exception callback is a function matching ModbusSlaveExceptionCallback called when the slave reports an exception. This callback is called even if the response frame is not sent to the master (e.g. when the exception is caused by a broadcasted request in Modbus RTU). This is an internal way of signalizing slave failure.

When called, the callback is provided with pointer to ModbusSlave, function that reported the excepion and a ModbusExceptionCode.

The exception callback must return ModbusError, but the return value is ignored. Preferably, it should return MODBUS_OK.

Note
This functionality should not be relied upon and may be subject to change in future versions of the library. For your own safety, please always return MODBUS_OK from this callback.

An example of a simple callback, printing out exception information:

/*
Exception callback for printing out exceptions
*/
ModbusError slaveExceptionCallback(
const ModbusSlave *slave,
uint8_t function,
{
printf(
"Slave reports an exception %s (function %d)\n",
function);
// Always return MODBUS_OK
return MODBUS_OK;
}
ModbusExceptionCode
Represents a Modbus exception code.
Definition base.h:230
const char * modbusExceptionCodeStr(ModbusExceptionCode code)
Returns a string containing the name of the ModbusExceptionCode enum value.
Definition debug.impl.h:67

Request processing

After successful initialization of the slave device, it's ready to accept requests from the master. The requests can be processed using one of the three functions: modbusParseRequestPDU(), modbusParseRequestRTU() and modbusParseRequestTCP().

Calling each one of these results in an attempt to parse the request frame, a series of calls to the Register callback, an optional call to slave-exception callback and a response frame being generated for the master device.

The response frame can be accessed using modbusSlaveGetResponse() and its length can be acquired using modbusSlaveGetResponseLength(). These functions may not be called if modbusIsOk() for the returned ModbusErrorInfo is false.

Note
In case the request is a Modbus RTU broadcast message, the response is still generated, but then discarded. Please ensure that you correctly handle modbusSlaveGetResponseLength() return value of 0. While it may seem sub-optimal, it significantly simplifies the structure of parsing functions. Moreover, in case of read requests, it ensures that appropriate register read queries will always be made to the register callback function allowing the user to reliably act upon these events. Similarly, if the request triggers an exception, the Slave exception callback is called, regardless of whether a response frame was produced. This is an internal way of signalizing a failure to comply.
#define SLAVE_ADDRESS 7
// For Modbus RTU
err = modbusParseRequestRTU(&slave, SLAVE_ADDRESS, buffer, length);
// Handle 'serious' errors such as memory allocation problems
handleSlaveError(err);
// Handle errors related to the structure of the request frame
// i.e. frames meant for other slaves, invalid CRC etc.
// Usually, though, you want to simply ignore these.
handleRequestErrors(err);
// If the function did not return an error, the response can be accessed and is ready to be sent
// to the master. The response frame can be acquired using modbusSlaveGetResponse()
// and is modbusSlaveGetResponseLength() bytes long. Beware that response frame can be empty
// in some cases!
if (modbusIsOk(err))
sendToMaster(modbusSlaveGetResponse(&slave), modbusSlaveGetResponseLength(&slave));
// Optionally, the response buffer can be freed now
static ModbusError modbusGetRequestError(ModbusErrorInfo err)
Returns request error from ModbusErrorInfo.
Definition base.h:462
static ModbusError modbusGetGeneralError(ModbusErrorInfo err)
Returns general error from ModbusErrorInfo.
Definition base.h:452
ModbusErrorInfo modbusParseRequestRTU(ModbusSlave *status, uint8_t slaveAddress, const uint8_t *request, uint16_t requestLength)
Parses provided Modbus RTU request frame and generates a Modbus RTU response.
Definition slave.impl.h:299
static void modbusSlaveFreeResponse(ModbusSlave *status)
Frees memory allocated for slave's response frame.
Definition slave.h:192
static uint16_t modbusSlaveGetResponseLength(const ModbusSlave *status)
Returns the length of the response generated by the slave.
Definition slave.h:158
static const uint8_t * modbusSlaveGetResponse(const ModbusSlave *status)
Returns a pointer to the response generated by the slave.
Definition slave.h:147

Slave cleanup

In order to destroy the ModbusSlave structure, simply call modbusSlaveDestroy():

void modbusSlaveDestroy(ModbusSlave *status)
Frees memory allocated in the ModbusSlave struct.
Definition slave.impl.h:99