☰
基于STM32F407的FX3U PLC串口转以太网与4G云网关实战
2026/10/21 16:47:28 网站建设 项目流程

上周在仓库翻出一台用了快十年的三菱FX3U V50固件PLC,生产设备本身没动过,但信息化改造要求把温度、转速、运行时长这些参数全部传到办公室上位机,最好还能推上云平台。这台机器只有一个串口,没网口也没4G,按原厂思路去加扩展模块,价格和周期都有点劝退。手里正好压着一块STM32F407开发板,于是翻出之前的底层通信源码,把这台老PLC折腾成了“串口进、以太网和4G两条路出”的智能网关。如果你也维护这类不带网口的PLC,或者想把PLC数据从车间搬到远端监控,这篇分享可以给你一个比较完整的参考。

1. 项目起点:为什么老 FX3U 反而需要 STM32F407 这类“中继桥”

1.1 FX3U 的原始通信能力有多“原始”

三菱FX3U系列PLC出厂时自带一个编程口,通常走RS422,另可通过功能板扩展一路RS232或者RS485。这个串口的本职工作是给编程软件上传下载程序,也可以按三菱MC协议做上位机通信,读写D寄存器、M继电器这些数据。但它本质上是一个“点对点近距离”接口,线缆一般也就几米到几十米,波特率顶到115200,车间里的强电环境一复杂,布线稍长就开始丢帧。更关键的是,这个口没法直接接入以太网或4G,业务要跨楼层、跨厂区、甚至跨城市访问,根本没戏。

最常见的补法有两个方向:加原厂以太网模块,或者挂一个串口DTU接4G。前者稳定但贵,而且只解决局域网问题,4G还得再叠一个路由器;后者能上云,但多数DTU只能做透传,数据到了云端还是一堆没有结构的原始字节,上位机拿过去没法直接用。用STM32F407做桥接的意义就在这里:把PLC串口数据解析出来之后,同时走以太网和4G两条通道,局域网内给上位机提供Modbus TCP,广域网侧把数据推到云平台,一套硬件解决两种需求。

1.2 为什么选用 STM32F407 而不是更便宜的 MCU

选型的时候其实犹豫过几个方向。STM32F103很便宜,但内部不带以太网MAC,要外挂W5500这类芯片,协议栈和网络功能被硬件绑死,灵活度不够。树莓派开发快,跑个Linux什么都能干,但那东西启动要几十秒,功耗好几瓦,体积也不小,放工业现场24小时通电,抗振动抗干扰都不如裸奔的MCU靠谱。STM32F407则是Cortex-M4核,主频168MHz,带单精度FPU,1MB Flash和192KB RAM,外设方面最吸引人的是内置的以太网MAC,外接一颗PHY芯片就能实现10/100M以太网。再加上它有多个USART,一个口连PLC、一个口连4G模块、再留一个口输出调试日志,资源不太紧张,成本还远低于商用网关。

我整理过一张选型对照表,给想复刻这类项目的人做个参考:

方案网络能力成本量级开发复杂度可定制性典型问题
原厂以太网模块仅以太网高低(配置为主)差无4G,占扩展接口
串口4G DTU仅4G中低(透传为主)差数据不结构化,改协议麻烦
STM32F103+W5500以太网低中中协议栈耦合,串口资源紧
STM32F407方案以太网+4G低高(需写底层)高开发周期长,靠源码维护

这套方案真正值钱的不是“能不能通”,而是同一条串口数据怎么同时喂给以太网和4G两条链路,并且稳定跑下去。后面说的底层源码,就是围绕这件事展开的。

2. FX3U V50 固件下的通信协议,先把它吃透再动手

2.1 MC协议的帧结构:底层源码必须按帧解析,不是按字节

做底层代码之前,第一件事是搞懂PLC发的数据长什么样。三菱FX3U的串口协议是MC协议的一个子集,上位机做主站,发一条指令帧,PLC做从站,返回应答帧。指令帧里包含命令代码、寄存器类型、起始地址、部件数量、校验码;应答帧包含结束码和数据区。一个读寄存器指令的十六进制文本帧大致长这样:

02 46 57 44 30 30 46 45 30 30 31 30 30 30 31 03 36 32

其中02是帧头,46 57 44是读命令,中间是地址和数量,03是帧尾,最后两位是校验,具体内容会随固件版本和通信格式变化,我这里不逐字节展开。重要的是处理思路:串口底层一定不能把数据当“裸字节流”看待,接收一个字节处理一个字节,那样一旦遇到半个帧、插了噪声、或者校验失败,整个状态就再也对不齐了。

