跳转至

Modbus TCP协议实现与应用开发

学习目标

完成本教程后,你将能够:

  • 理解Modbus TCP协议的架构和工作原理
  • 掌握Modbus TCP与Modbus RTU的区别
  • 熟悉MBAP报头和功能码的使用
  • 实现Modbus TCP客户端和服务器
  • 使用lwIP协议栈开发Modbus TCP应用
  • 进行Modbus TCP网络调试和故障排除
  • 实现工业设备的远程监控和控制

前置要求

在开始本教程之前,你需要:

知识要求: - 熟悉TCP/IP网络协议基础 - 了解Modbus RTU协议原理 - 掌握C语言编程 - 理解客户端-服务器架构

技能要求: - 能够使用STM32或ESP32进行开发 - 会使用lwIP或其他TCP/IP协议栈 - 了解工业自动化基本概念 - 熟悉网络调试工具(Wireshark、Modbus Poll)

准备工作

硬件准备

名称 数量 说明 参考型号
STM32开发板 1 带以太网的MCU STM32F407/F429
ESP32开发板 1 可选,用于WiFi方案 ESP32-DevKitC
以太网线 1 Cat5e或更高 -
路由器/交换机 1 用于网络连接 -
USB调试器 1 ST-Link或J-Link -

软件准备

  • 开发环境
  • STM32CubeIDE或Arduino IDE
  • lwIP协议栈(STM32)或ModbusTCP库(ESP32)
  • 测试工具
  • Modbus Poll(主站模拟器)
  • Modbus Slave(从站模拟器)
  • Wireshark(网络抓包)
  • Python + pymodbus(脚本测试)

环境配置

安装Modbus测试工具

# 安装pymodbus
pip install pymodbus

# 下载Modbus Poll/Slave
# 访问 https://www.modbustools.com/

Modbus TCP协议概述

什么是Modbus TCP?

Modbus TCP是Modbus协议在以太网上的实现,将Modbus应用层协议封装在TCP/IP协议中,实现工业设备的网络通信。

核心特点: - 基于以太网:使用标准TCP/IP协议 - 开放标准:免费使用,广泛支持 - 简单可靠:继承Modbus协议的简单性 - 远程访问:支持跨网络通信 - 高速传输:比串口Modbus更快

Modbus TCP vs Modbus RTU

特性 Modbus RTU Modbus TCP
传输介质 RS232/RS485串口 以太网
物理层 串行通信 TCP/IP
地址范围 1-247 0-255(单元标识符)
校验方式 CRC16 TCP校验和
传输速度 9600-115200 bps 10/100/1000 Mbps
通信距离 <1200m 无限制(通过路由)
成本 中等
应用场景 现场总线 工业以太网

Modbus TCP协议架构

+----------------------------------------------------------+
|                    应用层                                  |
|  +--------------------------------------------------+    |
|  |              Modbus应用协议                       |    |
|  |  功能码 + 数据                                    |    |
|  +--------------------------------------------------+    |
+----------------------------------------------------------+
|                    MBAP报头                               |
|  +--------------------------------------------------+    |
|  |  事务ID | 协议ID | 长度 | 单元ID                 |    |
|  |  2字节  | 2字节  | 2字节 | 1字节                 |    |
|  +--------------------------------------------------+    |
+----------------------------------------------------------+
|                    TCP层                                  |
|  +--------------------------------------------------+    |
|  |              TCP协议(端口502)                   |    |
|  +--------------------------------------------------+    |
+----------------------------------------------------------+
|                    IP层                                   |
|  +--------------------------------------------------+    |
|  |              IP协议                               |    |
|  +--------------------------------------------------+    |
+----------------------------------------------------------+
|                    以太网层                               |
|  +--------------------------------------------------+    |
|  |              以太网帧                             |    |
|  +--------------------------------------------------+    |
+----------------------------------------------------------+

MBAP报头详解

MBAP(Modbus Application Protocol)报头是Modbus TCP特有的,用于在TCP/IP网络上传输Modbus消息。

MBAP报头结构

typedef struct {
    uint16_t transaction_id;  // 事务标识符(0-65535)
    uint16_t protocol_id;      // 协议标识符(固定为0)
    uint16_t length;           // 后续字节数(单元ID+PDU长度)
    uint8_t  unit_id;          // 单元标识符(从站地址)
} mbap_header_t;

字段说明

  1. 事务标识符(Transaction ID)
  2. 由客户端生成,用于匹配请求和响应
  3. 每个请求使用不同的ID
  4. 服务器响应时原样返回

  5. 协议标识符(Protocol ID)

  6. 固定为0x0000
  7. 用于标识Modbus协议

  8. 长度(Length)

  9. 后续字节数 = 单元ID(1字节) + PDU长度
  10. 不包括MBAP报头本身的6字节

  11. 单元标识符(Unit ID)

  12. 类似于Modbus RTU的从站地址
  13. 0xFF表示广播(不常用)
  14. 通常设置为1或0

Modbus TCP帧格式示例

读取保持寄存器请求

请求帧(12字节):
00 01        - 事务ID = 1
00 00        - 协议ID = 0
00 06        - 长度 = 6字节
01           - 单元ID = 1
03           - 功能码 = 03(读保持寄存器)
00 00        - 起始地址 = 0
00 0A        - 数量 = 10个寄存器

十六进制:00 01 00 00 00 06 01 03 00 00 00 0A

读取保持寄存器响应

响应帧(27字节):
00 01        - 事务ID = 1(与请求相同)
00 00        - 协议ID = 0
00 17        - 长度 = 23字节
01           - 单元ID = 1
03           - 功能码 = 03
14           - 字节数 = 20字节(10个寄存器×2字节)
00 64        - 寄存器0 = 100
00 C8        - 寄存器1 = 200
01 2C        - 寄存器2 = 300
...          - 其他寄存器数据

十六进制:00 01 00 00 00 17 01 03 14 00 64 00 C8 01 2C ...

常用功能码

功能码 名称 说明 请求数据 响应数据
01 Read Coils 读线圈 起始地址+数量 字节数+状态
02 Read Discrete Inputs 读离散输入 起始地址+数量 字节数+状态
03 Read Holding Registers 读保持寄存器 起始地址+数量 字节数+数据
04 Read Input Registers 读输入寄存器 起始地址+数量 字节数+数据
05 Write Single Coil 写单个线圈 地址+值 地址+值
06 Write Single Register 写单个寄存器 地址+值 地址+值
15 Write Multiple Coils 写多个线圈 地址+数量+数据 地址+数量
16 Write Multiple Registers 写多个寄存器 地址+数量+数据 地址+数量

异常响应

当服务器无法处理请求时,返回异常响应:

异常响应帧:
00 01        - 事务ID
00 00        - 协议ID
00 03        - 长度 = 3字节
01           - 单元ID
83           - 功能码 = 0x03 + 0x80(异常标志)
02           - 异常码 = 02(非法数据地址)

常见异常码

异常码 名称 说明
01 Illegal Function 不支持的功能码
02 Illegal Data Address 非法数据地址
03 Illegal Data Value 非法数据值
04 Slave Device Failure 从站设备故障
05 Acknowledge 确认(长时间操作)
06 Slave Device Busy 从站设备忙

步骤1:Modbus TCP服务器实现(STM32 + lwIP)

1.1 数据结构定义

#include "lwip/tcp.h"
#include <stdint.h>
#include <string.h>

// MBAP报头结构
typedef struct __attribute__((packed)) {
    uint16_t transaction_id;
    uint16_t protocol_id;
    uint16_t length;
    uint8_t  unit_id;
} mbap_header_t;

// Modbus TCP帧结构
typedef struct {
    mbap_header_t header;
    uint8_t function_code;
    uint8_t data[252];  // 最大PDU长度
} modbus_tcp_frame_t;

// 寄存器数据
#define HOLDING_REG_START   0
#define HOLDING_REG_COUNT   100
#define INPUT_REG_START     0
#define INPUT_REG_COUNT     50
#define COIL_START          0
#define COIL_COUNT          64
#define DISCRETE_START      0
#define DISCRETE_COUNT      64

static uint16_t holding_registers[HOLDING_REG_COUNT];
static uint16_t input_registers[INPUT_REG_COUNT];
static uint8_t coils[COIL_COUNT / 8];
static uint8_t discrete_inputs[DISCRETE_COUNT / 8];

// Modbus TCP服务器PCB
static struct tcp_pcb *modbus_tcp_pcb;

1.2 字节序转换

Modbus TCP使用大端字节序(网络字节序):

// 主机字节序转网络字节序
static uint16_t htons_modbus(uint16_t hostshort)
{
    return ((hostshort & 0xFF) << 8) | ((hostshort >> 8) & 0xFF);
}

// 网络字节序转主机字节序
static uint16_t ntohs_modbus(uint16_t netshort)
{
    return ((netshort & 0xFF) << 8) | ((netshort >> 8) & 0xFF);
}

