Minimal ESP-IDF Modbus TCP example

 Date: June 26, 2022

As the ESP-IDF Modbus TCP is rather complex I decided to strip it down to a bare minimum.

This implemention is based on ESP-IDF v4.4.1.

It increases an uint16_t datafield on address 1 if the Holding register is getting read.

Findings:

  • There aren’t any callback-routines available to update the register values before the actual reading is taking place.
  • The event thrown by mbc_slave_check_event() comes AFTER the read/write.

The network interface ip_netif_ptr must me initialized as descriped in protocol_examples_common.

#include <stdio.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"

#include "mbcontroller.h"

#include "wifi.h"

#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

#define MB_REG_HOLDING_START_AREA0 0
#define MB_REG_HOLD_CNT 1

static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;

static const char *TAG = "modbus";

mb_register_area_descriptor_t reg_area;
uint16_t holding_reg_area[MB_REG_HOLD_CNT] = {0};

void modbus_loop()
{
    mb_param_info_t reg_info;

    while (1)
    {
        ESP_LOGI(TAG, "Waiting for modbus request");
        mb_event_group_t event = mbc_slave_check_event(MB_READ_WRITE_MASK);

        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";

        if (event & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD))
        {
            /* This is where the magic happens */
            portENTER_CRITICAL(&param_lock);
            holding_reg_area[0]++;
            portEXIT_CRITICAL(&param_lock);

            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);
        }
    }
}

void modbus_init()
{
    mb_register_area_descriptor_t reg_area;

    void *slave_handler = NULL;

    // Initialization of Modbus controller
    ESP_ERROR_CHECK(mbc_slave_init_tcp(&slave_handler));

    mb_communication_info_t comm_info = {
        .ip_port = 502,
        .ip_addr_type = MB_IPV4,
        .ip_mode = MB_MODE_TCP,
        .ip_addr = NULL,
        .ip_netif_ptr = (void *)get_example_netif()
    };

    ESP_ERROR_CHECK(mbc_slave_setup((void *)&comm_info));

    reg_area.type = MB_PARAM_HOLDING;
    reg_area.start_offset = MB_REG_HOLDING_START_AREA0;
    reg_area.address = (void *)&holding_reg_area[0];
    reg_area.size = sizeof(holding_reg_area) << 1;
    ESP_ERROR_CHECK(mbc_slave_set_descriptor(reg_area));

    ESP_ERROR_CHECK(mbc_slave_start());
}

Running it on a ESP32 results in following logging output when executing a Holding read.

I (11071) MB_TCP_SLAVE_PORT: Protocol stack initialized.
I (11081) MB_TCP_SLAVE_PORT: Socket (#54), listener  on port: 502, errno=0
I (202771) MB_TCP_SLAVE_PORT: Socket (#55), accept client connection from address: 192.168.1.46
I (202771) modbus: HOLDING READ (202282472 us), ADDR:1, TYPE:2, INST_ADDR:0x3ffb4152, SIZE:1
E (202791) MB_TCP_SLAVE_PORT: Socket (#55)(192.168.1.46), connection closed by peer.

Following Python Script was used to test it.

from pyModbusTCP.client import ModbusClient

c = ModbusClient('192.168.1.45', port=502, auto_open=True, debug=False)

data = c.read_holding_registers(0)
print (data)

Resulting in following console output:

dev@dev:~/esp-idf$ python3 test.py 
[0]
dev@dev:~/esp-idf$ python3 test.py 
[1]
dev@dev:~/esp-idf$ python3 test.py 
[2]
dev@dev:~/esp-idf$ python3 test.py 
[3]

Previous
⏪ ESP-IDF v4.4.1 is having a faulty OpenOCD and hidden Python 2 dependency coming from GDB

Next
Useful, affordable tools for daily embedded development tasks ⏩