我的做法是在驱动层加一个接收状态机:空闲状态收到帧头进入接收状态,持续收数据直到帧尾,然后做长度校验和校验码校验,失败就丢掉整帧回到空闲态。这个状态机同样适用于4G模块的AT指令解析,一鱼多吃。另外要注意FX3U编程口默认往往是偶校验,别想当然按“无校验”去配串口参数,这个配置错了,收到的数据全是乱码,光这一条就能卡掉一半的联调时间。

2.2 V50 固件版本背后需要注意的两个细节

标题里的V50,我理解成FX3U的固件版本(V5.00系列)。我在源码里并没有针对V50把逻辑写死,因为现场PLC的固件版本五花八门,老设备和新设备在通信行为上多少有点差异。对底层代码来说,最明显的影响有两个:一个是指令响应的时序不一样,老固件在某些地址上响应偏慢,新固件可能加了额外的校验流程;另一个是读取CPU型号和软版本号的时候,返回的信息格式有细微差别。

所以我建议在系统初始化阶段先发一条读取型号的指令帧,把返回的固件版本记录进日志,然后用一个版本号字段去选择后续的超时参数和重试策略。这样同一套源码在三菱FX3U的不同批次、不同固件上都能跑,而不是今天能通、明天换台设备就莫名其妙挂了。

2.3 电气连接和波特率:先算算带宽够不够

FX3U编程口的电平是RS422,STM32F407的USART是TTL电平,中间必须加收发器,推荐直接用带隔离的RS422/RS485模块,内部带DC-DC隔离电源那种。别图省事直接把PLC信号飞线进MCU,工业现场一旦有共模干扰,轻则乱码丢帧,严重的时候MCU串口直接被干报废。

波特率建议从9600bps起步。算一笔账:9600bps大约是每秒960字节,一条读寄存器命令大概18字节,响应大概60字节,加上帧间隔,一个完整轮询约80字节,也就是说一秒钟大约能完成10到12个轮询。监控20个寄存器、每100毫秒刷一次完全够用。如果寄存器数量很大,再考虑拉高波特率,但这时对双绞线质量和距离的要求也上来了,屏蔽和接地没做好,速度越高反而越容易出错。

这里有个值得固化进源码的调度规则:串口轮询不能无脑循环,必须“发一条指令,等应答,处理完,再发下一条”,同时给每条应答设一个明确超时,比如50ms,超时重试三次还不行就记录链路故障。把这个“半双工调度”写清楚,后面联调能省一大堆麻烦。

3. STM32F407 底层源码的分层设计与关键代码路径

3.1 串口驱动:DMA加IDLE中断才适合PLC轮询

STM32F407的串口如果逐字节进中断,CPU占用率会很难看,尤其当两个串口同时跑的时候,一边是PLC轮询,一边是4G模块AT指令收发,整体中断量非常大。底层源码强烈建议用“DMA接收+总线空闲中断”组合:串口DMA把数据自动搬进内存,当总线一段时间空闲,硬件产生IDLE中断,软件在中断里把当前DMA缓冲区有效长度的数据复制到环形缓冲区。

代码骨架大致是这样:

// 串口DMA接收配置 HAL_UART_Receive_DMA(&huart3, dma_rx_buf, DMA_BUF_SIZE); // 在USARTx_IRQHandler中判断IDLE标志并回调 void UART_IDLE_Callback(UART_HandleTypeDef* huart) { uint16_t len = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx); ringbuf_write(&fx3u_rx_ring, dma_rx_buf, len); __HAL_DMA_SET_COUNTER(huart->hdmarx, DMA_BUF_SIZE); }

几个经验点必须说清楚:第一,DMA缓冲区不要太小,512字节起步,高速率下小了必漏。第二,中断里只入队,不做协议解析,协议解析放到主循环里,别把CPU时间全烧在中断上下文。第三,环形缓冲区的读写指针在多“生产者”场景下要设计成互斥安全的,最省事的办法是DMA缓冲区做成双缓冲,收满一半切另外一半,避免读写同一块内存。

3.2 以太网通路:LwIP跑TCP服务,对外暴露Modbus TCP

STM32F407的MAC要外接一颗PHY芯片,常用的是LAN8720A,通过RMII接口连接。PHY初始化有几个容易踩的点:复位引脚时序必须符合PHY手册,MDIO能读回PHY ID才算通信建立,RMII参考时钟最好用外部50MHz晶振,别依赖MCU内部时钟倍频,否则网络可能时好时坏。这些都稳定之后,再初始化LwIP协议栈,裸机环境下跑一个TCP服务器监听502端口,对外就是一个Modbus TCP节点。

关键工作其实在协议转换层。上位机发来一个Modbus读保持寄存器请求(功能码03),MCU解析出地址和点数,组装对应的MC协议读指令,通过串口发给FX3U,然后阻塞等待PLC返回,拿到结果再按Modbus格式回复上位机。底层源码里必须维护一张“地址映射表”,把PLC寄存器地址、Modbus地址、缩放系数放在一起,方便后期现场调整点位。这里没有太多玄学,但地址表设计好,后面排查问题会轻松非常多。

