404
+ +Page not found
+ + +diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..085b854 --- /dev/null +++ b/404.html @@ -0,0 +1,137 @@ + + +
+ + + + +Page not found
+ + +This section provides a full reference of all the functions, macros and types that MicroTBX-Modbus offers.
+Macro | +Description | +
---|---|
TBX_MB_VERSION_MAIN |
+Main version number of MicroTBX-Modbus. | +
TBX_MB_VERSION_MINOR |
+Minor version number of MicroTBX-Modbus. | +
TBX_MB_VERSION_PATCH |
+Patch number of MicroTBX-Modbus. | +
Function codes.
+Macro | +Description | +
---|---|
TBX_MB_FC01_READ_COILS |
+Modbus function code 01 - Read Coils. | +
TBX_MB_FC02_READ_DISCRETE_INPUTS |
+Modbus function code 02 - Read Discrete Inputs. | +
TBX_MB_FC03_READ_HOLDING_REGISTERS |
+Modbus function code 03 - Read Holding Registers. | +
TBX_MB_FC04_READ_INPUT_REGISTERS |
+Modbus function code 04 - Read Input Registers. | +
TBX_MB_FC05_WRITE_SINGLE_COIL |
+Modbus function code 05 - Write Single Coil. | +
TBX_MB_FC06_WRITE_SINGLE_REGISTER |
+Modbus function code 06 - Write Single Register. | +
TBX_MB_FC08_DIAGNOSTICS |
+Modbus function code 08 - Diagnostics. | +
TBX_MB_FC15_WRITE_MULTIPLE_COILS |
+Modbus function code 15 - Write Multiple Coils. | +
TBX_MB_FC16_WRITE_MULTIPLE_REGISTERS |
+Modbus function code 16 - Write Multiple Registers. | +
Exception codes.
+Macro | +Description | +
---|---|
TBX_MB_EC01_ILLEGAL_FUNCTION |
+Modbus exception code 01 - Illegal function. | +
TBX_MB_EC02_ILLEGAL_DATA_ADDRESS |
+Modbus exception code 02 - Illegal data address. | +
TBX_MB_EC03_ILLEGAL_DATA_VALUE |
+Modbus exception code 03 - Illegal data value. | +
TBX_MB_EC04_SERVER_DEVICE_FAILURE |
+Modbus exception code 04 - Server device failure. | +
Diagnostics sub function codes.
+Macro | +Description | +
---|---|
TBX_MB_DIAG_SC_QUERY_DATA |
+Diagnostics sub-function code - Return Query Data. | +
TBX_MB_DIAG_SC_CLEAR_COUNTERS |
+Diagnostics sub-function code - Clear Counters. | +
TBX_MB_DIAG_SC_BUS_MESSAGE_COUNT |
+Diagnostics sub-function code - Return Bus Message Count. |
+
TBX_MB_DIAG_SC_BUS_COMM_ERROR_COUNT |
+Diagnostics sub-function code - Return Bus Communication Error Count. |
+
TBX_MB_DIAG_SC_BUS_EXCEPTION_ERROR_COUNT |
+Diagnostics sub-function code - Return Bus Exception Error Count. |
+
TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT |
+Diagnostics sub-function code - Return Server Message Count. |
+
TBX_MB_DIAG_SC_SERVER_NO_RESPONSE_COUNT |
+Diagnostics sub-function code - Return Server No Response Count. |
+
Miscellaneous.
+Macro | +Description | +
---|---|
TBX_MB_FC_EXCEPTION_MASK |
+Bit mask to OR to the function code to flag it as an exception response. | +
Node address.
+Macro | +Description | +
---|---|
TBX_MB_TP_NODE_ADDR_BROADCAST |
+Node address value for broadcast purposes. | +
TBX_MB_TP_NODE_ADDR_MIN |
+Minimum value of a valid node address. | +
TBX_MB_TP_NODE_ADDR_MAX |
+Maximum value of a valid node address. | +
Protocol data unit (PDU).
+Macro | +Description | +
---|---|
TBX_MB_TP_PDU_CODE_LEN_MAX |
+Maximum size of the "Function code" at the start of a PDU. | +
TBX_MB_TP_PDU_DATA_LEN_MAX |
+Maximum number of data bytes inside a PDU. This excludes the function code. |
+
TBX_MB_TP_PDU_MAX_LEN |
+Maximum length of a PDU. | +
typedef void * tTbxMbServer
+
+Handle to a Modbus server channel object, in the format of an opaque pointer.
+typedef enum
+{
+ TBX_MB_SERVER_OK = 0U,
+ TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR,
+ TBX_MB_SERVER_ERR_DEVICE_FAILURE
+} tTbxMbServerResult
+
+numerated type with all supported return values for the callbacks.
+typedef tTbxMbServerResult (* tTbxMbServerReadInput)(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t * value)
+
+Modbus server callback function for reading a discrete input.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Pointer to write the value of the input to. Use TBX_ON if the input is on, TBX_OFF otherwise. |
+
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef tTbxMbServerResult (* tTbxMbServerReadCoil)(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t * value)
+
+Modbus server callback function for reading a coil.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Pointer to write the value of the coil to. Use TBX_ON if the coils is on, TBX_OFF otherwise. |
+
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef tTbxMbServerResult (* tTbxMbServerWriteCoil)(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t value)
+
+Modbus server callback function for writing a coil.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Coil value. Use TBX_ON to activate the coil, TBX_OFF otherwise. |
+
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef tTbxMbServerResult (* tTbxMbServerReadInputReg)(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t * value)
+
+Modbus server callback function for reading an input register.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Pointer to write the value of the input register to. | +
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef tTbxMbServerResult (* tTbxMbServerReadHoldingReg)(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t * value)
+
+Modbus server callback function for reading a holding register.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Pointer to write the value of the holding register to. | +
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef tTbxMbServerResult (* tTbxMbServerWriteHoldingReg)(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t value)
+
+Modbus server callback function for writing a holding register.
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
addr |
+Element address (0 ..65535 ). |
+
value |
+Value of the holding register. | +
Return value | +
---|
TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data elementaddress is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. |
+
typedef uint8_t (* tTbxMbServerCustomFunction)(tTbxMbServer channel,
+ uint8_t const * rxPdu,
+ uint8_t * txPdu,
+ uint8_t * len)
+
+Modbus server callback function for implementing custom function code handling. Thanks to this functionality, the user can support Modbus function codes that are either currently not supported or user defined extensions.
+The rxPdu
and txPdu
parameters are pointers to the byte array of the PDU. The first byte (i.e. rxPdu[0]
) contains the function code, followed by its data bytes. Upon calling the callback, the len
parameter contains the length of rxPdu
. When preparing the response, you can write the length of the txPdu
response to len
as well.
Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object that triggered the callback. | +
rxPdu |
+Pointer to a byte array for reading the received PDU. | +
txPdu |
+Pointer to a byte array for writing the response PDU. | +
len |
+Pointer to the PDU length, including the function code. | +
Return value | +
---|
TBX_TRUE if the callback function handled the received function code and prepared a response PDU.TBX_FALSE otherwise. |
+
typedef void * tTbxMbClient
+
+Handle to a Modbus client channel object, in the format of an opaque pointer.
+typedef void * tTbxMbTp
+
+Handle to a Modbus transport layer object, in the format of an opaque pointer.
+typedef enum
+{
+ TBX_MB_UART_PORT1 = 0U,
+ TBX_MB_UART_PORT2,
+ TBX_MB_UART_PORT3,
+ TBX_MB_UART_PORT4,
+ TBX_MB_UART_PORT5,
+ TBX_MB_UART_PORT6,
+ TBX_MB_UART_PORT7,
+ TBX_MB_UART_PORT8,
+ TBX_MB_UART_NUM_PORT
+} tTbxMbUartPort
+
+Enumerated type with all supported UART ports.
+typedef enum
+{
+ TBX_MB_UART_1200BPS = 0U,
+ TBX_MB_UART_2400BPS,
+ TBX_MB_UART_4800BPS,
+ TBX_MB_UART_9600BPS,
+ TBX_MB_UART_19200BPS,
+ TBX_MB_UART_38400BPS,
+ TBX_MB_UART_57600BPS,
+ TBX_MB_UART_115200BPS,
+ TBX_MB_UART_NUM_BAUDRATE
+} tTbxMbUartBaudrate
+
+Enumerated type with all supported UART baudrates.
+typedef enum
+{
+ TBX_MB_UART_7_DATABITS = 0U,
+ TBX_MB_UART_8_DATABITS,
+ TBX_MB_UART_NUM_DATABITS
+} tTbxMbUartDatabits
+
+Enumerated type with all supported UART data bits modes.
+typedef enum
+{
+ TBX_MB_UART_1_STOPBITS = 0U,
+ TBX_MB_UART_2_STOPBITS,
+ TBX_MB_UART_NUM_STOPBITS
+} tTbxMbUartStopbits
+
+Enumerated type with all supported parity modes.
+typedef enum
+{
+ TBX_MB_ODD_PARITY = 0U,
+ TBX_MB_EVEN_PARITY,
+ TBX_MB_NO_PARITY,
+ TBX_MB_UART_NUM_PARITY
+} tTbxMbUartParity
+
+Enumerated type with all supported parity modes.
+tTbxMbServer TbxMbServerCreate(tTbxMbTp transport)
+
+Creates a Modbus server channel object and assigns the specified Modbus transport layer to the channel for packet transmission and reception.
+This example creates a Modbus RTU server channel object for a node with address 10
:
/* Construct a Modbus RTU transport layer object. */
+tTbxMbTp modbusTp = TbxMbRtuCreate(10U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+/* Construct a Modbus server object. */
+tTbxMbServer modbusServer = TbxMbServerCreate(modbusTp);
+
+Parameter | +Description | +
---|---|
transport |
+Handle to a previously created Modbus transport layer object to assign to the channel. | +
Return value | +
---|
Handle to the newly created Modbus server channel object if successful, NULL otherwise. |
+
void TbxMbServerFree(tTbxMbServer channel)
+
+Releases a Modbus server channel object, previously created with TbxMbServerCreate().
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object to release. | +
void TbxMbServerSetCallbackReadInput(tTbxMbServer channel,
+ tTbxMbServerReadInput callback)
+
+Registers the callback function that this server calls, whenever a client requests the reading of a specific discrete input.
+The example connects the state of two digital inputs to the Modbus discrete inputs at addresses 10000
to 10001
:
tTbxMbServerResult AppReadInput(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t * value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_OK;
+
+ /* Filter on the requested discrete input address. */
+ switch (addr)
+ {
+ case 10000U:
+ *value = BspDigitalIn(BSP_DIGITAL_IN1);
+ break;
+
+ case 10001U:
+ *value = BspDigitalIn(BSP_DIGITAL_IN2);
+ break;
+
+ default:
+ /* Unsupported discrete input address. */
+ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+ break;
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for reading the Modbus discrete inputs. */
+TbxMbServerSetCallbackReadInput(modbusServer, AppReadInput);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackReadCoil(tTbxMbServer channel,
+ tTbxMbServerReadCoil callback)
+
+Registers the callback function that this server calls, whenever a client requests the reading of a specific coil.
+The example assumes the application stores the state of two coils in an array with name appCoils[]
. Whenever a client requests the reading of the Modbus coils at addresses 0
to 1
, the currently stored values in the appCoils[]
array are returned:
uint8_t appCoils[2] = { TBX_ON, TBX_OFF };
+
+tTbxMbServerResult AppReadCoil(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t * value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+
+ /* Supported coil address? */
+ if (addr <= 1U)
+ {
+ /* Store the current coil state. */
+ *value = appCoils[addr];
+ result = TBX_MB_SERVER_OK;
+ }
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for reading the Modbus coils. */
+TbxMbServerSetCallbackReadCoil(modbusServer, AppReadCoil);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackWriteCoil(tTbxMbServer channel,
+ tTbxMbServerWriteCoil callback)
+
+Registers the callback function that this server calls, whenever a client requests the writing of a specific coil.
+The example connects the Modbus coil addresses 0
to 1
to the state of two digital outputs:
tTbxMbServerResult AppWriteCoil(tTbxMbServer channel,
+ uint16_t addr,
+ uint8_t value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_OK;
+
+ /* Filter on the requested coil address. */
+ switch (addr)
+ {
+ case 0U:
+ BspDigitalOut(BSP_DIGITAL_OUT1, value);
+ break;
+
+ case 1U:
+ BspDigitalOut(BSP_DIGITAL_OUT2, value);
+ break;
+
+ default:
+ /* Unsupported coil address. */
+ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+ break;
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for writing the Modbus coils. */
+TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackReadInputReg(tTbxMbServer channel,
+ tTbxMbServerReadInputReg callback)
+
+Registers the callback function that this server calls, whenever a client requests the reading of a specific input register.
+The example connects the state of two analog inputs to the Modbus input registers at addresses 30000
to 30001
:
tTbxMbServerResult AppReadInputReg(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t * value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_OK;
+
+ /* Filter on the requested input register address. */
+ switch (addr)
+ {
+ case 30000U:
+ *value = BspAnalogIn(BSP_ANALOG_IN1);
+ break;
+
+ case 30001U:
+ *value = BspAnalogIn(BSP_ANALOG_IN2);
+ break;
+
+ default:
+ /* Unsupported input register address. */
+ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+ break;
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for reading the Modbus input registers. */
+TbxMbServerSetCallbackReadInputReg(modbusServer, AppReadInputReg);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackReadHoldingReg(tTbxMbServer channel,
+ tTbxMbServerReadHoldingReg callback)
+
+Registers the callback function that this server calls, whenever a client requests the reading of a specific holding register.
+The example assumes the application stores the state of two holding registers in an array with name appHoldingRegs[]
. Whenever a client requests the reading of the Modbus holding registers at addresses 40000
to 40001
, the currently stored values in the appHoldingRegs[]
array are returned:
uint16_t appHoldingRegs[2] = { 1234, 5678 };
+
+tTbxMbServerResult AppReadHoldingReg(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t * value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+
+ /* Supported holding register address? */
+ if ( (addr >= 40000) && (addr <= 40001U) )
+ {
+ /* Store the holding register state. */
+ *value = appHoldingReg[addr - 40000U];
+ result = TBX_MB_SERVER_OK;
+ }
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for reading the Modbus holding registers. */
+TbxMbServerSetCallbackReadHoldingReg(modbusServer, AppReadHoldingReg);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackWriteHoldingReg(tTbxMbServer channel,
+ tTbxMbServerWriteHoldingReg callback)
+
+Registers the callback function that this server calls, whenever a client requests the writing of a specific holding register.
+The example connects the Modbus holding registers addresses 40000
to 40001
to two 8-bit PWM output signals:
tTbxMbServerResult AppWriteHoldingReg(tTbxMbServer channel,
+ uint16_t addr,
+ uint16_t value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_OK;
+
+ /* Filter on the requested holding register address. */
+ switch (addr)
+ {
+ case 40000U:
+ /* PWM supports 8-bit duty cycle. */
+ if (value <= 255U)
+ {
+ BspPwmOut(BSP_PWM_OUT1, (uint8_t)value);
+ }
+ else
+ {
+ result = TBX_MB_SERVER_ERR_DEVICE_FAILURE;
+ }
+ break;
+
+ case 40001U:
+ /* PWM supports 8-bit duty cycle. */
+ if (value <= 255U)
+ {
+ BspPwmOut(BSP_PWM_OUT2, (uint8_t)value);
+ }
+ else
+ {
+ result = TBX_MB_SERVER_ERR_DEVICE_FAILURE;
+ }
+ break;
+
+ default:
+ /* Unsupported holding register address. */
+ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+ break;
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for writing the Modbus holding registers. */
+TbxMbServerSetCallbackWriteHoldingReg(modbusServer, AppWriteHoldingReg);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
void TbxMbServerSetCallbackCustomFunction (tTbxMbServer channel,
+ tTbxMbServerCustomFunction callback)
+
+Registers the callback function that this server calls, whenever it received a PDU containing a function code not currently supported. With the aid of this callback function the user can implement support for new function codes.
+The example shows how to add support for function code 17 (Report Server ID). It's the counter-part to the example for TbxMbClientCustomFunction(). According to the Modbus protocol, the response to the Report Server ID request is device specific. The device implementation decides the number of bytes for the Server ID and if additional data is added to the response. The following code snippet implements support for Report Server ID, where the actual server ID is 16-bits and the response contains no additional data:
+uint8_t AppReportServerIdCallback(tTbxMbServer channel,
+ uint8_t const * rxPdu,
+ uint8_t * txPdu,
+ uint8_t * len)
+{
+ uint8_t result = TBX_FALSE;
+
+ /* Function code 17 - Report Server ID? */
+ if (rxPdu[0] == 17U)
+ {
+ /* Check the expected request length. */
+ if (*len == 1U)
+ {
+ /* Prepare the response. */
+ txPdu[0] = 17U; /* Function code. */
+ txPdu[1] = 3U; /* Byte count. */
+ TbxMbCommonStoreUInt16BE(0x1234U, &txPdu[2]); /* server ID. */
+ txPdu[4] = 0xFFU; /* Run indicator status = ON. */
+ *len = 5U;
+ /* Function code handled. */
+ result = TBX_TRUE;
+ }
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Set the callback for handling custom function codes. */
+TbxMbServerSetCallbackCustomFunction(modbusServer, AppReportServerIdCallback);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus server channel object. | +
callback |
+Pointer to the callback function. | +
tTbxMbClient TbxMbClientCreate(tTbxMbTp transport,
+ uint16_t responseTimeout,
+ uint16_t turnaroundDelay)
+
+Creates a Modbus client channel object and assigns the specified Modbus transport layer to the channel for packet transmission and reception.
+This example creates a Modbus RTU client channel object. Note the the nodeAddr
parameter of function TbxMbRtuCreate() is not applicable when used on a client and should simply be set to a value of 0
:
/* Construct a Modbus RTU transport layer object. */
+tTbxMbTp modbusTp = TbxMbRtuCreate(0U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+/* Construct a Modbus client object. */
+tTbxMbClient modbusClient = TbxMbClientCreate(modbusTp, 1000U, 100U);
+
+Parameter | +Description | +
---|---|
transport |
+Handle to a previously created Modbus transport layer object to assign to the channel. |
+
responseTimeout |
+Maximum time in milliseconds to wait for a response from the Modbus server, after sending a PDU. |
+
turnaroundDelay |
+Delay time in milliseconds after sending a broadcast PDU to give all recipients sufficient time to process the PDU. |
+
Return value | +
---|
Handle to the newly created Modbus client channel object if successful, NULL otherwise. |
+
void TbxMbClientFree(tTbxMbClient channel)
+
+Releases a Modbus client channel object, previously created with TbxMbClientCreate().
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel object to release. | +
uint8_t TbxMbClientReadCoils(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint16_t num,
+ uint8_t * coils)
+
+Reads the coil(s) from the server with the specified node address.
+The example reads the state of two coils at Modbus addresses 0
to 1
, from a Modbus server with node address 10
:
uint8_t coils[2] = { 0 };
+
+TbxMbClientReadCoils(modbusClient, 10U, 0U, 2U, coils);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the coil read operation. | +
num |
+Number of elements to read from the coils data table. Range can be 1 ..2000 . |
+
coils |
+Pointer to array with TBX_ON / TBX_OFF values where the coil state will be written to. |
+
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientReadInputs(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint16_t num,
+ uint8_t * inputs)
+
+Reads the discrete input(s) from the server with the specified node address.
+The example reads the state of two discrete inputs at Modbus addresses 10000
to 10001
, from a Modbus server with node address 10
:
uint8_t inputs[2] = { 0 };
+
+TbxMbClientReadInputs(modbusClient, 10U, 10000U, 2U, inputs);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the discrete input read operation. |
+
num |
+Number of elements to read from the discrete inputs data table. Range can be 1 ..2000 . |
+
inputs |
+Pointer to array with TBX_ON / TBX_OFF values where the discrete input state will bewritten to. |
+
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientReadInputRegs(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint8_t num,
+ uint16_t * inputRegs)
+
+Reads the input register(s) from the server with the specified node address.
+The example reads two input registers at Modbus addresses 30000
to 30001
, from a Modbus server with node address 10
:
uint16_t inputRegs[2] = { 0 };
+
+TbxMbClientReadInputRegs(modbusClient, 10U, 30000U, 2U, inputRegs);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the input register read operation. |
+
num |
+Number of elements to read from the input registers data table. Range can be 1 ..125 . |
+
inputRegs |
+Pointer to array where the input register values will be written to. | +
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientReadHoldingRegs(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint8_t num,
+ uint16_t * holdingRegs)
+
+Reads the holding register(s) from the server with the specified node address.
+The example reads two holding registers at Modbus addresses 40000
to 40001
, from a Modbus server with node address 10
:
uint16_t holdingRegs[2] = { 0 };
+
+TbxMbClientReadHoldingRegs(modbusClient, 10U, 40000U, 2U, holdingRegs);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the holding register read operation. |
+
num |
+Number of elements to read from the holding registers data table. Range can be1 ..125 . |
+
holdingRegs |
+Pointer to array where the holding register values will be written to. | +
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientWriteCoils(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint16_t num,
+ uint8_t const * coils)
+
+Writes the coil(s) to the server with the specified node address.
+The example writes the state of two coils at Modbus addresses 0
to 1
, to a Modbus server with node address 10
:
uint8_t coils[2] = { TBX_OFF, TBX_OFF };
+
+TbxMbClientWriteCoils(modbusClient, 10U, 0U, 2U, coils);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the coil write operation. | +
num |
+Number of elements to write to the coils data table. Range can be 1 ..1968 . |
+
coils |
+Pointer to array with the desired TBX_ON / TBX_OFF coil values. |
+
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientWriteHoldingRegs(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t addr,
+ uint8_t num,
+ uint16_t const * holdingRegs)
+
+Writes the holding register(s) to the server with the specified node address.
+The example writes two holding registers at Modbus addresses 40000
to 40001
, to a Modbus server with node address 10
:
uint16_t holdingRegs[2] = { 63U, 127U };
+
+TbxMbClientWriteHoldingRegs(modbusClient, 10U, 40000U, 2U, holdingRegs);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
addr |
+Starting element address (0..65535) in the Modbus data table for the holding register write operation. |
+
num |
+Number of elements to write to the holding registers data table. Range can be1 ..123 . |
+
holdingRegs |
+Pointer to array with the desired holding register values. | +
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientDiagnostics(tTbxMbClient channel,
+ uint8_t node,
+ uint16_t subcode,
+ uint16_t * count)
+
+Perform diagnostic operation on the server for checking the communication system.
+The example obtains the number of packets with a correct CRC, received by a Modbus server with node address 10
:
uint16_t count = 0U;
+
+TbxMbClientDiagnostics(modbusClient, 10U, TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT, &count);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
subcode |
+Sub-function code for specifying the diagnostic operation to perform. Currently supported values: - TBX_MB_DIAG_SC_QUERY_DATA - TBX_MB_DIAG_SC_CLEAR_COUNTERS - TBX_MB_DIAG_SC_BUS_MESSAGE_COUNT - TBX_MB_DIAG_SC_BUS_COMM_ERROR_COUNT - TBX_MB_DIAG_SC_BUS_EXCEPTION_ERROR_COUNT - TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT - TBX_MB_DIAG_SC_SERVER_NO_RESPONSE_COUNT |
+
count |
+Location where the retrieved count value will be written to. Only applicable for the sub-function codes that end with _COUNT . |
+
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
uint8_t TbxMbClientCustomFunction(tTbxMbClient channel,
+ uint8_t node,
+ uint8_t const * txPdu,
+ uint8_t * rxPdu,
+ uint8_t * len)
+
+Send a custom function code PDU to the server and receive its response PDU. Thanks to this functionality, the user can support Modbus function codes that are either currently not supported or user defined extensions.
+The txPdu
and rxPdu
parameters are pointers to the byte array of the PDU. The first byte (i.e. txPdu[0]
) contains the function code, followed by its data bytes. When calling this function, set the len
parameter to the length of the txPdu
. This function updates the len
parameter with the length of the received PDU, which it stores in rxPdu
.
The example shows how to add support for function code 17 (Report Server ID). It's the counter-part to the example for TbxMbServerSetCallbackCustomFunction(). According to the Modbus protocol, the response to the Report Server ID request is device specific. The device implementation decides the number of bytes for the Server ID and if additional data is added to the response. The following code snippet implements support for Report Server ID, where it reads out the 16-bit server ID of a Modbus server with node address 10
:
uint16_t AppReportServerId(tTbxMbClient channel,
+ uint8_t node)
+{
+ /* static to lower stack load. */
+ static uint8_t response[TBX_MB_TP_PDU_MAX_LEN];
+ uint8_t request[1] = { 17U };
+ uint8_t len = 1U;
+ uint16_t result = 0U;
+
+ /* Transceive function code 17 - Report Server ID. */
+ if (TbxMbClientCustomFunction(channel, node, request,
+ response, &len) == TBX_OK)
+ {
+ /* Response length as expected? */
+ if (len == 5U)
+ {
+ /* Not an exception response and byte count correct? */
+ if ((response[0] == 17U) && (response[1] == 3U))
+ {
+ /* Read out the received server ID. */
+ result = TbxMbCommonExtractUInt16BE(&response[2]);
+ }
+ }
+ }
+
+ /* Give the result back to the caller. */
+ return result;
+}
+
+/* Read the server ID. */
+uint16_t serverId = AppReportServerId(modbusClient, 10U);
+
+Parameter | +Description | +
---|---|
channel |
+Handle to the Modbus client channel for the requested operation. | +
node |
+The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . |
+
txPdu |
+Pointer to a byte array with the PDU to transmit. | +
rxPdu |
+Pointer to a byte array with the received response PDU. | +
len |
+Pointer to the PDU length, including the function code. | +
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
void TbxMbEventTask(void)
+
+Task function that drives the entire Modbus stack. It processes internally generated events. How to call this function depends on the selected operating system abstraction layer (OSAL), which you determine based on the source/osal/tbxmb_XXX.c
source file you compile and link with your firmware.
In a traditional superloop application (tbxmb_superloop.c
), call this function continuously in the infinite program loop:
#include <microtbx.h>
+#include <microtbxmodbus.h>
+
+void main(void)
+{
+ /* TODO Initialize the clock, enable peripherals and configure GPIO pins. */
+ /* TODO Construct a Modbus transport layer object. */
+ /* TODO Construct a Modbus client or server object. */
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEventTask();
+ }
+}
+
+When using an RTOS (e.g. tbxmb_freertos.c
), create a new task during application initialization and call this function from this task's infinite loop:
#include <microtbx.h>
+#include <microtbxmodbus.h>
+#include "FreeRTOS.h"
+#include "task.h"
+
+void AppModbusTask(void * pvParameters);
+
+void main(void)
+{
+ /* TODO Initialize the clock, enable peripherals and configure GPIO pins. */
+ /* TODO Construct a Modbus transport layer object. */
+ /* TODO Construct a Modbus client or server object. */
+
+ /* Create the Modbus task. */
+ xTaskCreate(AppModbusTask, "ModbusTask", configMINIMAL_STACK_SIZE, NULL, 4U, NULL);
+ /* Start the RTOS scheduler. Note that this function does not return. */
+ vTaskStartScheduler();
+}
+
+void AppModbusTask(void * pvParameters)
+{
+ /* Enter infinite task loop. */
+ for (;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEventTask();
+ }
+}
+
+There is one exception: When using a traditional super application in combination with just a Modbus client. In this case you can omit the call to this task function. With this combination, the communication with a Modbus server happens in a blocking manner and the event task is automatically called internally, while blocking. Convenient and easy, but not optimal from a run-time performance. For this reason it is recommended to use an RTOS in combination with a Modbus client.
+uint16_t TbxMbCommonExtractUInt16BE(uint8_t const * data)
+
+Helper function to extract an unsigned 16-bit value from the data of a Modbus packet, where 16-bit values are always stored in the big endian format.
+Parameter | +Description | +
---|---|
data |
+Pointer to the byte array that holds the two bytes to extract, stored in the big endian format. |
+
Return value | +
---|
The 16-bit unsigned integer value. | +
void TbxMbCommonStoreUInt16BE(uint16_t value,
+ uint8_t * data)
+
+Helper function to store an unsigned 16-bit value in the data of a Modbus packet, where 16-bit values are always stored in the big endian format.
+Parameter | +Description | +
---|---|
value |
+The unsigned 16-bit value to store. | +
data |
+Pointer to the byte array where to store the value in the big endian format. | +
tTbxMbTp TbxMbRtuCreate(uint8_t nodeAddr,
+ tTbxMbUartPort port,
+ tTbxMbUartBaudrate baudrate,
+ tTbxMbUartStopbits stopbits,
+ tTbxMbUartParity parity)
+
+Creates a Modbus RTU transport layer object, which can later on be linked to a Modbus client or server channel.
+Example for the following communication settings:
+tTbxMbTp modbusTp = TbxMbRtuCreate(10U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+
+Parameter | +Description | +
---|---|
nodeAddr |
+The address of the node. Can be in the range 1 ..247 for a server node. Set it to 0 fora client. |
+
port |
+The serial port to use. The actual meaning of the serial port is hardware dependent. It typically maps to the UART peripheral number. E.g. TBX_MB_UART_PORT1 = USART1 onan STM32. |
+
baudrate |
+The desired communication speed. | +
stopbits |
+Number of stop bits at the end of a character. | +
parity |
+Parity bit type to use. | +
Return value | +
---|
Handle to the newly created RTU transport layer object if successful, NULL otherwise. |
+
void TbxMbRtuFree(tTbxMbTp transport)
+
+Releases a Modbus RTU transport layer object, previously created with TbxMbRtuCreate().
+Parameter | +Description | +
---|---|
transport |
+Handle to RTU transport layer object to release. | +
void TbxMbUartTransmitComplete(tTbxMbUartPort port)
+
+Event function to signal to the UART module that the entire transfer, initiated by TbxMbPortUartTransmit
, completed. This function should be called by the hardware specific UART port (located in tbxmb_port.c
) at TX interrupt level.
Parameter | +Description | +
---|---|
port |
+The serial port that the transfer completed on. | +
void TbxMbUartDataReceived(tTbxMbUartPort port,
+ uint8_t const * data,
+ uint8_t len)
+
+Event function to signal the reception of new data to the UART module. This function should be called by the hardware specific UART port (located in tbxmb_port.c
) at RX interrupt level.
Parameter | +Description | +
---|---|
port |
+The serial port that the transfer completed on. | +
data |
+Byte array with newly received data. | +
len |
+Number of newly received bytes. | +
No exact statistics on C++ usage for embedded systems is available. However, the rough estimation is that somewhere between at least 20% and 50% of all embedded software projects make use of C++. Unfortunately, most embedded components and libraries do not take this into consideration.
+This is where MicroTBX-Modbus differs: Its C API was carefully crafted, such that easy-to-use C++ wrappers can encompass its entire functionality. In fact, it's actually easier to code with MicroTBX-Modbus in C++, compared to C. This section presents how to use the included C++ wrappers.
+The following illustration presents the UML class diagrams of the C++ wrappers:
+ +To add the C++ wrappers to your software project, complete the following steps:
+source/extra/cplusplus/
directory to your project..cpp
files are compiled and linked during a build..hpp
files to your compiler's include search path.Alternatively, when using CMake to manager your project's build system, add microtbx-modbus-extra-cpp
to its target_link_libraries()
list.
Add the following lines to each source-file, where you intend to make use of MicroTBX-Modbus:
+#include <microtbx.h>
+#include <microtbxmodbus.hpp>
+
+Similar to the getting started instructions, we'll take an empty C++ embedded software application as a starting point:
+#include "board.hpp"
+
+void main(void)
+{
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ Board::Init();
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+
+ }
+}
+
+We'll create a Modbus server step-by-step with the following properties:
+Create a new class, with a name of your choosing, which derives from TbxMbServerRtu
. For example AppModbusServer
and located in a header file called appmodbusserver.hpp
. In the constructor's initializer list, call the base class constructor to specify the RTU specific properties:
#include <microtbx.h>
+#include <microtbxmodbus.hpp>
+
+class AppModbusServer : public TbxMbServerRtu
+{
+public:
+ AppModbusServer()
+ : TbxMbServerRtu(0x0A, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY) { }
+ virtual ~AppModbusServer() { }
+};
+
+As a next step, we'll override method writeCoil
and implement it such that this Modbus server changes the state of an LED, whenever it receives a coil write request at address 0
:
#include <microtbx.h>
+#include <microtbxmodbus.hpp>
+
+class AppModbusServer : public TbxMbServerRtu
+{
+public:
+ AppModbusServer()
+ : TbxMbServerRtu(0x0A, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY) { }
+ virtual ~AppModbusServer() { }
+
+ tTbxMbServerResult writeCoil(uint16_t addr, bool value) override
+ {
+ tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+
+ /* Request to write the coil at address 0? */
+ if (addr == 0U)
+ {
+ if (value == TBX_ON)
+ {
+ Board::LedOn();
+ }
+ else
+ {
+ Board::LedOff();
+ }
+ result = TBX_MB_SERVER_OK;
+ }
+ return result;
+ }
+};
+
+That's all there is to developing a Modbus server with the MicroTBX-Modbus C++ wrappers. To actually use this newly created class, create an instance of it and call the event task in the infinite program loop:
+#include "board.hpp"
+#include "appmodbusserver.hpp"
+
+void main(void)
+{
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ Board::Init();
+
+ /* Create Modbus server instance. */
+ AppModbusServer modbusServer;
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEvent::task();
+ }
+}
+
+We'll build an application, which implements a Modbus client. It'll behave as the counter part to the Modbus server application. You could take the same approach, were you create a new class, which derives from TbxMbClientRtu
. However, since this class does not contain any overridable methods, we can also just directly create a new instance of it:
TbxMbClientRtu modbusClient(1000U, 100U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+
+With the help of method writeCoils
, we can request the Modbus server at node address 10
to turn on its LED, located at coil address 0
:
uint8_t coils[1] = { TBX_ON };
+
+modbusClient.writeCoils(10U, 0U, 1U, coils);
+
+Here follows the example application with all of this implemented, for completion purposes:
+#include <microtbx.h>
+#include <microtbxmodbus.hpp>
+#include "board.hpp"
+
+void main(void)
+{
+ uint8_t coils[1] = { TBX_ON };
+
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ Board::Init();
+
+ /* Create Modbus client instance. */
+ TbxMbClientRtu modbusClient(1000U, 100U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+
+ /* Turn on one coil at address 0 on the server with node address 10. */
+ modbusClient.writeCoils(10U, 0U, 1U, coils);
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+
+ }
+}
+
+Note that for a Modbus client that uses a superloop OSAL, there is no need to call TbxMbEvent::task()
. The methods that communicate with the server block until the transmission completes and a response is received (if applicable). The event task is called internally while blocking.
Convenient and easy, but not optimal from a run-time performance perspective. For this reason, it is recommended to use an RTOS on the Modbus client, instead of a superloop type application. In the case of an RTOS, it is necessary to call TbxMbEvent::task()
in a separate task that drives the Modbus stack.
If you're new to MicroTBX-Modbus, one of the first questions will be: How I do setup a Modbus server with it? The goal of this section is to answer exactly that question. As a starting point we'll take an empty embedded software application:
+#include "board.h"
+
+void main(void)
+{
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ BoardInit();
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+
+ }
+}
+
+We'll create a Modbus server step-by-step with the following properties:
+The first step is always the construction of a transport layer object. It's the object that handles the actual transmission and reception of communication packets:
+/* Construct a Modbus RTU transport layer object. */
+tTbxMbTp modbusTp = TbxMbRtuCreate(10U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+
+
+With the transport layer object created, we continue with constructing a server channel object and attaching the transport layer object to it:
+/* Construct a Modbus server object. */
+tTbxMbServer modbusServer = TbxMbServerCreate(modbusTp);
+
+An event task function drives the MicroTBX-Modbus stack. We just need to continuously call it in the program's infinite superloop:
+/* Continuously call the Modbus stack event task function. */
+TbxMbEventTask();
+
+Our example Modbus server should enable a Modbus client to change the state of an LED, whenever it receives a coil write request. For this we'll implement a callback function, with a name of our choosing, and then register this callback function for coil write requests:
+tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_OK;
+
+ /* Request to write the coil at address 0? */
+ if (addr == 0U)
+ {
+ if (value == TBX_ON)
+ {
+ BoardLedOn();
+ }
+ else
+ {
+ BoardLedOff();
+ }
+ }
+ /* Unsupported coil address. */
+ else
+ {
+ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+ }
+ return result;
+}
+
+/* Set the callback for accessing the coils in the Modbus data table. */
+TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);
+
+Assembling it all together into our initial empty application results in this:
+#include <microtbx.h>
+#include <microtbxmodbus.h>
+#include "board.h"
+
+tTbxMbTp modbusTp;
+tTbxMbServer modbusServer;
+
+tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value);
+
+void main(void)
+{
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ BoardInit();
+ /* Construct a Modbus RTU transport layer object. */
+ modbusTp = TbxMbRtuCreate(10, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+ /* Construct a Modbus server object. */
+ modbusServer = TbxMbServerCreate(modbusTp);
+ /* Set the callback for accessing the coils in the Modbus data table. */
+ TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);
+
+ /* Enter the program's infinite loop. */
+ for(;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEventTask();
+ }
+}
+
+tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+
+ /* Request to write the coil at address 0? */
+ if (addr == 0U)
+ {
+ if (value == TBX_ON)
+ {
+ BoardLedOn();
+ }
+ else
+ {
+ BoardLedOff();
+ }
+ result = TBX_MB_SERVER_OK;
+ }
+ return result;
+}
+
+And voilà, you now have a fully functional Modbus server. You can extend it by adding support for:
+The process is the same: You implement the callback function and then register it with the channel using a TbxMbServerSetCallbackXxx()
API function. Refer to the API reference for more details.
The previous example assumed a traditional superloop type application. Thanks to the ever increasing processing power and available RAM and ROM memory on modern microcontrollers, the use of a real-time operating system (RTOS) is more common.
+For this reason, MicroTBX-Modbus ships with an operating system abstraction layer (OSAL). In this section, we'll upgrade the previous superloop example to use FreeRTOS instead of a traditional superloop.
+As a first step, re-configure your project to compile and link the correct OSAL source file:
+source/osal/tbxmb_superloop.c
from your project.source/osal/tbxmb_freertos.c
to your project.In case you use CMake to manage your project's build system, update its target_link_libraries()
:
microtbx-modbus-osal-superloop
.microtbx-modbus-osal-freertos
.Instead of continuously calling TbxMbEventTask()
in the superloop, create a new RTOS task and call TbxMbEventTask()
in the task's infinite loop. You can assign it a priority of your liking that fits your application. Note that the MicroTBX-Modbus FreeRTOS OSAL source file automatically places the RTOS task in the waiting state, when no events are pending:
void AppModbusTask(void * pvParameters)
+{
+ /* Enter infinite task loop. */
+ for (;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEventTask();
+ }
+}
+
+/* Create the Modbus task. */
+xTaskCreate(AppModbusTask, "ModbusTask", configMINIMAL_STACK_SIZE, NULL, 4U, NULL);
+
+Here follows to previous example application, upgraded for FreeRTOS:
+#include <microtbx.h>
+#include <microtbxmodbus.h>
+#include "board.h"
+#include "FreeRTOS.h"
+#include "task.h"
+
+tTbxMbTp modbusTp;
+tTbxMbServer modbusServer;
+
+void AppModbusTask(void * pvParameters);
+tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value);
+
+void main(void)
+{
+ /* Initialize the clock, enable peripherals and configure GPIO pins. */
+ BoardInit();
+ /* Construct a Modbus RTU transport layer object. */
+ modbusTp = TbxMbRtuCreate(10, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
+ TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);
+ /* Construct a Modbus server object. */
+ modbusServer = TbxMbServerCreate(modbusTp);
+ /* Set the callback for accessing the coils in the Modbus data table. */
+ TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);
+ /* Create the Modbus task. */
+ xTaskCreate(AppModbusTask, "ModbusTask", configMINIMAL_STACK_SIZE, NULL, 4U, NULL);
+ /* Start the RTOS scheduler. Note that this function does not return. */
+ vTaskStartScheduler();
+}
+
+void AppModbusTask(void * pvParameters)
+{
+ /* Enter infinite task loop. */
+ for (;;)
+ {
+ /* Continuously call the Modbus stack event task function. */
+ TbxMbEventTask();
+ }
+}
+
+tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
+{
+ tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
+
+ /* Request to write the coil at address 0? */
+ if (addr == 0U)
+ {
+ if (value == TBX_ON)
+ {
+ BoardLedOn();
+ }
+ else
+ {
+ BoardLedOff();
+ }
+ result = TBX_MB_SERVER_OK;
+ }
+ return result;
+}
+
+After reading through this getting started section, you now have a basic understanding of how to set up a Modbus server. For ready-to-run examples, refer to the demo programs in the separate repository. It also includes example on how to set up a Modbus client, instead of a server:
+ +For more in-depth details on the API functions offered by MicroTBX-Modbus, head over to the API reference in this user manual. When your itchy to start adding MicroTBX-Modbus to your own embedded software program, continue with the integration section of this user manual.
+ +MicroTBX-Modbus is a modern Modbus communication stack, targeting microcontroller based embedded systems. Its aim is to be: easy to use, easy to port, high quality, well maintained, stable and flexible. The ideal solution for any embedded software engineer, with an interest in adding Modbus communication to their product.
+MicroTBX-Modbus currently supports the following Modbus function codes:
+Function Code | +Name | +
---|---|
1 | +Read Coils | +
2 | +Read Discrete Inputs | +
3 | +Read Holding Registers | +
4 | +Read Input Registers | +
5 | +Write Single Coil | +
6 | +Write Single Register | +
8 | +Diagnostics (sub codes: 0, 10, 11, 12, 13, 14, 15) | +
15 | +Write Multiple Coils | +
16 | +Write Multiple Registers | +
Note that MicroTBX-Modbus includes functionality, enabling you to extend it by adding support for additional and custom function codes.
+With Modbus being such a convenient and mature communication protocol, several other Modbus software stacks exist; Both closed sourced and open sourced. Why bother with developing and maintaining yet another one? It turns out that most of the existing ones all seem to be limiting on at least one front:
+MicroTBX-Modbus addresses all these limitations. Thanks to the flexible dual licensing model, you can start out right away with the open source GPLv3 version. Perfect for testing, evaluation and prototyping purposes. Once you're satisfied with it and would like to include MicroTBX-Modbus in your proprietary closed sourced product, you can move on to the commercial license.
+The only reason not to use MicroTBX-Modbus is that it currently only supports Modbus RTU communication. Note though that support for ASCII and TCP communication is planned for the future.
+With MicroTBX-Modbus being a modern communication stack, the focus is more on ease and flexibility of use for the developer, and less on keeping the ROM footprint low. Therefore the recommended system requirements are slightly higher than comparative Modbus software solutions:
+It is recommended to use a microcontroller with at least 32 KiB flash and 4 KiB RAM. However, it will run on a basic 8-bit microcontroller with just 10 KiB of flash and 1.5 KiB RAM. Although you then run out of storage space quickly, when adding your own firmware’s functionality.
+The getting started section of this user manual shows you how to quickly setup a Modbus server and client. Definitely worth a glance if you're new to MicroTBX-Modbus.
+For those who want to see MicroTBX-Modbus in action, you can find ready-to-run demo programs located in a separate repository. These demo programs target an ever growing collection of popular microcontroller evaluation boards and serve as a good starting point:
+ +If you're ready to integrate MicroTBX-Modbus into your own embedded software project, head over to the integration section of this user manual for detailed instructions.
+MicroTBX-Modbus itself is hardware independent. To handle the hardware specifics of your microcontroller, you just need to implement a few port functions. A template source-file is provided. You can find detailed instructions in the portation section of this user manual.
+Once you got everything up-and-running and would like to use MicroTBX-Modbus in the closed source proprietary firmware of your product, make sure to upgrade to the commercial license. The default GPLv3 licensed version is not suitable for that use case.
+ +To make the MicroTBX-Modbus functionality available to your embedded software project, you need to integrate its source code into your project. This section of the user manual walks you through this process step-by-step. It covers two integration approaches:
+Classical integration, where you add the source files manually to your project and configure your build environment accordingly.
+CMake integration, where you leverage the power of CMake to do the heavy-lifting.
+As a reference, you can look at the separate repository with MicroTBX-Modbus demo programs. It includes demo programs for different microcontroller boards that are preconfigured and already have MicroTBX-Modbus fully integrated.
+Since MicroTBX-Modbus builds upon the MicroTBX base component, make sure you already integrated the MicroTBX base component into your embedded software project. You can find the MicroTBX integration instructions in the MicroTBX user manual.
+Adding MicroTBX-Modbus to your software project is a simple five step process:
+source
directory to your project.source/template/tbxmb_port.c
port template source file to your project.source/osal/tbxmb_XXX.c
for your selected operating system to your project..c
files are compiled and linked during a build..h
files to your compiler's include search path.The use of CMake to manage the build environment rapidly gains popularity among embedded software developers. It makes adding third-party libraries, such as MicroTBX-Modbus, a breeze:
+add_subdirectory()
to register the MicroTBX-Modbus interface library. source/template/tbxmb_port.c
port template source file to your project and add it as a source file to add_executable()
. microtbx-modbus
interface library to target_link_libraries()
. microtbx-modbus-osal-XXX
interface library for your selected operating system to target_link_libraries()
. Minimal CMakeLists.txt
example, if you copied MicroTBX-Modbus to directory third_party/microtbx-modbus
:
project(MyProject)
+
+add_subdirectory(third_party/microtbx-modbus)
+
+add_executable(MyProject
+ main.c
+ tbxmb_port.c
+)
+
+target_link_libraries(MyProject
+ microtbx-modbus
+ microtbx-modbus-osal-superloop
+)
+
+The MicroTBX-Modbus source code itself is fully hardware independent. The tbxmb_port.c
port source file implements the hardware specifics. This means that you only need to update this source file, to get MicroTBX-Modbus working on your specific microcontroller system. You can find detailed instructions, on how to port MicroTBX-Modbus to your platform, in the portation section of this user manual.
Add the following lines to each source-file, where you intend to make use of MicroTBX-Modbus:
+#include <microtbx.h>
+#include <microtbxmodbus.h>
+
+
+ By default, MicroTBX-Modbus is licensed under version 3 of the GNU GPL (GPLv3). Thanks to the GPLv3, MicroTBX-Modbus can be released with full source code and is perfect for these use cases:
+The GPLv3 licensed version of MicroTBX-Modbus is not suitable for these use cases:
+To circumvent the restrictions and responsibilities that come with the GPLv3, your company can purchase a commercially licensed version of MicroTBX-Modbus. With your commercially licensed version of MicroTBX-Modbus, you can include and make use of this software in your closed source proprietary firmware.
+Refer to the following license comparison matrix to decide on the suitable MicroTBX-Modbus license for your product:
+Question | +GNU GPL version 3 | +Commercial license | +
---|---|---|
Is MicroTBX-Modbus free? | +yes | +no | +
Do I have the right to change the MicroTBX-Modbus source code? |
+yes | +yes | +
Can I use MicroTBX-Modbus in my closed source product? |
+no | +yes | +
Do I have to open my source code? | +yes | +no | +
Do I have to open source my changes to MicroTBX-Modbus? |
+yes | +no | +
Do I have to offer the MicroTBX-Modbus source code to users of my product? |
+yes | +no | +
Do I have to document that my product uses MicroTBX-Modbus? |
+yes | +no | +
Can I redistribute MicroTBX-Modbus in source code format? |
+yes | +no | +
Can I receive professional technical support on a commercial basis? |
+no | +yes | +
The development and maintenance, needed to make available a stable, high quality and open source embedded software component, takes significant engineering time and effort. From experience with the OpenBLT bootloader, Feaser learned that relying solely on donations is unfortunately not a viable and sustainable option. The dual licensing model offers the best of both worlds, making it a win-win scenario for all its users:
+To purchase the commercial license, contact Feaser to request a quote. Based on the quote, you can generate and e-mail us your purchase order. You can expect to receive an order confirmation within one business days. Afterwards, Feaser starts working on putting together your commercially licensed MicroTBX-Modbus software package, which will be delivered to you electronically.
+What happens if I do include the GPLv3 version of MicroTBX-Modbus in my own software?
+As long as you do not distribute your software to someone else, nothing really happens. However, the moment you either give or sell your software or a product containing your software, you are required to open source the source code of your entire software. The GPLv3 is infectious; any code that uses GPLv3 software, automatically becomes GPLv3 as well.
+Are there any differences between the GPLv3 and commercially licensed versions of MicroTBX-Modbus?
+The only changes are the license text in the license file and the license information in the source files. The actual API and functionality of MicroTBX-Modbus is exactly the same.
+What are the restrictions of the MicroTBX-Modbus commercial license?
+The only real restriction of the commercial license is that you cannot redistribute your commercially licensed version of MicroTBX-Modbus to third parties in source code format (including your customers and users). Binary format (object-code or executable) is of course allowed. Feel free to contact Feaser to request a sample of the commercial license for you to review.
+Do I need to pay additional royalties?
+The commercial license is a one-time fee. Once you purchased it, your company can include MicroTBX-Modbus in all its products, without having to pay per-unit royalties.
+How can I obtain pricing information for the commercial license for MicroTBX-Modbus?
+E-mail Feaser a quote request and we'll send you a quote, which includes pricing information.
+What do I receive after purchasing the MicroTBX-Modbus commercial license?
+After reception of your purchase order, you'll receive an order confirmation typically within one business day. Afterwards we'll prepare your commercially licensed version of MicroTBX-Modbus and deliver it to you electronically. Estimated delivery time is within a few days.
+How can I pay for the MicroTBX-Modbus commercial license?
+Invoicing takes place after delivery of your commercially licensed version of MicroTBX-Modbus. Payment can be made via direct bank transfer or online by credit card. Detailed payment information is included on the invoice.
+ +Static code analysis was performed to verify compliance with MISRA-C 2012. This document lists the compliance exceptions:
+Directive | +Type | +Rationale | +
---|---|---|
2.5 | +advisory | +Especially in reusable modules or peripheral drivers, macro definitions can remain unused in the module or driver itself, but should be kept for the end-user. For example version macros and configuration options. |
+
11.5 | +advisory | +Conversions from pointer to void to pointer to other type. This is needed after allocating memory from the heap and then initializing a pointer to point to this allocated memory. Used for example when allocating memory to build a linked list. |
+
MicroTBX-Modbus comes with a build-in hardware abstraction layer. Essentially, this means that you only need to adjust the implementation of a few functions, to get the communication stack working on your specific microcontroller system. You can find a framework for all these functions at this location:
+source/template/tbxmb_port.c
Furthermore, the demo programs, located in a separate repository, already include ready-made ports:
+ +When tasked with getting MicroTBX-Modbus running on your specific microcontroller system, follow these steps:
+tbxmb_port.c
and make whatever little tweaks needed to adjust it for your microcontroller system. Chances are that it already works, without making any changes.tbxmb_port.c
file from the source/template/
directory and use it as a starting point. Check the source code comments that lead with TODO ##Port
. They contain hints about what you need to implement. The remainder of this section explains in more detail how to implement the port functions. Note that you can also outsource this effort to Feaser.
+MicroTBX-Modbus needs a time reference. For example to monitor the RTU communication's 1.5 and 3.5 character times. To get these timings right a free running counter, incrementing every 50 microseconds, provides a time reference.
+uint16_t TbxMbPortTimerCount(void)
+
+This function obtains the current value of this counter and assumes that you already initialized a timer, during application initialization, to have its free running counter counting upwards at a 20 kHz frequency.
+In contrast to most other Modbus communication stacks, MicroTBX-Modbus does not rely on an interrupt driven timer. It just needs the value of a timer peripheral's counter register, initialized to count upwards once every 50 microseconds. Benefits of this approach are that it has no interrupt overhead and that you can still reuse the timer for other purposes (input capture, PWM, output compare, etc.), as long as it can work with a 20 kHz base timer.
+Timers are a scarce resource on microcontrollers. Therefore it is also possible to use the free running counter of a timer that runs at a different frequency. Just make sure to adjust the counter value in this function accordingly. For example, if you choose to reuse your RTOS' 1 millisecond system tick, you need to multiply its tick counter value by 20 to simulate a 20 kHz timer. This does of course have a negative impact on the accuracy of the RTU 1.5 and 3.5 character timings, so there's a trade-off involved.
+Return value | +
---|
Free running counter value as a 16-bit value. If your timer's counter value is more than 16-bit, simply typecast it to uint16_t . |
+
The RTU and ASCII transport layers depend on a UART communication peripheral for the low-level data exchange. It is recommended to use a classical approach, where the transmission completion and reception of each byte triggers an interrupt.
+You could leverage the capability of a direct memory access (DMA) peripheral, in combination with the UART, as this lowers the interrupt overhead. However, this should not be used for data reception in combination with an RTU transport layer. The 1.5 character time between bytes can then not be properly monitored. DMA can be used for transmission, but the processing time of the byte transmit complete event is very short and therefore dedicating a DMA just for this is probably not worth it.
+void TbxMbPortUartInit(tTbxMbUartPort port,
+ tTbxMbUartBaudrate baudrate,
+ tTbxMbUartDatabits databits,
+ tTbxMbUartStopbits stopbits,
+ tTbxMbUartParity parity)
+
+Initialize the UART channel by performing the following steps:
+Note that the actual meaning of the serial port number (port
) is up to you. It typically maps to the UART peripheral number. E.g. TBX_MB_UART_PORT1
= USART1 on an STM32. However, it doesn't have to. Let's say you only use two UART peripherals on your microcontroller system: USART2 and USART6. In this case it makes logical sense to map TBX_MB_UART_PORT1
to USART2 and TBX_MB_UART_PORT2
to USART6.
Parameter | +Description | +
---|---|
port |
+The serial port to use. | +
baudrate |
+The desired communication speed. | +
databits |
+Number of databits for a character. | +
stopbits |
+Number of stop bits at the end of a character. | +
parity |
+Parity bit type to use. | +
uint8_t TbxMbPortUartTransmit(tTbxMbUartPort port,
+ uint8_t const * data,
+ uint16_t len)
+
+Start the transfer of len
bytes from the data
array on the specified serial port
:
data[0]
) to the UART transmit data register.len
is 1), otherwise enable the transmit data register empty (TXE) interrupt.For managing the entire transfer, It is recommended to save transfer related information in a global (volatile
) variable. It can then be accessed and updated in the transmit interrupt handler (TbxMbPortUartTxInterrupt()). This is what the template does with the transmitInfo[]
array.
Note that you have mutual exclusive access to the bytes in the data
array, until you call TbxMbUartTransmitComplete(). This means that you do not need to copy the data bytes to a local buffer. This approach keeps RAM requirements low and benefits the run-time performance. Just make sure to call TbxMbUartTransmitComplete()
once all bytes are transmitted or an error was detected, to release access to the data
array.
Parameter | +Description | +
---|---|
port |
+The serial port to start the data transfer on. | +
data |
+Byte array with data to transmit. | +
len |
+Number of bytes to transmit. | +
Return value | +
---|
TBX_OK if successful, TBX_ERROR otherwise. |
+
void TbxMbPortUartTxInterrupt(tTbxMbUartPort port)
+
+UART transmit complete and data register empty interrupt handler. Should be called from your UART interrupt handler, upon detection of this event, and do the following:
+Parameter | +Description | +
---|---|
port |
+The serial port that generated the interrupt. | +
void TbxMbPortUartRxInterrupt(tTbxMbUartPort port)
+
+UART reception data register full interrupt handler. Should be called from your UART interrupt handler, upon detection of this event, and do the following:
+Parameter | +Description | +
---|---|
port |
+The serial port that generated the interrupt. | +
' + escapeHtml(summary) +'
' + noResultsText + '
'); + } +} + +function doSearch () { + var query = document.getElementById('mkdocs-search-query').value; + if (query.length > min_search_length) { + if (!window.Worker) { + displayResults(search(query)); + } else { + searchWorker.postMessage({query: query}); + } + } else { + // Clear results for short queries + displayResults([]); + } +} + +function initSearch () { + var search_input = document.getElementById('mkdocs-search-query'); + if (search_input) { + search_input.addEventListener("keyup", doSearch); + } + var term = getSearchTermFromLocation(); + if (term) { + search_input.value = term; + doSearch(); + } +} + +function onWorkerMessage (e) { + if (e.data.allowSearch) { + initSearch(); + } else if (e.data.results) { + var results = e.data.results; + displayResults(results); + } else if (e.data.config) { + min_search_length = e.data.config.min_search_length-1; + } +} + +if (!window.Worker) { + console.log('Web Worker API not supported'); + // load index in main thread + $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { + console.log('Loaded worker'); + init(); + window.postMessage = function (msg) { + onWorkerMessage({data: msg}); + }; + }).fail(function (jqxhr, settings, exception) { + console.error('Could not load worker.js'); + }); +} else { + // Wrap search in a web worker + var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); + searchWorker.postMessage({init: true}); + searchWorker.onmessage = onWorkerMessage; +} diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..c039673 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Introduction to MicroTBX-Modbus MicroTBX-Modbus is a modern Modbus communication stack, targeting microcontroller based embedded systems. Its aim is to be: easy to use, easy to port, high quality, well maintained, stable and flexible. The ideal solution for any embedded software engineer, with an interest in adding Modbus communication to their product. Features Carefully crafted application programming interface ( API ), focusing on ease of use. Written in the C programming language (C99) with high MISRA compliance . Supports both Modbus client and Modbus server functionality. Supports multi channel for both the Modbus client and server. Flexible dual licensing model. Designed such that it can be used with and without and RTOS. Easy to integrate into existing software projects, especially when using CMake. Includes C++ wrappers for those preferring to develop in an object-oriented manner. Quick and simple to adjust to your microcontroller system. No compile-time configuration needed. Option to implement additional and custom Modbus function codes. Supported function codes MicroTBX-Modbus currently supports the following Modbus function codes: Function Code Name 1 Read Coils 2 Read Discrete Inputs 3 Read Holding Registers 4 Read Input Registers 5 Write Single Coil 6 Write Single Register 8 Diagnostics (sub codes: 0, 10, 11, 12, 13, 14, 15) 15 Write Multiple Coils 16 Write Multiple Registers Note that MicroTBX-Modbus includes functionality, enabling you to extend it by adding support for additional and custom function codes. Why another Modbus stack? With Modbus being such a convenient and mature communication protocol, several other Modbus software stacks exist; Both closed sourced and open sourced. Why bother with developing and maintaining yet another one? It turns out that most of the existing ones all seem to be limiting on at least one front: No multi-channel support. Not including both client and server functionality. Not being able to add support for additional and custom function codes. Skipping features needed for protocol compliance. No open source option. Not actively maintained. MicroTBX-Modbus addresses all these limitations. Thanks to the flexible dual licensing model, you can start out right away with the open source GPLv3 version. Perfect for testing, evaluation and prototyping purposes. Once you're satisfied with it and would like to include MicroTBX-Modbus in your proprietary closed sourced product, you can move on to the commercial license. The only reason not to use MicroTBX-Modbus is that it currently only supports Modbus RTU communication. Note though that support for ASCII and TCP communication is planned for the future. System requirements With MicroTBX-Modbus being a modern communication stack, the focus is more on ease and flexibility of use for the developer, and less on keeping the ROM footprint low. Therefore the recommended system requirements are slightly higher than comparative Modbus software solutions: It is recommended to use a microcontroller with at least 32 KiB flash and 4 KiB RAM. However, it will run on a basic 8-bit microcontroller with just 10 KiB of flash and 1.5 KiB RAM. Although you then run out of storage space quickly, when adding your own firmware\u2019s functionality. Next steps The getting started section of this user manual shows you how to quickly setup a Modbus server and client. Definitely worth a glance if you're new to MicroTBX-Modbus. For those who want to see MicroTBX-Modbus in action, you can find ready-to-run demo programs located in a separate repository. These demo programs target an ever growing collection of popular microcontroller evaluation boards and serve as a good starting point: https://github.com/feaser/microtbx-demos If you're ready to integrate MicroTBX-Modbus into your own embedded software project, head over to the integration section of this user manual for detailed instructions. MicroTBX-Modbus itself is hardware independent. To handle the hardware specifics of your microcontroller, you just need to implement a few port functions. A template source-file is provided. You can find detailed instructions in the portation section of this user manual. Once you got everything up-and-running and would like to use MicroTBX-Modbus in the closed source proprietary firmware of your product, make sure to upgrade to the commercial license . The default GPLv3 licensed version is not suitable for that use case.","title":"Home"},{"location":"#introduction-to-microtbx-modbus","text":"MicroTBX-Modbus is a modern Modbus communication stack, targeting microcontroller based embedded systems. Its aim is to be: easy to use, easy to port, high quality, well maintained, stable and flexible. The ideal solution for any embedded software engineer, with an interest in adding Modbus communication to their product.","title":"Introduction to MicroTBX-Modbus"},{"location":"#features","text":"Carefully crafted application programming interface ( API ), focusing on ease of use. Written in the C programming language (C99) with high MISRA compliance . Supports both Modbus client and Modbus server functionality. Supports multi channel for both the Modbus client and server. Flexible dual licensing model. Designed such that it can be used with and without and RTOS. Easy to integrate into existing software projects, especially when using CMake. Includes C++ wrappers for those preferring to develop in an object-oriented manner. Quick and simple to adjust to your microcontroller system. No compile-time configuration needed. Option to implement additional and custom Modbus function codes.","title":"Features"},{"location":"#supported-function-codes","text":"MicroTBX-Modbus currently supports the following Modbus function codes: Function Code Name 1 Read Coils 2 Read Discrete Inputs 3 Read Holding Registers 4 Read Input Registers 5 Write Single Coil 6 Write Single Register 8 Diagnostics (sub codes: 0, 10, 11, 12, 13, 14, 15) 15 Write Multiple Coils 16 Write Multiple Registers Note that MicroTBX-Modbus includes functionality, enabling you to extend it by adding support for additional and custom function codes.","title":"Supported function codes"},{"location":"#why-another-modbus-stack","text":"With Modbus being such a convenient and mature communication protocol, several other Modbus software stacks exist; Both closed sourced and open sourced. Why bother with developing and maintaining yet another one? It turns out that most of the existing ones all seem to be limiting on at least one front: No multi-channel support. Not including both client and server functionality. Not being able to add support for additional and custom function codes. Skipping features needed for protocol compliance. No open source option. Not actively maintained. MicroTBX-Modbus addresses all these limitations. Thanks to the flexible dual licensing model, you can start out right away with the open source GPLv3 version. Perfect for testing, evaluation and prototyping purposes. Once you're satisfied with it and would like to include MicroTBX-Modbus in your proprietary closed sourced product, you can move on to the commercial license. The only reason not to use MicroTBX-Modbus is that it currently only supports Modbus RTU communication. Note though that support for ASCII and TCP communication is planned for the future.","title":"Why another Modbus stack?"},{"location":"#system-requirements","text":"With MicroTBX-Modbus being a modern communication stack, the focus is more on ease and flexibility of use for the developer, and less on keeping the ROM footprint low. Therefore the recommended system requirements are slightly higher than comparative Modbus software solutions: It is recommended to use a microcontroller with at least 32 KiB flash and 4 KiB RAM. However, it will run on a basic 8-bit microcontroller with just 10 KiB of flash and 1.5 KiB RAM. Although you then run out of storage space quickly, when adding your own firmware\u2019s functionality.","title":"System requirements"},{"location":"#next-steps","text":"The getting started section of this user manual shows you how to quickly setup a Modbus server and client. Definitely worth a glance if you're new to MicroTBX-Modbus. For those who want to see MicroTBX-Modbus in action, you can find ready-to-run demo programs located in a separate repository. These demo programs target an ever growing collection of popular microcontroller evaluation boards and serve as a good starting point: https://github.com/feaser/microtbx-demos If you're ready to integrate MicroTBX-Modbus into your own embedded software project, head over to the integration section of this user manual for detailed instructions. MicroTBX-Modbus itself is hardware independent. To handle the hardware specifics of your microcontroller, you just need to implement a few port functions. A template source-file is provided. You can find detailed instructions in the portation section of this user manual. Once you got everything up-and-running and would like to use MicroTBX-Modbus in the closed source proprietary firmware of your product, make sure to upgrade to the commercial license . The default GPLv3 licensed version is not suitable for that use case.","title":"Next steps"},{"location":"apiref/","text":"API reference This section provides a full reference of all the functions, macros and types that MicroTBX-Modbus offers. Macros Version Macro Description TBX_MB_VERSION_MAIN Main version number of MicroTBX-Modbus. TBX_MB_VERSION_MINOR Minor version number of MicroTBX-Modbus. TBX_MB_VERSION_PATCH Patch number of MicroTBX-Modbus. Common Function codes. Macro Description TBX_MB_FC01_READ_COILS Modbus function code 01 - Read Coils. TBX_MB_FC02_READ_DISCRETE_INPUTS Modbus function code 02 - Read Discrete Inputs. TBX_MB_FC03_READ_HOLDING_REGISTERS Modbus function code 03 - Read Holding Registers. TBX_MB_FC04_READ_INPUT_REGISTERS Modbus function code 04 - Read Input Registers. TBX_MB_FC05_WRITE_SINGLE_COIL Modbus function code 05 - Write Single Coil. TBX_MB_FC06_WRITE_SINGLE_REGISTER Modbus function code 06 - Write Single Register. TBX_MB_FC08_DIAGNOSTICS Modbus function code 08 - Diagnostics. TBX_MB_FC15_WRITE_MULTIPLE_COILS Modbus function code 15 - Write Multiple Coils. TBX_MB_FC16_WRITE_MULTIPLE_REGISTERS Modbus function code 16 - Write Multiple Registers. Exception codes. Macro Description TBX_MB_EC01_ILLEGAL_FUNCTION Modbus exception code 01 - Illegal function. TBX_MB_EC02_ILLEGAL_DATA_ADDRESS Modbus exception code 02 - Illegal data address. TBX_MB_EC03_ILLEGAL_DATA_VALUE Modbus exception code 03 - Illegal data value. TBX_MB_EC04_SERVER_DEVICE_FAILURE Modbus exception code 04 - Server device failure. Diagnostics sub function codes. Macro Description TBX_MB_DIAG_SC_QUERY_DATA Diagnostics sub-function code - Return Query Data. TBX_MB_DIAG_SC_CLEAR_COUNTERS Diagnostics sub-function code - Clear Counters. TBX_MB_DIAG_SC_BUS_MESSAGE_COUNT Diagnostics sub-function code - Return Bus Message Count. TBX_MB_DIAG_SC_BUS_COMM_ERROR_COUNT Diagnostics sub-function code - Return Bus Communication Error Count. TBX_MB_DIAG_SC_BUS_EXCEPTION_ERROR_COUNT Diagnostics sub-function code - Return Bus Exception Error Count. TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT Diagnostics sub-function code - Return Server Message Count. TBX_MB_DIAG_SC_SERVER_NO_RESPONSE_COUNT Diagnostics sub-function code - Return Server No Response Count. Miscellaneous. Macro Description TBX_MB_FC_EXCEPTION_MASK Bit mask to OR to the function code to flag it as an exception response. Transport layer Node address. Macro Description TBX_MB_TP_NODE_ADDR_BROADCAST Node address value for broadcast purposes. TBX_MB_TP_NODE_ADDR_MIN Minimum value of a valid node address. TBX_MB_TP_NODE_ADDR_MAX Maximum value of a valid node address. Protocol data unit (PDU). Macro Description TBX_MB_TP_PDU_CODE_LEN_MAX Maximum size of the \"Function code\" at the start of a PDU. TBX_MB_TP_PDU_DATA_LEN_MAX Maximum number of data bytes inside a PDU. This excludes the function code. TBX_MB_TP_PDU_MAX_LEN Maximum length of a PDU. Types Server tTbxMbServer typedef void * tTbxMbServer Handle to a Modbus server channel object, in the format of an opaque pointer. tTbxMbServerResult typedef enum { TBX_MB_SERVER_OK = 0U, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR, TBX_MB_SERVER_ERR_DEVICE_FAILURE } tTbxMbServerResult numerated type with all supported return values for the callbacks. tTbxMbServerReadInput typedef tTbxMbServerResult (* tTbxMbServerReadInput)(tTbxMbServer channel, uint16_t addr, uint8_t * value) Modbus server callback function for reading a discrete input. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Pointer to write the value of the input to. Use TBX_ON if the input is on, TBX_OFF otherwise. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerReadCoil typedef tTbxMbServerResult (* tTbxMbServerReadCoil)(tTbxMbServer channel, uint16_t addr, uint8_t * value) Modbus server callback function for reading a coil. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Pointer to write the value of the coil to. Use TBX_ON if the coils is on, TBX_OFF otherwise. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerWriteCoil typedef tTbxMbServerResult (* tTbxMbServerWriteCoil)(tTbxMbServer channel, uint16_t addr, uint8_t value) Modbus server callback function for writing a coil. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Coil value. Use TBX_ON to activate the coil, TBX_OFF otherwise. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerReadInputReg typedef tTbxMbServerResult (* tTbxMbServerReadInputReg)(tTbxMbServer channel, uint16_t addr, uint16_t * value) Modbus server callback function for reading an input register. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Pointer to write the value of the input register to. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerReadHoldingReg typedef tTbxMbServerResult (* tTbxMbServerReadHoldingReg)(tTbxMbServer channel, uint16_t addr, uint16_t * value) Modbus server callback function for reading a holding register. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Pointer to write the value of the holding register to. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerWriteHoldingReg typedef tTbxMbServerResult (* tTbxMbServerWriteHoldingReg)(tTbxMbServer channel, uint16_t addr, uint16_t value) Modbus server callback function for writing a holding register. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. addr Element address ( 0 .. 65535 ). value Value of the holding register. Return value TBX_MB_SERVER_OK if successful, TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR if the specific data element address is not supported by this server, TBX_MB_SERVER_ERR_DEVICE_FAILURE otherwise. tTbxMbServerCustomFunction typedef uint8_t (* tTbxMbServerCustomFunction)(tTbxMbServer channel, uint8_t const * rxPdu, uint8_t * txPdu, uint8_t * len) Modbus server callback function for implementing custom function code handling. Thanks to this functionality, the user can support Modbus function codes that are either currently not supported or user defined extensions. The rxPdu and txPdu parameters are pointers to the byte array of the PDU. The first byte (i.e. rxPdu[0] ) contains the function code, followed by its data bytes. Upon calling the callback, the len parameter contains the length of rxPdu . When preparing the response, you can write the length of the txPdu response to len as well. Parameter Description channel Handle to the Modbus server channel object that triggered the callback. rxPdu Pointer to a byte array for reading the received PDU. txPdu Pointer to a byte array for writing the response PDU. len Pointer to the PDU length, including the function code. Return value TBX_TRUE if the callback function handled the received function code and prepared a response PDU. TBX_FALSE otherwise. Client tTbxMbClient typedef void * tTbxMbClient Handle to a Modbus client channel object, in the format of an opaque pointer. Transport layer tTbxMbTp typedef void * tTbxMbTp Handle to a Modbus transport layer object, in the format of an opaque pointer. UART tTbxMbUartPort typedef enum { TBX_MB_UART_PORT1 = 0U, TBX_MB_UART_PORT2, TBX_MB_UART_PORT3, TBX_MB_UART_PORT4, TBX_MB_UART_PORT5, TBX_MB_UART_PORT6, TBX_MB_UART_PORT7, TBX_MB_UART_PORT8, TBX_MB_UART_NUM_PORT } tTbxMbUartPort Enumerated type with all supported UART ports. tTbxMbUartBaudrate typedef enum { TBX_MB_UART_1200BPS = 0U, TBX_MB_UART_2400BPS, TBX_MB_UART_4800BPS, TBX_MB_UART_9600BPS, TBX_MB_UART_19200BPS, TBX_MB_UART_38400BPS, TBX_MB_UART_57600BPS, TBX_MB_UART_115200BPS, TBX_MB_UART_NUM_BAUDRATE } tTbxMbUartBaudrate Enumerated type with all supported UART baudrates. tTbxMbUartDatabits typedef enum { TBX_MB_UART_7_DATABITS = 0U, TBX_MB_UART_8_DATABITS, TBX_MB_UART_NUM_DATABITS } tTbxMbUartDatabits Enumerated type with all supported UART data bits modes. tTbxMbUartStopbits typedef enum { TBX_MB_UART_1_STOPBITS = 0U, TBX_MB_UART_2_STOPBITS, TBX_MB_UART_NUM_STOPBITS } tTbxMbUartStopbits Enumerated type with all supported parity modes. tTbxMbUartParity typedef enum { TBX_MB_ODD_PARITY = 0U, TBX_MB_EVEN_PARITY, TBX_MB_NO_PARITY, TBX_MB_UART_NUM_PARITY } tTbxMbUartParity Enumerated type with all supported parity modes. Functions Server TbxMbServerCreate tTbxMbServer TbxMbServerCreate(tTbxMbTp transport) Creates a Modbus server channel object and assigns the specified Modbus transport layer to the channel for packet transmission and reception. This example creates a Modbus RTU server channel object for a node with address 10 : /* Construct a Modbus RTU transport layer object. */ tTbxMbTp modbusTp = TbxMbRtuCreate(10U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS, TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY); /* Construct a Modbus server object. */ tTbxMbServer modbusServer = TbxMbServerCreate(modbusTp); Parameter Description transport Handle to a previously created Modbus transport layer object to assign to the channel. Return value Handle to the newly created Modbus server channel object if successful, NULL otherwise. TbxMbServerFree void TbxMbServerFree(tTbxMbServer channel) Releases a Modbus server channel object, previously created with TbxMbServerCreate() . Parameter Description channel Handle to the Modbus server channel object to release. TbxMbServerSetCallbackReadInput void TbxMbServerSetCallbackReadInput(tTbxMbServer channel, tTbxMbServerReadInput callback) Registers the callback function that this server calls, whenever a client requests the reading of a specific discrete input. The example connects the state of two digital inputs to the Modbus discrete inputs at addresses 10000 to 10001 : tTbxMbServerResult AppReadInput(tTbxMbServer channel, uint16_t addr, uint8_t * value) { tTbxMbServerResult result = TBX_MB_SERVER_OK; /* Filter on the requested discrete input address. */ switch (addr) { case 10000U: *value = BspDigitalIn(BSP_DIGITAL_IN1); break; case 10001U: *value = BspDigitalIn(BSP_DIGITAL_IN2); break; default: /* Unsupported discrete input address. */ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; break; } /* Give the result back to the caller. */ return result; } /* Set the callback for reading the Modbus discrete inputs. */ TbxMbServerSetCallbackReadInput(modbusServer, AppReadInput); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackReadCoil void TbxMbServerSetCallbackReadCoil(tTbxMbServer channel, tTbxMbServerReadCoil callback) Registers the callback function that this server calls, whenever a client requests the reading of a specific coil. The example assumes the application stores the state of two coils in an array with name appCoils[] . Whenever a client requests the reading of the Modbus coils at addresses 0 to 1 , the currently stored values in the appCoils[] array are returned: uint8_t appCoils[2] = { TBX_ON, TBX_OFF }; tTbxMbServerResult AppReadCoil(tTbxMbServer channel, uint16_t addr, uint8_t * value) { tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; /* Supported coil address? */ if (addr <= 1U) { /* Store the current coil state. */ *value = appCoils[addr]; result = TBX_MB_SERVER_OK; } /* Give the result back to the caller. */ return result; } /* Set the callback for reading the Modbus coils. */ TbxMbServerSetCallbackReadCoil(modbusServer, AppReadCoil); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackWriteCoil void TbxMbServerSetCallbackWriteCoil(tTbxMbServer channel, tTbxMbServerWriteCoil callback) Registers the callback function that this server calls, whenever a client requests the writing of a specific coil. The example connects the Modbus coil addresses 0 to 1 to the state of two digital outputs: tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value) { tTbxMbServerResult result = TBX_MB_SERVER_OK; /* Filter on the requested coil address. */ switch (addr) { case 0U: BspDigitalOut(BSP_DIGITAL_OUT1, value); break; case 1U: BspDigitalOut(BSP_DIGITAL_OUT2, value); break; default: /* Unsupported coil address. */ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; break; } /* Give the result back to the caller. */ return result; } /* Set the callback for writing the Modbus coils. */ TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackReadInputReg void TbxMbServerSetCallbackReadInputReg(tTbxMbServer channel, tTbxMbServerReadInputReg callback) Registers the callback function that this server calls, whenever a client requests the reading of a specific input register. The example connects the state of two analog inputs to the Modbus input registers at addresses 30000 to 30001 : tTbxMbServerResult AppReadInputReg(tTbxMbServer channel, uint16_t addr, uint16_t * value) { tTbxMbServerResult result = TBX_MB_SERVER_OK; /* Filter on the requested input register address. */ switch (addr) { case 30000U: *value = BspAnalogIn(BSP_ANALOG_IN1); break; case 30001U: *value = BspAnalogIn(BSP_ANALOG_IN2); break; default: /* Unsupported input register address. */ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; break; } /* Give the result back to the caller. */ return result; } /* Set the callback for reading the Modbus input registers. */ TbxMbServerSetCallbackReadInputReg(modbusServer, AppReadInputReg); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackReadHoldingReg void TbxMbServerSetCallbackReadHoldingReg(tTbxMbServer channel, tTbxMbServerReadHoldingReg callback) Registers the callback function that this server calls, whenever a client requests the reading of a specific holding register. The example assumes the application stores the state of two holding registers in an array with name appHoldingRegs[] . Whenever a client requests the reading of the Modbus holding registers at addresses 40000 to 40001 , the currently stored values in the appHoldingRegs[] array are returned: uint16_t appHoldingRegs[2] = { 1234, 5678 }; tTbxMbServerResult AppReadHoldingReg(tTbxMbServer channel, uint16_t addr, uint16_t * value) { tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; /* Supported holding register address? */ if ( (addr >= 40000) && (addr <= 40001U) ) { /* Store the holding register state. */ *value = appHoldingReg[addr - 40000U]; result = TBX_MB_SERVER_OK; } /* Give the result back to the caller. */ return result; } /* Set the callback for reading the Modbus holding registers. */ TbxMbServerSetCallbackReadHoldingReg(modbusServer, AppReadHoldingReg); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackWriteHoldingReg void TbxMbServerSetCallbackWriteHoldingReg(tTbxMbServer channel, tTbxMbServerWriteHoldingReg callback) Registers the callback function that this server calls, whenever a client requests the writing of a specific holding register. The example connects the Modbus holding registers addresses 40000 to 40001 to two 8-bit PWM output signals: tTbxMbServerResult AppWriteHoldingReg(tTbxMbServer channel, uint16_t addr, uint16_t value) { tTbxMbServerResult result = TBX_MB_SERVER_OK; /* Filter on the requested holding register address. */ switch (addr) { case 40000U: /* PWM supports 8-bit duty cycle. */ if (value <= 255U) { BspPwmOut(BSP_PWM_OUT1, (uint8_t)value); } else { result = TBX_MB_SERVER_ERR_DEVICE_FAILURE; } break; case 40001U: /* PWM supports 8-bit duty cycle. */ if (value <= 255U) { BspPwmOut(BSP_PWM_OUT2, (uint8_t)value); } else { result = TBX_MB_SERVER_ERR_DEVICE_FAILURE; } break; default: /* Unsupported holding register address. */ result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR; break; } /* Give the result back to the caller. */ return result; } /* Set the callback for writing the Modbus holding registers. */ TbxMbServerSetCallbackWriteHoldingReg(modbusServer, AppWriteHoldingReg); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. TbxMbServerSetCallbackCustomFunction void TbxMbServerSetCallbackCustomFunction (tTbxMbServer channel, tTbxMbServerCustomFunction callback) Registers the callback function that this server calls, whenever it received a PDU containing a function code not currently supported. With the aid of this callback function the user can implement support for new function codes. The example shows how to add support for function code 17 ( Report Server ID ). It's the counter-part to the example for TbxMbClientCustomFunction() . According to the Modbus protocol, the response to the Report Server ID request is device specific. The device implementation decides the number of bytes for the Server ID and if additional data is added to the response. The following code snippet implements support for Report Server ID , where the actual server ID is 16-bits and the response contains no additional data: uint8_t AppReportServerIdCallback(tTbxMbServer channel, uint8_t const * rxPdu, uint8_t * txPdu, uint8_t * len) { uint8_t result = TBX_FALSE; /* Function code 17 - Report Server ID? */ if (rxPdu[0] == 17U) { /* Check the expected request length. */ if (*len == 1U) { /* Prepare the response. */ txPdu[0] = 17U; /* Function code. */ txPdu[1] = 3U; /* Byte count. */ TbxMbCommonStoreUInt16BE(0x1234U, &txPdu[2]); /* server ID. */ txPdu[4] = 0xFFU; /* Run indicator status = ON. */ *len = 5U; /* Function code handled. */ result = TBX_TRUE; } } /* Give the result back to the caller. */ return result; } /* Set the callback for handling custom function codes. */ TbxMbServerSetCallbackCustomFunction(modbusServer, AppReportServerIdCallback); Parameter Description channel Handle to the Modbus server channel object. callback Pointer to the callback function. Client TbxMbClientCreate tTbxMbClient TbxMbClientCreate(tTbxMbTp transport, uint16_t responseTimeout, uint16_t turnaroundDelay) Creates a Modbus client channel object and assigns the specified Modbus transport layer to the channel for packet transmission and reception. This example creates a Modbus RTU client channel object. Note the the nodeAddr parameter of function TbxMbRtuCreate() is not applicable when used on a client and should simply be set to a value of 0 : /* Construct a Modbus RTU transport layer object. */ tTbxMbTp modbusTp = TbxMbRtuCreate(0U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS, TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY); /* Construct a Modbus client object. */ tTbxMbClient modbusClient = TbxMbClientCreate(modbusTp, 1000U, 100U); Parameter Description transport Handle to a previously created Modbus transport layer object to assign to the channel. responseTimeout Maximum time in milliseconds to wait for a response from the Modbus server, after sending a PDU. turnaroundDelay Delay time in milliseconds after sending a broadcast PDU to give all recipients sufficient time to process the PDU. Return value Handle to the newly created Modbus client channel object if successful, NULL otherwise. TbxMbClientFree void TbxMbClientFree(tTbxMbClient channel) Releases a Modbus client channel object, previously created with TbxMbClientCreate() . Parameter Description channel Handle to the Modbus client channel object to release. TbxMbClientReadCoils uint8_t TbxMbClientReadCoils(tTbxMbClient channel, uint8_t node, uint16_t addr, uint16_t num, uint8_t * coils) Reads the coil(s) from the server with the specified node address. The example reads the state of two coils at Modbus addresses 0 to 1 , from a Modbus server with node address 10 : uint8_t coils[2] = { 0 }; TbxMbClientReadCoils(modbusClient, 10U, 0U, 2U, coils); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the coil read operation. num Number of elements to read from the coils data table. Range can be 1 .. 2000 . coils Pointer to array with TBX_ON / TBX_OFF values where the coil state will be written to. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientReadInputs uint8_t TbxMbClientReadInputs(tTbxMbClient channel, uint8_t node, uint16_t addr, uint16_t num, uint8_t * inputs) Reads the discrete input(s) from the server with the specified node address. The example reads the state of two discrete inputs at Modbus addresses 10000 to 10001 , from a Modbus server with node address 10 : uint8_t inputs[2] = { 0 }; TbxMbClientReadInputs(modbusClient, 10U, 10000U, 2U, inputs); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the discrete input read operation. num Number of elements to read from the discrete inputs data table. Range can be 1 .. 2000 . inputs Pointer to array with TBX_ON / TBX_OFF values where the discrete input state will be written to. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientReadInputRegs uint8_t TbxMbClientReadInputRegs(tTbxMbClient channel, uint8_t node, uint16_t addr, uint8_t num, uint16_t * inputRegs) Reads the input register(s) from the server with the specified node address. The example reads two input registers at Modbus addresses 30000 to 30001 , from a Modbus server with node address 10 : uint16_t inputRegs[2] = { 0 }; TbxMbClientReadInputRegs(modbusClient, 10U, 30000U, 2U, inputRegs); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the input register read operation. num Number of elements to read from the input registers data table. Range can be 1 .. 125 . inputRegs Pointer to array where the input register values will be written to. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientReadHoldingRegs uint8_t TbxMbClientReadHoldingRegs(tTbxMbClient channel, uint8_t node, uint16_t addr, uint8_t num, uint16_t * holdingRegs) Reads the holding register(s) from the server with the specified node address. The example reads two holding registers at Modbus addresses 40000 to 40001 , from a Modbus server with node address 10 : uint16_t holdingRegs[2] = { 0 }; TbxMbClientReadHoldingRegs(modbusClient, 10U, 40000U, 2U, holdingRegs); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the holding register read operation. num Number of elements to read from the holding registers data table. Range can be 1 .. 125 . holdingRegs Pointer to array where the holding register values will be written to. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientWriteCoils uint8_t TbxMbClientWriteCoils(tTbxMbClient channel, uint8_t node, uint16_t addr, uint16_t num, uint8_t const * coils) Writes the coil(s) to the server with the specified node address. The example writes the state of two coils at Modbus addresses 0 to 1 , to a Modbus server with node address 10 : uint8_t coils[2] = { TBX_OFF, TBX_OFF }; TbxMbClientWriteCoils(modbusClient, 10U, 0U, 2U, coils); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the coil write operation. num Number of elements to write to the coils data table. Range can be 1 .. 1968 . coils Pointer to array with the desired TBX_ON / TBX_OFF coil values. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientWriteHoldingRegs uint8_t TbxMbClientWriteHoldingRegs(tTbxMbClient channel, uint8_t node, uint16_t addr, uint8_t num, uint16_t const * holdingRegs) Writes the holding register(s) to the server with the specified node address. The example writes two holding registers at Modbus addresses 40000 to 40001 , to a Modbus server with node address 10 : uint16_t holdingRegs[2] = { 63U, 127U }; TbxMbClientWriteHoldingRegs(modbusClient, 10U, 40000U, 2U, holdingRegs); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . addr Starting element address (0..65535) in the Modbus data table for the holding register write operation. num Number of elements to write to the holding registers data table. Range can be 1 .. 123 . holdingRegs Pointer to array with the desired holding register values. Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientDiagnostics uint8_t TbxMbClientDiagnostics(tTbxMbClient channel, uint8_t node, uint16_t subcode, uint16_t * count) Perform diagnostic operation on the server for checking the communication system. The example obtains the number of packets with a correct CRC, received by a Modbus server with node address 10 : uint16_t count = 0U; TbxMbClientDiagnostics(modbusClient, 10U, TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT, &count); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . subcode Sub-function code for specifying the diagnostic operation to perform. Currently supported values: - TBX_MB_DIAG_SC_QUERY_DATA - TBX_MB_DIAG_SC_CLEAR_COUNTERS - TBX_MB_DIAG_SC_BUS_MESSAGE_COUNT - TBX_MB_DIAG_SC_BUS_COMM_ERROR_COUNT - TBX_MB_DIAG_SC_BUS_EXCEPTION_ERROR_COUNT - TBX_MB_DIAG_SC_SERVER_MESSAGE_COUNT - TBX_MB_DIAG_SC_SERVER_NO_RESPONSE_COUNT count Location where the retrieved count value will be written to. Only applicable for the sub-function codes that end with _COUNT . Return value TBX_OK if successful, TBX_ERROR otherwise. TbxMbClientCustomFunction uint8_t TbxMbClientCustomFunction(tTbxMbClient channel, uint8_t node, uint8_t const * txPdu, uint8_t * rxPdu, uint8_t * len) Send a custom function code PDU to the server and receive its response PDU. Thanks to this functionality, the user can support Modbus function codes that are either currently not supported or user defined extensions. The txPdu and rxPdu parameters are pointers to the byte array of the PDU. The first byte (i.e. txPdu[0] ) contains the function code, followed by its data bytes. When calling this function, set the len parameter to the length of the txPdu . This function updates the len parameter with the length of the received PDU, which it stores in rxPdu . The example shows how to add support for function code 17 ( Report Server ID ). It's the counter-part to the example for TbxMbServerSetCallbackCustomFunction() . According to the Modbus protocol, the response to the Report Server ID request is device specific. The device implementation decides the number of bytes for the Server ID and if additional data is added to the response. The following code snippet implements support for Report Server ID , where it reads out the 16-bit server ID of a Modbus server with node address 10 : uint16_t AppReportServerId(tTbxMbClient channel, uint8_t node) { /* static to lower stack load. */ static uint8_t response[TBX_MB_TP_PDU_MAX_LEN]; uint8_t request[1] = { 17U }; uint8_t len = 1U; uint16_t result = 0U; /* Transceive function code 17 - Report Server ID. */ if (TbxMbClientCustomFunction(channel, node, request, response, &len) == TBX_OK) { /* Response length as expected? */ if (len == 5U) { /* Not an exception response and byte count correct? */ if ((response[0] == 17U) && (response[1] == 3U)) { /* Read out the received server ID. */ result = TbxMbCommonExtractUInt16BE(&response[2]); } } } /* Give the result back to the caller. */ return result; } /* Read the server ID. */ uint16_t serverId = AppReportServerId(modbusClient, 10U); Parameter Description channel Handle to the Modbus client channel for the requested operation. node The address of the server. This parameter is transport layer dependent. It is needed on RTU/ASCII, yet don't care for TCP unless it is a gateway to an RTU network. If it's don't care, set it to a value of 1 . txPdu Pointer to a byte array with the PDU to transmit. rxPdu Pointer to a byte array with the received response PDU. len Pointer to the PDU length, including the function code. Return value TBX_OK if successful, TBX_ERROR otherwise. Event TbxMbEventTask void TbxMbEventTask(void) Task function that drives the entire Modbus stack. It processes internally generated events. How to call this function depends on the selected operating system abstraction layer (OSAL), which you determine based on the source/osal/tbxmb_XXX.c source file you compile and link with your firmware. In a traditional superloop application ( tbxmb_superloop.c ), call this function continuously in the infinite program loop: #include