tcp_slave例程移植

添加组件

复制示例工程中的idf_component.yml文件夹

优化:在CMakeList.txt当中添加这句

编译即可,有一个警告暂时不用管(?)

 移植并自定义static_ip_example库内容

在路径C:\Users\用户名\esp\v5.5.1\esp-idf\examples\common_components下找到protocol_examples_common文件夹

工程根目录下创建<components>文件夹并复制<protocol_examples_common>文件夹在目录下

修改<components>文件夹其中的cMake文件内容和下面一致(将依赖修改为公共)

完善<main>文件夹路径下的cMake文件内容(添加一行依赖)

idf_component_register(SRCS "main.c"
                    PRIV_REQUIRES protocol_examples_common nvs_flash
                    INCLUDE_DIRS ".")

编译即可通过

第一步:Kconfig 添加配置选项

在 `protocol_examples_common/Kconfig` 中添加:

位置:

    endif  ← EXAMPLE_CONNECT_THREAD 的结束

    # ← 在这里插入下面的静态 IP 配置代码块内容

    config EXAMPLE_CONNECT_IPV4      ← 原来的 IPV4 配置
        bool
        depends on LWIP_IPV4
        default n if EXAMPLE_CONNECT_THREAD
        default y

内容:

    # ============================================
    # 新增:静态 IP 配置选项(插入到这里)
    # ============================================
    choice EXAMPLE_CONNECT_IP_TYPE
        prompt "IP Address Type"
        default EXAMPLE_CONNECT_DHCP
        help
            Select how the device obtains IP address.
            DHCP: Automatic from server (default)
            Static: Manually configured IP

        config EXAMPLE_CONNECT_DHCP
            bool "DHCP (Automatic)"
            help
                Obtain IP address automatically from DHCP server

        config EXAMPLE_CONNECT_STATIC_IP
            bool "Static IP (Manual)"
            help
                Use manually configured static IP address
    endchoice

    # 静态 IP 的具体参数(仅在选中 Static IP 时显示)
    if EXAMPLE_CONNECT_STATIC_IP
        config EXAMPLE_STATIC_IP_ADDR
            string "Static IP Address"
            default "192.168.1.50"
            help
                Static IPv4 address for this device
                Example: 192.168.1.50

        config EXAMPLE_STATIC_NETMASK
            string "Netmask"
            default "255.255.255.0"
            help
                Network mask (subnet mask)
                Example: 255.255.255.0

        config EXAMPLE_STATIC_GW
            string "Gateway"
            default "192.168.1.1"
            help
                Default gateway IP address
                Can be left empty if not used
                Example: 192.168.1.1
    endif
    # ============================================
    # 新增结束
    # ============================================

第二步:connect.h 声明函数

protocol_examples_common/include/protocol_examples_common.h 中添加:

esp_err_t example_connect_static_ip(void);

第三步:connect.c 实现静态IP逻辑

核心思路:在创建 esp_netif 后、启动网络前,设置静态IP配置。

修改位置1:添加头文件

在文件顶部确认包含 lwip/inet.h(用于 IP 地址转换)

#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/inet.h"          // ← 确保有这个,用于 ipaddr_aton()

修改驱动添加具体驱动逻辑

connect.c

/*
 * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

#include <string.h>
#include "protocol_examples_common.h"
#include "example_common_private.h"
#include "sdkconfig.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_wifi_default.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "lwip/err.h"
#include "lwip/sys.h"

#include "lwip/ip4_addr.h"

static const char *TAG = "example_common";

#if CONFIG_EXAMPLE_CONNECT_IPV6
/* types of ipv6 addresses to be displayed on ipv6 events */
const char *example_ipv6_addr_types_to_str[6] = {
    "ESP_IP6_ADDR_IS_UNKNOWN",
    "ESP_IP6_ADDR_IS_GLOBAL",
    "ESP_IP6_ADDR_IS_LINK_LOCAL",
    "ESP_IP6_ADDR_IS_SITE_LOCAL",
    "ESP_IP6_ADDR_IS_UNIQUE_LOCAL",
    "ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6"};
#endif

/**
 * @brief Checks the netif description if it contains specified prefix.
 * All netifs created within common connect component are prefixed with the module TAG,
 * so it returns true if the specified netif is owned by this module
 */
bool example_is_our_netif(const char *prefix, esp_netif_t *netif)
{
    return strncmp(prefix, esp_netif_get_desc(netif), strlen(prefix) - 1) == 0;
}

static bool netif_desc_matches_with(esp_netif_t *netif, void *ctx)
{
    return strcmp(ctx, esp_netif_get_desc(netif)) == 0;
}

esp_netif_t *get_example_netif_from_desc(const char *desc)
{
    return esp_netif_find_if(netif_desc_matches_with, (void *)desc);
}

static esp_err_t print_all_ips_tcpip(void *ctx)
{
    const char *prefix = ctx;
    // iterate over active interfaces, and print out IPs of "our" netifs
    esp_netif_t *netif = NULL;
    while ((netif = esp_netif_next_unsafe(netif)) != NULL)
    {
        if (example_is_our_netif(prefix, netif))
        {
            ESP_LOGI(TAG, "Connected to %s", esp_netif_get_desc(netif));
#if CONFIG_EXAMPLE_CONNECT_IPV4
            esp_netif_ip_info_t ip;
            ESP_ERROR_CHECK(esp_netif_get_ip_info(netif, &ip));

            ESP_LOGI(TAG, "- IPv4 address: " IPSTR ",", IP2STR(&ip.ip));
#endif
#if CONFIG_EXAMPLE_CONNECT_IPV6
            esp_ip6_addr_t ip6[MAX_IP6_ADDRS_PER_NETIF];
            int ip6_addrs = esp_netif_get_all_ip6(netif, ip6);
            for (int j = 0; j < ip6_addrs; ++j)
            {
                esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j]));
                ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(ip6[j]), example_ipv6_addr_types_to_str[ipv6_type]);
            }
#endif
        }
    }
    return ESP_OK;
}

void example_print_all_netif_ips(const char *prefix)
{
    // Print all IPs in TCPIP context to avoid potential races of removing/adding netifs when iterating over the list
    esp_netif_tcpip_exec(print_all_ips_tcpip, (void *)prefix);
}

esp_err_t example_connect(void)
{
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
    if (example_ethernet_connect() != ESP_OK)
    {
        return ESP_FAIL;
    }

    // ============================================
    // 新增:以太网静态IP配置(插入到这里)
    // ============================================
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
    {
        // 获取以太网接口句柄
        esp_netif_t *eth_netif = esp_netif_get_handle_from_ifkey("ETH_DEF");
        if (eth_netif != NULL) {
            ESP_LOGI(TAG, "Applying static IP to Ethernet interface");
            esp_err_t err = example_set_static_ip(eth_netif);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to set static IP for Ethernet: %s", esp_err_to_name(err));
                return err;
            }
        } else {
            ESP_LOGW(TAG, "Ethernet interface not found, cannot set static IP");
        }
    }
#endif
    // ============================================
    
    ESP_ERROR_CHECK(esp_register_shutdown_handler(&example_ethernet_shutdown));
#endif

#if CONFIG_EXAMPLE_CONNECT_WIFI
    if (example_wifi_connect() != ESP_OK)
    {
        return ESP_FAIL;
    }

    // ============================================
    // 新增:为WiFi接口配置静态IP
    // ============================================
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
    esp_netif_t *wifi_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    if (wifi_netif != NULL)
    {
        example_set_static_ip(wifi_netif);
    }
#endif
    // ============================================

    ESP_ERROR_CHECK(esp_register_shutdown_handler(&example_ethernet_shutdown));
#endif

#if CONFIG_EXAMPLE_CONNECT_THREAD
    if (example_thread_connect() != ESP_OK) {
        return ESP_FAIL;
    }
    
    // ============================================
    // 新增:Thread静态IP配置(可选,IPv6通常用SLAAC)
    // ============================================
    // 注意:Thread通常使用IPv6,静态IPv4配置可能不适用
    // 如需配置,获取接口句柄:"THREAD_DEF"
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
    ESP_LOGW(TAG, "Static IP for Thread not implemented, using default");
#endif
    // ============================================
    
    ESP_ERROR_CHECK(esp_register_shutdown_handler(&example_thread_shutdown));
#endif

#if CONFIG_EXAMPLE_CONNECT_PPP
    if (example_ppp_connect() != ESP_OK) {
        return ESP_FAIL;
    }
    
    // ============================================
    // 新增:PPP静态IP配置(可选,PPP通常由服务器分配)
    // ============================================
    // 注意:PPP通常从服务器获取IP,静态配置可能不适用
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
    ESP_LOGW(TAG, "Static IP for PPP not implemented, using peer assigned");
#endif
    // ============================================
    
    ESP_ERROR_CHECK(esp_register_shutdown_handler(&example_ppp_shutdown));
#endif

#if CONFIG_EXAMPLE_CONNECT_ETHERNET
    example_print_all_netif_ips(EXAMPLE_NETIF_DESC_ETH);
#endif

#if CONFIG_EXAMPLE_CONNECT_WIFI
    example_print_all_netif_ips(EXAMPLE_NETIF_DESC_STA);
#endif

#if CONFIG_EXAMPLE_CONNECT_THREAD
    example_print_all_netif_ips(EXAMPLE_NETIF_DESC_THREAD);
#endif

#if CONFIG_EXAMPLE_CONNECT_PPP
    example_print_all_netif_ips(EXAMPLE_NETIF_DESC_PPP);
#endif

    return ESP_OK;
}

esp_err_t example_disconnect(void)
{
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
    example_ethernet_shutdown();
    ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&example_ethernet_shutdown));
#endif
#if CONFIG_EXAMPLE_CONNECT_WIFI
    example_wifi_shutdown();
    ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&example_wifi_shutdown));
#endif
    return ESP_OK;
}

// ============================================
// 新增:静态IP配置函数(添加到这里)
// ============================================

#if CONFIG_EXAMPLE_CONNECT_STATIC_IP

/**
 * @brief 为指定的网络接口配置静态IP地址
 *
 * @param netif 网络接口句柄(以太网或WiFi)
 * @return esp_err_t ESP_OK 成功,其他失败
 */
esp_err_t example_set_static_ip(esp_netif_t *netif)
{
    ESP_LOGI(TAG, "Configuring static IP address");

    // 1.停止DHCP
    esp_err_t err = esp_netif_dhcpc_stop(netif);
    if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
        ESP_LOGE(TAG, "Failed to stop DHCP: %s", esp_err_to_name(err));
        return err;
    }
    
    // 2.设置静态IP(使用 esp_ip4addr_aton,直接赋值给 .addr 成员)
    esp_netif_ip_info_t ip_info = {0};
    
    ip_info.ip.addr = esp_ip4addr_aton(CONFIG_EXAMPLE_STATIC_IP_ADDR);
    ip_info.netmask.addr = esp_ip4addr_aton(CONFIG_EXAMPLE_STATIC_NETMASK);
    ip_info.gw.addr = esp_ip4addr_aton(CONFIG_EXAMPLE_STATIC_GW);

    // 3. 应用静态IP配置
    err = esp_netif_set_ip_info(netif, &ip_info);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to set static IP: %s", esp_err_to_name(err));
        return err;
    }

    // 4. 记录配置结果
    ESP_LOGI(TAG, "Static IP configured successfully:");
    ESP_LOGI(TAG, "  IP:      " IPSTR, IP2STR(&ip_info.ip));
    ESP_LOGI(TAG, "  Netmask: " IPSTR, IP2STR(&ip_info.netmask));
    ESP_LOGI(TAG, "  Gateway: " IPSTR, IP2STR(&ip_info.gw));

    return ESP_OK;
}