还有一个绕不开的问题:以太网请求和4G上行都会访问同一条串口链路,两边不能同时往PLC发指令。裸机环境下没有线程锁,我就用一个全局“总线忙”标志位加临界区保护,把“组帧→发送→等待响应→处理结果”这个完整过程封装成一个不可重入的原语调用。这样无论哪条路要用串口,都得先拿这个“令牌”,从根上避免两边互相踩踏。

3.3 4G模块通路:AT指令建链,再进透传模式

4G模块接在另一个USART上,初始化流程本质是AT指令串:上电延时,发AT确认模块响应,查信号强度,等待网络注册成功,再建TCP连接到服务器的IP和端口。我用过的模块主要是SIM7600和EC20两个方向,流程大体一致,细节指令略有差异。建链成功之后,切到透明传输模式,这时候模块不再解析AT指令,直接把串口收到的数据发到远端TCP连接,远端发来的数据也原样给到串口。透明传输模式下,之前搭建的串口状态机逻辑基本能复用,只是出口从本地变成了远端服务器。

这里要特别注意AT指令的响应匹配。模块的响应有OK、ERROR、+QIOPEN: 0等等,不能简单用字符串查找,最好也做一个轻量状态机,一条指令发出去,等对应成功标志,超时才算失败。透明传输切进去之前,要把AT回显关掉,不然后面业务数据里会混进一串“OK”之类的字模,云平台解析时满头问号。

3.4 上行链路的分工与数据格式

以太网和4G两条链路的定位不同,代码也要分开。以太网通道服务局域网内的上位机,协议固定走Modbus TCP,看重兼容性和实时性;4G通道面向云平台,为了省流量和方便端侧解析,我推荐自定义轻量二进制帧,而不是JSON。二进制帧格式可以自己定,比如设备标识2字节、功能码1字节、数据长度2字节、数据区N字节、CRC校验2字节。Cortex-M4虽然带FPU,但MCU拼JSON字符串既费Flash又不方便解析,PLC采集的这种结构化寄存器数据,用二进制帧明显更合适。

两条链路共用同一份采集快照。底层主循环周期轮询PLC寄存器,每拿到一次完整结果,就同时更新以太网侧缓存和4G侧发送队列。这样做的好处是两条通道读到的永远是同一份数据,不会一边是上一次的值、另一边是当前值,平台侧对不齐。

4. 联调实测中的真实问题:从模拟到真机的完整排查链路

4.1 先用PC模拟PLC,把协议调通再上真机

这是我最想强调的第一步。拿到STM32F407和FX3U真机后,别急着直接接线,先用一个USB转串口模块把MCU的PLC口接到电脑,电脑上用串口助手或者一段Python脚本模拟PLC从站响应。MCU发读寄存器指令,模拟端按协议回一个正确应答帧,立刻就能验证MCU是否发出了正确指令帧;模拟端故意回一个错误校验帧,MCU应该能打印出帧错误日志。这样调试的大部分时间都不占用真机,效率高得多。

一个简单的模拟脚本框架可以这样:

import serial ser = serial.Serial('COM17', 9600, parity=serial.PARITY_EVEN, timeout=0.1) while True: frame = ser.read(64) if frame.startswith(b'\x02'): # 按协议结构组装应答帧,替换成实际帧内容 response = b'\x02' + b'...device_data...' + b'\x03' + b'\x02\x00' ser.write(response)

这里没必要纠结原厂具体帧内容,关键是先把串口数据链路、校验、状态机调稳。实测下来,所谓“连不上PLC”的问题,有九成在模拟阶段就能提前暴露。

4.2 真机联调时最容易出现的串口帧错位

模拟通道没问题再上真机,第一个常见故障是帧错位。PLC上电后,编程口可能和PLC运行日志混数据,或者有别的上位机软件同时占用了串口,MCU收到的帧经常是乱的。我的排查思路是固定的几步:先用逻辑分析仪抓PLC侧引脚波形,确认通信线上确实有有效数据;再看MCU日志,打印每次DMA中断收到的原始字节,检查帧头是不是每次都在同一位置;如果帧头对不齐,优先检查波特率、停止位、校验位这类基础配置,FX3U默认偶校验这个坑值得单独记一笔;如果波形正常但MCU解析不到,就往DMA缓冲区大小上怀疑,把缓冲区调大一档再试。

这套排查链路看着基础,但它好在每一步都隔离了一个变量。比“怀疑协议有问题然后一顿乱改”要可靠太多。底层源码的调试日志在这里价值极大,建议平时就把DEBUG日志挂在一个独立串口上,运行状态一眼能看穿。

