Modbus Slave API Overview

The sections below represent typical programming workflow for the slave API which should be called in following order:

  1. Modbus Port Initialization - Initialization of Modbus controller interface for the selected port.

  2. Configuring Slave Data Access - Configure data descriptors to access slave parameters.

  3. Slave Communication Options - Allows to setup communication options for selected port.

  4. Slave Communication - Start stack and sending / receiving data. Filter events when master accesses the register areas.

  5. Modbus Slave Teardown - Destroy Modbus controller and its resources.

Configuring Slave Data Access

The following functions must be called when the Modbus controller slave port is already initialized. Refer to Modbus Port Initialization.

The slave stack requires the user to define structures (memory storage areas) that store the Modbus parameters accessed by stack. These structures should be prepared by the user and be assigned to the Modbus controller interface using mbc_slave_set_descriptor() API call before the start of communication. The slave task can call the mbc_slave_check_event() function which will block until the Modbus master access the slave. The slave task can then get information about the data being accessed.


One slave can define several area descriptors per each type of Modbus register area with different start_offset.

Register area is defined by using the mb_register_area_descriptor_t structure.

Table 3 Modbus register area descriptor




Zero based register relative offset for defined register area. Example: register address = 40002 ( 4x register area - Function 3 - holding register ), start_offset = 2


Type of the Modbus register area. Refer to mb_param_type_t for more information.


A pointer to the memory area which is used to store the register data for this area descriptor.


The size of the memory area in bytes which is used to store register data.


The function initializes Modbus communication descriptors for each type of Modbus register area (Holding Registers, Input Registers, Coils (single bit output), Discrete Inputs). Once areas are initialized and the mbc_slave_start() API is called the Modbus stack can access the data in user data structures by request from master.

#define MB_REG_INPUT_START_AREA0    (0)
#define MB_REG_HOLD_CNT             (100)
#define MB_REG_INPUT_CNT            (100)

mb_register_area_descriptor_t reg_area; // Modbus register area descriptor structure
unit16_t holding_reg_area[MB_REG_HOLD_CNT] = {0}; // storage area for holding registers
unit16_t input_reg_area[MB_REG_INPUT_CNT] = {0}; // storage area for input registers

reg_area.type = MB_PARAM_HOLDING;                               // Set type of register area
reg_area.start_offset = MB_REG_HOLDING_START_AREA0;             // Offset of register area in Modbus protocol
reg_area.address = (void*)&holding_reg_area[0];                 // Set pointer to storage instance
reg_area.size = sizeof(holding_reg_area) << 1;                  // Set the size of register storage area in bytes

reg_area.type = MB_PARAM_INPUT;
reg_area.start_offset = MB_REG_INPUT_START_AREA0;
reg_area.address = (void*)&input_reg_area[0];
reg_area.size = sizeof(input_reg_area) << 1;

At least one area descriptor per each Modbus register type must be set in order to provide register access to its area. If the master tries to access an undefined area, the stack will generate a Modbus exception.

Direct access to register area from user application must be protected by critical section:

holding_reg_area[2] += 10;

Slave Communication Options

The function initializes the Modbus controller interface and its active context (tasks, RTOS objects and other resources).


The function is used to setup communication parameters of the Modbus stack.

Example initialization of Modbus TCP communication:


mb_communication_info_t comm_info = {
    .ip_port = MB_TCP_PORT,                    // Modbus TCP port number (default = 502)
    .ip_addr_type = MB_IPV4,                   // version of IP protocol
    .ip_mode = MB_MODE_TCP,                    // Port communication mode
    .ip_addr = NULL,                           // This field keeps the client IP address to bind, NULL - bind to any client
    .ip_netif_ptr = esp_netif_ptr              // esp_netif_ptr - pointer to the corresponding network interface

// Setup communication parameters and start stack

Example initialization of Modbus serial communication:

#define MB_SLAVE_DEV_SPEED 9600
#define MB_SLAVE_ADDR 1

// Setup communication parameters and start stack
mb_communication_info_t comm_info = {
    .mode = MB_MODE_RTU,                    // Communication type
    .slave_addr = MB_SLAVE_ADDR,            // Short address of the slave
    .port = MB_SLAVE_PORT_NUM,              // UART physical port number
    .baudrate = MB_SLAVE_DEV_SPEED,         // Baud rate for communication
    .parity = MB_PARITY_NONE                // Parity option


Slave Communication

The function below is used to start Modbus controller interface and allows communication.




The blocking call to function waits for a event specified (represented as an event mask parameter). Once the master accesses the parameter and the event mask matches the parameter type, the application task will be unblocked and function will return the corresponding event mb_event_group_t which describes the type of register access being done.


The function gets information about accessed parameters from the Modbus controller event queue. The KConfig CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE key can be used to configure the notification queue size. The timeout parameter allows a timeout to be specified when waiting for a notification. The mb_param_info_t structure contains information about accessed parameter.

Table 4 Description of the register info structure: mb_param_info_t




the time stamp of the event when defined parameter is accessed


start Modbus register accessed by master


type of the Modbus register area being accessed (See the mb_event_group_t for more information)


memory address that corresponds to accessed register in defined area descriptor


number of registers being accessed by master

Example to get event when holding or input registers accessed in the slave:

#define MB_PAR_INFO_GET_TOUT    (10 / portTICK_RATE_MS)

// The function blocks while waiting for register access
mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);

// Get information about data accessed from master
ESP_ERROR_CHECK(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (event & MB_READ_MASK) ? "READ" : "WRITE";

// Filter events and process them accordingly
    ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
} else if (event & (MB_EVENT_INPUT_REG_RD)) {
    ESP_LOGI(TAG, "INPUT %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",

Modbus Slave Teardown

This function stops the Modbus communication stack, destroys the controller interface, and frees all used active objects allocated for the slave.