#endif // CONFIG_EXAMPLE_CONNECT_STATIC_IP
// ============================================
// 新增结束
// ============================================

eth_connect.c

/*
 * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

#include <string.h>
#include "protocol_examples_common.h"
#include "example_common_private.h"
#include "esp_event.h"
#include "esp_eth.h"
#if CONFIG_ETH_USE_SPI_ETHERNET
#include "driver/spi_master.h"
#endif // CONFIG_ETH_USE_SPI_ETHERNET
#include "esp_log.h"
#include "esp_mac.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"


static const char *TAG = "ethernet_connect";
static SemaphoreHandle_t s_semph_get_ip_addrs = NULL;
#if CONFIG_EXAMPLE_CONNECT_IPV6
static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL;
#endif

static esp_netif_t *eth_start(void);
static void eth_stop(void);


/** Event handler for Ethernet events */

static void eth_on_got_ip(void *arg, esp_event_base_t event_base,
                      int32_t event_id, void *event_data)
{
    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    if (!example_is_our_netif(EXAMPLE_NETIF_DESC_ETH, event->esp_netif)) {
        return;
    }
    ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
    xSemaphoreGive(s_semph_get_ip_addrs);
}

#if CONFIG_EXAMPLE_CONNECT_IPV6

static void eth_on_got_ipv6(void *arg, esp_event_base_t event_base,
                        int32_t event_id, void *event_data)
{
    ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
    if (!example_is_our_netif(EXAMPLE_NETIF_DESC_ETH, event->esp_netif)) {
        return;
    }
    esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
    ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif),
             IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]);
    if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) {
        xSemaphoreGive(s_semph_get_ip6_addrs);
    }
}

static void on_eth_event(void *esp_netif, esp_event_base_t event_base,
                         int32_t event_id, void *event_data)
{
    switch (event_id) {
    case ETHERNET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "Ethernet Link Up");
        ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(esp_netif));
        break;
    default:
        break;
    }
}

#endif // CONFIG_EXAMPLE_CONNECT_IPV6

static esp_eth_handle_t s_eth_handle = NULL;
static esp_eth_mac_t *s_mac = NULL;
static esp_eth_phy_t *s_phy = NULL;
static esp_eth_netif_glue_handle_t s_eth_glue = NULL;

static esp_netif_t *eth_start(void)
{
    esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
    // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
    esp_netif_config.if_desc = EXAMPLE_NETIF_DESC_ETH;
    esp_netif_config.route_prio = 64;
    esp_netif_config_t netif_config = {
        .base = &esp_netif_config,
        .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
    };
    esp_netif_t *netif = esp_netif_new(&netif_config);
    assert(netif);

    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    mac_config.rx_task_stack_size = CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE;
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    phy_config.phy_addr = CONFIG_EXAMPLE_ETH_PHY_ADDR;
    phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_PHY_RST_GPIO;
#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
    eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
    esp32_emac_config.smi_gpio.mdc_num = CONFIG_EXAMPLE_ETH_MDC_GPIO;
    esp32_emac_config.smi_gpio.mdio_num = CONFIG_EXAMPLE_ETH_MDIO_GPIO;
    s_mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#if CONFIG_EXAMPLE_ETH_PHY_GENERIC
    s_phy = esp_eth_phy_new_generic(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_IP101
    s_phy = esp_eth_phy_new_ip101(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
    s_phy = esp_eth_phy_new_rtl8201(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
    s_phy = esp_eth_phy_new_lan87xx(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
    s_phy = esp_eth_phy_new_dp83848(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ80XX
    s_phy = esp_eth_phy_new_ksz80xx(&phy_config);
#endif
#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
    gpio_install_isr_service(0);
    spi_bus_config_t buscfg = {
        .miso_io_num = CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO,
        .mosi_io_num = CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO,
        .sclk_io_num = CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
    spi_device_interface_config_t spi_devcfg = {
        .mode = 0,
        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
        .spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
        .queue_size = 20
    };
#if CONFIG_EXAMPLE_USE_DM9051
    /* dm9051 ethernet driver is based on spi driver */
    eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
    dm9051_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
    s_mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
    s_phy = esp_eth_phy_new_dm9051(&phy_config);
#elif CONFIG_EXAMPLE_USE_W5500
    /* w5500 ethernet driver is based on spi driver */
    eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
    w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
    s_mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
    s_phy = esp_eth_phy_new_w5500(&phy_config);
#endif
#elif CONFIG_EXAMPLE_USE_OPENETH
    phy_config.autonego_timeout_ms = 100;
    s_mac = esp_eth_mac_new_openeth(&mac_config);
    s_phy = esp_eth_phy_new_dp83848(&phy_config);
#endif

    // Install Ethernet driver
    esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy);
    ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));

#if CONFIG_EXAMPLE_USE_SPI_ETHERNET
    /* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
       We set the ESP_MAC_ETH mac address as the default, if you want to use ESP_MAC_EFUSE_CUSTOM mac address, please enable the
       configuration: `ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC`
    */
    uint8_t eth_mac[6] = {0};
    ESP_ERROR_CHECK(esp_read_mac(eth_mac, ESP_MAC_ETH));
    ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_MAC_ADDR, eth_mac));
#endif // CONFIG_EXAMPLE_USE_SPI_ETHERNET

    // combine driver with netif
    s_eth_glue = esp_eth_new_netif_glue(s_eth_handle);
    esp_netif_attach(netif, s_eth_glue);

    // Register user defined event handlers
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &eth_on_got_ip, NULL));
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event, netif));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &eth_on_got_ipv6, NULL));
#endif

    esp_eth_start(s_eth_handle);
    return netif;
}

static void eth_stop(void)
{
    esp_netif_t *eth_netif = get_example_netif_from_desc(EXAMPLE_NETIF_DESC_ETH);
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, &eth_on_got_ip));
#if CONFIG_EXAMPLE_CONNECT_IPV6
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &eth_on_got_ipv6));
    ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event));
#endif
    ESP_ERROR_CHECK(esp_eth_stop(s_eth_handle));
    ESP_ERROR_CHECK(esp_eth_del_netif_glue(s_eth_glue));
    ESP_ERROR_CHECK(esp_eth_driver_uninstall(s_eth_handle));
    s_eth_handle = NULL;
    ESP_ERROR_CHECK(s_phy->del(s_phy));
    ESP_ERROR_CHECK(s_mac->del(s_mac));

    esp_netif_destroy(eth_netif);
}

esp_eth_handle_t get_example_eth_handle(void)
{
    return s_eth_handle;
}

/* tear down connection, release resources */
void example_ethernet_shutdown(void)
{
    if (s_semph_get_ip_addrs == NULL) {
        return;
    }
    vSemaphoreDelete(s_semph_get_ip_addrs);
    s_semph_get_ip_addrs = NULL;
#if CONFIG_EXAMPLE_CONNECT_IPV6
    vSemaphoreDelete(s_semph_get_ip6_addrs);
    s_semph_get_ip6_addrs = NULL;
#endif
    eth_stop();
}

esp_err_t example_ethernet_connect(void)
{
#if CONFIG_EXAMPLE_CONNECT_IPV4
    s_semph_get_ip_addrs = xSemaphoreCreateBinary();
    if (s_semph_get_ip_addrs == NULL) {
        return ESP_ERR_NO_MEM;
    }
#endif
#if CONFIG_EXAMPLE_CONNECT_IPV6
    s_semph_get_ip6_addrs = xSemaphoreCreateBinary();
    if (s_semph_get_ip6_addrs == NULL) {
        vSemaphoreDelete(s_semph_get_ip_addrs);
        return ESP_ERR_NO_MEM;
    }
#endif
    eth_start();

    // ============================================
    // 修改:Static IP 模式下跳过 DHCP 等待
    // ============================================
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
    // 静态IP模式:不需要等待DHCP,直接返回成功
    ESP_LOGI(TAG, "Static IP mode, skipping DHCP wait");
    // 短暂延时确保以太网硬件就绪
    vTaskDelay(pdMS_TO_TICKS(100));
    
#else
    // DHCP模式:原有逻辑,阻塞等待IP分配
    ESP_LOGI(TAG, "Waiting for IP(s).");
    
#if CONFIG_EXAMPLE_CONNECT_IPV4
    xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY);
#endif
#if CONFIG_EXAMPLE_CONNECT_IPV6
    xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY);
#endif

#endif  // CONFIG_EXAMPLE_CONNECT_STATIC_IP
    // ============================================

    return ESP_OK;
}

wifi_connect.c

/*
 * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
 */

#include <string.h>
#include "protocol_examples_common.h"
#include "example_common_private.h"
#include "esp_log.h"

#if CONFIG_EXAMPLE_CONNECT_WIFI

static const char *TAG = "example_connect";
static esp_netif_t *s_example_sta_netif = NULL;
static SemaphoreHandle_t s_semph_get_ip_addrs = NULL;
#if CONFIG_EXAMPLE_CONNECT_IPV6
static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL;
#endif

static int s_retry_num = 0;

static void example_handler_on_wifi_disconnect(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    s_retry_num++;
    if (s_retry_num > CONFIG_EXAMPLE_WIFI_CONN_MAX_RETRY) {
        ESP_LOGI(TAG, "WiFi Connect failed %d times, stop reconnect.", s_retry_num);
        /* let example_wifi_sta_do_connect() return */
        if (s_semph_get_ip_addrs) {
            xSemaphoreGive(s_semph_get_ip_addrs);
        }
#if CONFIG_EXAMPLE_CONNECT_IPV6
        if (s_semph_get_ip6_addrs) {
            xSemaphoreGive(s_semph_get_ip6_addrs);
        }
#endif
        example_wifi_sta_do_disconnect();
        return;
    }
    wifi_event_sta_disconnected_t *disconn = event_data;
    if (disconn->reason == WIFI_REASON_ROAMING) {
        ESP_LOGD(TAG, "station roaming, do nothing");
        return;
    }
    ESP_LOGI(TAG, "Wi-Fi disconnected %d, trying to reconnect...", disconn->reason);
    esp_err_t err = esp_wifi_connect();
    if (err == ESP_ERR_WIFI_NOT_STARTED) {
        return;
    }
    ESP_ERROR_CHECK(err);
}

static void example_handler_on_wifi_connect(void *esp_netif, esp_event_base_t event_base,
                            int32_t event_id, void *event_data)
{
#if CONFIG_EXAMPLE_CONNECT_IPV6
    esp_netif_create_ip6_linklocal(esp_netif);
#endif // CONFIG_EXAMPLE_CONNECT_IPV6
}

static void example_handler_on_sta_got_ip(void *arg, esp_event_base_t event_base,
                      int32_t event_id, void *event_data)
{
    s_retry_num = 0;
    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    if (!example_is_our_netif(EXAMPLE_NETIF_DESC_STA, event->esp_netif)) {
        return;
    }
    ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
    if (s_semph_get_ip_addrs) {
        xSemaphoreGive(s_semph_get_ip_addrs);
    } else {
        ESP_LOGI(TAG, "- IPv4 address: " IPSTR ",", IP2STR(&event->ip_info.ip));
    }
}

#if CONFIG_EXAMPLE_CONNECT_IPV6
static void example_handler_on_sta_got_ipv6(void *arg, esp_event_base_t event_base,
                        int32_t event_id, void *event_data)
{
    ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
    if (!example_is_our_netif(EXAMPLE_NETIF_DESC_STA, event->esp_netif)) {
        return;
    }
    esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
    ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif),
             IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]);

    if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) {
        if (s_semph_get_ip6_addrs) {
            xSemaphoreGive(s_semph_get_ip6_addrs);
        } else {
            ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]);
        }
    }
}
#endif // CONFIG_EXAMPLE_CONNECT_IPV6


