基于STM32和W5500芯片的Modbus TCP协议栈实现

系统架构与W5500优势

为何选择W5500硬件TCP/IP芯片

对比维度 W5500硬件方案 软件TCP/IP栈 (如LWIP)
CPU占用率 < 5% 30-50%
内存需求 很小 (仅需缓冲) 较大 (10KB+ RAM)
开发难度 简单 (SPI接口) 复杂 (协议栈移植)
网络性能 稳定,硬件处理 依赖CPU性能
实时性 高,确定性延迟 受任务调度影响

完整系统设计方案

1. 硬件连接 (STM32F103 + W5500)

复制代码
STM32F103C8T6 (主控) <--SPI--> W5500 (以太网芯片) <--RJ45--> 网络
       |                                |
    GPIO控制                         25MHz晶振
       |                                |
    重置引脚                        3.3V供电

引脚连接示例:

c 复制代码
// W5500 SPI引脚配置
#define W5500_SPI_PORT        SPI1
#define W5500_CS_GPIO         GPIOA
#define W5500_CS_PIN          GPIO_PIN_4
#define W5500_RST_GPIO        GPIOA
#define W5500_RST_PIN         GPIO_PIN_3
#define W5500_INT_GPIO        GPIOA  
#define W5500_INT_PIN         GPIO_PIN_2

// SPI配置:模式0,8位数据,高速

2. 软件架构设计

复制代码
应用层:    Modbus TCP服务器
           ↕
传输层:    TCP Socket管理 (端口502)
           ↕
网络层:    W5500驱动 + IP协议处理
           ↕
硬件层:    SPI通信 + GPIO控制

核心代码实现

文件1: w5500_driver.c - W5500底层驱动

c 复制代码
#include "w5500.h"
#include "spi.h"

// W5500寄存器地址定义
#define W5500_MR          0x0000      // 模式寄存器
#define W5500_GAR         0x0001      // 网关地址
#define W5500_SUBR        0x0005      // 子网掩码
#define W5500_SHAR        0x0009      // 源MAC地址
#define W5500_SIPR        0x000F      // 源IP地址
#define W5500_RTR         0x0017      // 重传时间
#define W5500_RCR         0x0019      // 重传计数
#define W5500_SOCK_REG(n) (0x0400 + 0x100 * (n)) // Socket寄存器基址

// SPI读写函数
static void W5500_Select(void) {
    HAL_GPIO_WritePin(W5500_CS_GPIO, W5500_CS_PIN, GPIO_PIN_RESET);
}

static void W5500_Deselect(void) {
    HAL_GPIO_WritePin(W5500_CS_GPIO, W5500_CS_PIN, GPIO_PIN_SET);
}

uint8_t W5500_ReadByte(uint16_t addr) {
    uint8_t cmd[3], data;
    
    cmd[0] = (addr >> 8) & 0xFF;    // 地址高字节
    cmd[1] = addr & 0xFF;           // 地址低字节
    cmd[2] = 0x00;                  // 读命令
    
    W5500_Select();
    HAL_SPI_Transmit(&hspi1, cmd, 3, 100);
    HAL_SPI_Receive(&hspi1, &data, 1, 100);
    W5500_Deselect();
    
    return data;
}

void W5500_WriteByte(uint16_t addr, uint8_t data) {
    uint8_t cmd[4];
    
    cmd[0] = (addr >> 8) & 0xFF;    // 地址高字节
    cmd[1] = addr & 0xFF;           // 地址低字节
    cmd[2] = 0x80;                  // 写命令
    cmd[3] = data;                  // 数据
    
    W5500_Select();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    W5500_Deselect();
}