// 或使用lwIP提供的函数
#include "lwip/def.h"
// htons(), ntohs(), htonl(), ntohl()

1.3 功能码处理函数

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

static uint16_t modbus_read_holding_registers(uint16_t start_addr, 
                                                uint16_t quantity,
                                                uint8_t *response_data)
{
    // 检查地址范围
    if(start_addr >= HOLDING_REG_COUNT || 
       start_addr + quantity > HOLDING_REG_COUNT ||
       quantity == 0 || quantity > 125)
    {
        return 0x02;  // 异常码:非法数据地址
    }

    // 字节数
    uint8_t byte_count = quantity * 2;
    response_data[0] = byte_count;

    // 复制寄存器数据
    for(uint16_t i = 0; i < quantity; i++)
    {
        uint16_t reg_value = holding_registers[start_addr + i];
        response_data[1 + i*2] = (reg_value >> 8) & 0xFF;
        response_data[2 + i*2] = reg_value & 0xFF;
    }

    return byte_count + 1;  // 返回响应数据长度
}

写单个寄存器(功能码06)

static uint16_t modbus_write_single_register(uint16_t reg_addr,
                                               uint16_t reg_value,
                                               uint8_t *response_data)
{
    // 检查地址范围
    if(reg_addr >= HOLDING_REG_COUNT)
    {
        return 0x02;  // 异常码:非法数据地址
    }

    // 写入寄存器
    holding_registers[reg_addr] = reg_value;

    // 构建响应(回显请求)
    response_data[0] = (reg_addr >> 8) & 0xFF;
    response_data[1] = reg_addr & 0xFF;
    response_data[2] = (reg_value >> 8) & 0xFF;
    response_data[3] = reg_value & 0xFF;

    return 4;  // 响应数据长度
}

写多个寄存器(功能码16)

static uint16_t modbus_write_multiple_registers(uint16_t start_addr,
                                                  uint16_t quantity,
                                                  uint8_t byte_count,
                                                  uint8_t *reg_data,
                                                  uint8_t *response_data)
{
    // 检查参数
    if(start_addr >= HOLDING_REG_COUNT ||
       start_addr + quantity > HOLDING_REG_COUNT ||
       quantity == 0 || quantity > 123 ||
       byte_count != quantity * 2)
    {
        return 0x03;  // 异常码:非法数据值
    }

    // 写入寄存器
    for(uint16_t i = 0; i < quantity; i++)
    {
        uint16_t reg_value = (reg_data[i*2] << 8) | reg_data[i*2 + 1];
        holding_registers[start_addr + i] = reg_value;
    }

    // 构建响应
    response_data[0] = (start_addr >> 8) & 0xFF;
    response_data[1] = start_addr & 0xFF;
    response_data[2] = (quantity >> 8) & 0xFF;
    response_data[3] = quantity & 0xFF;

    return 4;
}

读取线圈(功能码01)

static uint16_t modbus_read_coils(uint16_t start_addr,
                                    uint16_t quantity,
                                    uint8_t *response_data)
{
    // 检查地址范围
    if(start_addr >= COIL_COUNT ||
       start_addr + quantity > COIL_COUNT ||
       quantity == 0 || quantity > 2000)
    {
        return 0x02;
    }

    // 计算字节数
    uint8_t byte_count = (quantity + 7) / 8;
    response_data[0] = byte_count;

    // 清零响应数据
    memset(&response_data[1], 0, byte_count);

    // 复制线圈状态
    for(uint16_t i = 0; i < quantity; i++)
    {
        uint16_t coil_addr = start_addr + i;
        uint8_t byte_index = coil_addr / 8;
        uint8_t bit_index = coil_addr % 8;

        if(coils[byte_index] & (1 << bit_index))
        {
            response_data[1 + i/8] |= (1 << (i % 8));
        }
    }

    return byte_count + 1;
}

1.4 Modbus TCP请求处理

static void modbus_process_request(struct tcp_pcb *tpcb, struct pbuf *p)
{
    if(p->tot_len < 8)  // 最小帧长度:MBAP(7) + 功能码(1)
    {
        pbuf_free(p);
        return;
    }

    // 解析MBAP报头
    uint8_t *data = (uint8_t *)p->payload;
    mbap_header_t header;

    header.transaction_id = (data[0] << 8) | data[1];
    header.protocol_id = (data[2] << 8) | data[3];
    header.length = (data[4] << 8) | data[5];
    header.unit_id = data[6];

    // 检查协议ID
    if(header.protocol_id != 0)
    {
        pbuf_free(p);
        return;
    }

    // 获取功能码
    uint8_t function_code = data[7];

    // 准备响应缓冲区
    uint8_t response[260];  // MBAP(7) + 功能码(1) + 数据(最大252)
    uint16_t response_len = 0;
    uint8_t exception_code = 0;

    // 根据功能码处理
    switch(function_code)
    {
        case 0x03:  // 读保持寄存器
        {
            uint16_t start_addr = (data[8] << 8) | data[9];
            uint16_t quantity = (data[10] << 8) | data[11];

            response[7] = 0x03;  // 功能码
            uint16_t data_len = modbus_read_holding_registers(start_addr, quantity, &response[8]);

            if(data_len == 0x02 || data_len == 0x03)
            {
                exception_code = data_len;
            }
            else
            {
                response_len = 8 + data_len;
            }
            break;
        }

        case 0x06:  // 写单个寄存器
        {
            uint16_t reg_addr = (data[8] << 8) | data[9];
            uint16_t reg_value = (data[10] << 8) | data[11];

            response[7] = 0x06;
            uint16_t data_len = modbus_write_single_register(reg_addr, reg_value, &response[8]);

            if(data_len == 0x02 || data_len == 0x03)
            {
                exception_code = data_len;
            }
            else
            {
                response_len = 8 + data_len;
            }
            break;
        }

        case 0x10:  // 写多个寄存器
        {
            uint16_t start_addr = (data[8] << 8) | data[9];
            uint16_t quantity = (data[10] << 8) | data[11];
            uint8_t byte_count = data[12];

            response[7] = 0x10;
            uint16_t data_len = modbus_write_multiple_registers(start_addr, quantity, 
                                                                  byte_count, &data[13], &response[8]);

            if(data_len == 0x02 || data_len == 0x03)
            {
                exception_code = data_len;
            }
            else
            {
                response_len = 8 + data_len;
            }
            break;
        }

        case 0x01:  // 读线圈
        {
            uint16_t start_addr = (data[8] << 8) | data[9];
            uint16_t quantity = (data[10] << 8) | data[11];

            response[7] = 0x01;
            uint16_t data_len = modbus_read_coils(start_addr, quantity, &response[8]);

            if(data_len == 0x02)
            {
                exception_code = data_len;
            }
            else
            {
                response_len = 8 + data_len;
            }
            break;
        }

        default:
            exception_code = 0x01;  // 非法功能码
            break;
    }

    // 构建响应
    if(exception_code != 0)
    {
        // 异常响应
        response[0] = (header.transaction_id >> 8) & 0xFF;
        response[1] = header.transaction_id & 0xFF;
        response[2] = 0x00;
        response[3] = 0x00;
        response[4] = 0x00;
        response[5] = 0x03;  // 长度 = 3
        response[6] = header.unit_id;
        response[7] = function_code | 0x80;  // 异常标志
        response[8] = exception_code;
        response_len = 9;
    }
    else
    {
        // 正常响应 - 填充MBAP报头
        response[0] = (header.transaction_id >> 8) & 0xFF;
        response[1] = header.transaction_id & 0xFF;
        response[2] = 0x00;
        response[3] = 0x00;
        uint16_t length = response_len - 6;
        response[4] = (length >> 8) & 0xFF;
        response[5] = length & 0xFF;
        response[6] = header.unit_id;
    }

    // 发送响应
    tcp_write(tpcb, response, response_len, TCP_WRITE_FLAG_COPY);
    tcp_output(tpcb);

    pbuf_free(p);
}

1.5 TCP回调函数

// 接收回调
static err_t modbus_tcp_recv(void *arg, struct tcp_pcb *tpcb, 
                              struct pbuf *p, err_t err)
{
    if(p == NULL)
    {
        // 连接关闭
        tcp_close(tpcb);
        return ERR_OK;
    }

    // 处理Modbus请求
    modbus_process_request(tpcb, p);

    return ERR_OK;
}

// 接受连接回调
static err_t modbus_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    printf("Modbus TCP client connected: %s\r\n", 
           ip4addr_ntoa(&newpcb->remote_ip));

    // 设置接收回调
    tcp_recv(newpcb, modbus_tcp_recv);

    return ERR_OK;
}