void example_wifi_start(void)
{
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA();
    // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
    esp_netif_config.if_desc = EXAMPLE_NETIF_DESC_STA;
    esp_netif_config.route_prio = 128;
    s_example_sta_netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
    esp_wifi_set_default_wifi_sta_handlers();

    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());
}


void example_wifi_stop(void)
{
    esp_err_t err = esp_wifi_stop();
    if (err == ESP_ERR_WIFI_NOT_INIT) {
        return;
    }
    ESP_ERROR_CHECK(err);
    ESP_ERROR_CHECK(esp_wifi_deinit());
    ESP_ERROR_CHECK(esp_wifi_clear_default_wifi_driver_and_handlers(s_example_sta_netif));
    esp_netif_destroy(s_example_sta_netif);
    s_example_sta_netif = NULL;
}

esp_err_t example_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait)
{
    if (wait) {
        s_semph_get_ip_addrs = xSemaphoreCreateBinary();
        if (s_semph_get_ip_addrs == NULL) {
            return ESP_ERR_NO_MEM;
        }
#if CONFIG_EXAMPLE_CONNECT_IPV6
        s_semph_get_ip6_addrs = xSemaphoreCreateBinary();
        if (s_semph_get_ip6_addrs == NULL) {
            vSemaphoreDelete(s_semph_get_ip_addrs);
            return ESP_ERR_NO_MEM;
        }
#endif
    }
    s_retry_num = 0;
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &example_handler_on_wifi_disconnect, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &example_handler_on_sta_got_ip, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &example_handler_on_wifi_connect, s_example_sta_netif));
#if CONFIG_EXAMPLE_CONNECT_IPV6
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &example_handler_on_sta_got_ipv6, NULL));
#endif

    ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    esp_err_t ret = esp_wifi_connect();
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "WiFi connect failed! ret:%x", ret);
        return ret;
    }
    
    // ============================================
    // 修改:Static IP 模式下跳过 DHCP 等待
    // ============================================
    if (wait) {
#if CONFIG_EXAMPLE_CONNECT_STATIC_IP
        // 静态IP模式:不需要等待DHCP,短暂延时确保WiFi关联完成
        ESP_LOGI(TAG, "Static IP mode, waiting for WiFi association...");
        // 等待WiFi连接成功(不是等待IP),最多5秒
        int wait_count = 0;
        while (s_retry_num == 0 && wait_count < 50) {
            vTaskDelay(pdMS_TO_TICKS(100));
            wait_count++;
        }
        if (s_retry_num > 0) {
            ESP_LOGW(TAG, "WiFi connection failed");
            return ESP_FAIL;
        }
        ESP_LOGI(TAG, "WiFi associated, static IP will be configured later");
        
#else
        // DHCP模式:原有逻辑,阻塞等待IP分配
        ESP_LOGI(TAG, "Waiting for IP(s)");
#if CONFIG_EXAMPLE_CONNECT_IPV4
        xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY);
        vSemaphoreDelete(s_semph_get_ip_addrs);
        s_semph_get_ip_addrs = NULL;
#endif
#if CONFIG_EXAMPLE_CONNECT_IPV6
        xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY);
        vSemaphoreDelete(s_semph_get_ip6_addrs);
        s_semph_get_ip6_addrs = NULL;
#endif
        if (s_retry_num > CONFIG_EXAMPLE_WIFI_CONN_MAX_RETRY) {
            return ESP_FAIL;
        }
#endif  // CONFIG_EXAMPLE_CONNECT_STATIC_IP
    }
    // ============================================

    return ESP_OK;
}

esp_err_t example_wifi_sta_do_disconnect(void)
{
    ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &example_handler_on_wifi_disconnect));
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &example_handler_on_sta_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &example_handler_on_wifi_connect));
#if CONFIG_EXAMPLE_CONNECT_IPV6
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &example_handler_on_sta_got_ipv6));
#endif
    return esp_wifi_disconnect();
}

void example_wifi_shutdown(void)
{
    example_wifi_sta_do_disconnect();
    example_wifi_stop();
}

esp_err_t example_wifi_connect(void)
{
    ESP_LOGI(TAG, "Start example_connect.");
    example_wifi_start();
    wifi_config_t wifi_config = {
        .sta = {
#if !CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
            .ssid = CONFIG_EXAMPLE_WIFI_SSID,
            .password = CONFIG_EXAMPLE_WIFI_PASSWORD,
#endif
            .scan_method = EXAMPLE_WIFI_SCAN_METHOD,
            .sort_method = EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD,
            .threshold.rssi = CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD,
            .threshold.authmode = EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD,
        },
    };
#if CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN
    example_configure_stdin_stdout();
    char buf[sizeof(wifi_config.sta.ssid)+sizeof(wifi_config.sta.password)+2] = {0};
    ESP_LOGI(TAG, "Please input ssid password:");
    fgets(buf, sizeof(buf), stdin);
    int len = strlen(buf);
    buf[len-1] = '\0'; /* removes '\n' */
    memset(wifi_config.sta.ssid, 0, sizeof(wifi_config.sta.ssid));

    char *rest = NULL;
    char *temp = strtok_r(buf, " ", &rest);
    strncpy((char*)wifi_config.sta.ssid, temp, sizeof(wifi_config.sta.ssid));
    memset(wifi_config.sta.password, 0, sizeof(wifi_config.sta.password));
    temp = strtok_r(NULL, " ", &rest);
    if (temp) {
        strncpy((char*)wifi_config.sta.password, temp, sizeof(wifi_config.sta.password));
    } else {
        wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN;
    }
#endif
    return example_wifi_sta_do_connect(wifi_config, true);
}


#endif /* CONFIG_EXAMPLE_CONNECT_WIFI */

main.c