// W5500初始化
uint8_t W5500_Init(void) {
    // 1. 硬件复位
    HAL_GPIO_WritePin(W5500_RST_GPIO, W5500_RST_PIN, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(W5500_RST_GPIO, W5500_RST_PIN, GPIO_PIN_SET);
    HAL_Delay(100);
    
    // 2. 验证通信
    W5500_WriteByte(W5500_MR, 0x80); // 软复位
    HAL_Delay(10);
    
    uint8_t version = W5500_ReadByte(0x0039); // 读版本号
    if (version != 0x04) { // W5500版本号应为0x04
        return 0; // 初始化失败
    }
    
    // 3. 配置网络参数
    uint8_t mac_addr[6] = {0x00, 0x08, 0xDC, 0x01, 0x02, 0x03};
    uint8_t ip_addr[4] = {192, 168, 1, 100};
    uint8_t subnet[4] = {255, 255, 255, 0};
    uint8_t gateway[4] = {192, 168, 1, 1};
    
    // 设置MAC地址
    for (int i = 0; i < 6; i++) {
        W5500_WriteByte(W5500_SHAR + i, mac_addr[i]);
    }
    
    // 设置IP地址
    for (int i = 0; i < 4; i++) {
        W5500_WriteByte(W5500_SIPR + i, ip_addr[i]);
        W5500_WriteByte(W5500_SUBR + i, subnet[i]);
        W5500_WriteByte(W5500_GAR + i, gateway[i]);
    }
    
    // 4. 配置重传参数
    W5500_WriteByte(W5500_RTR, 0x07D0 >> 8);   // 重传时间2000ms
    W5500_WriteByte(W5500_RTR + 1, 0x07D0 & 0xFF);
    W5500_WriteByte(W5500_RCR, 0x08);          // 重试8次
    
    return 1; // 初始化成功
}

// Socket初始化 (Modbus TCP使用Socket 0)
uint8_t W5500_SocketInit(uint8_t sock_num, uint16_t port) {
    uint16_t sock_base = W5500_SOCK_REG(sock_num);
    
    // 关闭Socket
    W5500_WriteByte(sock_base + 0x0001, 0x10); // SOCK_CLOSE
    
    // 设置Socket为TCP模式
    W5500_WriteByte(sock_base + 0x0000, 0x01); // Sn_MR = TCP
    
    // 设置端口号
    W5500_WriteByte(sock_base + 0x0004, port >> 8);
    W5500_WriteByte(sock_base + 0x0004 + 1, port & 0xFF);
    
    // 设置接收/发送缓冲区大小 (各2KB)
    W5500_WriteByte(sock_base + 0x001E, 0x10); // 接收缓冲区大小
    W5500_WriteByte(sock_base + 0x001F, 0x10); // 发送缓冲区大小
    
    // 打开Socket监听
    W5500_WriteByte(sock_base + 0x0001, 0x01); // SOCK_LISTEN
    
    return 1;
}

文件2: modbus_tcp.c - Modbus TCP协议处理

c 复制代码
#include "modbus_tcp.h"

// Modbus TCP事务标识符 (用于跟踪请求-响应对)
static uint16_t transaction_id = 0;

// Modbus PDU结构
#pragma pack(push, 1)
typedef struct {
    uint16_t transaction_id;  // 事务标识符
    uint16_t protocol_id;     // 协议标识符 (0=Modbus)
    uint16_t length;          // 后续字节数
    uint8_t unit_id;          // 从站地址
    uint8_t function_code;    // 功能码
    uint8_t data[252];        // 数据域 (最大252字节)
} ModbusTCP_Header;

typedef struct {
    uint16_t address;         // 寄存器地址
    uint16_t quantity;        // 寄存器数量
} Modbus_Read_Request;

typedef struct {
    uint8_t byte_count;       // 字节数
    uint16_t data[125];       // 寄存器数据 (最多125个)
} Modbus_Read_Response;
#pragma pack(pop)

// 支持的Modbus功能码
#define MODBUS_FC_READ_COILS          0x01
#define MODBUS_FC_READ_INPUTS         0x02
#define MODBUS_FC_READ_HOLDING_REGS   0x03
#define MODBUS_FC_READ_INPUT_REGS     0x04
#define MODBUS_FC_WRITE_SINGLE_COIL   0x05
#define MODBUS_FC_WRITE_SINGLE_REG    0x06
#define MODBUS_FC_WRITE_MULTIPLE_REGS 0x10

// 异常响应码
#define MODBUS_EXCEPTION_ILLEGAL_FUNCTION  0x01
#define MODBUS_EXCEPTION_ILLEGAL_ADDRESS   0x02
#define MODBUS_EXCEPTION_ILLEGAL_VALUE     0x03

// 设备数据存储区 (示例)
static uint16_t holding_registers[100] = {0};   // 保持寄存器
static uint16_t input_registers[50] = {0};      // 输入寄存器
static uint8_t coils[20] = {0};                 // 线圈
static uint8_t discrete_inputs[20] = {0};       // 离散输入

// 处理Modbus TCP请求
uint16_t ModbusTCP_ProcessRequest(uint8_t *request, uint16_t req_len, 
                                   uint8_t *response, uint8_t unit_id) {
    ModbusTCP_Header *req_header = (ModbusTCP_Header *)request;
    ModbusTCP_Header *resp_header = (ModbusTCP_Header *)response;
    
    // 验证协议标识符 (必须为0)
    if (req_header->protocol_id != 0) {
        return 0;
    }
    
    // 复制事务ID和协议ID
    resp_header->transaction_id = req_header->transaction_id;
    resp_header->protocol_id = 0;
    resp_header->unit_id = unit_id;
    
    // 验证单元ID (支持广播地址0)
    if (req_header->unit_id != unit_id && req_header->unit_id != 0) {
        return 0;
    }
    
    // 处理功能码
    uint8_t exception_code = 0;
    uint16_t data_len = 0;
    
    switch (req_header->function_code) {
        case MODBUS_FC_READ_HOLDING_REGS:
            data_len = ProcessReadHoldingRegisters(request + 7, 
                                                   response + 7, 
                                                   &exception_code);
            resp_header->function_code = exception_code ? 
                (req_header->function_code | 0x80) : req_header->function_code;
            break;
            
        case MODBUS_FC_WRITE_SINGLE_REG:
            data_len = ProcessWriteSingleRegister(request + 7, 
                                                  response + 7, 
                                                  &exception_code);
            resp_header->function_code = exception_code ? 
                (req_header->function_code | 0x80) : req_header->function_code;
            break;
            
        case MODBUS_FC_WRITE_MULTIPLE_REGS:
            data_len = ProcessWriteMultipleRegisters(request + 7, 
                                                     response + 7, 
                                                     &exception_code);
            resp_header->function_code = exception_code ? 
                (req_header->function_code | 0x80) : req_header->function_code;
            break;
            
        default:
            // 不支持的函数码
            data_len = 1; // 异常码占1字节
            response[7] = req_header->function_code | 0x80;
            response[8] = MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
            break;
    }
    
    // 设置响应长度 (MBAP头6字节 + 数据长度)
    resp_header->length = data_len + 1; // +1是unit_id
    
    // 如果发生异常,数据长度为3 (功能码+异常码)
    if (exception_code) {
        resp_header->length = 3;
    }
    
    // 总响应长度 = MBAP头(7字节) + 数据长度
    return 7 + data_len;
}

// 处理读保持寄存器请求 (功能码0x03)
static uint16_t ProcessReadHoldingRegisters(uint8_t *request, uint8_t *response, 
                                            uint8_t *exception) {
    Modbus_Read_Request *read_req = (Modbus_Read_Request *)request;
    Modbus_Read_Response *read_resp = (Modbus_Read_Response *)response;
    
    uint16_t start_addr = __bswap_16(read_req->address);
    uint16_t quantity = __bswap_16(read_req->quantity);
    
    // 验证参数
    if (quantity == 0 || quantity > 125) {
        *exception = MODBUS_EXCEPTION_ILLEGAL_VALUE;
        response[0] = 0x83; // 异常功能码
        response[1] = *exception;
        return 2;
    }
    
    if (start_addr + quantity > sizeof(holding_registers)/2) {
        *exception = MODBUS_EXCEPTION_ILLEGAL_ADDRESS;
        response[0] = 0x83;
        response[1] = *exception;
        return 2;
    }
    
    // 构造正常响应
    read_resp->byte_count = quantity * 2;
    
    for (uint16_t i = 0; i < quantity; i++) {
        read_resp->data[i] = __bswap_16(holding_registers[start_addr + i]);
    }
    
    *exception = 0;
    return 1 + read_resp->byte_count; // 1字节(byte_count) + 数据字节
}

// 处理写单个寄存器请求 (功能码0x06)
static uint16_t ProcessWriteSingleRegister(uint8_t *request, uint8_t *response,
                                           uint8_t *exception) {
    uint16_t address = __bswap_16(*(uint16_t *)(request));
    uint16_t value = __bswap_16(*(uint16_t *)(request + 2));
    
    // 验证地址
    if (address >= sizeof(holding_registers)/2) {
        *exception = MODBUS_EXCEPTION_ILLEGAL_ADDRESS;
        response[0] = 0x86;
        response[1] = *exception;
        return 2;
    }
    
    // 写入寄存器
    holding_registers[address] = value;
    
    // 回显写入的值 (按Modbus协议要求)
    *(uint16_t *)response = __bswap_16(address);
    *(uint16_t *)(response + 2) = __bswap_16(value);
    
    *exception = 0;
    return 4; // 地址(2字节) + 值(2字节)
}

文件3: main.c - 主应用程序

c 复制代码
#include "main.h"
#include "w5500.h"
#include "modbus_tcp.h"

// 网络配置
#define MODBUS_TCP_PORT     502
#define DEVICE_UNIT_ID      1       // Modbus从站地址
#define SOCKET_NUMBER       0       // 使用Socket 0

// 缓冲区
static uint8_t rx_buffer[1024];
static uint8_t tx_buffer[1024];

// 状态枚举
typedef enum {
    SOCKET_CLOSED = 0,
    SOCKET_LISTEN,
    SOCKET_ESTABLISHED,
    SOCKET_CLOSE_WAIT
} SocketState;

void SystemClock_Config(void);
void Error_Handler(void);

int main(void) {
    // HAL库初始化
    HAL_Init();
    SystemClock_Config();
    
    // 外设初始化
    MX_GPIO_Init();
    MX_SPI1_Init();
    
    printf("STM32 + W5500 Modbus TCP Server Starting...\r\n");
    
    // 1. 初始化W5500
    if (!W5500_Init()) {
        printf("W5500 Initialization Failed!\r\n");
        Error_Handler();
    }
    
    printf("W5500 Initialized Successfully\r\n");
    
    // 2. 打印网络信息
    printf("IP Address: %d.%d.%d.%d\r\n", 
           W5500_ReadByte(W5500_SIPR),
           W5500_ReadByte(W5500_SIPR + 1),
           W5500_ReadByte(W5500_SIPR + 2),
           W5500_ReadByte(W5500_SIPR + 3));
    printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
           W5500_ReadByte(W5500_SHAR),
           W5500_ReadByte(W5500_SHAR + 1),
           W5500_ReadByte(W5500_SHAR + 2),
           W5500_ReadByte(W5500_SHAR + 3),
           W5500_ReadByte(W5500_SHAR + 4),
           W5500_ReadByte(W5500_SHAR + 5));
    
    // 3. 初始化Socket
    if (!W5500_SocketInit(SOCKET_NUMBER, MODBUS_TCP_PORT)) {
        printf("Socket Initialization Failed!\r\n");
        Error_Handler();
    }
    
    printf("Socket Initialized, Listening on Port %d\r\n", MODBUS_TCP_PORT);
    
    // 4. 初始化设备数据
    for (int i = 0; i < 100; i++) {
        holding_registers[i] = i * 10; // 示例数据
    }
    
    // 5. 主循环
    while (1) {
        SocketState state = CheckSocketState(SOCKET_NUMBER);
        
        switch (state) {
            case SOCKET_LISTEN:
                // 检查是否有新的连接
                if (CheckNewConnection(SOCKET_NUMBER)) {
                    printf("New Modbus TCP Connection Established\r\n");
                }
                break;
                
            case SOCKET_ESTABLISHED:
                // 检查是否有数据到达
                uint16_t rx_len = CheckSocketReceive(SOCKET_NUMBER);
                if (rx_len > 0) {
                    // 读取数据
                    uint16_t actual_len = ReadSocketData(SOCKET_NUMBER, 
                                                         rx_buffer, 
                                                         rx_len);
                    
                    if (actual_len >= 12) { // Modbus TCP最小请求长度
                        // 处理Modbus请求
                        uint16_t tx_len = ModbusTCP_ProcessRequest(
                            rx_buffer, actual_len, tx_buffer, DEVICE_UNIT_ID);
                        
                        if (tx_len > 0) {
                            // 发送响应
                            SendSocketData(SOCKET_NUMBER, tx_buffer, tx_len);
                            printf("Processed Modbus Request, Sent %d bytes\r\n", 
                                   tx_len);
                        }
                    }
                }
                break;
                
            case SOCKET_CLOSE_WAIT:
                // 对方关闭连接,重置Socket
                W5500_SocketClose(SOCKET_NUMBER);
                W5500_SocketInit(SOCKET_NUMBER, MODBUS_TCP_PORT);
                printf("Connection Closed, Socket Reset\r\n");
                break;
                
            default:
                break;
        }
        
        // 其他任务可以在这里执行
        ProcessBackgroundTasks();
        
        HAL_Delay(10); // 10ms延时
    }
}

