Modbus::Frame
At the heart of EZModbus is the Modbus::Frame
structure - a representation of a Modbus message that serves as the common language between all components of the library. It is used internally by all the application components, but you will have to manipulate it directly to be able to send and receive Modbus messages using the Client
.
// The core data structure you'll work with
struct Frame {
Modbus::MsgType type; // REQUEST or RESPONSE
Modbus::FunctionCode fc; // Function code (READ_COILS, WRITE_REGISTER, etc.)
uint8_t slaveId; // Device ID (1-247, or 0 for broadcast)
uint16_t regAddress; // Starting register address
uint16_t regCount; // Number of registers/coils to read/write
std::array<uint16_t, 125> data; // Register values or coil states (packed)
Modbus::ExceptionCode exceptionCode; // Error code (if any) from the slave device
};
The memory footprint of a Frame
is fixed: 268 bytes. It is the size required to store the data of the largest request payload (on 125 registers or 2000 coils).
Definition
The Frame
structure is an enhanced representation of a raw Modbus message. It contains all the information needed to:
Create requests: Fill the fields to specify what you want to read or write
Send messages: Pass to
Client::sendRequest()
to transmit over the networkProcess responses: Examine the returned frame to extract values or check for errors
Handle exceptions: Check for Modbus protocol errors reported by slave devices
Think of a Frame
as a bidirectional envelope for your Modbus communication. For requests, you populate it with your command details. For responses, the library fills it with the returned data, ready for your application to use.
Two aspects will make your life easier when manipulating Frame
in Modbus client mode:
The library automatically “enriches” the response frame so it keeps all information from the initial request. That means
regAddress
,regCount
&data
will match your original request even if the Modbus spec doesn’t state so (depending on the case, some of these fields aren’t part of the raw Modbus message)A response which is an exception will keep the original function code intact in the
fc
field. TheexceptionCode
field will hold the error code (illegal data address, slave device failure…). No need to flip bits to check if a response is an exception! Just check the value of theexceptionCode
field: if it’s a regular response, it will beNULL_EXCEPTION (= 0)
.
The Frame
structure eliminates the need to understand low-level Modbus protocol details like PDUs, ADUs, coil bit packing or byte ordering - EZModbus handles all of that for you behind the scenes, and hands you a ready-to-use object filled with proper data once the response is received.
Reading/writing register data in Frame
In order to optimize memory usage without dynamic allocation, the data
field in Modbus::Frame
stores registers & coils in a unique array: coils are packed to occupy as little space as possible.
Register data is thus typically not accessed directly by reading or writing the data
array, but by using a simple API that will take care of storing & retrieving data in the correct format:
Writing frame data
Those methods will set the data
field of the Frame
structure from a value or set of values :
During Frame
initialization
Frame
initializationpackRegisters
: set frame data from a unique register value or list of registers valuesFrom a vector:
packRegisters(std::vector<uint16_t> regs)
From a buffer:
packRegisters(uint16_t* buf, size_t len)
In place :
packRegisters(std::initializer_list<uint16_t>)
packCoils
: set frame data from a unique coil state or list of coils statesFrom a vector:
packCoils(std::vector<bool> regs)
From a buffer:
packCoils(bool* buf, size_t len)
In place:
packCoils(std::initializer_list<bool>)
Out of convenience, packCoils
also include overloads for uint16_t
in addition to bool
, where any non-zero value will be considered as a true
state.
After Frame
initialization
Frame
initializationFrame::setRegisters
: set frame data & register count from a unique or list of registersFrame::setCoils
: set frame data & register count from a unique or list of coils
The types used for arguments are the same as the packXXX()
methods.
Examples
// From initializer list, in place
Modbus::Frame request = {
.type = Modbus::REQUEST,
.fc = Modbus::WRITE_REGISTER,
.slaveId = 1,
.regAddress = 100,
.regCount = 4,
.data = Modbus::packRegisters({100, 83, 94, 211})
};
// From a vector, in place
std::vector<bool> coils = {true, false, true, true, false};
Modbus::Frame request = {
.type = Modbus::REQUEST,
.fc = Modbus::WRITE_COILS,
.slaveId = 1,
.regAddress = 20,
.regCount = coils.size(),
.data = Modbus::packCoils(coils)
};
// From a buffer, after init
uint16_t values[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};
Modbus::Frame request;
request.setRegisters(values, (sizeof(values) / sizeof(values[0])));
...
Reading frame data
Those methods will recover the data from a struct, either for a specific register or all of them:
Reading a specific register
Frame::getRegister(size_t idx)
: returns auint16_t
with the register value at indexidx
Frame::getCoil(size_t idx)
: returns abool
with the coil state at indexidx
Fetching all registers
Frame::getRegisters()
: returns all registers valuesTo a vector:
Frame::getRegisters()
returns astd::vector<uint16_t>
To a buffer:
Frame::getRegisters(uint16_t* buf, size_t len)
writes registers values into the provided buffer & returns asize_t
with the number of registers actually fetched
Frame::getCoils()
: returns all coil valuesTo a vector:
Frame::getCoils()
returns astd::vector<bool>
To a buffer:
Frame::getCoils(bool* buf, size_t len)
writes coil states into the provided buffer & returns asize_t
with the number of coils actually fetched
Examples
// REGISTERS EXAMPLES
Modbus::Frame registerResponse;
// Get register value at index 4
// (returns 0 if the index is out of bounds -> make sure the index is valid!)
uint16_t regValue = registerResponse.getRegister(4);
// Copy data into a vector
// (returns an empty vector if regCount is 0)
std::vector<uint16_t> regValues = registerResponse.getRegisters();
// Copy data into a buffer
// (returns the number of elements actually copied)
uint16_t buffer[10];
size_t copied = registerResponse.getRegisters(buffer, (sizeof(buffer) / sizeof(buffer[0])));
// COILS EXAMPLES
Modbus::Frame coilResponse;
// Get coil value at index 2
// (returns false if the index is out of bounds -> make sure the index is valid!)
bool coilValue = coilResponse.getCoil(2);
// Copy data into a vector
// (returns an empty vector if regCount is 0)
std::vector<bool> coilValues = coilResponse.getCoils();
// Copy data into a buffer
// (returns the number of elements actually copied)
bool buffer[10];
size_t copied = coilResponse.getCoils(buffer, (sizeof(buffer) / sizeof(buffer[0])));
Unit conversion
EZModbus offers in the ModbusCodec
namespace, two conversion functions to convert a float
from/to a pair of registers encoded in IEEE 754 format. It follows the "big-endian word, little-endian byte" (3-2-1-0) convention.
Modbus::Frame frame; // The request/response frame
float floatVal; // Your source/target float value
// Encode a float to Modbus registers & set request frame data
uint16_t buffer[2];
ModbusCodec::floatToRegisters(floatVal, buffer);
frame.packCoils(buffer, 2);
// Extract values from response frame & parse float value
uint16_t buffer[2];
size_t copied = frame.getCoils(buffer, 2);
if (copied == 2) floatVal = ModbusCodec::registersToFloat(buffer);
Last updated