// 初始化Modbus TCP服务器
void modbus_tcp_server_init(void)
{
    // 初始化寄存器数据
    for(int i = 0; i < HOLDING_REG_COUNT; i++)
    {
        holding_registers[i] = i * 10;
    }

    for(int i = 0; i < INPUT_REG_COUNT; i++)
    {
        input_registers[i] = i * 5;
    }

    // 创建TCP PCB
    modbus_tcp_pcb = tcp_new();

    if(modbus_tcp_pcb == NULL)
    {
        printf("Failed to create Modbus TCP PCB\r\n");
        return;
    }

    // 绑定到端口502
    err_t err = tcp_bind(modbus_tcp_pcb, IP_ADDR_ANY, 502);

    if(err != ERR_OK)
    {
        printf("Failed to bind Modbus TCP port: %d\r\n", err);
        tcp_close(modbus_tcp_pcb);
        return;
    }

    // 开始监听
    modbus_tcp_pcb = tcp_listen(modbus_tcp_pcb);

    // 设置接受回调
    tcp_accept(modbus_tcp_pcb, modbus_tcp_accept);

    printf("Modbus TCP server started on port 502\r\n");
}

步骤2:Modbus TCP客户端实现

2.1 客户端数据结构

typedef struct {
    struct tcp_pcb *pcb;
    ip_addr_t server_ip;
    uint16_t server_port;
    uint16_t transaction_id;
    bool connected;
    uint8_t rx_buffer[260];
    uint16_t rx_len;
} modbus_tcp_client_t;

static modbus_tcp_client_t modbus_client;

2.2 客户端请求构建

// 构建读保持寄存器请求
static uint16_t modbus_build_read_holding_request(uint8_t *buffer,
                                                    uint16_t transaction_id,
                                                    uint8_t unit_id,
                                                    uint16_t start_addr,
                                                    uint16_t quantity)
{
    // MBAP报头
    buffer[0] = (transaction_id >> 8) & 0xFF;
    buffer[1] = transaction_id & 0xFF;
    buffer[2] = 0x00;  // 协议ID
    buffer[3] = 0x00;
    buffer[4] = 0x00;  // 长度
    buffer[5] = 0x06;
    buffer[6] = unit_id;

    // PDU
    buffer[7] = 0x03;  // 功能码
    buffer[8] = (start_addr >> 8) & 0xFF;
    buffer[9] = start_addr & 0xFF;
    buffer[10] = (quantity >> 8) & 0xFF;
    buffer[11] = quantity & 0xFF;

    return 12;
}

// 构建写单个寄存器请求
static uint16_t modbus_build_write_single_register_request(uint8_t *buffer,
                                                             uint16_t transaction_id,
                                                             uint8_t unit_id,
                                                             uint16_t reg_addr,
                                                             uint16_t reg_value)
{
    // MBAP报头
    buffer[0] = (transaction_id >> 8) & 0xFF;
    buffer[1] = transaction_id & 0xFF;
    buffer[2] = 0x00;
    buffer[3] = 0x00;
    buffer[4] = 0x00;
    buffer[5] = 0x06;
    buffer[6] = unit_id;

    // PDU
    buffer[7] = 0x06;  // 功能码
    buffer[8] = (reg_addr >> 8) & 0xFF;
    buffer[9] = reg_addr & 0xFF;
    buffer[10] = (reg_value >> 8) & 0xFF;
    buffer[11] = reg_value & 0xFF;

    return 12;
}

// 构建写多个寄存器请求
static uint16_t modbus_build_write_multiple_registers_request(uint8_t *buffer,
                                                                uint16_t transaction_id,
                                                                uint8_t unit_id,
                                                                uint16_t start_addr,
                                                                uint16_t quantity,
                                                                uint16_t *reg_values)
{
    // MBAP报头
    buffer[0] = (transaction_id >> 8) & 0xFF;
    buffer[1] = transaction_id & 0xFF;
    buffer[2] = 0x00;
    buffer[3] = 0x00;
    uint16_t length = 7 + quantity * 2;
    buffer[4] = (length >> 8) & 0xFF;
    buffer[5] = length & 0xFF;
    buffer[6] = unit_id;

    // PDU
    buffer[7] = 0x10;  // 功能码
    buffer[8] = (start_addr >> 8) & 0xFF;
    buffer[9] = start_addr & 0xFF;
    buffer[10] = (quantity >> 8) & 0xFF;
    buffer[11] = quantity & 0xFF;
    buffer[12] = quantity * 2;  // 字节数

    // 寄存器数据
    for(uint16_t i = 0; i < quantity; i++)
    {
        buffer[13 + i*2] = (reg_values[i] >> 8) & 0xFF;
        buffer[14 + i*2] = reg_values[i] & 0xFF;
    }

    return 13 + quantity * 2;
}

2.3 客户端TCP回调

// 接收回调
static err_t modbus_client_recv(void *arg, struct tcp_pcb *tpcb,
                                 struct pbuf *p, err_t err)
{
    modbus_tcp_client_t *client = (modbus_tcp_client_t *)arg;

    if(p == NULL)
    {
        // 连接关闭
        client->connected = false;
        tcp_close(tpcb);
        return ERR_OK;
    }

    // 复制数据到接收缓冲区
    if(p->tot_len <= sizeof(client->rx_buffer))
    {
        pbuf_copy_partial(p, client->rx_buffer, p->tot_len, 0);
        client->rx_len = p->tot_len;

        // 解析响应
        modbus_parse_response(client);
    }

    pbuf_free(p);
    return ERR_OK;
}

// 连接回调
static err_t modbus_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
    modbus_tcp_client_t *client = (modbus_tcp_client_t *)arg;

    if(err == ERR_OK)
    {
        client->connected = true;
        printf("Modbus TCP client connected\r\n");

        // 设置接收回调
        tcp_recv(tpcb, modbus_client_recv);
    }
    else
    {
        printf("Modbus TCP connection failed: %d\r\n", err);
    }

    return ERR_OK;
}

// 初始化客户端
void modbus_tcp_client_init(const char *server_ip, uint16_t server_port)
{
    // 解析服务器IP
    ip4addr_aton(server_ip, &modbus_client.server_ip);
    modbus_client.server_port = server_port;
    modbus_client.transaction_id = 1;
    modbus_client.connected = false;

    // 创建TCP PCB
    modbus_client.pcb = tcp_new();

    if(modbus_client.pcb == NULL)
    {
        printf("Failed to create Modbus TCP client PCB\r\n");
        return;
    }

    // 设置回调参数
    tcp_arg(modbus_client.pcb, &modbus_client);

    // 连接到服务器
    err_t err = tcp_connect(modbus_client.pcb, &modbus_client.server_ip,
                            modbus_client.server_port, modbus_client_connected);

    if(err != ERR_OK)
    {
        printf("Failed to initiate connection: %d\r\n", err);
        tcp_close(modbus_client.pcb);
    }
}

2.4 客户端API函数

// 读取保持寄存器
err_t modbus_read_holding_registers_client(uint16_t start_addr,
                                            uint16_t quantity,
                                            uint16_t *reg_values)
{
    if(!modbus_client.connected)
    {
        return ERR_CONN;
    }

    uint8_t request[12];
    uint16_t len = modbus_build_read_holding_request(request,
                                                      modbus_client.transaction_id++,
                                                      1,  // unit_id
                                                      start_addr,
                                                      quantity);

    // 发送请求
    err_t err = tcp_write(modbus_client.pcb, request, len, TCP_WRITE_FLAG_COPY);
    if(err != ERR_OK)
    {
        return err;
    }

    tcp_output(modbus_client.pcb);

    // 等待响应(简化版,实际应使用回调或信号量)
    uint32_t timeout = HAL_GetTick() + 1000;
    while(modbus_client.rx_len == 0 && HAL_GetTick() < timeout)
    {
        // 等待接收
    }

    if(modbus_client.rx_len == 0)
    {
        return ERR_TIMEOUT;
    }

    // 解析响应
    uint8_t *data = modbus_client.rx_buffer;
    uint8_t function_code = data[7];

    if(function_code == 0x03)
    {
        uint8_t byte_count = data[8];
        for(uint16_t i = 0; i < quantity; i++)
        {
            reg_values[i] = (data[9 + i*2] << 8) | data[10 + i*2];
        }
        modbus_client.rx_len = 0;
        return ERR_OK;
    }
    else if(function_code == 0x83)
    {
        // 异常响应
        uint8_t exception_code = data[8];
        printf("Modbus exception: %d\r\n", exception_code);
        modbus_client.rx_len = 0;
        return ERR_VAL;
    }

    return ERR_VAL;
}

// 写单个寄存器
err_t modbus_write_single_register_client(uint16_t reg_addr, uint16_t reg_value)
{
    if(!modbus_client.connected)
    {
        return ERR_CONN;
    }

    uint8_t request[12];
    uint16_t len = modbus_build_write_single_register_request(request,
                                                                modbus_client.transaction_id++,
                                                                1,
                                                                reg_addr,
                                                                reg_value);

    err_t err = tcp_write(modbus_client.pcb, request, len, TCP_WRITE_FLAG_COPY);
    if(err != ERR_OK)
    {
        return err;
    }

    tcp_output(modbus_client.pcb);

    return ERR_OK;
}

步骤3:ESP32 Modbus TCP实现

3.1 Arduino + ModbusTCP库