// 检查Socket状态
SocketState CheckSocketState(uint8_t sock_num) {
    uint16_t sock_base = W5500_SOCK_REG(sock_num);
    uint8_t status = W5500_ReadByte(sock_base + 0x0003); // Sn_SR
    
    switch (status) {
        case 0x00: return SOCKET_CLOSED;
        case 0x13: return SOCKET_LISTEN;
        case 0x17: return SOCKET_ESTABLISHED;
        case 0x1C: return SOCKET_CLOSE_WAIT;
        default: return SOCKET_CLOSED;
    }
}

// 检查新连接
uint8_t CheckNewConnection(uint8_t sock_num) {
    uint16_t sock_base = W5500_SOCK_REG(sock_num);
    uint8_t ir = W5500_ReadByte(sock_base + 0x0002); // Sn_IR
    
    if (ir & 0x01) { // CON位为1表示有新连接
        // 清除中断标志
        W5500_WriteByte(sock_base + 0x0002, 0x01);
        return 1;
    }
    
    return 0;
}

调试与测试方案

1. Modbus调试工具配置

c 复制代码
// 使用Modbus Poll/Master进行测试时注意:
// 1. 连接设置: TCP/IP, Port 502
// 2. 从站地址: 1 (DEVICE_UNIT_ID)
// 3. 功能码测试顺序:
//    - 0x03: 读保持寄存器 (地址0-99)
//    - 0x06: 写单个寄存器
//    - 0x10: 写多个寄存器