4.3 4G模块连不上的三类原因:公网地址、域名解析和模块注册时序

实际测4G链路,最典型的故障是模块信号明明有,TCP却死活连不上。我把踩过的原因归成三类:第一,服务器没有公网地址或者端口没放行,4G模块只能从公网访问,内网服务器直接连不上;第二,模块拿到的地址是运营商私网地址,服务器侧安全策略也可能拒绝对非信任地址开放端口;第三,AT指令里用了域名而不是IP,模块的DNS解析不稳定,连接一直卡在解析阶段直接超时。

解决办法也成套路:第一步,服务器绑定公网地址并放行端口,用IP直连的方式建TCP连接,链路稳定后再考虑换域名;如果现场实在没法开放公网端口,就走服务器主动出站的模式,让模块侧作为客户端连到云平台,路径最短。另外4G模块从透传模式退出再进入时,必须先发+++,但这个字符序列对间隔有要求,字符间不能拖太久,否则模块当成数据流处理,软件里最好把它封装成一个带固定延时的序列函数,别散在主循环里发送。

4.4 断线重连与看门狗:稳定性的最后一公里

联调通了只是开始,长期运行必须把各种断开场景全部处理掉。我在源码里做了三层保护:第一层是独立硬件看门狗,喂狗周期2秒,主循环卡死直接复位;第二层是PLC通信任务看门狗,连续三次轮询无响应就重置串口协议状态机,再不行就记故障、发告警;第三层是网络链路恢复,以太网TCP断开了要主动关闭socket并释放内存,4G模块注册状态丢失就先下电再重新注册,整个重连逻辑封装成独立函数,由周期任务轮询触发。

这里强调一点:4G模块复位后,必须等网络重新注册完成才能发起下一轮连接,不能盲目快速重试,否则把SIM卡状态和模块内部状态机一起搞混乱,反而更难恢复。

5. 方案边界与可扩展方向:哪些项目适合做,哪些直接买商用的省心

5.1 自己写底层源码和买现成产品,差在哪里

把话说明白,自己写这套网关并不是为了全面替代原厂方案。原厂以太网模块和商用DTU的好处是稳定、有售后、开箱即用,适合项目周期紧、现场没人会维护代码的团队。自己做的好处是灵活、成本低、协议可定制,既能对接上位机又能直接推云平台,以后加采集点只需要改地址映射表,不用再花钱升级硬件。但代价也很清楚:FX3U的协议细节要自己维护,4G模块型号一换,AT指令适配就要重来一轮,所以这方案更适合本来就有嵌入式开发能力、愿意长期维护源码的团队。

5.2 适用项目类型与硬件升级方向

我判断这套方案适合三类项目:一是老旧设备改造,现场已经有不少FX3U,不想整体换PLC;二是数据采集点数不多、刷新周期在几百毫秒到秒级的监控场景;三是同时需要局域网访问和4G云上报的混合需求。不适合的场景其实更明确:要求毫秒级实时控制、数百台PLC大规模组网、或者现场人员完全没有代码维护能力,这类需求还是直接买商用网关更稳妥。

硬件方向要迭代的话,可以考虑把PHY换成功耗更低、温度范围更宽的型号,MCU从F407升到F429就能带屏做现场可视化。外加MicroSD卡做断网缓存,4G链路中断时PLC数据先写卡,恢复网络后再续传,这样在没有稳定公网环境时也能保证现场数据不丢。软件层面的下一步是迁到FreeRTOS,把串口发送、网络处理、云上报拆成独立任务,代码结构更清晰,但要额外花精力处理任务栈大小和内存对齐。

5.3 从“能通”到“好用”的几个加分项

最后说几个让这套底层源码真正好用的加分项。第一,固件里留一个轻量配置接口,通过串口或以太网就能修改PLC站点号、寄存器映射、服务器地址,而不是每次改参数都重新烧录。第二,给采集数据打时间戳,用MCU自带RTC维护,断网补发时云平台能还原真实时序。第三,日志做分级,正常跑只输出ERROR,排查现场隐患时才打开DEBUG,避免长时间运行内存被日志塞满。这些细节解决的不是“通不通”的问题,而是决定一套源码能不能在项目里长期活下来的问题。

我在实际调试里最深的体会是,做老PLC加装通信模块这种事,九成时间都花在协议解析和通信稳定性上,但真正的硬骨头往往藏在最后一成的边界情况里——模块注册慢、缓冲区满了、链路断了又恢复。源码设计之初,就应该把超时、重试、缓存、看门狗这些失败路径当成第一公民对待,千万不要等通信正常之后再去补丁。按这个思路调整完,我手里那台设备已经连续跑了几个月没出过岔子,也希望这篇分享能让你少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询