系统架构与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
实际部署注意事项
-
工业环境抗干扰
- 在SPI线上串联22Ω电阻
- 靠近W5500的VCC和GND之间添加100nF去耦电容
- 使用屏蔽网线,RJ45接口加网络变压器
-
固件升级方案
c// 通过Modbus实现IAP升级 // 保留寄存器地址用于固件传输 #define REG_FW_VERSION 0x1000 #define REG_FW_CONTROL 0x1001 #define REG_FW_DATA 0x1002 -
安全增强
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; }