安装库

# 在Arduino IDE中安装
# 工具 -> 管理库 -> 搜索"ModbusTCP-ESP32"

服务器示例

#include <WiFi.h>
#include <ModbusTCP.h>

const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";

// Modbus TCP服务器
ModbusTCP mb;

// 寄存器定义
#define HREG_START      0
#define HREG_COUNT      100
#define IREG_START      0
#define IREG_COUNT      50
#define COIL_START      0
#define COIL_COUNT      64

void setup() {
    Serial.begin(115200);

    // 连接WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi连接成功");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());

    // 启动Modbus TCP服务器
    mb.server();

    // 添加保持寄存器
    for(int i = 0; i < HREG_COUNT; i++) {
        mb.addHreg(HREG_START + i, i * 10);
    }

    // 添加输入寄存器
    for(int i = 0; i < IREG_COUNT; i++) {
        mb.addIreg(IREG_START + i, 0);
    }

    // 添加线圈
    for(int i = 0; i < COIL_COUNT; i++) {
        mb.addCoil(COIL_START + i, false);
    }

    Serial.println("Modbus TCP服务器已启动");
}

void loop() {
    // 处理Modbus请求
    mb.task();

    // 更新传感器数据到输入寄存器
    static unsigned long lastUpdate = 0;
    if(millis() - lastUpdate > 1000) {
        lastUpdate = millis();

        // 模拟传感器数据
        float temperature = 20.0 + random(0, 100) / 10.0;
        float humidity = 50.0 + random(0, 300) / 10.0;
        uint16_t pressure = 1000 + random(0, 100);

        mb.Ireg(IREG_START + 0, (uint16_t)(temperature * 10));
        mb.Ireg(IREG_START + 1, (uint16_t)(humidity * 10));
        mb.Ireg(IREG_START + 2, pressure);
        mb.Ireg(IREG_START + 3, millis() / 1000);  // 运行时间
    }

    // 根据线圈状态控制LED
    if(mb.Coil(COIL_START)) {
        digitalWrite(LED_BUILTIN, HIGH);
    } else {
        digitalWrite(LED_BUILTIN, LOW);
    }

    delay(10);
}

客户端示例

#include <WiFi.h>
#include <ModbusTCP.h>

const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";

// Modbus TCP客户端
ModbusTCP mb;

// 服务器IP地址
IPAddress server(192, 168, 1, 100);

void setup() {
    Serial.begin(115200);

    // 连接WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi连接成功");

    // 初始化Modbus TCP客户端
    mb.client();

    Serial.println("Modbus TCP客户端已启动");
}

void loop() {
    // 连接到服务器
    if(!mb.isConnected(server)) {
        mb.connect(server);
        Serial.println("连接到Modbus TCP服务器...");
        delay(1000);
        return;
    }

    // 读取保持寄存器
    if(mb.readHreg(server, 0, 10)) {
        Serial.println("保持寄存器:");
        for(int i = 0; i < 10; i++) {
            Serial.printf("  [%d] = %d\n", i, mb.Hreg(i));
        }
    }

    delay(1000);

    // 读取输入寄存器
    if(mb.readIreg(server, 0, 4)) {
        float temp = mb.Ireg(0) / 10.0;
        float humi = mb.Ireg(1) / 10.0;
        uint16_t pressure = mb.Ireg(2);
        uint16_t uptime = mb.Ireg(3);

        Serial.printf("温度: %.1f°C, 湿度: %.1f%%, 气压: %d hPa, 运行时间: %ds\n",
                      temp, humi, pressure, uptime);
    }

    delay(1000);

    // 写入寄存器
    static uint16_t counter = 0;
    mb.writeHreg(server, 0, counter++);

    delay(1000);

    // 控制线圈
    static bool ledState = false;
    ledState = !ledState;
    mb.writeCoil(server, 0, ledState);
    Serial.printf("LED状态: %s\n", ledState ? "ON" : "OFF");

    delay(2000);
}

步骤4:Python测试与调试

4.1 使用pymodbus测试

安装pymodbus

pip install pymodbus

读取寄存器

from pymodbus.client import ModbusTcpClient
import time

# 连接到Modbus TCP服务器
client = ModbusTcpClient('192.168.1.100', port=502)

if client.connect():
    print("连接成功")

    # 读取保持寄存器
    result = client.read_holding_registers(address=0, count=10, slave=1)

    if not result.isError():
        print("保持寄存器:")
        for i, value in enumerate(result.registers):
            print(f"  寄存器{i}: {value}")
    else:
        print(f"读取失败: {result}")

    # 读取输入寄存器
    result = client.read_input_registers(address=0, count=4, slave=1)

    if not result.isError():
        temp = result.registers[0] / 10.0
        humi = result.registers[1] / 10.0
        pressure = result.registers[2]
        uptime = result.registers[3]

        print(f"\n传感器数据:")
        print(f"  温度: {temp}°C")
        print(f"  湿度: {humi}%")
        print(f"  气压: {pressure} hPa")
        print(f"  运行时间: {uptime}秒")

    # 读取线圈
    result = client.read_coils(address=0, count=8, slave=1)

    if not result.isError():
        print(f"\n线圈状态: {result.bits[:8]}")

    client.close()
else:
    print("连接失败")

写入寄存器

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('192.168.1.100', port=502)

if client.connect():
    # 写单个保持寄存器
    result = client.write_register(address=0, value=1234, slave=1)
    if not result.isError():
        print("写入成功")

    # 写多个保持寄存器
    values = [100, 200, 300, 400, 500]
    result = client.write_registers(address=0, values=values, slave=1)
    if not result.isError():
        print(f"写入{len(values)}个寄存器成功")

    # 写单个线圈
    result = client.write_coil(address=0, value=True, slave=1)
    if not result.isError():
        print("线圈设置为ON")

    # 写多个线圈
    coil_values = [True, False, True, False, True, False, True, False]
    result = client.write_coils(address=0, values=coil_values, slave=1)
    if not result.isError():
        print(f"写入{len(coil_values)}个线圈成功")

    client.close()

连续监控

from pymodbus.client import ModbusTcpClient
import time

def monitor_modbus_device(ip, port=502, interval=1):
    """连续监控Modbus设备"""
    client = ModbusTcpClient(ip, port=port)

    if not client.connect():
        print("连接失败")
        return

    print(f"开始监控 {ip}:{port}")
    print("按Ctrl+C停止\n")

    try:
        while True:
            # 读取输入寄存器
            result = client.read_input_registers(address=0, count=4, slave=1)

            if not result.isError():
                temp = result.registers[0] / 10.0
                humi = result.registers[1] / 10.0
                pressure = result.registers[2]
                uptime = result.registers[3]

                timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
                print(f"[{timestamp}] 温度:{temp:5.1f}°C  湿度:{humi:5.1f}%  "
                      f"气压:{pressure:4d}hPa  运行:{uptime:6d}s")
            else:
                print(f"读取错误: {result}")

            time.sleep(interval)

    except KeyboardInterrupt:
        print("\n监控停止")
    finally:
        client.close()

# 使用示例
monitor_modbus_device('192.168.1.100', interval=2)

4.2 使用Modbus Poll测试

配置Modbus Poll

  1. 启动Modbus Poll
  2. 创建新连接
  3. Connection -> Connect
  4. Connection: Modbus TCP/IP
  5. IP Address: 192.168.1.100
  6. Port: 502
  7. Slave ID: 1

  8. 配置读取定义

  9. Setup -> Read/Write Definition
  10. Function: 03 (Read Holding Registers)
  11. Address: 0
  12. Quantity: 10
  13. Scan Rate: 1000ms

  14. 开始轮询

  15. 点击"Poll"按钮
  16. 观察寄存器值的变化

测试写入功能

  1. **双击寄存器值**进行修改
  2. 观察设备响应
  3. 检查写入是否成功

4.3 使用Wireshark抓包分析

抓包过滤

# 只显示Modbus TCP流量
tcp.port == 502

# 显示特定IP的Modbus流量
ip.addr == 192.168.1.100 && tcp.port == 502

# 显示Modbus请求
modbus.func_code == 3

# 显示Modbus异常
modbus.except_code

分析Modbus TCP帧

  1. 查看MBAP报头
  2. Transaction ID
  3. Protocol ID
  4. Length
  5. Unit ID

  6. 查看PDU

  7. Function Code
  8. Data

  9. 检查异常

  10. 异常功能码(原功能码 + 0x80)
  11. 异常码

导出统计信息

Statistics -> Conversations -> TCP
  - 查看Modbus TCP连接统计
  - 吞吐量、延迟、丢包率

Statistics -> Protocol Hierarchy
  - 查看Modbus协议占比

步骤5:故障排除与调试

5.1 常见问题诊断

问题1:无法连接到服务器

症状: - 客户端连接超时 - 无法建立TCP连接

诊断步骤

