STM32F407实现三菱FX3U串口转以太网/4G协议转换网关 如果你手头有一台三菱 FX3U V50 固件级的 PLC又不想换控制器只想以最低成本把它接进局域网、再通过 4G 上公网那么把 STM32F407 用作协议转换网关是一条投入少、灵活性高的路子。我最近刚完成了一整套基于 F407 的底层源码适配核心思路一句话就能讲清楚F407 从串口读取 FX3U 的编程口协议数据解析完成后再按 TCP 或 4G socket 的格式转发出去。整个链路里F407 充当的是“翻译器加路由器”右边对接 PLC左边对接以太网和 4G 模块哪个链路可用就走哪个。这套方案适合两类人来参考一类是搞嵌入式开发的工程师想知道怎么在 F407 上把 LWIP、串口状态机、4G AT 指令这些散装模块串成一套可落地的工业协议网关另一类是现场电气工程师手里有一堆 FX3U 要采集数据但不想买成品协议转换器想了解板级方案到底怎么搭、哪些坑会埋伏在调试路上。全文不会只给一个“能跑”的结论而是把协议帧、校验、地址换算、网络链路、调试方法都拆开讲尽量做到看完能自己动手复刻。1. 项目整体设计为什么用 F407 做协议转换网关1.1 现场痛点与选型动机FX3U 本身带 232、485 通信口也支持加装专门以太网扩展模块但真正到项目现场会发现两个现实问题。第一扩展模块的采购成本和供货周期在非标项目里经常不划算尤其是只需要读取二三十个 D 寄存器、偶尔写几个 M 继电器这种轻量需求单独配一个专用扩展模块显得有点浪费。第二FX3U 原生的通信口通常已经被触摸屏、变频器或者伺服占了剩下能用的接口非常有限如果不想动现场原有接线的拓扑就必须在外部加一个“旁路设备”而旁路设备最常见的形式就是串口转网络网关。STM32F407 被选中首先是因为这颗芯片自带以太网 MAC内部有 DMA 控制器配合一颗外置 PHY 芯片就能跑标准 TCP/IP 协议栈。其次它的串口资源足够分配一个串口给 PLC一个串口给 4G 模块还有一个串口可以留作调试。168MHz 的主频处理 FX3U 编程口协议这种轻量协议峰值负载下也就是几个百分点的 CPU 占用率完全不会成为瓶颈。最后F407 的生态成熟度太高了LWIP 移植、DMA 收发、环形队列这些基础代码都有大量现成参考做底层源码的时候不需要从零造轮子。1.2 通信链路方案取舍以太网为主、4G 为辅实际项目里现场有局域网的场景太常见了但如果只有局域网设备一旦跨网段、跨地域就很难统一管理。所以这套源码的通信层采用双链路设计以太网链路走 LWIP用于现场局域网内的快速数据访问响应延迟可以做到 10ms 级别4G 链路走 AT 指令 socket用于远程运维和云平台接入延迟高一些但胜在没有物理布线约束。4G 模块的实现方式这里要特别说明一下。常见做法有两种一种是 PPP 拨号让 F407 直接获得一个 IP 地址然后把 LWIP 跑在 PPP 之上另一种是用内置 TCP/IP 协议栈的 4G 模块F407 只需要通过串口发 AT 指令让模块去建立 socket 连接。我选择的是后一种原因很实在PPP 拨号需要额外的协议栈适配和更多内存开销而大多数通用 4G 模组已经把 TCP/IP 栈固化在模块内部F407 这边只做 AT 指令封装和数据透传。这样做虽然损失了一部分灵活性但底层的网络拥塞控制、链路重连都交给模块处理代码复杂度大幅下降。1.3 硬件架构与数据流向整个网关的硬件连接并不复杂。F407 的 UART1 接 PLC 的编程口一般通过 RS422 或者 RS232 电平转换芯片过渡ETH 的 RMII 接口接一颗支持 RMII 的 PHY 芯片另一个 UART 接 4G 模块的串口 TX/RX模块供电单独做好和主控板隔离。数据流向分三条路径。第一条是上行数据采集F407 定时向 FX3U 发起读软元件请求PLC 返回的响应帧被 F407 解析然后按网络协议格式封装从以太网或者 4G 链路发往远端。第二条是下行控制远端发送写指令F407 收到后转换成 FX3U 编程口写帧再通过串口发给 PLC。第三条是本地透传当远端只是想把 PLC 当成一个裸串口设备调试时F407 可以退化成一条“串口转网线”的管道但实际项目中我很少这么用因为无脑透传会把网络粘包问题全数带到 PLC 侧。2. 核心协议解析FX3U V50 的编程口底层格式2.1 帧结构拆解从 STX 到校验和项目资料上标注的 V50我理解的是 FX3U 系列在某批固件版本体系里的一个常用代号并不代表通信协议本身有多个不同版本。这一版源码按 V50 对应的软元件表和指令表做的适配重点在串口编程口协议。FX3U 的编程口协议最常见的帧结构是命令帧、地址段、数据段、结束符和校验段组成整体上看是一串 ASCII 十六进制字符而不是二进制裸数据。一个标准的读请求帧可以这样表示开头是 STX 字符编码是 0x02接着是命令字段比如读命令用 ASCII 字符“0”写命令用“1”然后是首地址按软元件编号换算成 4 位 ASCII 十六进制再后面是读取长度用 2 位 ASCII 十六进制表示要读取的软元件个数之后是 ETX 结束符编码是 0x03最后是两个 ASCII 字符组成的校验和。写请求的帧和读请求类似只是命令字段变成“1”并且在长度的后面要附加实际要写入的数据。如果数据量超过一条帧的容量还需要拆成多条写帧依次发送每次的地址要对应递增。我在调试时最容易忽略的就是“长度”这个字段的含义FX 协议里它表示的是软元件个数不是字节数。比如读取 D0 到 D9 共 10 个字长度字段是 0A但返回的数据部分是 20 个字节因为每个 D 寄存器占 2 字节。如果把这个搞混解析出来的数据就会错位。2.2 校验和计算与超时机制FX3U 编程口协议的校验方式比较简单是把从命令字段开始到 ETX 结束符为止的所有字节做循环累加取累加结果的低 8 位然后把低 8 位数值按十六进制转成两个大写 ASCII 字符。比如累加结果是 0x8F帧尾就写字符“8”和“F”。这个校验强度虽然不如 CRC16但编程口协议本身面向短帧、低频次访问实际用下来很少出现因为校验漏判导致的错数据。代码实现上我建议在校验时把整个响应帧先收入缓冲区再统一计算不要在接收过程中边收边算因为帧尾没有接收完成之前你无法确定哪些字节属于校验段。另外超时机制比校验更重要。PLC 在扫描周期里响应编程口请求的延迟不稳定快的时候几毫秒慢的时候可能有几十毫秒。如果主控发了一条读请求后一直苦等一旦 PLC 因为某种原因没有返回整个通信链路就会卡死。我的做法是设置 200ms 超时超时后主动清空接收缓冲区然后重试重试三次仍然失败就判定 PLC 侧异常并上报远程端。uint8_t calc_check_sum(const uint8_t *buf, uint32_t len) { uint8_t sum 0; for (uint32_t i 0; i len; i) sum buf[i]; return sum; }2.3 常用软元件地址映射表FX3U 的软元件地址换算规则是底层源码里绝对不能错的部分。如果地址偏移搞错读出来的数据看起来正常实际上全是错的而且很难排查。下面是我在项目里验证过的一张核心换算表不同固件版本可能存在细微差异我强烈建议拿到实际 PLC 后先用官方编程软件对照软元件监控界面做一次校验再固化到代码里。软元件类型起始地址地址计算方式说明D 数据寄存器0x10000x1000 编号 × 2一个字占 2 字节M 内部继电器0x01000x0100 编号按位访问X 输入继电器0x00800x0080 输入号按位访问Y 输出继电器0x00A00x00A0 输出号按位访问S 状态继电器0x00000x0000 编号按位访问D 寄存器是最常用的比如要读 D100换算出来的地址就是 0x1000 100 × 2 0x10C8然后把 0x10C8 转成 ASCII 字符串“10C8”填到帧的地址字段。M 继电器按位访问如果一次要读 32 个连续的 M 点实际读取的是 32 个 bit 拼成的 4 字节数据在解析层需要按位拆分。有一个容易踩的坑是 X/Y 的编号。FX3U 的 X 输入和 Y 输出编号不是从 0 开始顺序递增的它遵循八进制编号规则也就是说 X7 后面是 X10而不是 X8。做地址换算时不能简单地把十进制编号直接丢进公式要先看清楚 PLCPLC 上丝印标注的编号到底是几进制否则地址会错得很隐蔽。2.4 串口接收状态机实现串口接收端我推荐用状态机加环形队列的组合而不是简单地按字符一个个判断。原因很简单一条完整的 FX3U 响应帧可能不是一次 DMA 中断就传完的中间会拆成多个数据包到达。你无法保证第一次收到的一定是帧头第二次收到的一定是命令字段所以必须建立一个接收状态机根据当前状态决定下一个字节放到哪一段。状态机至少需要这几个节点等待 STX、接收命令、接收地址、接收数据、等待 ETX、接收校验和。每收到一个字节就更新状态当走完校验和两个字符后整帧进入解析队列。实现时我一般把状态机放在串口接收中断的回调函数里只做状态迁移和数据搬移不在这里做业务解析这样中断处理时间可以压缩到微秒级。一个很关键的经验是环形队列的深度必须大于一条最大帧的长度我是直接按 512 字节配置的。FX3U 写寄存器时一次写入的字数如果比较多数据段会占到上百字节加上帧头和帧尾一条帧可以到 200 字节以上。如果队列只有 128 字节数据一溢出不光是这一帧丢的问题还会把下一帧的帧头冲掉整个链路进入连环错位状态。3. 网络出口设计与双链路实现3.1 以太网链路F407 与 LWIP 的对接以太网这部分F407 的内部 MAC 和外部 PHY 通过 RMII 接口相连。RMII 相比 MII 的最大好处是引脚少只需要 7 根信号线PCB 布线压力小代价是信号线频率翻倍到 50MHz对时钟精度要求更高。推荐在 PHY 的 REF_CLK 引脚上接一个独立有源 50MHz 晶振而不是让主控的 MCO 引脚输出时钟给 PHY后者虽然能省一块晶振但调试时经常因为时钟相位不对导致网络不通。LWIP 协议栈的配置是个重头戏。F407 内存有限不能像 PC 上那样放任 TCP 缓存随意分配必须根据最大连接数和数据吞吐量调整内存参数。我的做法是LWIP 的 MEM_SIZE 设为 16KB 左右PBUF_POOL_SIZE 设为 24 个每个 PBUF 缓冲区默认 1.5KB这样整体内存占用在 50KB 上下对 F407 的 128KB RAM 来说是安全的。数据交互采用裸机轮询加中断的方式。ETH 的中断服务程序负责把接收到的数据包从 DMA 描述符拷贝到 LWIP 的 PBUF 池主循环则调用 LWIP 的定时处理函数处理 TCP 的超时重传、ARP 老化等机制。注意不要在中断里调用 LWIP 的 netconn 或 socket API这些函数不是线程安全的裸机环境下会破坏协议栈内部状态。struct netif f407_netif; static ip4_addr_t g_ip, g_mask, g_gw; void network_init(void) { ip4addr_aton(192.168.1.50, g_ip); ip4addr_aton(255.255.255.0, g_mask); ip4addr_aton(192.168.1.1, g_gw); netif_add(f407_netif, g_ip, g_mask, g_gw, NULL, ethernetif_init, ethernet_input); netif_set_default(f407_netif); netif_set_up(f407_netif); }3.2 4G 链路AT 指令与 socket 建立流程4G 模块这边底层源码的抽象接口是“AT 指令发送函数”和“串口数据解析函数”。F407 只认这两类接口不关心模块具体是哪种型号。上电后模块需要一次完整的初始化流程任何一步返回错误都要有对应的重试逻辑。初始化流程拆解下来是这样第一步发送 AT 测试模块响应能收到“OK”说明串口波特率和接线正常第二步 ATE0 关闭回显不然模块会把收到的指令原样返回干扰后续数据解析第三步查询 SIM 卡状态ATCPIN? 返回 READY 才能继续第四步检查信号强度ATCSQ 返回的第二个数值如果低于阈值说明天线或信号有问题第五步激活 PDP 上下文让模块获得网络侧的 IP 地址。建立 TCP 连接的核心步骤是通过专用 AT 指令打开的 socket这一步各个品牌的模块命令差别比较大我这里以项目中实际用过的通用流程为例先通过指令设置 APN 信息再发起连接请求。连接建立成功后F407 就能直接往模块串口里写业务数据模块会透明传输到远端服务器。收到服务器的数据时如果模块工作在透传模式会直接送回串口不需要额外解析数据长度报头。一个要在源码里特别注意的点4G 模块刚上电时内部射频和网络注册需要时间一般要等 3 到 10 秒。如果 F407 上电后立刻发 AT 指令大概率收不到任何回包。所以初始化不能放在主循环里硬等应当做成一个独立状态机按时间片轮询给模块足够的启动时间。3.3 双链路切换与心跳保活以太网和 4G 两条链路并存时必须有一个明确的优先级策略。我的做法是以太网优先轮询检测以太网物理链路是否正常如果检测到网线断开或者远端服务无响应就自动切换到 4G 通道并向上层报告当前链路状态。等以太网恢复后再自动切回整个过程不需要人为干预。链路切换最怕的是抖动。网线物理连接刚断开那一瞬间PHY 芯片的状态寄存器可能还没及时更新这时盲目切换会造成来回跳变。我加入了一个“连续失败计数”机制只有当连续 5 次检测都失败才切换链路只失败一次只记录日志不动作。心跳机制也是保活的重头戏。4G 网络侧的 NAT 超时时间因运营商而异短的只要几十秒。如果设备只是静默待机服务器那个 TCP 连接会被网络侧悄悄拆除但两端都感知不到。处理方法是每隔 30 秒发一次应用层心跳数据内容尽量简洁只包含设备 ID 和时间戳。为了控制流量心跳频率不能太激进30 秒是我在功耗和实时性之间找到的一个折中值如果现场对流量成本敏感可以放宽到 60 秒。4. 调试过程与踩坑记录4.1 分模块调试先串口后网络整套系统动起来之前我强烈建议把大系统拆成三层独立调试串口层、协议层、网络层。很多人一上来就把 PLC、F407、4G 模块全部接好然后发现整个链路完全不通这时候无论查哪一段都像大海捞针。第一层先把 F407 当成一个纯串口转发器。用串口工具直接向 F407 发送精心构造的 FX3U 读请求帧观察 F407 是否能正确回复 PLC 的真实响应。这一步能验证串口参数、电平转换、接收状态机这些基础功能。如果这一层都过不了后面全是空中楼阁。第二层关掉网络功能只让 F407 解析 PLC 数据并打印到调试串口。此时重点验证软元件地址映射表、校验和算法、数据包顺序。我会故意构造一些边界数据比如读取 D0、D9999写入单个 M 点批量读写 64 个字看解析结果是否和预期一致。边界数据能暴露地址换算的乘法溢出、长度字段解析错误等问题。第三层打开网络用 PC 上的网络调试工具作为远端服务器分别测以太网连接和 4G 连接。这一层只验证网络传输方式是否正常比如 TCP 连接能否建立、数据包能否双向传输、粘包是否被正确处理不关心 PLC 协议。4.2 典型问题排查速查表调试过程中有一个特别诡异的现象值得展开说。PLC 返回的数据帧在 F407 的串口接收缓冲区里拆成了两段第一段包含 STX 和命令字段第二段包含地址和数据。如果你在中断回调里用了一个 if 判断“收到 STX 就认为这是一条新帧的开始”并且每次收到 STX 就清空缓冲区那第二段数据到达时就会把第一段覆盖掉。解决办法就是前文提到的状态机任何状态迁移都不能无脑清缓冲区只有收到完整校验和之后才能认为上一帧结束。另一个常见问题是校验和一直错误。我最初调试时以为是自己算错了后来发现是帧尾的判断条件不对。ETX 的 ASCII 码是 0x03但如果数据段里恰好也包含 0x03 这个字节二进制数据中的 0x03接收状态机就会提前认为帧结束了。解决方法是必须在协议层明确“帧结束只能由 ETX 触发”而且 ETX 之前必须已经收满地址段和数据段的预期长度也就是说不能用单纯的字符匹配来定位帧尾要结合长度字段进行预期管理。选一张典型的故障排查表放这里都是我实际遇到过的问题现象可能原因处理办法串口一直接收不到 PLC 响应波特率、数据位、停止位不匹配核对 PLC 编程口串口参数用串口抓包确认响应帧反复校验错误把二进制数据当成 ASCII 处理或帧尾判定错误统一按 ASCII 十六进制解析到 ETX 后再计算校验和读取 D 寄存器数值写反高低字节序没有交换D 寄存器的高字节在前注意大小端转换以太网 PHY 连接状态一直 downREF_CLK 时钟异常或 PHY 地址配置错误排查 PHY 芯片地址引脚和 50MHz 时钟信号4G 模块连接远程服务器超时APN 未设置、SIM 卡未注册、服务器端口未开放分步查询 ATCPIN、ATCSQ、ATCREG逐项定位网络偶尔断一下几秒后自动恢复4G 模块进入省电模式或链路静默超时关闭模块省电模式应用层增加心跳4.3 内存与实时性的平衡技巧F407 的 128KB RAM 看似不小但如果在 LWIP、环形队列、4G 发送缓冲、协议解析缓冲上都很“大方”很快就会发现内存不够用。我的分配策略是串口环形队列每个 1KB一共 3 个串口占 3KBLWIP 的 PBUF 池和内存堆合计约 50KB4G 发送缓冲区 2KB协议解析层缓冲区 512 字节。这样整体剩余内存还有一半以上不会因为内存碎片导致随机死机。实时性方面串口中断的优先级必须高于 ETH 中断否则接收 PLC 数据时会被网络中断抢占极端情况下会丢字符。反过来ETH 中断又不能一直阻塞串口因为 LWIP 的数据包处理时间不固定。我的优先级划分参考是串口接收中断最高4G 模块串口中断次之Ethernet 中断稍低一些主循环做业务逻辑。主循环不能做明显的阻塞等待。很多人习惯用 HAL_Delay 做发送间隔控制这在裸机轮询框架里非常危险因为延时期间所有串口和网络事件都得不到处理。正确做法是维护一个毫秒级 tick用非阻塞的定时查询来控制发送间隔、超时校验和心跳周期。这样即使某一时刻 TCP 重传占用了较多时间PLC 的通信也不会被饿死。4.4 现场联调的终极验证方法全部模块都通以后还需要做一次完整链路验证。我习惯的做法是用远端服务器发一条写指令把 FX3U 的某个 M 继电器置 ON同时用 PLC 程序让这个 M 点驱动一个输出指示灯现场看灯亮不亮。这个验证方法比单纯看数据更直观因为数据从远端到网关再到 PLC整个闭环可能只有几十毫秒数据帧是对的但现场输出没动作说明问题出在 PLC 程序或者地址映射上。反向验证也一样把 FX3U 的一个 D 寄存器手动改成一个已知值然后从远端服务器读取。如果读到值和设定值一致说明上行链路的所有环节都通了。这样双向验证完整个系统才算真正可以交付出场。5. 最后再分享一点经验这一套底层源码在项目里跑稳定之后我最大的感受是边缘网关最难的不是某一个单项技术而是把“PLC 协议解析”和“网络传输”两件原本独立的事情揉在一起时出现的各种隐含耦合。串口状态机、LWIP 内存配置、4G 模块的 AT 流程单独拿出来都不算难真正的功夫全花在边界条件处理上。如果让我重新再做一遍我会更早引入一个完整的日志系统。最初调试 4G 链路时AT 指令的返回数据只有几十字节当时图省事就直接往调试串口打结果模块上电瞬间的注册过程日志和后续的 socket 日志混在一起根本看不清时序。后来改成带时间戳的环形日志缓冲把每个关键状态变化都记录到内存里复现问题的时候再统一导出效率高了很多。最后再分享一个小技巧用串口抓包工具或逻辑分析仪同时监看 F407 的 PLC 串口和 4G 模块串口两边的时间戳对齐就能非常直观地看到一条数据从 PLC 发出到 F407 转发再到 4G 上报的总延迟。这个延迟数据在项目交付时特别好用不管是验收还是排障都能让现场的人一眼看清链路瓶颈在哪里。如果你也在做类似的 PLC 联网改造希望这份底层源码的拆解能帮你少走几个弯路。