2. Wireshark抓包分析

bash 复制代码
# 过滤条件:
modbus && ip.addr == 192.168.1.100  # 只看设备通信
tcp.port == 502                    # Modbus TCP端口

3. 常见问题排查表

现象 可能原因 解决方案
无法ping通 1. 网线问题 2. IP冲突 3. W5500初始化失败 1. 检查硬件连接 2. 更换IP地址 3. 检查SPI通信
Modbus无响应 1. 端口未监听 2. 请求格式错误 3. 缓冲区不足 1. 确认Socket状态 2. 检查字节序 3. 增大缓冲区
连接频繁断开 1. 超时设置过短 2. 网络干扰 3. Keep-Alive未启用 1. 调整RTR寄存器 2. 检查网络质量 3. 实现心跳机制

性能优化建议

1. 多Socket支持

c 复制代码
// 可以创建多个Socket支持多客户端
#define MAX_SOCKETS 4
uint8_t active_sockets[MAX_SOCKETS] = {0};

// 轮询处理所有Socket
for (int i = 0; i < MAX_SOCKETS; i++) {
    if (active_sockets[i]) {
        ProcessSocketData(i);
    }
}

2. DMA传输优化

c 复制代码
// 使用DMA进行SPI数据传输
void W5500_SPI_DMA_Write(uint16_t addr, uint8_t *data, uint16_t len) {
    uint8_t cmd[3];
    cmd[0] = (addr >> 8) & 0xFF;
    cmd[1] = addr & 0xFF;
    cmd[2] = 0x80; // 写命令
    
    W5500_Select();
    HAL_SPI_Transmit_DMA(&hspi1, cmd, 3);
    // 等待传输完成
    HAL_SPI_Transmit_DMA(&hspi1, data, len);
    W5500_Deselect();
}

