在嵌入式物联网应用中,稳定可靠的网络通信往往是项目成功的关键。STM32F103CBT6作为经典的Cortex-M3内核微控制器,搭配W5500这款全硬件TCP/IP协议栈芯片,能够为工业现场提供稳定的有线网络连接方案。本文将带您深入实战,从硬件设计到代码移植,完整实现一个具备断线重连机制的TCP客户端。
W5500与STM32F103CBT6通过SPI接口通信,典型连接方式如下:
| W5500引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| SCS | PA4 | SPI片选 |
| SCLK | PA5 | SPI时钟 |
| MOSI | PA7 | 主出从入 |
| MISO | PA6 | 主入从出 |
| RSTn | PB0 | 硬件复位 |
| INTn | PB1 | 中断信号 |
特别注意:工业环境中建议在INTn引脚添加RC滤波电路(如100Ω电阻串联10nF电容到地),可有效抑制电磁干扰导致的误中断。
在STM32CubeMX中配置SPI1时,建议采用以下参数组合:
c复制hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 9MHz @72MHz主频
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
提示:SPI时钟不宜超过20MHz,过高的速率可能导致信号完整性问题。工业现场建议使用屏蔽双绞线连接。
W5500官方驱动库需要做以下关键修改:
c复制// 原厂驱动中的硬件访问接口需要替换为HAL库版本
#define IINCHIP_READ(addr) W5500_ReadByte(addr)
#define IINCHIP_WRITE(addr,data) W5500_WriteByte(addr,data)
uint8_t W5500_ReadByte(uint32_t addr) {
uint8_t cmd[3] = {addr>>8, addr&0xFF, 0x00};
uint8_t data;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, cmd, &data, 3, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
return data;
}
c复制void W5500_HardReset(void) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(10); // 保持低电平至少500ns
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(100); // 等待芯片稳定
}
推荐使用结构体管理网络配置:
c复制typedef struct {
uint8_t mac[6];
uint8_t ip[4];
uint8_t subnet[4];
uint8_t gateway[4];
uint8_t dns[4];
} Network_ConfigTypeDef;
const Network_ConfigTypeDef default_config = {
.mac = {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56},
.ip = {192, 168, 1, 100},
.subnet = {255, 255, 255, 0},
.gateway = {192, 168, 1, 1},
.dns = {8, 8, 8, 8}
};
可靠的TCP客户端需要状态机管理连接过程:
mermaid复制stateDiagram
[*] --> DISCONNECTED
DISCONNECTED --> CONNECTING: 触发连接
CONNECTING --> CONNECTED: 连接成功
CONNECTING --> DISCONNECTED: 连接失败
CONNECTED --> DISCONNECTED: 检测到断线
CONNECTED --> RECONNECTING: 发送失败
RECONNECTING --> CONNECTED: 重连成功
RECONNECTING --> DISCONNECTED: 重连超时
对应代码实现:
c复制typedef enum {
TCP_STATE_DISCONNECTED,
TCP_STATE_CONNECTING,
TCP_STATE_CONNECTED,
TCP_STATE_RECONNECTING
} TCP_StateTypeDef;
void TCPClient_StateMachine(TCP_ClientTypeDef *client) {
static uint32_t last_attempt = 0;
switch(client->state) {
case TCP_STATE_DISCONNECTED:
if(HAL_GetTick() - last_attempt > RECONNECT_INTERVAL) {
client->state = TCP_STATE_CONNECTING;
last_attempt = HAL_GetTick();
}
break;
case TCP_STATE_CONNECTING:
if(socket(client->sock_num, Sn_MR_TCP, client->local_port, 0)) {
if(connect(client->sock_num, client->server_ip, client->server_port)) {
client->state = TCP_STATE_CONNECTED;
}
}
// 添加30秒超时判断
if(HAL_GetTick() - last_attempt > 30000) {
close(client->sock_num);
client->state = TCP_STATE_DISCONNECTED;
}
break;
case TCP_STATE_CONNECTED:
// 心跳检测逻辑
if(HAL_GetTick() - client->last_activity > HEARTBEAT_TIMEOUT) {
close(client->sock_num);
client->state = TCP_STATE_DISCONNECTED;
}
break;
}
}
发送缓冲区管理:
c复制#define TX_BUF_SIZE 1460 // 以太网MTU减去IP/TCP头
typedef struct {
uint8_t buffer[TX_BUF_SIZE];
uint16_t write_idx;
uint16_t pending_bytes;
} TX_BufferTypeDef;
void TCP_SendData(TCP_ClientTypeDef *client, uint8_t *data, uint16_t len) {
uint16_t remaining = len;
while(remaining > 0) {
uint16_t chunk = MIN(TX_BUF_SIZE - client->tx_buf.write_idx, remaining);
memcpy(&client->tx_buf.buffer[client->tx_buf.write_idx], data, chunk);
client->tx_buf.write_idx += chunk;
client->tx_buf.pending_bytes += chunk;
remaining -= chunk;
data += chunk;
if(client->tx_buf.write_idx >= TX_BUF_SIZE) {
TCP_FlushBuffer(client);
}
}
}
void TCP_FlushBuffer(TCP_ClientTypeDef *client) {
if(client->tx_buf.pending_bytes == 0) return;
uint16_t sent = send(client->sock_num, client->tx_buf.buffer,
client->tx_buf.pending_bytes);
if(sent > 0) {
// 移动剩余数据到缓冲区头部
memmove(client->tx_buf.buffer, &client->tx_buf.buffer[sent],
client->tx_buf.pending_bytes - sent);
client->tx_buf.write_idx -= sent;
client->tx_buf.pending_bytes -= sent;
client->last_activity = HAL_GetTick();
} else {
client->state = TCP_STATE_RECONNECTING;
}
}
建立完善的错误分类处理体系:
| 错误类型 | 检测方法 | 恢复策略 |
|---|---|---|
| 物理层错误 | SPI通信超时 | 硬件复位W5500 |
| 协议栈错误 | Sn_SR寄存器异常 | 关闭并重新初始化Socket |
| 网络中断 | 心跳包超时 | 指数退避重连 |
| 服务器拒绝 | 连接立即关闭 | 等待冷却期后重试 |
实现代码示例:
c复制void TCP_ErrorHandler(TCP_ClientTypeDef *client, TCP_ErrorType error) {
static uint8_t retry_count = 0;
switch(error) {
case TCP_ERR_PHY:
W5500_HardReset();
client->state = TCP_STATE_DISCONNECTED;
break;
case TCP_ERR_STACK:
close(client->sock_num);
client->state = TCP_STATE_DISCONNECTED;
break;
case TCP_ERR_TIMEOUT:
if(retry_count < MAX_RETRIES) {
uint32_t delay = 1000 * (1 << retry_count); // 指数退避
HAL_Delay(delay);
retry_count++;
client->state = TCP_STATE_CONNECTING;
} else {
retry_count = 0;
client->state = TCP_STATE_DISCONNECTED;
}
break;
}
}
在工程中添加以下监控点:
c复制typedef struct {
uint32_t tx_bytes_total;
uint32_t rx_bytes_total;
uint32_t connect_count;
uint32_t error_count;
uint32_t max_retry_count;
float avg_rtt_ms;
} TCP_MetricsTypeDef;
void TCP_UpdateMetrics(TCP_ClientTypeDef *client) {
client->metrics.tx_bytes_total += client->tx_buf.pending_bytes;
// 通过定期时间戳计算RTT
static uint32_t last_ping = 0;
if(client->last_ping_reply != 0) {
float rtt = client->last_ping_reply - last_ping;
client->metrics.avg_rtt_ms =
(client->metrics.avg_rtt_ms * 0.9) + (rtt * 0.1);
}
last_ping = HAL_GetTick();
}
在工业现场部署时,这些指标可通过Modbus TCP或MQTT协议上报到监控系统,实现远程诊断。