// 1. 检查网络连通性
void check_network_connectivity(const char *ip)
{
    // Ping测试
    printf("Ping %s...\r\n", ip);

    // 使用lwIP的ping功能或系统命令
    // ping_send_now(&target_ip);
}

// 2. 检查端口是否开放
void check_port_status(void)
{
    // 使用netstat或类似工具
    // 确认端口502是否监听
    printf("Checking port 502...\r\n");
}

// 3. 检查防火墙设置
// 确保防火墙允许端口502通信

可能原因: - IP地址配置错误 - 服务器未启动 - 防火墙阻止连接 - 网络不通

问题2:请求超时

症状: - 发送请求后无响应 - 超时错误

诊断代码

// 添加超时处理
err_t modbus_read_with_timeout(uint16_t start_addr, uint16_t quantity,
                                uint16_t *reg_values, uint32_t timeout_ms)
{
    uint32_t start_time = HAL_GetTick();

    // 发送请求
    err_t err = modbus_send_request(start_addr, quantity);
    if(err != ERR_OK)
    {
        return err;
    }

    // 等待响应
    while(modbus_client.rx_len == 0)
    {
        if(HAL_GetTick() - start_time > timeout_ms)
        {
            printf("Modbus request timeout\r\n");
            return ERR_TIMEOUT;
        }

        // 处理其他任务
        osDelay(10);
    }

    // 解析响应
    return modbus_parse_response(reg_values, quantity);
}

可能原因: - 服务器响应慢 - 网络延迟高 - 服务器处理请求失败 - 请求参数错误

问题3:数据错误

症状: - 读取的数据不正确 - 字节序错误

诊断代码

// 验证字节序
void verify_byte_order(void)
{
    uint16_t test_value = 0x1234;
    uint8_t *bytes = (uint8_t *)&test_value;

    printf("System byte order: ");
    if(bytes[0] == 0x34)
    {
        printf("Little Endian\r\n");
    }
    else
    {
        printf("Big Endian\r\n");
    }

    // Modbus使用大端字节序
    printf("Modbus requires Big Endian\r\n");
}

// 数据验证
bool validate_modbus_response(uint8_t *data, uint16_t len)
{
    if(len < 8)
    {
        printf("Response too short\r\n");
        return false;
    }

    // 检查协议ID
    uint16_t protocol_id = (data[2] << 8) | data[3];
    if(protocol_id != 0)
    {
        printf("Invalid protocol ID: 0x%04X\r\n", protocol_id);
        return false;
    }

    // 检查长度字段
    uint16_t length = (data[4] << 8) | data[5];
    if(length + 6 != len)
    {
        printf("Length mismatch: expected %d, got %d\r\n", length + 6, len);
        return false;
    }

    return true;
}

问题4:异常响应

症状: - 收到异常码 - 操作失败

异常码处理

void handle_modbus_exception(uint8_t exception_code)
{
    printf("Modbus Exception: ");

    switch(exception_code)
    {
        case 0x01:
            printf("Illegal Function (0x01)\r\n");
            printf("  - 功能码不支持\r\n");
            break;

        case 0x02:
            printf("Illegal Data Address (0x02)\r\n");
            printf("  - 地址超出范围\r\n");
            break;

        case 0x03:
            printf("Illegal Data Value (0x03)\r\n");
            printf("  - 数据值非法\r\n");
            break;

        case 0x04:
            printf("Slave Device Failure (0x04)\r\n");
            printf("  - 设备故障\r\n");
            break;

        case 0x05:
            printf("Acknowledge (0x05)\r\n");
            printf("  - 长时间操作,稍后重试\r\n");
            break;

        case 0x06:
            printf("Slave Device Busy (0x06)\r\n");
            printf("  - 设备忙,稍后重试\r\n");
            break;

        default:
            printf("Unknown Exception (0x%02X)\r\n", exception_code);
            break;
    }
}

5.2 调试工具

串口调试输出

// 启用详细调试信息
#define MODBUS_DEBUG    1

#if MODBUS_DEBUG
#define MODBUS_LOG(fmt, ...) printf("[MODBUS] " fmt "\r\n", ##__VA_ARGS__)
#else
#define MODBUS_LOG(fmt, ...)
#endif

// 使用示例
void modbus_send_request(uint8_t *data, uint16_t len)
{
    MODBUS_LOG("Sending request, length: %d", len);

    // 打印十六进制数据
    printf("Data: ");
    for(uint16_t i = 0; i < len; i++)
    {
        printf("%02X ", data[i]);
    }
    printf("\r\n");

    // 发送数据
    tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY);
}

数据包记录

// 记录所有Modbus通信
typedef struct {
    uint32_t timestamp;
    bool is_request;
    uint8_t data[260];
    uint16_t length;
} modbus_log_entry_t;

#define LOG_SIZE    100
static modbus_log_entry_t modbus_log[LOG_SIZE];
static uint16_t log_index = 0;

void modbus_log_packet(bool is_request, uint8_t *data, uint16_t len)
{
    modbus_log_entry_t *entry = &modbus_log[log_index];

    entry->timestamp = HAL_GetTick();
    entry->is_request = is_request;
    entry->length = len;
    memcpy(entry->data, data, len);

    log_index = (log_index + 1) % LOG_SIZE;
}

// 打印日志
void modbus_print_log(void)
{
    printf("=== Modbus Communication Log ===\r\n");

    for(uint16_t i = 0; i < LOG_SIZE; i++)
    {
        modbus_log_entry_t *entry = &modbus_log[i];

        if(entry->length == 0)
            continue;

        printf("[%lu] %s: ", entry->timestamp, 
               entry->is_request ? "REQ" : "RSP");

        for(uint16_t j = 0; j < entry->length; j++)
        {
            printf("%02X ", entry->data[j]);
        }
        printf("\r\n");
    }
}

步骤6:性能优化

6.1 连接管理

连接池

#define MAX_CONNECTIONS     5

typedef struct {
    struct tcp_pcb *pcb;
    bool in_use;
    uint32_t last_activity;
    uint8_t client_id;
} modbus_connection_t;

static modbus_connection_t connections[MAX_CONNECTIONS];

// 获取空闲连接
modbus_connection_t* get_free_connection(void)
{
    for(int i = 0; i < MAX_CONNECTIONS; i++)
    {
        if(!connections[i].in_use)
        {
            connections[i].in_use = true;
            connections[i].last_activity = HAL_GetTick();
            return &connections[i];
        }
    }

    return NULL;  // 无空闲连接
}

// 超时清理
void cleanup_idle_connections(void)
{
    uint32_t now = HAL_GetTick();
    const uint32_t timeout = 60000;  // 60秒超时

    for(int i = 0; i < MAX_CONNECTIONS; i++)
    {
        if(connections[i].in_use)
        {
            if(now - connections[i].last_activity > timeout)
            {
                printf("Closing idle connection %d\r\n", i);
                tcp_close(connections[i].pcb);
                connections[i].in_use = false;
            }
        }
    }
}

批量读写

// 批量读取多个寄存器区域
err_t modbus_batch_read(uint16_t *addresses, uint16_t *quantities, 
                        uint16_t count, uint16_t **results)
{
    for(uint16_t i = 0; i < count; i++)
    {
        err_t err = modbus_read_holding_registers_client(addresses[i],
                                                          quantities[i],
                                                          results[i]);
        if(err != ERR_OK)
        {
            return err;
        }

        // 短暂延迟避免服务器过载
        osDelay(10);
    }

    return ERR_OK;
}

// 批量写入
err_t modbus_batch_write(uint16_t *addresses, uint16_t *values, uint16_t count)
{
    for(uint16_t i = 0; i < count; i++)
    {
        err_t err = modbus_write_single_register_client(addresses[i], values[i]);
        if(err != ERR_OK)
        {
            return err;
        }

        osDelay(10);
    }

    return ERR_OK;
}

6.2 数据缓存

// 寄存器缓存
typedef struct {
    uint16_t address;
    uint16_t value;
    uint32_t timestamp;
    bool valid;
} register_cache_t;

#define CACHE_SIZE      100
static register_cache_t register_cache[CACHE_SIZE];

// 从缓存读取
bool read_from_cache(uint16_t address, uint16_t *value, uint32_t max_age_ms)
{
    for(int i = 0; i < CACHE_SIZE; i++)
    {
        if(register_cache[i].valid && 
           register_cache[i].address == address)
        {
            uint32_t age = HAL_GetTick() - register_cache[i].timestamp;

            if(age < max_age_ms)
            {
                *value = register_cache[i].value;
                return true;
            }
        }
    }

    return false;
}

// 更新缓存
void update_cache(uint16_t address, uint16_t value)
{
    // 查找现有条目
    for(int i = 0; i < CACHE_SIZE; i++)
    {
        if(register_cache[i].address == address)
        {
            register_cache[i].value = value;
            register_cache[i].timestamp = HAL_GetTick();
            register_cache[i].valid = true;
            return;
        }
    }

    // 添加新条目(简化版,实际应使用LRU算法)
    static int next_index = 0;
    register_cache[next_index].address = address;
    register_cache[next_index].value = value;
    register_cache[next_index].timestamp = HAL_GetTick();
    register_cache[next_index].valid = true;
    next_index = (next_index + 1) % CACHE_SIZE;
}