3. 看门狗保护

c 复制代码
// 防止程序死机
void IWDG_Init(void) {
    // 独立看门狗,4秒超时
    IWDG->KR = 0xCCCC; // 启动看门狗
    IWDG->KR = 0x5555; // 允许访问PR和RLR
    IWDG->PR = 4;      // 预分频64
    IWDG->RLR = 1250;  // 重载值 (4秒)
    IWDG->KR = 0xAAAA; // 重载计数器
}

// 主循环中喂狗
while (1) {
    IWDG->KR = 0xAAAA; // 喂狗
    // ... 主循环代码
}

参考代码 基于STM32 W5500 开发的modbus tcp 协议 www.3dddown.com/csa/112773.html

实际部署注意事项

  1. 工业环境抗干扰

    • 在SPI线上串联22Ω电阻
    • 靠近W5500的VCC和GND之间添加100nF去耦电容
    • 使用屏蔽网线,RJ45接口加网络变压器
  2. 固件升级方案

    c 复制代码
    // 通过Modbus实现IAP升级
    // 保留寄存器地址用于固件传输
    #define REG_FW_VERSION    0x1000
    #define REG_FW_CONTROL    0x1001
    #define REG_FW_DATA       0x1002
  3. 安全增强

    c 复制代码
    // 简单的IP过滤
    uint8_t allowed_ips[][4] = {
        {192, 168, 1, 10},
        {192, 168, 1, 20}
    };
    
    uint8_t CheckIPAllowed(uint8_t *ip) {
        for (int i = 0; i < sizeof(allowed_ips)/4; i++) {
            if (memcmp(ip, allowed_ips[i], 4) == 0)
                return 1;
        }
        return 0;
    }
相关推荐
上大科技蔡生2 小时前
CS5715:2.7V~26V宽输入,单节锂电池适用,最高36V输出,省掉电感电流检测电阻,软启动时间可调,异步升压DCDC控制器
单片机·嵌入式硬件·dcdc
芯思路2 小时前
STM32开发学习笔记之三【按键】
笔记·stm32·学习
CQ_YM2 小时前
51单片机(1)
单片机·嵌入式硬件·51单片机
qq_401700413 小时前
单片机之ADC(模拟数字转换器)
单片机·嵌入式硬件
无事好时节3 小时前
51 单片机GPIO / 按键 / 中断 / 定时器 / PWM
单片机·嵌入式硬件
一枝小雨5 小时前
【OTA专题】17 打通Bootloader与App逻辑之间的通信
stm32·单片机·嵌入式·流程图·freertos·ota·bootloader
2401_863318637 小时前
基于单片机的家庭防盗报警系统
单片机·嵌入式硬件
一枝小雨7 小时前
【OTA专题】18 OTA性能优化:优化bootloader存储空间与固件完整性校验(CRC)
stm32·单片机·性能优化·嵌入式·freertos·ota·bootloader
iYun在学C7 小时前
驱动程序(注册字符设备)
linux·嵌入式硬件