/*
 * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

// FreeModbus Slave Example ESP32
// ESP32 FreeModbus 从机示例程序 - 超详细注释版本

#include <stdio.h>
// 标准输入输出库,提供 printf(), sprintf(), snprintf() 等函数
// 输入:格式化字符串和参数
// 输出:格式化的文本到标准输出或字符串缓冲区

#include "esp_err.h"
// ESP-IDF 错误处理头文件
// 定义 esp_err_t 类型:int32_t,0 表示成功(ESP_OK),非0表示各种错误码
// 常用错误码:ESP_OK(0), ESP_FAIL(-1), ESP_ERR_NO_MEM(0x101), ESP_ERR_INVALID_ARG(0x102) 等

#include "sdkconfig.h"
// SDK 配置文件,由 menuconfig 工具生成
// 包含所有 Kconfig 配置选项的宏定义,如 CONFIG_FMB_TCP_PORT_DEFAULT 等

#include "esp_log.h"
// ESP-IDF 日志系统头文件
// 提供分级日志功能:ESP_LOGE(错误), ESP_LOGW(警告), ESP_LOGI(信息), ESP_LOGD(调试), ESP_LOGV(详细)
// 输入:TAG 标签和格式化字符串
// 输出:带时间戳和颜色区分的日志到串口

#include "esp_system.h"
// ESP32 系统级功能头文件
// 提供系统信息获取、重启、内存管理等功能
// 主要函数:esp_get_free_heap_size(), esp_restart(), esp_chip_info() 等

#include "esp_wifi.h"
// ESP32 WiFi 驱动头文件
// 提供 WiFi STA/AP 模式配置、连接、扫描等功能
// 主要函数:esp_wifi_init(), esp_wifi_set_mode(), esp_wifi_start(), esp_wifi_connect() 等

#include "esp_event.h"
// ESP-IDF 事件系统头文件
// 提供异步事件处理机制,基于 FreeRTOS 的事件循环
// 主要函数:esp_event_loop_create_default(), esp_event_handler_register() 等

#include "esp_log.h"
// 日志头文件(重复包含,编译器会处理)

#include "nvs_flash.h"
// 非易失性存储(Non-Volatile Storage)头文件
// 提供键值对存储,用于保存 WiFi 配置、校准数据等
// 主要函数:nvs_flash_init(), nvs_open(), nvs_set_i32(), nvs_get_str() 等

#include "mdns.h"
// mDNS (多播DNS)服务头文件
// 用于局域网内的服务发现,无需知道 IP 地址即可通过主机名访问设备
// 主要函数:mdns_init(), mdns_hostname_set(), mdns_service_add() 等

#include "esp_netif.h"
// ESP32 网络接口抽象层(Network Interface Abstraction)
// 统一处理 WiFi、以太网、PPP 等不同网络接口的 TCP/IP 配置
// 主要函数:esp_netif_init(), esp_netif_create_default_wifi_sta() 等

#include "esp_mac.h"
// MAC 地址操作头文件
// 提供读取和转换 MAC 地址的功能
// 主要函数:esp_read_mac(), esp_derive_local_mac() 等

#include "protocol_examples_common.h"
// ESP-IDF 协议示例公共头文件
// 提供 example_connect(), example_disconnect(), get_example_netif() 等辅助函数
// 简化 WiFi/以太网连接的配置过程

#include "mbcontroller.h"
// Modbus 控制器头文件
// 包含 esp_modbus_master.h 和 esp_modbus_slave.h
// 提供 Modbus 主从机通用的类型定义和宏

#include "modbus_params.h"
// Modbus 参数结构体定义头文件(用户自定义)
// 定义以下全局变量:
// - holding_reg_params_t holding_reg_params;  // 保持寄存器参数
// - input_reg_params_t input_reg_params;      // 输入寄存器参数
// - coil_reg_params_t coil_reg_params;        // 线圈寄存器参数
// - discrete_reg_params_t discrete_reg_params; // 离散输入寄存器参数

// ============================================================================
// 宏定义部分 - 详细说明
// ============================================================================

#define MB_TCP_PORT_NUMBER      (CONFIG_FMB_TCP_PORT_DEFAULT)
// Modbus TCP 标准端口号,默认 502
// 来源:sdkconfig.h 中的 CONFIG_FMB_TCP_PORT_DEFAULT 配置项
// 类型:uint16_t
// 用途:mbc_slave_setup() 的 comm_info.ip_port 参数

#define MB_MDNS_PORT            (502)
// mDNS 服务广播端口号,与 Modbus TCP 端口一致
// 类型:uint16_t
// 用途:mdns_service_add() 的 port 参数

// 以下宏用于计算 Modbus 寄存器的偏移地址
// Modbus 协议中寄存器地址以 16 位字(Word)为单位,偏移量 = 字节偏移 / 2

#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) >> 1))
// 功能:计算保持寄存器结构体中某字段的 Modbus 地址偏移
// 输入:field - holding_reg_params_t 结构体中的字段名
// 处理:offsetof 获取字段字节偏移,>>1 相当于除以2(转换为字偏移)
// 输出:uint16_t 类型的 Modbus 寄存器地址偏移
// 示例:HOLD_OFFSET(holding_data0) = 0, HOLD_OFFSET(holding_data4) = 8
// 说明:每个 float 占 4 字节 = 2 个 Modbus 寄存器,所以 data4 的偏移是 4*2=8

#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) >> 1))
// 功能:计算输入寄存器结构体中某字段的 Modbus 地址偏移
// 输入/输出/处理:同上,针对 input_reg_params_t 结构体

#define MB_REG_DISCRETE_INPUT_START         (0x0000)
// 离散输入寄存器的起始 Modbus 地址
// 类型:uint16_t
// 说明:离散输入是只读的布尔量,对应 modbus_params.h 中的 discrete_reg_params

#define MB_REG_COILS_START                  (0x0000)
// 线圈寄存器的起始 Modbus 地址
// 类型:uint16_t
// 说明:线圈是可读写的布尔量,对应 modbus_params.h 中的 coil_reg_params

#define MB_REG_INPUT_START_AREA0            (INPUT_OFFSET(input_data0))
// 输入寄存器区域0的起始偏移
// 计算结果:0(input_data0 在结构体起始位置)
// 用途:mbc_slave_set_descriptor() 的 reg_area.start_offset 参数

#define MB_REG_INPUT_START_AREA1            (INPUT_OFFSET(input_data4))
// 输入寄存器区域1的起始偏移
// 计算结果:8(input_data4 前面有4个float,4*4/2=8个字)

#define MB_REG_HOLDING_START_AREA0          (HOLD_OFFSET(holding_data0))
// 保持寄存器区域0的起始偏移:0

#define MB_REG_HOLDING_START_AREA1          (HOLD_OFFSET(holding_data4))
// 保持寄存器区域1的起始偏移:8

#define MB_PAR_INFO_GET_TOUT                (10)
// 获取参数信息的超时时间
// 单位:毫秒(ms)
// 用途:mbc_slave_get_param_info() 的 timeout 参数
// 说明:如果 10ms 内没有 Modbus 事件,函数返回 ESP_ERR_TIMEOUT

#define MB_CHAN_DATA_MAX_VAL                (10)
// 演示循环终止阈值
// 类型:float
// 用途:当 holding_data0 >= 10 时,演示程序结束
// 说明:用于控制示例程序的运行时长,实际应用中可删除

#define MB_CHAN_DATA_OFFSET                 (1.1f)
// holding_data0 的每次增量
// 类型:float
// 用途:每次访问 holding_data0 时增加该值
// 说明:1.1f 表示浮点数 1.1,用于演示浮点寄存器的累加

#define MB_READ_MASK                        (MB_EVENT_INPUT_REG_RD \
                                                | MB_EVENT_HOLDING_REG_RD \
                                                | MB_EVENT_DISCRETE_RD \
                                                | MB_EVENT_COILS_RD)
// Modbus 读事件掩码(位或组合)
// 组成:
//   - MB_EVENT_INPUT_REG_RD:输入寄存器读事件(位定义在 esp_modbus_common.h)
//   - MB_EVENT_HOLDING_REG_RD:保持寄存器读事件
//   - MB_EVENT_DISCRETE_RD:离散输入读事件
//   - MB_EVENT_COILS_RD:线圈读事件
// 用途:mbc_slave_check_event() 的参数,用于过滤读事件

#define MB_WRITE_MASK                       (MB_EVENT_HOLDING_REG_WR \
                                                | MB_EVENT_COILS_WR)
// Modbus 写事件掩码
// 组成:
//   - MB_EVENT_HOLDING_REG_WR:保持寄存器写事件
//   - MB_EVENT_COILS_WR:线圈写事件
// 说明:输入寄存器和离散输入是只读的,所以没有写事件

#define MB_READ_WRITE_MASK                  (MB_READ_MASK | MB_WRITE_MASK)
// 读写事件合并掩码
// 用途:监听所有寄存器访问事件(读+写)

#define MB_SLAVE_ADDR (CONFIG_MB_SLAVE_ADDR)
// Modbus 从机地址(Unit ID)
// 来源:sdkconfig.h 中的 CONFIG_MB_SLAVE_ADDR,默认通常为 1
// 类型:uint8_t
// 用途:mbc_slave_setup() 的 comm_info.slave_uid 参数
// 说明:在 Modbus TCP 中,从机地址包含在 MBAP 报文头中,用于区分不同从机

// ============================================================================
// 全局变量定义
// ============================================================================

static const char *TAG = "SLAVE_TEST";
// 日志标签字符串
// 类型:const char*
// 用途:ESP_LOGx 宏的第一个参数,用于标识日志来源模块
// 显示效果:I (1234) SLAVE_TEST: 日志内容

static portMUX_TYPE param_lock = portMUX_INITIALIZER_UNLOCKED;
// 临界区互斥锁(自旋锁)
// 类型:portMUX_TYPE(实际上是 uint32_t 的封装)
// 初始化:portMUX_INITIALIZER_UNLOCKED 表示初始未锁定状态
// 用途:保护共享资源(holding_reg_params)的并发访问
// 相关函数:
//   - portENTER_CRITICAL(&param_lock):进入临界区,禁用中断/调度器
//   - portEXIT_CRITICAL(&param_lock):退出临界区,恢复中断/调度器
// 说明:ESP32 是双核处理器,需要临界区保护防止竞态条件

// ============================================================================
// mDNS 服务相关代码(条件编译)
// ============================================================================

#if CONFIG_MB_MDNS_IP_RESOLVER
// 条件编译:仅在 menuconfig 中启用 CONFIG_MB_MDNS_IP_RESOLVER 时编译
// 功能:启用 mDNS 服务发现,允许 Modbus 主机通过域名而非 IP 地址连接

// 以下宏用于将 32 位设备 ID 拆分为 4 个字节,用于生成字符串

#define MB_ID_BYTE0(id) ((uint8_t)(id))
// 功能:提取 32 位整数的最低字节(第 0 字节,LSB)
// 输入:id - uint32_t 类型的设备 ID
// 输出:uint8_t 类型的第 0 字节
// 原理:(uint8_t) 强制转换会截断高 24 位,保留低 8 位

#define MB_ID_BYTE1(id) ((uint8_t)(((uint16_t)(id) >> 8) & 0xFF))
// 功能:提取 32 位整数的第 1 字节
// 输入:id - uint32_t 类型的设备 ID
// 处理:先右移 8 位,再与 0xFF 按位与确保只保留低 8 位
// 输出:uint8_t 类型的第 1 字节

#define MB_ID_BYTE2(id) ((uint8_t)(((uint32_t)(id) >> 16) & 0xFF))
// 功能:提取 32 位整数的第 2 字节
// 输入:id - uint32_t 类型的设备 ID
// 处理:右移 16 位,保留第 16-23 位

#define MB_ID_BYTE3(id) ((uint8_t)(((uint32_t)(id) >> 24) & 0xFF))
// 功能:提取 32 位整数的最高字节(第 3 字节,MSB)
// 输入:id - uint32_t 类型的设备 ID
// 处理:右移 24 位,保留最高 8 位

#define MB_ID2STR(id) MB_ID_BYTE0(id), MB_ID_BYTE1(id), MB_ID_BYTE2(id), MB_ID_BYTE3(id)
// 功能:将 32 位 ID 展开为 4 个 uint8_t 参数,用于 sprintf 的格式化
// 输入:id - uint32_t 类型的设备 ID
// 输出:4 个独立的 uint8_t 值,可用 %02X%02X%02X%02X 格式化为 8 位十六进制字符串
// 示例:MB_ID2STR(0x12345678) 展开为 0x78, 0x56, 0x34, 0x12(小端序)

#if CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT
#define MB_DEVICE_ID (uint32_t)CONFIG_FMB_CONTROLLER_SLAVE_ID
#endif
// 条件定义:如果支持从机 ID 功能,定义 MB_DEVICE_ID 宏
// 来源:CONFIG_FMB_CONTROLLER_SLAVE_ID 来自 menuconfig 配置
// 类型:uint32_t
// 用途:生成唯一的设备标识字符串

#define MB_MDNS_INSTANCE(pref) pref"mb_slave_tcp"
// 功能:生成 mDNS 实例名称
// 输入:pref - 前缀字符串
// 输出:拼接后的字符串,如 MB_MDNS_INSTANCE("esp32_") = "esp32_mb_slave_tcp"
// 原理:C 语言预处理器字符串自动拼接

// ----------------------------------------------------------------------------
// 内联函数:gen_mac_str
// ----------------------------------------------------------------------------

// convert mac from binary format to string
// 功能:将 6 字节二进制 MAC 地址转换为冒号分隔的十六进制字符串
// 输入参数:
//   - mac: const uint8_t* 类型,指向 6 字节 MAC 地址数组的指针
//   - pref: char* 类型,前缀字符串,可为空字符串 "\0"
//   - mac_str: char* 类型,输出缓冲区,至少需 13 字节(6*2字符 + 1结束符)
// 输出:
//   - 返回值:char* 类型,指向 mac_str(方便链式调用)
//   - mac_str 缓冲区:填充格式化后的 MAC 地址字符串,如 "A1B2C3D4E5F6"
// 示例:gen_mac_str(sta_mac, "DEV_", buffer) -> "DEV_A1B2C3D4E5F6"
static inline char* gen_mac_str(const uint8_t* mac, char* pref, char* mac_str)
{
    // sprintf 格式化字符串
    // 格式说明:
    //   %s - 插入 pref 前缀字符串
    //   %02X - 以两位大写十六进制格式输出一个字节,不足两位前补0
    //   MAC2STR(mac) - ESP-IDF 宏,将 mac[0..5] 展开为 6 个参数
    sprintf(mac_str, "%s%02X%02X%02X%02X%02X%02X", pref, MAC2STR(mac));
    return mac_str;
    // 返回缓冲区指针,支持如 printf("%s", gen_mac_str(...)) 的用法
}

// ----------------------------------------------------------------------------
// 内联函数:gen_id_str
// ----------------------------------------------------------------------------

// 功能:生成包含设备 ID 的服务名称字符串
// 输入参数:
//   - service_name: char* 类型,基础服务名称前缀
//   - slave_id_str: char* 类型,输出缓冲区,建议大小 32 字节
// 输出:
//   - 返回值:char* 类型,指向 slave_id_str
//   - slave_id_str 缓冲区:如 "my_service1234ABCD"(ID为0x1234ABCD时)
// 说明:将 32 位设备 ID 追加到服务名后,形成唯一标识
static inline char* gen_id_str(char* service_name, char* slave_id_str)
{
    // %02X%02X%02X%02X 格式需要 4 个 uint8_t 参数,MB_ID2STR 宏正好展开为 4 个
    sprintf(slave_id_str, "%s%02X%02X%02X%02X", service_name, MB_ID2STR(MB_DEVICE_ID));
    return slave_id_str;
}

// ----------------------------------------------------------------------------
// 内联函数:gen_host_name_str
// ----------------------------------------------------------------------------

// 功能:生成 mDNS 主机名
// 输入参数:
//   - service_name: char* 类型,服务名称
//   - name: char* 类型,输出缓冲区
// 输出:
//   - 返回值:char* 类型,指向 name
//   - name 缓冲区:格式为 "service_name_XX",XX 是从机地址的十六进制
// 示例:gen_host_name_str("mb_slave", buffer) -> "mb_slave_01"(地址为1时)
static inline char* gen_host_name_str(char* service_name, char* name)
{
    // %02X 将以两位十六进制格式输出 MB_SLAVE_ADDR
    sprintf(name, "%s_%02X", service_name, MB_SLAVE_ADDR);
    return name;
}

// ----------------------------------------------------------------------------
// 函数:start_mdns_service
// ----------------------------------------------------------------------------

// 功能:初始化并启动 mDNS 服务,注册 Modbus TCP 服务
// 输入参数:无
// 输出:无(通过 ESP_ERROR_CHECK 处理错误,失败会重启)
// 副作用:
//   - 初始化 mDNS 协议栈
//   - 设置主机名和实例名
//   - 注册 _modbus._tcp 服务
//   - 添加 TXT 记录(MAC 地址、设备 ID)
// 调用时机:在 WiFi 连接成功后调用
static void start_mdns_service(void)
{
    char temp_str[32] = {0};
    // 临时字符串缓冲区,32字节足以容纳各种格式化字符串
    // 初始化为0确保字符串以\0结尾
    
    uint8_t sta_mac[6] = {0};
    // 存储 WiFi STA 接口的 MAC 地址,6字节数组
    // 初始化为0避免未定义行为
    
    ESP_ERROR_CHECK(esp_read_mac(sta_mac, ESP_MAC_WIFI_STA));
    // 函数:esp_err_t esp_read_mac(uint8_t* mac, esp_mac_type_t type)
    // 功能:读取指定接口的 MAC 地址
    // 输入:
    //   - mac: uint8_t[6] 数组指针,用于接收 MAC 地址
    //   - type: esp_mac_type_t 类型,指定接口类型
    //     * ESP_MAC_WIFI_STA - WiFi Station 接口
    //     * ESP_MAC_WIFI_SOFTAP - WiFi SoftAP 接口
    //     * ESP_MAC_BT - 蓝牙接口
    //     * ESP_MAC_ETH - 以太网接口
    // 输出:
    //   - 返回值:esp_err_t,ESP_OK 表示成功
    //   - mac 数组:填充 6 字节 MAC 地址
    // 说明:ESP_ERROR_CHECK 宏会在失败时打印错误并调用 abort()
    
    char* hostname = gen_host_name_str(MB_MDNS_INSTANCE(""), temp_str);
    // 生成主机名,如 "mb_slave_tcp_01"
    // MB_MDNS_INSTANCE("") 展开为 "mb_slave_tcp"
    
    //initialize mDNS
    ESP_ERROR_CHECK(mdns_init());
    // 函数:esp_err_t mdns_init(void)
    // 功能:初始化 mDNS 协议栈,分配资源,启动 mDNS 任务
    // 输入:无
    // 输出:
    //   - 返回值:esp_err_t
    //     * ESP_OK - 初始化成功
    //     * ESP_ERR_INVALID_STATE - 已经初始化
    //     * ESP_ERR_NO_MEM - 内存不足
    // 副作用:创建后台任务处理 mDNS 报文
    
    //set mDNS hostname (required if you want to advertise services)
    ESP_ERROR_CHECK(mdns_hostname_set(hostname));
    // 函数:esp_err_t mdns_hostname_set(const char* hostname)
    // 功能:设置 mDNS 主机名,局域网内可通过 hostname.local 访问设备
    // 输入:
    //   - hostname: const char*,主机名字符串,符合 DNS 命名规范
    //     * 长度:1-63 字符
    //     * 字符:字母、数字、连字符,不能以连字符开头或结尾
    // 输出:
    //   - 返回值:esp_err_t,ESP_OK 表示成功
    // 示例:设置 "esp32-modbus" 后,可通过 esp32-modbus.local 访问
    
    ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
    // 打印日志确认主机名设置成功

    //set default mDNS instance name
    ESP_ERROR_CHECK(mdns_instance_name_set(MB_MDNS_INSTANCE("esp32_")));
    // 函数:esp_err_t mdns_instance_name_set(const char* instance_name)
    // 功能:设置服务的实例名称,用于区分同一服务的多个实例
    // 输入:instance_name - 实例描述字符串,如 "esp32_mb_slave_tcp"
    // 输出:esp_err_t,ESP_OK 表示成功
    // 说明:实例名是给人看的描述,主机名是机器解析用的标识

    //structure with TXT records
    mdns_txt_item_t serviceTxtData[] = {
        {"board","esp32"}
    };
    // TXT 记录结构体数组,用于存储服务的附加信息
    // mdns_txt_item_t 定义:
    //   typedef struct {
    //       const char* key;    // 键名,如 "board", "version"
    //       const char* value;  // 键值,如 "esp32", "1.0"
    //   } mdns_txt_item_t;
    // 用途:客户端可查询 TXT 记录获取设备信息
    
    //initialize service
    ESP_ERROR_CHECK(mdns_service_add(hostname, "_modbus", "_tcp", MB_MDNS_PORT, serviceTxtData, 1));
    // 函数:esp_err_t mdns_service_add(const char* instance, const char* service, 
    //                                   const char* proto, uint16_t port, 
    //                                   mdns_txt_item_t txt[], size_t num_items)
    // 功能:注册 mDNS 服务,使设备在局域网内可被发现
    // 输入:
    //   - instance: 实例名称,通常与主机名相同
    //   - service: 服务类型,如 "_modbus", "_http", "_ftp"
    //   - proto: 协议,"_tcp" 或 "_udp"
    //   - port: 服务端口号,这里是 502
    //   - txt: TXT 记录数组指针,可为 NULL
    //   - num_items: TXT 记录数量,这里是 1
    // 输出:
    //   - 返回值:esp_err_t
    //     * ESP_OK - 服务添加成功
    //     * ESP_ERR_INVALID_ARG - 参数错误
    //     * ESP_ERR_NO_MEM - 内存不足
    // 发现方式:其他设备可通过 DNS-SD 查询 _modbus._tcp 发现此服务
    
    //add mac key string text item
    ESP_ERROR_CHECK(mdns_service_txt_item_set("_modbus", "_tcp", "mac", gen_mac_str(sta_mac, "\0", temp_str)));
    // 函数:esp_err_t mdns_service_txt_item_set(const char* service, const char* proto,
    //                                            const char* key, const char* value)
    // 功能:添加或更新服务的 TXT 记录项
    // 输入:
    //   - service: 服务类型,"_modbus"
    //   - proto: 协议,"_tcp"
    //   - key: 键名,"mac"
    //   - value: 键值,如 "A1B2C3D4E5F6"(MAC地址字符串)
    // 输出:esp_err_t,ESP_OK 表示成功
    // 用途:客户端可通过 MAC 地址唯一识别设备
    
    //add slave id key txt item
    ESP_ERROR_CHECK( mdns_service_txt_item_set("_modbus", "_tcp", "mb_id", gen_id_str("\0", temp_str)));
    // 添加设备 ID 的 TXT 记录
    // key: "mb_id"
    // value: 8位十六进制设备 ID,如 "1234ABCD"
}

// ----------------------------------------------------------------------------
// 函数:stop_mdns_service
// ----------------------------------------------------------------------------

// 功能:停止 mDNS 服务,释放资源
// 输入参数:无
// 输出:无
// 副作用:停止 mDNS 任务,释放内存,取消服务注册
// 调用时机:程序退出或网络断开时
static void stop_mdns_service(void)
{
    mdns_free();
    // 函数:void mdns_free(void)
    // 功能:释放 mDNS 协议栈占用的所有资源
    // 输入:无
    // 输出:无
    // 副作用:
    //   - 停止 mDNS 任务
    //   - 释放所有服务记录
    //   - 关闭 UDP 套接字
    // 注意:调用后如需再次使用 mDNS,需要重新调用 mdns_init()
}

#endif
// 结束 CONFIG_MB_MDNS_IP_RESOLVER 条件编译块

// ============================================================================
// 函数:setup_reg_data
// ============================================================================

// 功能:初始化所有 Modbus 寄存器的默认值
// 输入参数:无
// 输出:无(修改全局变量)
// 副作用:修改以下全局结构体的字段:
//   - discrete_reg_params(离散输入)
//   - holding_reg_params(保持寄存器)
//   - coil_reg_params(线圈)
//   - input_reg_params(输入寄存器)
// 调用时机:Modbus 从机初始化完成后,开始服务前
static void setup_reg_data(void)
{
    // Define initial state of parameters
    // 定义参数的初始状态
    
    discrete_reg_params.discrete_input0 = 1;
    discrete_reg_params.discrete_input1 = 0;
    discrete_reg_params.discrete_input2 = 1;
    discrete_reg_params.discrete_input3 = 0;
    discrete_reg_params.discrete_input4 = 1;
    discrete_reg_params.discrete_input5 = 0;
    discrete_reg_params.discrete_input6 = 1;
    discrete_reg_params.discrete_input7 = 0;
    // 初始化 8 个离散输入为交替的 1 和 0
    // 类型:uint8_t(通常定义为位域或数组)
    // Modbus 地址:0-7(从 MB_REG_DISCRETE_INPUT_START 开始)
    // 访问权限:只读(主机不能写入)

    holding_reg_params.holding_data0 = 1.34;
    holding_reg_params.holding_data1 = 2.56;
    holding_reg_params.holding_data2 = 3.78;
    holding_reg_params.holding_data3 = 4.90;
    // 初始化保持寄存器区域0的 4 个浮点数
    // 类型:float(32位 IEEE 754 浮点)
    // Modbus 地址:0-7(每个 float 占 2 个寄存器)
    // 内存布局:data0 在地址 0-1,data1 在地址 2-3,以此类推
    // 访问权限:读写(主机可读取和写入)
    // 字节序:ESP32 是小端序,Modbus 是大端序,协议栈自动转换

    holding_reg_params.holding_data4 = 5.67;
    holding_reg_params.holding_data5 = 6.78;
    holding_reg_params.holding_data6 = 7.79;
    holding_reg_params.holding_data7 = 8.80;
    // 初始化保持寄存器区域1的 4 个浮点数
    // Modbus 地址:8-15(从 MB_REG_HOLDING_START_AREA1 开始)
    
    coil_reg_params.coils_port0 = 0x55;
    coil_reg_params.coils_port1 = 0xAA;
    // 初始化线圈寄存器
    // 0x55 = 01010101b(位 0,2,4,6 为 1)
    // 0xAA = 10101010b(位 1,3,5,7 为 1)
    // 类型:uint8_t(通常定义为结构体位域)
    // Modbus 地址:0-15(coils_port0 对应 0-7,coils_port1 对应 8-15)
    // 访问权限:读写
    
    input_reg_params.input_data0 = 1.12;
    input_reg_params.input_data1 = 2.34;
    input_reg_params.input_data2 = 3.56;
    input_reg_params.input_data3 = 4.78;
    input_reg_params.input_data4 = 1.12;
    input_reg_params.input_data5 = 2.34;
    input_reg_params.input_data6 = 3.56;
    input_reg_params.input_data7 = 4.78;
    // 初始化 8 个输入寄存器的浮点数值
    // 类型:float
    // Modbus 地址:0-15(区域0:0-7,区域1:8-15)
    // 访问权限:只读
}

// ============================================================================
// 函数:slave_operation_func
// ============================================================================

// 功能:Modbus 从机的主处理循环,监听并响应主机的寄存器访问请求
// 输入参数:
//   - arg: void* 类型,任务参数(未使用,为 NULL)
// 输出:无(void 返回)
// 副作用:
//   - 无限循环处理 Modbus 事件,直到 holding_data0 >= 10
//   - 打印详细的寄存器访问日志
//   - 累加 holding_data0 的值
//   - 修改 coil_reg_params.coils_port1 作为结束标志
// 执行环境:通常在单独的任务中运行,但此示例直接在 app_main 调用
static void slave_operation_func(void *arg)
{
    mb_param_info_t reg_info; 
    // 参数信息结构体,用于接收 Modbus 事件详情
    // 定义在 esp_modbus_slave.h:
    // typedef struct {
    //     uint32_t time_stamp;      // 事件时间戳(微秒)
    //     uint16_t mb_offset;       // Modbus 寄存器偏移地址
    //     mb_event_group_t type;    // 事件类型(读/写,寄存器类型)
    //     uint8_t* address;         // 实际内存地址指针
    //     size_t size;              // 访问的寄存器数量(字节数/2)
    // } mb_param_info_t;
    
    // keeps the Modbus registers access information

    ESP_LOGI(TAG, "Modbus slave stack initialized.");
    ESP_LOGI(TAG, "Start modbus test...");
    // 打印启动日志

    // The cycle below will be terminated when parameter holding_data0
    // incremented each access cycle reaches the CHAN_DATA_MAX_VAL value.
    // 循环终止条件:holding_data0 的值达到或超过 MB_CHAN_DATA_MAX_VAL(10)
    // 每次访问 holding_data0 时增加 1.1,大约需要 9 次访问后终止
    
    for(;holding_reg_params.holding_data0 < MB_CHAN_DATA_MAX_VAL;) {
        // 循环条件检查 holding_data0 的值
        
        // Check for read/write events of Modbus master for certain events
        (void)mbc_slave_check_event(MB_READ_WRITE_MASK);
        // 函数:mb_event_group_t mbc_slave_check_event(mb_event_group_t group)
        // 功能:检查是否有指定的 Modbus 事件发生(非阻塞或阻塞等待)
        // 输入:
        //   - group: mb_event_group_t 类型,事件位掩码,指定关心的事件类型
        //     * MB_EVENT_HOLDING_REG_RD - 保持寄存器读
        //     * MB_EVENT_HOLDING_REG_WR - 保持寄存器写
        //     * MB_EVENT_INPUT_REG_RD - 输入寄存器读
        //     * MB_EVENT_COILS_RD - 线圈读
        //     * MB_EVENT_COILS_WR - 线圈写
        //     * MB_EVENT_DISCRETE_RD - 离散输入读
        // 输出:
        //   - 返回值:mb_event_group_t,实际发生的事件位图
        // 行为:
        //   - 如果有事件发生,立即返回事件类型
        //   - 如果没有,阻塞等待直到有事件或超时(取决于实现)
        // (void) 表示忽略返回值,因为下面会调用 get_param_info 获取详细信息
        
        ESP_ERROR_CHECK_WITHOUT_ABORT(mbc_slave_get_param_info(&reg_info, MB_PAR_INFO_GET_TOUT));
        // 函数:esp_err_t mbc_slave_get_param_info(mb_param_info_t* reg_info, uint32_t timeout)
        // 功能:从事件队列获取详细的参数访问信息
        // 输入:
        //   - reg_info: mb_param_info_t* 指针,用于接收事件详情
        //   - timeout: uint32_t,超时时间(毫秒)
        //     * 0 - 不等待,立即返回
        //     * portMAX_DELAY - 永久等待
        //     * MB_PAR_INFO_GET_TOUT (10) - 等待最多 10ms
        // 输出:
        //   - 返回值:esp_err_t
        //     * ESP_OK - 成功获取信息
        //     * ESP_ERR_TIMEOUT - 超时,没有事件
        //     * ESP_ERR_INVALID_ARG - 参数错误
        //   - reg_info 结构体:填充事件详细信息
        // ESP_ERROR_CHECK_WITHOUT_ABORT:出错时打印日志但不重启
        
        const char* rw_str = (reg_info.type & MB_READ_MASK) ? "READ" : "WRITE";
        // 判断操作类型:如果事件类型与读掩码有交集,则为"READ",否则为"WRITE"
        // 用于日志输出标识读或写操作

        // Filter events and process them accordingly
        // 根据事件类型过滤并分别处理
        
        if(reg_info.type & (MB_EVENT_HOLDING_REG_WR | MB_EVENT_HOLDING_REG_RD)) {
            // 保持寄存器事件(读或写)
            // 位与操作检查事件类型是否包含保持寄存器读或写标志
            
            // Get parameter information from parameter queue
            ESP_LOGI(TAG, "HOLDING %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
                            rw_str,
                            reg_info.time_stamp,
                            (unsigned)reg_info.mb_offset,
                            (unsigned)reg_info.type,
                            (uint32_t)reg_info.address,
                            (unsigned)reg_info.size);
            // 打印保持寄存器访问日志
            // 格式说明:
            //   - HOLDING:寄存器类型
            //   - %s:读或写
            //   - %" PRIu32 ":时间戳(微秒),PRIu32 是跨平台的 uint32_t 格式宏
            //   - ADDR:%u:Modbus 寄存器地址偏移
            //   - TYPE:%u:事件类型原始值
            //   - INST_ADDR:0x%" PRIx32 ":实际内存地址(十六进制)
            //   - SIZE:%u:访问的寄存器数量
            
            if (reg_info.address == (uint8_t*)&holding_reg_params.holding_data0)
            // 检查访问的是否是 holding_data0 变量
            // 通过比较内存地址判断,而非比较 Modbus 地址
            // 这种设计允许将任意内存变量映射到 Modbus 寄存器
            {
                portENTER_CRITICAL(&param_lock);
                // 进入临界区:禁用中断和任务调度,防止竞态条件
                // 必须成对使用 portEXIT_CRITICAL
                
                holding_reg_params.holding_data0 += MB_CHAN_DATA_OFFSET;
                // 累加 1.1,演示数据变化
                
                if (holding_reg_params.holding_data0 >= (MB_CHAN_DATA_MAX_VAL - MB_CHAN_DATA_OFFSET)) {
                    coil_reg_params.coils_port1 = 0xFF;
                    // 当接近最大值时,设置 coils_port1 为 0xFF(全1)
                    // 作为循环结束的信号标志
                }
                
                portEXIT_CRITICAL(&param_lock);
                // 退出临界区:恢复中断和任务调度
            }
            
        } else if (reg_info.type & MB_EVENT_INPUT_REG_RD) {
            // 输入寄存器读事件(只读,无写事件)
            ESP_LOGI(TAG, "INPUT READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
                            reg_info.time_stamp,
                            (unsigned)reg_info.mb_offset,
                            (unsigned)reg_info.type,
                            (uint32_t)reg_info.address,
                            (unsigned)reg_info.size);
            // 打印输入寄存器读日志,格式同上
                            
        } else if (reg_info.type & MB_EVENT_DISCRETE_RD) {
            // 离散输入读事件(只读)
            ESP_LOGI(TAG, "DISCRETE READ (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
                            reg_info.time_stamp,
                            (unsigned)reg_info.mb_offset,
                            (unsigned)reg_info.type,
                            (uint32_t)reg_info.address,
                            (unsigned)reg_info.size);
                            
        } else if (reg_info.type & (MB_EVENT_COILS_RD | MB_EVENT_COILS_WR)) {
            // 线圈读或写事件
            ESP_LOGI(TAG, "COILS %s (%" PRIu32 " us), ADDR:%u, TYPE:%u, INST_ADDR:0x%" PRIx32 ", SIZE:%u",
                            rw_str,
                            reg_info.time_stamp,
                            (unsigned)reg_info.mb_offset,
                            (unsigned)reg_info.type,
                            (uint32_t)reg_info.address,
                            (unsigned)reg_info.size);
                            
            if (coil_reg_params.coils_port1 == 0xFF) break;
            // 检查结束标志:如果 coils_port1 被设置为 0xFF,跳出循环
            // 这允许外部 Modbus 主机通过写 coils_port1 来停止演示
        }
    }
    
    // Destroy of Modbus controller on alarm
    ESP_LOGI(TAG,"Modbus controller destroyed.");
    // 循环结束,打印终止日志
    
    vTaskDelay(100);
    // 函数:void vTaskDelay(const TickType_t xTicksToDelay)
    // 功能:任务延迟,让出 CPU 时间片
    // 输入:xTicksToDelay - 延迟的 tick 数,100 表示 100 个 tick
    // 实际时间取决于 configTICK_RATE_HZ(通常 1000Hz,即 1ms/tick)
    // 所以 100 ticks = 100ms
    // 用途:确保日志输出完成,给看门狗喂狗时间
}

// ============================================================================
// 函数:init_services
// ============================================================================

// 功能:初始化所有系统服务(NVS、网络、WiFi/以太网、mDNS)
// 输入参数:无
// 输出:
//   - 返回值:esp_err_t
//     * ESP_OK - 所有服务初始化成功
//     * ESP_ERR_INVALID_STATE - 某项服务初始化失败
// 副作用:初始化多个系统组件,分配资源
// 调用顺序:必须在调用任何使用这些服务的函数之前调用
static esp_err_t init_services(void)
{
    esp_err_t result = nvs_flash_init();
    // 函数:esp_err_t nvs_flash_init(void)
    // 功能:初始化默认的 NVS 分区,准备进行键值对读写
    // 输入:无
    // 输出:
    //   - ESP_OK - 初始化成功
    //   - ESP_ERR_NO_FREE_PAGES - NVS 分区已满或损坏
    //   - ESP_ERR_NOT_FOUND - 找不到 NVS 分区
    //   - ESP_ERR_NVS_NEW_VERSION_FOUND - NVS 格式版本不匹配
    
    if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // 处理 NVS 初始化失败的两种情况
      ESP_ERROR_CHECK(nvs_flash_erase());
      // 函数:esp_err_t nvs_flash_erase(void)
      // 功能:擦除整个默认 NVS 分区,清除所有数据
      // 注意:会丢失所有已存储的 WiFi 密码、配置等数据
      
      result = nvs_flash_init();
      // 重新初始化,此时应该成功
    }
    
    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
                            TAG,
                            "nvs_flash_init fail, returns(0x%x).",
                            (int)result);
    // 宏:MB_RETURN_ON_FALSE(a, err_code, tag, format, ...)
    // 定义在 esp_modbus_common.h
    // 功能:如果条件 a 为假,打印错误日志并返回 err_code
    // 输入:
    //   - a: 条件表达式
    //   - err_code: 错误码
    //   - tag: 日志标签
    //   - format: 错误信息格式字符串
    // 行为:相当于 if (!(a)) { ESP_LOGE(tag, format, ...); return err_code; }

    result = esp_netif_init();
    // 函数:esp_err_t esp_netif_init(void)
    // 功能:初始化 TCP/IP 网络接口层,创建 LwIP 核心任务
    // 必须在创建任何网络接口之前调用
    // 输出:ESP_OK 或错误码
    
    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
                            TAG,
                            "esp_netif_init fail, returns(0x%x).",
                            (int)result);

    result = esp_event_loop_create_default();
    // 函数:esp_err_t esp_event_loop_create_default(void)
    // 功能:创建默认的系统事件循环(Event Loop)
    // 事件循环用于异步处理 WiFi 连接、IP 获取等事件
    // 输出:ESP_OK 或 ESP_ERR_NO_MEM(内存不足)
    
    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
                            TAG,
                            "esp_event_loop_create_default fail, returns(0x%x).",
                            (int)result);

#if CONFIG_MB_MDNS_IP_RESOLVER
    // Start mdns service and register device
    start_mdns_service();
    // 启动 mDNS 服务,必须在网络连接前或后调用(通常在有 IP 后)
#endif

    // This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
    // Read "Establishing Wi-Fi or Ethernet Connection" section in
    // examples/protocols/README.md for more information about this function.
    result = example_connect();
    // 函数:esp_err_t example_connect(void)
    // 来源:protocol_examples_common 组件
    // 功能:根据 menuconfig 配置自动连接 WiFi 或以太网
    // 行为:
    //   - 如果是 WiFi:读取 NVS 中的凭证,连接 AP,等待 IP
    //   - 如果是以太网:初始化 PHY,启动以太网,等待 IP
    // 阻塞性:阻塞函数,直到获得 IP 地址或失败
    // 输出:ESP_OK(成功连接并获取 IP)或错误码
    
    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "example_connect fail, returns(0x%x).",
                                (int)result);

#if CONFIG_EXAMPLE_CONNECT_WIFI
    // 如果配置为 WiFi 连接
    result = esp_wifi_set_ps(WIFI_PS_NONE);
    // 函数:esp_err_t esp_wifi_set_ps(wifi_ps_type_t type)
    // 功能:设置 WiFi 省电模式(Power Save)
    // 输入:
    //   - type: 
    //     * WIFI_PS_NONE - 关闭省电,保持全功率(推荐用于 Modbus 服务器)
    //     * WIFI_PS_MIN_MODEM - 最小省电模式
    //     * WIFI_PS_MAX_MODEM - 最大省电模式
    // 说明:Modbus 需要及时响应,关闭省电模式避免延迟
    
    MB_RETURN_ON_FALSE((result == ESP_OK), ESP_ERR_INVALID_STATE,
                                   TAG,
                                   "esp_wifi_set_ps fail, returns(0x%x).",
                                   (int)result);
#endif

    return ESP_OK;
    // 所有服务初始化成功
}

// ============================================================================
// 函数:destroy_services
// ============================================================================

// 功能:清理并释放所有系统服务资源(与 init_services 相反)
// 输入参数:无
// 输出:
//   - 返回值:esp_err_t,ESP_OK 或最后一个错误
// 副作用:停止网络、释放资源、断开连接
// 调用时机:程序退出前,或需要重新初始化时
static esp_err_t destroy_services(void)
{
    esp_err_t err = ESP_OK;
    // 临时存储错误码

    err = example_disconnect();
    // 函数:esp_err_t example_disconnect(void)
    // 功能:断开 WiFi 或以太网连接,释放网络资源
    // 与 example_connect() 对应
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                   TAG,
                                   "example_disconnect fail, returns(0x%x).",
                                   (int)err);

    err = esp_event_loop_delete_default();
    // 函数:esp_err_t esp_event_loop_delete_default(void)
    // 功能:删除默认事件循环,释放相关资源
    // 注意:确保没有事件处理器还在运行
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                       TAG,
                                       "esp_event_loop_delete_default fail, returns(0x%x).",
                                       (int)err);

    err = esp_netif_deinit();
    // 函数:esp_err_t esp_netif_deinit(void)
    // 功能:反初始化网络接口层,停止 LwIP 任务
    // 某些配置下可能返回 ESP_ERR_NOT_SUPPORTED(可接受)
    
    MB_RETURN_ON_FALSE((err == ESP_OK || err == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_STATE,
                                        TAG,
                                        "esp_netif_deinit fail, returns(0x%x).",
                                        (int)err);

    err = nvs_flash_deinit();
    // 函数:esp_err_t nvs_flash_deinit(void)
    // 功能:关闭 NVS,释放资源
    // 注意:之后不能再使用 NVS,直到重新 init
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "nvs_flash_deinit fail, returns(0x%x).",
                                (int)err);

#if CONFIG_MB_MDNS_IP_RESOLVER
    stop_mdns_service();
    // 停止 mDNS 服务
#endif

    return err;
}

// ============================================================================
// 函数:slave_init
// ============================================================================

// 功能:初始化 Modbus TCP 从机,配置寄存器映射
// 输入参数:
//   - comm_info: mb_communication_info_t* 指针,通信参数结构体
//     结构体定义(简化):
//     typedef struct {
//         mb_mode_type_t ip_mode;      // 通信模式:MB_MODE_TCP
//         mb_tcp_addr_type_t ip_addr_type; // IP 类型:MB_IPV4 或 MB_IPV6
//         uint16_t ip_port;            // TCP 端口号
//         void* ip_addr;               // 绑定 IP,NULL 表示所有接口
//         void* ip_netif_ptr;          // 网络接口指针
//         uint8_t slave_uid;           // 从机地址(Unit ID)
//     } mb_communication_info_t;
// 输出:
//   - 返回值:esp_err_t,ESP_OK 表示成功
// 副作用:创建 Modbus 任务,监听 TCP 端口,注册寄存器区域
static esp_err_t slave_init(mb_communication_info_t* comm_info)
{
    mb_register_area_descriptor_t reg_area; 
    // 寄存器区域描述符结构体
    // 定义在 esp_modbus_slave.h:
    // typedef struct {
    //     uint16_t start_offset;      // Modbus 起始地址
    //     mb_param_type_t type;       // 寄存器类型(HOLDING/INPUT/COIL/DISCRETE)
    //     void* address;              // 内存地址指针
    //     size_t size;                // 区域大小(字节)
    // } mb_register_area_descriptor_t;
    
    void* slave_handler = NULL;
    // Modbus 从机句柄,由 mbc_slave_init_tcp 创建
    // 后续操作不需要直接使用,由协议栈内部管理

    // Initialization of Modbus controller
    esp_err_t err = mbc_slave_init_tcp(&slave_handler);
    // 函数:esp_err_t mbc_slave_init_tcp(void** handler)
    // 功能:初始化 Modbus TCP 从机控制器
    // 输入:
    //   - handler: void** 指针,用于接收从机句柄
    // 输出:
    //   - 返回值:esp_err_t
    //     * ESP_OK - 成功
    //     * ESP_ERR_NO_MEM - 内存不足
    //     * ESP_ERR_NOT_SUPPORTED - 端口类型不支持
    //   - *handler: 填充为有效的从机句柄
    // 副作用:
    //   - 创建 Modbus 协议栈任务
    //   - 初始化 TCP 服务器套接字(尚未监听)
    //   - 分配数据缓冲区
    
    MB_RETURN_ON_FALSE((err == ESP_OK && slave_handler != NULL), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mb controller initialization fail.");
    // 检查初始化是否成功且句柄有效

    comm_info->ip_addr = NULL; 
    // NULL 表示绑定到所有可用的网络接口(0.0.0.0)
    // 也可指定特定 IP,如 "192.168.1.100"
    
    comm_info->ip_netif_ptr = (void*)get_example_netif();
    // 函数:esp_netif_t* get_example_netif(void)
    // 功能:获取示例中创建的默认网络接口
    // 返回:esp_netif_t* 指针,用于 LwIP 网络操作
    // 说明:Modbus 协议栈需要知道使用哪个网络接口发送/接收数据
    
    comm_info->slave_uid = MB_SLAVE_ADDR;
    // 设置 Modbus 从机地址(Unit ID)
    // 在 TCP 模式下,这是 MBAP 报文头中的单元标识符
    // 用于区分同一 IP 上的多个虚拟从机(通常 TCP 一个 IP 一个从机,设为1即可)

    // Setup communication parameters and start stack
    err = mbc_slave_setup((void*)comm_info);
    // 函数:esp_err_t mbc_slave_setup(void* comm_info)
    // 功能:配置 Modbus 通信参数,启动协议栈
    // 输入:comm_info - 通信参数结构体指针(需要强制转换为 void*)
    // 输出:
    //   - ESP_OK - 配置成功
    //   - ESP_ERR_INVALID_ARG - 参数错误
    // 副作用:
    //   - 根据参数配置 TCP 服务器
    //   - 开始监听指定端口
    //   - 启动 Modbus 状态机任务
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                        TAG,
                                        "mbc_slave_setup fail, returns(0x%x).",
                                        (int)err);

    // The code below initializes Modbus register area descriptors
    // for Modbus Holding Registers, Input Registers, Coils and Discrete Inputs
    // 以下代码初始化 4 种 Modbus 寄存器区域的描述符
    
    // Initialization should be done for each supported Modbus register area according to register map.
    // 必须为每个支持的寄存器区域调用 mbc_slave_set_descriptor
    
    // When external master trying to access the register in the area that is not initialized
    // by mbc_slave_set_descriptor() API call then Modbus stack
    // will send exception response for this register area.
    // 如果主机访问未初始化的区域,协议栈会返回异常响应(Illegal Data Address)

    // --- 保持寄存器区域0 ---
    reg_area.type = MB_PARAM_HOLDING; 
    // 设置寄存器类型为保持寄存器(Holding Register)
    // 枚举值:MB_PARAM_HOLDING = 0
    
    reg_area.start_offset = MB_REG_HOLDING_START_AREA0; 
    // Modbus 协议地址偏移:0
    
    reg_area.address = (void*)&holding_reg_params.holding_data0; 
    // 内存地址:指向 holding_data0 变量
    // 协议栈会将 Modbus 地址 0-7 的访问映射到这个内存区域
    
    reg_area.size = (MB_REG_HOLDING_START_AREA1 - MB_REG_HOLDING_START_AREA0) << 1; 
    // 计算区域大小(字节):
    // MB_REG_HOLDING_START_AREA1 = 8, MB_REG_HOLDING_START_AREA0 = 0
    // 差值 = 8(字),<<1 = 16(字节)
    // 即 4 个 float,每个 4 字节,共 16 字节
    
    err = mbc_slave_set_descriptor(reg_area);
    // 函数:esp_err_t mbc_slave_set_descriptor(mb_register_area_descriptor_t descr_data)
    // 功能:注册寄存器区域描述符,建立 Modbus 地址到内存地址的映射
    // 输入:descr_data - 描述符结构体
    // 输出:
    //   - ESP_OK - 注册成功
    //   - ESP_ERR_INVALID_ARG - 参数无效(地址为空、大小为0等)
    //   - ESP_ERR_NO_MEM - 描述符链表已满
    // 原理:协议栈内部维护一个链表,存储所有区域的映射关系
    //       收到请求时,根据地址查找对应的内存区域进行读写
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);

    // --- 保持寄存器区域1 ---
    reg_area.type = MB_PARAM_HOLDING; 
    reg_area.start_offset = MB_REG_HOLDING_START_AREA1; // 8
    reg_area.address = (void*)&holding_reg_params.holding_data4; 
    reg_area.size = sizeof(float) << 2; 
    // sizeof(float) = 4,<<2 = 16(字节),即 4 个 float
    
    err = mbc_slave_set_descriptor(reg_area);
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);

    // --- 输入寄存器区域0 ---
    reg_area.type = MB_PARAM_INPUT;
    reg_area.start_offset = MB_REG_INPUT_START_AREA0; // 0
    reg_area.address = (void*)&input_reg_params.input_data0;
    reg_area.size = sizeof(float) << 2; // 16 字节(4 个 float)
    err = mbc_slave_set_descriptor(reg_area);
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                        TAG,
                                        "mbc_slave_set_descriptor fail, returns(0x%x).",
                                        (int)err);

    // --- 输入寄存器区域1 ---
    reg_area.type = MB_PARAM_INPUT;
    reg_area.start_offset = MB_REG_INPUT_START_AREA1; // 8
    reg_area.address = (void*)&input_reg_params.input_data4;
    reg_area.size = sizeof(float) << 2;
    err = mbc_slave_set_descriptor(reg_area);
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                        TAG,
                                        "mbc_slave_set_descriptor fail, returns(0x%x).",
                                        (int)err);

    // --- 线圈寄存器区域 ---
    reg_area.type = MB_PARAM_COIL;
    reg_area.start_offset = MB_REG_COILS_START; // 0
    reg_area.address = (void*)&coil_reg_params;
    // 指向整个结构体,包含 coils_port0 和 coils_port1
    
    reg_area.size = sizeof(coil_reg_params);
    // 结构体总大小,通常是 2 字节(2 个 uint8_t)
    
    err = mbc_slave_set_descriptor(reg_area);
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);

    // --- 离散输入寄存器区域 ---
    reg_area.type = MB_PARAM_DISCRETE;
    reg_area.start_offset = MB_REG_DISCRETE_INPUT_START; // 0
    reg_area.address = (void*)&discrete_reg_params;
    reg_area.size = sizeof(discrete_reg_params);
    err = mbc_slave_set_descriptor(reg_area);
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                    TAG,
                                    "mbc_slave_set_descriptor fail, returns(0x%x).",
                                    (int)err);

    // Set values into known state
    setup_reg_data();
    // 调用前面定义的函数,设置寄存器初始值

    // Starts of modbus controller and stack
    err = mbc_slave_start();
    // 函数:esp_err_t mbc_slave_start(void)
    // 功能:启动 Modbus 从机,开始接受主机连接
    // 副作用:
    //   - 启动 TCP 服务器监听(端口 502)
    //   - 开始处理 Modbus 报文
    //   - 可以响应主机的请求
    // 注意:必须在所有描述符注册完成后调用
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                        TAG,
                                        "mbc_slave_start fail, returns(0x%x).",
                                        (int)err);
    
    vTaskDelay(5);
    // 延迟 5ms,确保协议栈完全启动
    // 给调度器时间启动相关任务
    
    return err;
}

// ============================================================================
// 函数:slave_destroy
// ============================================================================

// 功能:销毁 Modbus 从机,释放所有资源
// 输入参数:无
// 输出:
//   - 返回值:esp_err_t,ESP_OK 表示成功
// 副作用:停止 Modbus 任务,关闭 TCP 服务器,释放内存
static esp_err_t slave_destroy(void)
{
    esp_err_t err = mbc_slave_destroy();
    // 函数:esp_err_t mbc_slave_destroy(void)
    // 功能:完全销毁 Modbus 从机控制器
    // 副作用:
    //   - 停止 Modbus 协议栈任务
    //   - 关闭 TCP 监听套接字
    //   - 断开所有客户端连接
    //   - 释放所有描述符链表内存
    //   - 释放数据缓冲区
    // 注意:销毁后如需再次使用,需要重新调用 mbc_slave_init_tcp
    
    MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE,
                                TAG,
                                "mbc_slave_destroy fail, returns(0x%x).",
                                (int)err);
    return err;
}

// ============================================================================
// 主函数:app_main
// ============================================================================

// 功能:应用程序入口点,ESP-IDF 框架调用
// 输入参数:无
// 输出:无(不返回,或返回后系统重启)
// 执行流程:初始化 → 运行 Modbus → 清理 → 结束
void app_main(void)
{
    ESP_ERROR_CHECK(init_services());
    // 调用 init_services() 初始化 NVS、网络、WiFi 等
    // ESP_ERROR_CHECK 宏:如果返回非 ESP_OK,打印错误并重启
    
    // Set UART log level
    esp_log_level_set(TAG, ESP_LOG_INFO);
    // 函数:void esp_log_level_set(const char* tag, esp_log_level_t level)
    // 功能:设置特定标签的日志级别
    // 输入:
    //   - tag: 标签字符串,"SLAVE_TEST"
    //   - level: 日志级别
    //     * ESP_LOG_NONE - 关闭日志
    //     * ESP_LOG_ERROR - 仅错误
    //     * ESP_LOG_WARN - 警告及以上
    //     * ESP_LOG_INFO - 信息及以上(当前设置)
    //     * ESP_LOG_DEBUG - 调试及以上
    //     * ESP_LOG_VERBOSE - 所有日志

    mb_communication_info_t comm_info = { 0 };
    // 通信参数结构体,全部初始化为0
    // 结构体成员见 slave_init 函数的注释

#if !CONFIG_EXAMPLE_CONNECT_IPV6
    comm_info.ip_addr_type = MB_IPV4;
    // 如果不支持 IPv6,使用 IPv4
#else
    comm_info.ip_addr_type = MB_IPV6;
    // 否则使用 IPv6
#endif

    comm_info.ip_mode = MB_MODE_TCP;
    // 设置通信模式为 TCP
    // 枚举值:MB_MODE_TCP = 0(通常)

    comm_info.ip_port = MB_TCP_PORT_NUMBER;
    // 设置端口号:502(标准 Modbus TCP 端口)

    ESP_ERROR_CHECK(slave_init(&comm_info));
    // 初始化 Modbus 从机,传入通信参数

    // The Modbus slave logic is located in this function (user handling of Modbus)
    slave_operation_func(NULL);
    // 运行 Modbus 从机业务逻辑
    // 此函数会阻塞,直到演示条件满足(holding_data0 >= 10)

    ESP_ERROR_CHECK(slave_destroy());
    // 销毁 Modbus 从机,释放资源
    
    ESP_ERROR_CHECK(destroy_services());
    // 清理所有系统服务
    
    // 程序结束,ESP32 会重启或进入空闲状态
}

配置

SDK配置

PC网络配置(同一网段)

可ping通

测试读取esp“寄存器”数值


// ============================================================================
// 函数:setup_reg_data
// ============================================================================

// 功能:初始化所有 Modbus 寄存器的默认值
// 输入参数:无
// 输出:无(修改全局变量)
// 副作用:修改以下全局结构体的字段:
//   - discrete_reg_params(离散输入)
//   - holding_reg_params(保持寄存器)
//   - coil_reg_params(线圈)
//   - input_reg_params(输入寄存器)
// 调用时机:Modbus 从机初始化完成后,开始服务前
static void setup_reg_data(void)
{
    // Define initial state of parameters
    // 定义参数的初始状态
    
    discrete_reg_params.discrete_input0 = 1;
    discrete_reg_params.discrete_input1 = 0;
    discrete_reg_params.discrete_input2 = 1;
    discrete_reg_params.discrete_input3 = 0;
    discrete_reg_params.discrete_input4 = 1;
    discrete_reg_params.discrete_input5 = 0;
    discrete_reg_params.discrete_input6 = 1;
    discrete_reg_params.discrete_input7 = 0;
    // 初始化 8 个离散输入为交替的 1 和 0
    // 类型:uint8_t(通常定义为位域或数组)
    // Modbus 地址:0-7(从 MB_REG_DISCRETE_INPUT_START 开始)
    // 访问权限:只读(主机不能写入)

    holding_reg_params.holding_data0 = 1.34;
    holding_reg_params.holding_data1 = 2.56;
    holding_reg_params.holding_data2 = 3.78;
    holding_reg_params.holding_data3 = 4.90;
    // 初始化保持寄存器区域0的 4 个浮点数
    // 类型:float(32位 IEEE 754 浮点)
    // Modbus 地址:0-7(每个 float 占 2 个寄存器)
    // 内存布局:data0 在地址 0-1,data1 在地址 2-3,以此类推
    // 访问权限:读写(主机可读取和写入)
    // 字节序:ESP32 是小端序,Modbus 是大端序,协议栈自动转换

    holding_reg_params.holding_data4 = 5.67;
    holding_reg_params.holding_data5 = 6.78;
    holding_reg_params.holding_data6 = 7.79;
    holding_reg_params.holding_data7 = 8.80;
    // 初始化保持寄存器区域1的 4 个浮点数
    // Modbus 地址:8-15(从 MB_REG_HOLDING_START_AREA1 开始)
    
    coil_reg_params.coils_port0 = 0x55;
    coil_reg_params.coils_port1 = 0xAA;
    // 初始化线圈寄存器
    // 0x55 = 01010101b(位 0,2,4,6 为 1)
    // 0xAA = 10101010b(位 1,3,5,7 为 1)
    // 类型:uint8_t(通常定义为结构体位域)
    // Modbus 地址:0-15(coils_port0 对应 0-7,coils_port1 对应 8-15)
    // 访问权限:读写
    
    input_reg_params.input_data0 = 1.12;
    input_reg_params.input_data1 = 2.34;
    input_reg_params.input_data2 = 3.56;
    input_reg_params.input_data3 = 4.78;
    input_reg_params.input_data4 = 1.12;
    input_reg_params.input_data5 = 2.34;
    input_reg_params.input_data6 = 3.56;
    input_reg_params.input_data7 = 4.78;
    // 初始化 8 个输入寄存器的浮点数值
    // 类型:float
    // Modbus 地址:0-15(区域0:0-7,区域1:8-15)
    // 访问权限:只读
}

读取保持寄存器(03功能码)

和设置中一样为1.34 , 2.56 等同样的数值

我来详细讲解如何用 Modbus Poll 读写你的所有寄存器类型,并给出具体配置示例。


你的寄存器映射表

表格

复制

寄存器类型 变量名 Modbus地址 数据类型 访问权限 当前值
离散输入 discrete_input0-7 10001-10008 bit (uint8) 只读 1,0,1,0,1,0,1,0
线圈 coils_port0 (位0-7) 00001-00008 bit (uint8) 读写 0x55 (01010101)
线圈 coils_port1 (位8-15) 00009-00016 bit (uint8) 读写 0xAA (10101010)
输入寄存器 input_data0-3 30001-30008 float (32位) 只读 1.12, 2.34, 3.56, 4.78
输入寄存器 input_data4-7 30009-30016 float (32位) 只读 1.12, 2.34, 3.56, 4.78
保持寄存器 holding_data0-3 40001-40008 float (32位) 读写 1.34, 2.56, 3.78, 4.90
保持寄存器 holding_data4-7 40009-40016 float (32位) 读写 5.67, 6.78, 7.79, 8.80

注意: float 占 2个寄存器(64位=4字节,但Modbus是16位寄存器)


一、读取保持寄存器(03功能码)- 已测试过

Modbus Poll 配置

Connection: Modbus TCP/IP
IP: 192.168.10.50
Port: 502

Setup → Read/Write Definition:
  Slave ID: 1
  Function: 03 Read Holding Registers (4x)
  Address: 0          ← 对应 40001
  Quantity: 8         ← 读取8个寄存器 = 4个float
  Scan Rate: 1000 ms

显示格式设置(关键!)

点击 Display → 32-bit Floating Point(显示32位浮点)

期望显示:

Address 0-1: 1.34    (holding_data0)
Address 2-3: 2.56    (holding_data1)
Address 4-5: 3.78    (holding_data2)
Address 6-7: 4.90    (holding_data3)

二、写入保持寄存器(06/16功能码)

方法1:单个写入(6功能码 - 写单个)

双击即可写入

方法2:批量写入(16功能码 - 写多个)

均改为6

三、读取输入寄存器(04功能码)- 只读

显示如下

四、读取线圈(01功能码)- 位操作

读取结果如下

五、写入线圈(05/15功能码)

方法1:双击单个线圈(05功能码)

  1. 双击 Address 0 的行

  2. 弹出对话框

    Address: 0
    Value: 1           ← 0=OFF, 1=ON
  3. 点击 OK

方法2:强制写入多个(15功能码)

  1. Setup → Read/Write Definition

  2. Function: 15 Write Multiple Coils

  3. 在表格中点击单元格切换 0/1

  4. Ctrl+W 写入

六、读取离散输入(02功能码)- 只读

Modbus Poll 配置

Setup → Read/Write Definition:
  Slave ID: 1
  Function: 02 Read Discrete Inputs (1x)    ← 02功能码
  Address: 0          ← 对应 10001
  Quantity: 8         ← 读取8个离散输入

期望显示:

Address 0: 1    (discrete_input0)
Address 1: 0    (discrete_input1)
Address 2: 1    (discrete_input2)
Address 3: 0    (discrete_input3)
Address 4: 1    (discrete_input4)
Address 5: 0    (discrete_input5)
Address 6: 1    (discrete_input6)
Address 7: 0    (discrete_input7)

注意: 离散输入是 只读 的,不能写入。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