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 using communication options.

  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.

Note

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

Field

Description

start_offset

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

type

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

address

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

size

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

mbc_slave_set_descriptor()

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_HOLDING_START_AREA0  (0)
#define MB_REG_HOLD_CNT             (100)
#define MB_REG_INPUT_CNT            (100)
....
static void *slave_handle = NULL;                   // Pointer to interface structure allocated by constructor
....
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!
ESP_ERROR_CHECK(mbc_slave_set_descriptor(slave_handle, reg_area));

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);
ESP_ERROR_CHECK(mbc_slave_set_descriptor(slave_handle, reg_area));

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.

The stack supports the extended data types when enabled through the the option CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND in kconfig menu. In this case the mapped data values can be initialized to specific format using Modbus Endianness Conversion API Reference. Please refer to secton Mapping Of Complex Data Types for more information about data types.

Example initialization of mapped values:

#include "mbcontroller.h"       // for mbcontroller defines and api
val_32_arr holding_float_abcd[2] = {0};
val_64_arr holding_double_ghefcdab[2] = {0};
...
// set the Modbus parameter to specific format
portENTER_CRITICAL(&param_lock); // critical section is required if the stack is active
mb_set_float_abcd(&holding_float_abcd[0], (float)12345.0);
mb_set_float_abcd(&holding_float_abcd[1], (float)12345.0);
mb_set_double_ghefcdab(&holding_double_ghefcdab[0], (double)12345.0);
portEXIT_CRITICAL(&param_lock);
...
// The actual abcd formatted value can be converted to actual float represenatation as below
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[0]));
ESP_LOGI("TEST", "Test value abcd: %f", mb_get_float_abcd(&holding_float_abcd[1]));
ESP_LOGI("TEST", "Test value ghefcdab: %lf", mb_get_double_ghefcdab(&holding_double_ghefcdab[0]));
...

Slave Communication

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

mbc_slave_start()

static void* slave_handle = NULL;
....
ESP_ERROR_CHECK(mbc_slave_start(slave_handle)); // The handle must be initialized prior to start call.

mbc_slave_check_event()

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.

mbc_slave_get_param_info()

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

Field

Description

time_stamp

the time stamp of the event when defined parameter is accessed

mb_offset

start Modbus register accessed by master

type

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

address

memory address that corresponds to accessed register in defined area descriptor

size

number of registers being accessed by master

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

#define MB_READ_MASK            (MB_EVENT_INPUT_REG_RD | MB_EVENT_HOLDING_REG_RD)
#define MB_WRITE_MASK           (MB_EVENT_HOLDING_REG_WR)
#define MB_READ_WRITE_MASK      (MB_READ_MASK | MB_WRITE_MASK)
#define MB_PAR_INFO_GET_TOUT    (10 / portTICK_RATE_MS)
....
static void *slave_handle = NULL;  // communication object handle
....
// Get the mask of the queued events, the function
// blocks while waiting for register access
(void)mbc_slave_check_event(mbc_slave_handle, MB_READ_WRITE_MASK);
// Obtain the parameter information from parameter queue regarding access from master
ESP_ERROR_CHECK(mbc_slave_get_param_info(mbc_slave_handle, &reg_info, MB_PAR_INFO_GET_TOUT));
const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";

// Filter events and process them accordingly
if (reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
    ESP_LOGI(TAG, "HOLDING %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
                rw_str,
                (uint32_t)reg_info.time_stamp,
                (uint32_t)reg_info.mb_offset,
                (uint32_t)reg_info.type,
                (uint32_t)reg_info.address,
                (uint32_t)reg_info.size);
} else if (reg_info.type & (MB_EVENT_INPUT_REG_RD)) {
    ESP_LOGI(TAG, "INPUT %s (%u us), ADDR:%u, TYPE:%u, INST_ADDR:0x%.4x, SIZE:%u",
                rw_str,
                (uint32_t)reg_info.time_stamp,
                (uint32_t)reg_info.mb_offset,
                (uint32_t)reg_info.type,
                (uint32_t)reg_info.address,
                (uint32_t)reg_info.size);
}

mbc_slave_lock()

mbc_slave_unlock()

The direct access to slave register area from user application must be protected by critical section. The following functions can be used to protect access to the data from registered mapping area while the communication object is active.

static void *slave_handle = NULL;  // communication object handle
...
(void)mbc_slave_lock(slave_handle); // ignore the returned error if the object is not actual
holding_reg_area[1] += 10; // the data is part of initialized register area accessed by slave
(void)mbc_slave_unlock(slave_handle);

The access to registered area shared between several slave objects from user application must be protected by critical section base on spin lock:

#include "freertos/FreeRTOS.h"
...
static portMUX_TYPE g_spinlock = portMUX_INITIALIZER_UNLOCKED;
...
portENTER_CRITICAL(&param_lock);
holding_reg_area[2] = 123;
portEXIT_CRITICAL(&param_lock);

Modbus Slave Teardown

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

mbc_slave_delete()

ESP_ERROR_CHECK(mbc_slave_delete(slave_handle)); // delete the master communication object defined by its handle