// 带缓存的读取
err_t modbus_read_with_cache(uint16_t address, uint16_t *value, uint32_t cache_time_ms)
{
    // 先尝试从缓存读取
    if(read_from_cache(address, value, cache_time_ms))
    {
        return ERR_OK;
    }

    // 缓存未命中,从设备读取
    err_t err = modbus_read_holding_registers_client(address, 1, value);

    if(err == ERR_OK)
    {
        // 更新缓存
        update_cache(address, *value);
    }

    return err;
}

6.3 异步处理

// 使用RTOS任务异步处理Modbus请求
typedef struct {
    uint8_t function_code;
    uint16_t address;
    uint16_t quantity;
    uint16_t *data;
    void (*callback)(err_t result, uint16_t *data);
} modbus_request_t;

QueueHandle_t modbus_request_queue;

// Modbus客户端任务
void modbus_client_task(void *arg)
{
    modbus_request_t request;

    while(1)
    {
        // 从队列获取请求
        if(xQueueReceive(modbus_request_queue, &request, portMAX_DELAY) == pdTRUE)
        {
            err_t result = ERR_OK;

            switch(request.function_code)
            {
                case 0x03:  // 读保持寄存器
                    result = modbus_read_holding_registers_client(request.address,
                                                                   request.quantity,
                                                                   request.data);
                    break;

                case 0x06:  // 写单个寄存器
                    result = modbus_write_single_register_client(request.address,
                                                                  request.data[0]);
                    break;

                default:
                    result = ERR_VAL;
                    break;
            }

            // 调用回调函数
            if(request.callback != NULL)
            {
                request.callback(result, request.data);
            }
        }
    }
}

// 异步读取
void modbus_read_async(uint16_t address, uint16_t quantity,
                       void (*callback)(err_t, uint16_t*))
{
    modbus_request_t request;
    request.function_code = 0x03;
    request.address = address;
    request.quantity = quantity;
    request.data = malloc(quantity * sizeof(uint16_t));
    request.callback = callback;

    xQueueSend(modbus_request_queue, &request, portMAX_DELAY);
}

// 回调函数示例
void read_complete_callback(err_t result, uint16_t *data)
{
    if(result == ERR_OK)
    {
        printf("Read complete: %d\r\n", data[0]);
    }
    else
    {
        printf("Read failed: %d\r\n", result);
    }

    free(data);
}

步骤7:安全考虑

7.1 访问控制

IP白名单

// IP白名单
typedef struct {
    ip4_addr_t ip;
    bool allowed;
} ip_whitelist_entry_t;

#define WHITELIST_SIZE  10
static ip_whitelist_entry_t ip_whitelist[WHITELIST_SIZE];

// 初始化白名单
void init_ip_whitelist(void)
{
    // 添加允许的IP地址
    IP4_ADDR(&ip_whitelist[0].ip, 192, 168, 1, 10);
    ip_whitelist[0].allowed = true;

    IP4_ADDR(&ip_whitelist[1].ip, 192, 168, 1, 20);
    ip_whitelist[1].allowed = true;
}

// 检查IP是否允许
bool is_ip_allowed(ip4_addr_t *ip)
{
    for(int i = 0; i < WHITELIST_SIZE; i++)
    {
        if(ip_whitelist[i].allowed &&
           ip4_addr_cmp(ip, &ip_whitelist[i].ip))
        {
            return true;
        }
    }

    return false;
}

// 在接受连接时检查
err_t modbus_tcp_accept_with_acl(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    if(!is_ip_allowed(&newpcb->remote_ip))
    {
        printf("Connection from %s denied (not in whitelist)\r\n",
               ip4addr_ntoa(&newpcb->remote_ip));
        tcp_abort(newpcb);
        return ERR_ABRT;
    }

    printf("Connection from %s accepted\r\n",
           ip4addr_ntoa(&newpcb->remote_ip));

    return modbus_tcp_accept(arg, newpcb, err);
}

寄存器访问权限

// 寄存器访问权限
typedef enum {
    ACCESS_NONE = 0,
    ACCESS_READ = 1,
    ACCESS_WRITE = 2,
    ACCESS_READ_WRITE = 3
} access_permission_t;

typedef struct {
    uint16_t start_addr;
    uint16_t end_addr;
    access_permission_t permission;
} register_permission_t;

static register_permission_t register_permissions[] = {
    {0, 9, ACCESS_READ_WRITE},      // 寄存器0-9:读写
    {10, 49, ACCESS_READ},          // 寄存器10-49:只读
    {50, 99, ACCESS_NONE},          // 寄存器50-99:禁止访问
};

// 检查访问权限
bool check_register_permission(uint16_t address, bool is_write)
{
    for(int i = 0; i < sizeof(register_permissions)/sizeof(register_permissions[0]); i++)
    {
        if(address >= register_permissions[i].start_addr &&
           address <= register_permissions[i].end_addr)
        {
            access_permission_t perm = register_permissions[i].permission;

            if(is_write)
            {
                return (perm == ACCESS_WRITE || perm == ACCESS_READ_WRITE);
            }
            else
            {
                return (perm == ACCESS_READ || perm == ACCESS_READ_WRITE);
            }
        }
    }

    return false;  // 默认拒绝
}

// 在处理请求时检查权限
uint16_t modbus_read_holding_registers_with_permission(uint16_t start_addr,
                                                        uint16_t quantity,
                                                        uint8_t *response_data)
{
    // 检查每个寄存器的读权限
    for(uint16_t i = 0; i < quantity; i++)
    {
        if(!check_register_permission(start_addr + i, false))
        {
            return 0x02;  // 异常码:非法数据地址
        }
    }

    // 权限检查通过,执行读取
    return modbus_read_holding_registers(start_addr, quantity, response_data);
}

7.2 防止DoS攻击

连接限制

#define MAX_CONNECTIONS_PER_IP  3
#define MAX_REQUESTS_PER_SECOND 10

typedef struct {
    ip4_addr_t ip;
    uint8_t connection_count;
    uint32_t request_count;
    uint32_t last_reset_time;
} client_stats_t;

#define MAX_CLIENTS     20
static client_stats_t client_stats[MAX_CLIENTS];

// 检查连接限制
bool check_connection_limit(ip4_addr_t *ip)
{
    // 查找或创建客户端统计
    client_stats_t *stats = NULL;

    for(int i = 0; i < MAX_CLIENTS; i++)
    {
        if(ip4_addr_cmp(&client_stats[i].ip, ip))
        {
            stats = &client_stats[i];
            break;
        }
    }

    if(stats == NULL)
    {
        // 新客户端,创建统计
        for(int i = 0; i < MAX_CLIENTS; i++)
        {
            if(client_stats[i].connection_count == 0)
            {
                stats = &client_stats[i];
                ip4_addr_copy(stats->ip, *ip);
                break;
            }
        }
    }

    if(stats == NULL)
    {
        return false;  // 客户端表已满
    }

    // 检查连接数限制
    if(stats->connection_count >= MAX_CONNECTIONS_PER_IP)
    {
        printf("Connection limit exceeded for %s\r\n", ip4addr_ntoa(ip));
        return false;
    }

    stats->connection_count++;
    return true;
}

// 检查请求速率限制
bool check_request_rate_limit(ip4_addr_t *ip)
{
    client_stats_t *stats = NULL;

    for(int i = 0; i < MAX_CLIENTS; i++)
    {
        if(ip4_addr_cmp(&client_stats[i].ip, ip))
        {
            stats = &client_stats[i];
            break;
        }
    }

    if(stats == NULL)
    {
        return true;  // 新客户端,允许
    }

    uint32_t now = HAL_GetTick();

    // 每秒重置计数器
    if(now - stats->last_reset_time > 1000)
    {
        stats->request_count = 0;
        stats->last_reset_time = now;
    }

    stats->request_count++;

    if(stats->request_count > MAX_REQUESTS_PER_SECOND)
    {
        printf("Request rate limit exceeded for %s\r\n", ip4addr_ntoa(ip));
        return false;
    }

    return true;
}

7.3 数据加密(Modbus Security)

虽然标准Modbus TCP不支持加密,但可以通过以下方式增强安全性:

使用TLS/SSL

// 使用mbedTLS实现Modbus over TLS
#include "mbedtls/ssl.h"
#include "mbedtls/net_sockets.h"

typedef struct {
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_net_context server_fd;
} modbus_tls_context_t;

