基于STM32F407开发调试,Modbus TCP服务器源程序。 采用LWIP网络通讯库,外部PHY采用LAN8720。 使用 modbus poll工具调试通过。 该工程可直接作为模板开发。 源码已应用于工业项目使用。

搞嵌入式开发的兄弟应该都遇到过这样的场景:甲方爸爸突然说要用Modbus TCP协议对接设备,而你手头正好有个STM32F407的板子。别慌,今天咱们就来聊聊怎么用LWIP+LAN8720快速搭建一个稳定的Modbus TCP服务器。

先上硬菜——网络初始化部分。这里有个坑要注意:LAN8720的复位引脚必须等PHY芯片内部稳压电路稳定后再操作。见过不少兄弟在这卡壳,明明配置都对就是ping不通。

// PHY硬件复位
void PHY_Reset(void)
{
    HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET);
    HAL_Delay(100);  // 关键等待时间!
    HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET);
    HAL_Delay(100);  // PHY启动需要时间
}

LWIP的配置文件中这几个参数得重点关照,特别是TCPMSS和TCPWND。在工业现场遇到过数据包被分割的情况,调整后吞吐量直接翻倍:

#define TCP_MSS     1460    // 以太网帧有效载荷最大值
#define TCP_WND     (4*TCP_MSS)  // 滑动窗口大小
#define MEM_SIZE    (20*1024)   // 内存池大小

核心的Modbus处理逻辑其实不复杂,重点在于协议头的解析。这里用状态机的处理方式比if-else瀑布流清爽得多:

void modbus_tcp_process(struct tcp_pcb *pcb)
{
    if(rcv_flag == 1){
        // 提取事务标识符
        trans_id = pbuf->payload[0] << 8 | pbuf->payload[1];
        
        // 校验协议标识符是否为Modbus
        if(pbuf->payload[2] != 0 || pbuf->payload[3] != 0){
            tcp_close(pcb);
            return;
        }
        
        // 处理功能码
        switch(pbuf->payload[7]){
            case 0x03: 
                handle_read_holding_registers(pcb, pbuf);
                break;
            case 0x10:
                handle_write_multiple_registers(pcb, pbuf);
                break;
            //...其他功能码处理
        }
    }
}

实测中发现LWIP的tcpwrite有时候会返回ERRMEM,这里加个重试机制能有效避免数据丢失:

err_t send_modbus_response(struct tcp_pcb *pcb, uint8_t *data, uint16_t len)
{
    int retry = 0;
    err_t err;
    
    while(retry++ < 3){
        err = tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY);
        if(err == ERR_OK) break;
        HAL_Delay(5);
    }
    
    if(err == ERR_OK){
        tcp_output(pcb);  // 立即触发数据发送
        return MODBUS_OK;
    }
    return MODBUS_ERROR;
}

调试时建议用Wireshark抓包看原始数据流,比Modbus Poll自带的监控更直观。遇到过某个厂家的PLC会发送带额外字节的异常报文,就是靠抓包定位到问题。

最后说下工程结构:

/Project
  ├── LWIP
  │   └── lwipopts.h   // 参数配置
  ├── Modbus
  │   ├── mbtcp.c      // TCP协议处理
  │   └── mbframe.c    // 数据帧解析
  └── ETH
      └── ethernetif.c // 网络驱动

这个架构在多个工业网关项目里验证过稳定性,连续运行最长的设备已经3年没重启过。源码里预留了RS485接口的Modbus RTU转换模块,需要时可以快速扩展成协议网关。

(注:文中代码经过简化处理,实际工程需考虑线程安全、超时重传等机制。完整源码可通过文末链接获取)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