// 初始化TLS
int modbus_tls_init(modbus_tls_context_t *ctx, const char *server_ip, uint16_t port)
{
    mbedtls_ssl_init(&ctx->ssl);
    mbedtls_ssl_config_init(&ctx->conf);
    mbedtls_net_init(&ctx->server_fd);

    // 连接到服务器
    char port_str[6];
    snprintf(port_str, sizeof(port_str), "%d", port);

    int ret = mbedtls_net_connect(&ctx->server_fd, server_ip, port_str, MBEDTLS_NET_PROTO_TCP);
    if(ret != 0)
    {
        return ret;
    }

    // 配置SSL
    mbedtls_ssl_config_defaults(&ctx->conf,
                                MBEDTLS_SSL_IS_CLIENT,
                                MBEDTLS_SSL_TRANSPORT_STREAM,
                                MBEDTLS_SSL_PRESET_DEFAULT);

    mbedtls_ssl_setup(&ctx->ssl, &ctx->conf);
    mbedtls_ssl_set_bio(&ctx->ssl, &ctx->server_fd,
                        mbedtls_net_send, mbedtls_net_recv, NULL);

    // 执行SSL握手
    while((ret = mbedtls_ssl_handshake(&ctx->ssl)) != 0)
    {
        if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            return ret;
        }
    }

    return 0;
}

// 通过TLS发送Modbus请求
int modbus_tls_send(modbus_tls_context_t *ctx, uint8_t *data, uint16_t len)
{
    return mbedtls_ssl_write(&ctx->ssl, data, len);
}

// 通过TLS接收Modbus响应
int modbus_tls_recv(modbus_tls_context_t *ctx, uint8_t *buffer, uint16_t max_len)
{
    return mbedtls_ssl_read(&ctx->ssl, buffer, max_len);
}

步骤8:实战应用

8.1 工业数据采集系统

系统架构

[传感器] → [PLC/RTU] → [Modbus TCP网关] → [SCADA系统]
          [STM32设备]
          [云平台/数据库]

数据采集代码

// 数据采集任务
void data_acquisition_task(void *arg)
{
    const uint16_t sensor_addresses[] = {0, 1, 2, 3, 4};  // 传感器寄存器地址
    const uint16_t sensor_count = 5;
    uint16_t sensor_values[sensor_count];

    while(1)
    {
        // 读取所有传感器数据
        err_t err = modbus_read_holding_registers_client(sensor_addresses[0],
                                                          sensor_count,
                                                          sensor_values);

        if(err == ERR_OK)
        {
            // 解析传感器数据
            float temperature = sensor_values[0] / 10.0;
            float humidity = sensor_values[1] / 10.0;
            uint16_t pressure = sensor_values[2];
            uint16_t flow_rate = sensor_values[3];
            uint16_t level = sensor_values[4];

            // 打印数据
            printf("Sensor Data:\r\n");
            printf("  Temperature: %.1f°C\r\n", temperature);
            printf("  Humidity: %.1f%%\r\n", humidity);
            printf("  Pressure: %d kPa\r\n", pressure);
            printf("  Flow Rate: %d L/min\r\n", flow_rate);
            printf("  Level: %d mm\r\n", level);

            // 存储到数据库或发送到云平台
            store_sensor_data(temperature, humidity, pressure, flow_rate, level);

            // 检查报警条件
            check_alarms(temperature, humidity, pressure, flow_rate, level);
        }
        else
        {
            printf("Failed to read sensor data: %d\r\n", err);
        }

        // 每5秒采集一次
        osDelay(5000);
    }
}

// 报警检查
void check_alarms(float temp, float humi, uint16_t pressure, 
                  uint16_t flow, uint16_t level)
{
    // 温度报警
    if(temp > 80.0)
    {
        printf("ALARM: High temperature! %.1f°C\r\n", temp);
        trigger_alarm(ALARM_HIGH_TEMP);
    }

    // 压力报警
    if(pressure > 150)
    {
        printf("ALARM: High pressure! %d kPa\r\n", pressure);
        trigger_alarm(ALARM_HIGH_PRESSURE);
    }

    // 液位报警
    if(level < 100)
    {
        printf("ALARM: Low level! %d mm\r\n", level);
        trigger_alarm(ALARM_LOW_LEVEL);
    }
}

8.2 远程设备控制

控制命令处理

// 控制命令结构
typedef struct {
    uint8_t device_id;
    uint8_t command;
    uint16_t parameter;
} control_command_t;

// 命令定义
#define CMD_START_MOTOR     0x01
#define CMD_STOP_MOTOR      0x02
#define CMD_SET_SPEED       0x03
#define CMD_SET_POSITION    0x04
#define CMD_RESET           0x05

// 执行控制命令
err_t execute_control_command(control_command_t *cmd)
{
    err_t err = ERR_OK;

    switch(cmd->command)
    {
        case CMD_START_MOTOR:
            // 写入启动命令到线圈
            err = modbus_write_single_coil_client(0, true);
            printf("Starting motor on device %d\r\n", cmd->device_id);
            break;

        case CMD_STOP_MOTOR:
            // 写入停止命令到线圈
            err = modbus_write_single_coil_client(0, false);
            printf("Stopping motor on device %d\r\n", cmd->device_id);
            break;

        case CMD_SET_SPEED:
            // 写入速度设定值到寄存器
            err = modbus_write_single_register_client(10, cmd->parameter);
            printf("Setting speed to %d on device %d\r\n", 
                   cmd->parameter, cmd->device_id);
            break;

        case CMD_SET_POSITION:
            // 写入位置设定值到寄存器
            err = modbus_write_single_register_client(20, cmd->parameter);
            printf("Setting position to %d on device %d\r\n",
                   cmd->parameter, cmd->device_id);
            break;

        case CMD_RESET:
            // 写入复位命令
            err = modbus_write_single_register_client(100, 0xFFFF);
            printf("Resetting device %d\r\n", cmd->device_id);
            break;

        default:
            printf("Unknown command: 0x%02X\r\n", cmd->command);
            err = ERR_VAL;
            break;
    }

    return err;
}

// 控制任务
void device_control_task(void *arg)
{
    QueueHandle_t cmd_queue = (QueueHandle_t)arg;
    control_command_t cmd;

    while(1)
    {
        // 从队列接收控制命令
        if(xQueueReceive(cmd_queue, &cmd, portMAX_DELAY) == pdTRUE)
        {
            err_t err = execute_control_command(&cmd);

            if(err == ERR_OK)
            {
                printf("Command executed successfully\r\n");
            }
            else
            {
                printf("Command execution failed: %d\r\n", err);
            }
        }
    }
}

8.3 Modbus TCP网关

RTU到TCP转换

// Modbus RTU到TCP网关
typedef struct {
    // RTU侧
    UART_HandleTypeDef *uart;
    uint8_t rtu_buffer[256];
    uint16_t rtu_len;

    // TCP侧
    struct tcp_pcb *tcp_pcb;
    uint8_t tcp_buffer[260];
    uint16_t tcp_len;
} modbus_gateway_t;

static modbus_gateway_t gateway;

// RTU帧转TCP帧
void rtu_to_tcp(uint8_t *rtu_frame, uint16_t rtu_len,
                uint8_t *tcp_frame, uint16_t *tcp_len)
{
    // RTU帧格式:[从站地址][功能码][数据][CRC16]
    // TCP帧格式:[MBAP报头][功能码][数据]

    // 提取RTU数据(去除CRC)
    uint8_t slave_addr = rtu_frame[0];
    uint8_t function_code = rtu_frame[1];
    uint16_t data_len = rtu_len - 3;  // 减去地址、功能码和CRC

    // 构建MBAP报头
    static uint16_t transaction_id = 1;
    tcp_frame[0] = (transaction_id >> 8) & 0xFF;
    tcp_frame[1] = transaction_id & 0xFF;
    tcp_frame[2] = 0x00;  // 协议ID
    tcp_frame[3] = 0x00;
    uint16_t length = data_len + 2;  // 单元ID + 功能码 + 数据
    tcp_frame[4] = (length >> 8) & 0xFF;
    tcp_frame[5] = length & 0xFF;
    tcp_frame[6] = slave_addr;  // 单元ID

    // 复制功能码和数据
    tcp_frame[7] = function_code;
    memcpy(&tcp_frame[8], &rtu_frame[2], data_len);

    *tcp_len = 8 + data_len;
    transaction_id++;
}

// TCP帧转RTU帧
void tcp_to_rtu(uint8_t *tcp_frame, uint16_t tcp_len,
                uint8_t *rtu_frame, uint16_t *rtu_len)
{
    // 提取TCP数据
    uint8_t unit_id = tcp_frame[6];
    uint8_t function_code = tcp_frame[7];
    uint16_t data_len = tcp_len - 8;

    // 构建RTU帧
    rtu_frame[0] = unit_id;
    rtu_frame[1] = function_code;
    memcpy(&rtu_frame[2], &tcp_frame[8], data_len);

    // 计算CRC
    uint16_t crc = modbus_crc16(rtu_frame, data_len + 2);
    rtu_frame[data_len + 2] = crc & 0xFF;
    rtu_frame[data_len + 3] = (crc >> 8) & 0xFF;

    *rtu_len = data_len + 4;
}

// CRC16计算
uint16_t modbus_crc16(uint8_t *data, uint16_t len)
{
    uint16_t crc = 0xFFFF;

    for(uint16_t i = 0; i < len; i++)
    {
        crc ^= data[i];

        for(uint8_t j = 0; j < 8; j++)
        {
            if(crc & 0x0001)
            {
                crc = (crc >> 1) ^ 0xA001;
            }
            else
            {
                crc >>= 1;
            }
        }
    }

    return crc;
}

最佳实践

9.1 代码组织

模块化设计

project/
├── modbus/
│   ├── modbus_tcp.c/h          # Modbus TCP核心
│   ├── modbus_client.c/h       # 客户端实现
│   ├── modbus_server.c/h       # 服务器实现
│   └── modbus_config.h         # 配置文件
├── network/
│   ├── lwip_init.c/h           # lwIP初始化
│   └── ethernetif.c/h          # 以太网驱动
└── application/
    ├── data_acquisition.c/h    # 数据采集
    ├── device_control.c/h      # 设备控制
    └── gateway.c/h             # 网关功能

9.2 错误处理

统一错误处理机制

typedef enum {
    MODBUS_OK = 0,
    MODBUS_ERR_TIMEOUT,
    MODBUS_ERR_CONN_FAILED,
    MODBUS_ERR_INVALID_RESPONSE,
    MODBUS_ERR_EXCEPTION,
    MODBUS_ERR_NO_MEMORY
} modbus_error_t;

// 错误处理函数
void handle_modbus_error(modbus_error_t error, const char *context)
{
    printf("[ERROR] %s: ", context);

    switch(error)
    {
        case MODBUS_ERR_TIMEOUT:
            printf("Timeout\r\n");
            // 重试逻辑
            break;

        case MODBUS_ERR_CONN_FAILED:
            printf("Connection failed\r\n");
            // 重连逻辑
            break;

        case MODBUS_ERR_EXCEPTION:
            printf("Modbus exception\r\n");
            // 异常处理
            break;

        default:
            printf("Unknown error %d\r\n", error);
            break;
    }
}

9.3 日志记录

// 日志级别
typedef enum {
    LOG_DEBUG,
    LOG_INFO,
    LOG_WARNING,
    LOG_ERROR
} log_level_t;

static log_level_t current_log_level = LOG_INFO;

// 日志函数
void modbus_log(log_level_t level, const char *format, ...)
{
    if(level < current_log_level)
    {
        return;
    }

    const char *level_str[] = {"DEBUG", "INFO", "WARN", "ERROR"};

    printf("[%s] ", level_str[level]);

    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);

    printf("\r\n");
}

// 使用示例
modbus_log(LOG_INFO, "Modbus TCP server started on port %d", 502);
modbus_log(LOG_ERROR, "Failed to read register %d", address);

9.4 性能监控

// 性能统计
typedef struct {
    uint32_t total_requests;
    uint32_t successful_requests;
    uint32_t failed_requests;
    uint32_t timeout_count;
    uint32_t exception_count;
    uint32_t total_response_time_ms;
    uint32_t min_response_time_ms;
    uint32_t max_response_time_ms;
} modbus_stats_t;

static modbus_stats_t modbus_stats;

// 更新统计
void update_modbus_stats(bool success, uint32_t response_time_ms)
{
    modbus_stats.total_requests++;

    if(success)
    {
        modbus_stats.successful_requests++;
        modbus_stats.total_response_time_ms += response_time_ms;

        if(response_time_ms < modbus_stats.min_response_time_ms ||
           modbus_stats.min_response_time_ms == 0)
        {
            modbus_stats.min_response_time_ms = response_time_ms;
        }

        if(response_time_ms > modbus_stats.max_response_time_ms)
        {
            modbus_stats.max_response_time_ms = response_time_ms;
        }
    }
    else
    {
        modbus_stats.failed_requests++;
    }
}

// 打印统计信息
void print_modbus_stats(void)
{
    printf("=== Modbus Statistics ===\r\n");
    printf("Total Requests: %u\r\n", modbus_stats.total_requests);
    printf("Successful: %u\r\n", modbus_stats.successful_requests);
    printf("Failed: %u\r\n", modbus_stats.failed_requests);
    printf("Timeout: %u\r\n", modbus_stats.timeout_count);
    printf("Exception: %u\r\n", modbus_stats.exception_count);

    if(modbus_stats.successful_requests > 0)
    {
        uint32_t avg_time = modbus_stats.total_response_time_ms / 
                           modbus_stats.successful_requests;
        printf("Avg Response Time: %u ms\r\n", avg_time);
        printf("Min Response Time: %u ms\r\n", modbus_stats.min_response_time_ms);
        printf("Max Response Time: %u ms\r\n", modbus_stats.max_response_time_ms);
    }

    float success_rate = (float)modbus_stats.successful_requests / 
                        modbus_stats.total_requests * 100.0;
    printf("Success Rate: %.2f%%\r\n", success_rate);
}

总结

通过本教程,你学习了:

  • ✅ Modbus TCP协议的架构和工作原理
  • ✅ MBAP报头和功能码的使用方法
  • ✅ STM32平台上的Modbus TCP服务器实现
  • ✅ Modbus TCP客户端开发
  • ✅ ESP32平台的Modbus TCP应用
  • ✅ Python测试和调试技术
  • ✅ 性能优化和安全考虑
  • ✅ 工业数据采集和设备控制应用
  • ✅ Modbus RTU到TCP网关实现

关键要点

  1. 协议理解:Modbus TCP在Modbus RTU基础上增加了MBAP报头
  2. 字节序:Modbus使用大端字节序(网络字节序)
  3. 端口:标准端口为502
  4. 异常处理:正确处理异常响应和超时
  5. 性能优化:使用连接池、缓存、异步处理
  6. 安全考虑:访问控制、速率限制、数据加密
  7. 实战应用:数据采集、设备控制、协议转换

进阶挑战

尝试以下挑战来巩固学习:

  1. 挑战1:实现Modbus TCP主从站自动发现功能
  2. 挑战2:添加Modbus数据记录和历史查询功能
  3. 挑战3:实现Modbus TCP冗余连接(主备服务器)
  4. 挑战4:开发Web界面监控Modbus设备状态
  5. 挑战5:实现Modbus TCP到MQTT的桥接

完整代码

完整的项目代码可以在GitHub上找到:

GitHub仓库: https://github.com/embedded-knowledge/modbus-tcp-tutorial

项目包含: - STM32 Modbus TCP服务器完整代码 - Modbus TCP客户端实现 - ESP32 Arduino示例 - Python测试脚本 - Modbus RTU到TCP网关代码

下一步

建议继续学习:

  • OPC UA协议 - 学习现代工业通信标准
  • EtherCAT协议 - 学习实时工业以太网
  • PROFINET协议 - 学习西门子工业以太网
  • 工业物联网平台 - 学习云端集成
  • 时间敏感网络(TSN) - 学习确定性以太网

参考资料

  1. Modbus官方文档
  2. Modbus Application Protocol V1.1b3
  3. Modbus Messaging Implementation Guide V1.0b
  4. 官网:https://modbus.org/

  5. 技术标准

  6. IEC 61158:工业通信网络标准
  7. IEEE 802.3:以太网标准

  8. 开源库

  9. libmodbus:https://libmodbus.org/
  10. pymodbus:https://github.com/riptideio/pymodbus
  11. ModbusTCP-ESP32:https://github.com/emelianov/modbus-esp8266

  12. 书籍推荐

  13. 《Modbus工业通信协议详解》
  14. 《工业以太网技术与应用》
  15. 《PLC与工业网络通信技术》

  16. 在线资源

  17. Modbus Organization:https://modbus.org/
  18. Industrial Ethernet Book:https://www.iebmedia.com/
  19. Control Engineering:https://www.controleng.com/

常见问题FAQ

Q1: Modbus TCP和Modbus RTU可以互相转换吗?

A: 可以,通过Modbus网关可以实现RTU和TCP之间的协议转换。主要区别是传输层(串口vs以太网)和帧格式(CRC vs MBAP报头)。

Q2: Modbus TCP的最大传输速度是多少?

A: 理论上受限于以太网速度(10/100/1000 Mbps),实际速度取决于: - 网络带宽 - 设备处理能力 - 请求/响应大小 - 网络延迟

Q3: 一个Modbus TCP服务器可以支持多少个客户端?

A: 标准没有限制,实际取决于: - 服务器硬件性能 - 内存大小 - TCP连接数限制 - 应用需求

通常嵌入式设备支持5-20个并发连接。

Q4: Modbus TCP安全吗?

A: 标准Modbus TCP没有内置安全机制,建议: - 使用VPN或专用网络 - 实施访问控制(IP白名单) - 使用TLS/SSL加密 - 部署防火墙

Q5: 如何选择Modbus RTU还是Modbus TCP?

A: 选择依据: - Modbus RTU:现场总线、短距离、成本敏感 - Modbus TCP:工业以太网、远程访问、高速传输


本教程持续更新中,欢迎反馈和建议!