第一次把倍福的TwinCAT3和Modbus-TCP Server绑在一起做项目时,我心里其实有点打鼓。PLC群里的老哥们聊起倍福,默认话题都是ADS、EtherCAT、高速IO,说到Modbus TCP总觉得是“其他PLC干的事”。可现场往往是残酷的:MES系统只认Modbus TCP,触摸屏只要标准协议,甲方IT机房根本不想听你解释什么是ADS。这篇东西就是把我从零搭建倍福PLC的Modbus-TCP Server过程中踩过的坑、翻过的文档、验证过的心得整理出来,给需要在TwinCAT3里做标准Modbus TCP通讯的工程师一个可以直接照做的路线,尤其是那些刚接触倍福、脑子还停留在三菱FX5U那种“原生自带Modbus”思路里的朋友。
如果你已经有了三菱FX5U或者西门子1200做Modbus通讯的经验,那理解起来会非常快,倍福这边只是换了一套工具链、换了一种映射思路而已。如果你连TwinCAT3都没安装过,也没关系,我会把环境准备、许可证、仿真运行、功能块调用、排错链路整个串起来,尽量少说废话。
1. 为什么要在倍福PLC上自建Modbus-TCP Server——先搞懂这步再动手
1.1 ADS虽然是倍福的亲儿子,但第三方系统不认
倍福的工业自动化平台核心是ADS(Automation Device Specification),TwinCAT3里面几乎所有通讯都在ADS的基础上跑。ADS功能极强,路由、变量访问、状态同步、实时性都是顶级的,但问题是:MES、组态软件、触摸屏、第三方仪表,绝大多数只支持Modbus TCP、OPC UA、Profinet、EtherNet/IP这几种“公开协议”。你不能要求甲方立刻去买一个支持ADS的中间件,更不能指望IT部门给车间开放ADS路由端口。
所以现实就是,倍福PLC当Server,对外提供Modbus TCP服务,让任何标准Modbus主站都能直接读写PLC内的数据。这个需求在产线数据采集、设备状态上报、触摸屏直连、多数上位机对接里极其常见。
1.2 直接用网关硬件,还是自己写Server
很多人第一反应是加一个Modbus TCP网关,比如用倍福的EL6652(EtherCAT端子模块)或者第三方协议转换器,把ADS转换成Modbus。这样做好处是简单,不需要写PLC通讯代码,网关配置一下就行。但坏处也很明显:成本高、网关本身多一个故障点、通讯延迟多一跳、参数修改要重新配置网关。
我自己的项目是车间里一台倍福做控制核心的包装设备,上位机只需要读电流、速度、产量这几个数,再写几个启停标志。为这点数据塞一个网关进去实在不划算,而且后续增加点位还要抱着笔记本去现场改网关配置,维护体验极差。所以在TwinCAT3里用官方TF6250通讯包自建Modbus TCP Server,才是治本的做法。
TF6250是Beckhoff官方的Modbus-TCP Server功能包,直接在PLC里实例化功能块,把一块内存区映射成Modbus寄存器区,主站随时来读。好处不用我多说,点位增减在PLC程序里改一下数组长度就行,调试时还能用Modbus Poll之类的小工具直接验证,非常顺手。
1.3 先看清楚Modbus Server和Modbus Client的差别
这里必须提醒一下:很多人搜索“TwinCAT3 Modbus TCP”时,第一篇结果往往是讲Client(也就是倍福作为主站去读第三方设备)的文章。别搞混了。
- Modbus Client(主站):倍福主动去连仪表、变频器,用TF6250/6251或者原生库里的FB_MBUS_CLIENT。
- Modbus Server(从站):倍福监听TCP端口,等别人来读,对外提供一个数据区,这才是咱们这篇文章的主角。
一个设备里同时又是Client又是Server也完全允许,只是要规划好端口和连接数量。我建议先只做Server,跑通了再考虑要不要叠加Client。
2. 环境准备与许可证激活:新手最容易卡住的三道坎
2.1 安装版本和组件,别急着一路Next
TwinCAT3不是独立软件,它是寄生在Visual Studio上的一个XAE(Engineering)环境,同时还有一套XAR(Runtime)实时内核。我开发的机器是Win10 64位,装的TwinCAT 3.1.4024.12,配合Visual Studio 2022,整体很顺。
安装时有几个组件一定要确认勾选,尤其是 Simulation 组件。这东西决定了你在没有倍福实体硬件时能不能把PLC程序跑起来。很多人下载安装包后习惯性全选,结果后面装完发现没法仿真,又要重装一遍,非常浪费时间。
一个经验是:装完TwinCAT后先打开TcXaeShell,新建一个TwinCAT XAE项目,如果能正常创建,再继续下一步。如果项目模板报错,多半是Visual Studio扩展没注册成功,需要以管理员身份重新运行一次安装程序。
2.2 “检测到高版本残留”这个鬼问题怎么解
热词里出现“twincat3 4024重装时检测到高版本残留”,这问题我身边好几个同事都撞上过。现象是:TwinCAT卸载后想重装一个指定的Build版本,结果安装程序直接弹窗拒绝,提示检测到高版本残留,或者Windows Installer层面注册信息没清干净。
我当时的处理过程是这样的,你可以照着试试:
- 先通过控制面板里的程序与功能卸载所有Beckhoff相关组件(XAE、XAR、ADS、TF6xxx等),不要留任何一个。
- 卸载后不要急着装新版,先重启一次电脑,让系统清理掉实时驱动和设备的占用。
- 删除安装目录残留:默认路径一般是
C:\Program Files (x86)\Beckhoff和C:\Program Files\Beckhoff,如果手动卸载过,大概率有残留文件夹,直接删掉。 - 打开注册表编辑器,搜索
Beckhoff和TwinCAT关键词,把能找到的项逐个删除。这一步有风险,操作前一定先备份注册表,或者用系统还原点兜底。 - 如果注册表里还有Windows Installer缓存锁着,用微软官方的“Program Install and Uninstall”疑难解答工具扫一遍残余记录。
- 再次重启,然后以管理员身份运行新版本安装包。
走完这套流程,绝大部分“高版本残留”的报错都能解决。反正核心思路就是把TwinCAT触碰过的地方全部清理干净,让系统觉得这台机器从没装过任何TwinCAT。
2.3 许可证激活:开发阶段怎么拿到试用授权
TwinCAT3在开发阶段用的是试用许可证。安装并启动TcXaeShell后,在解决方案资源管理器里右键“SYSTEM”节点,选择“License”,然后在License对话框里点击“7 Days Trial License”或“120 Days Trial License”按钮,按提示联网获取。
这里有个容易踩的坑:TF6250这类通讯功能包是有独立许可的,不是说你激活了TwinCAT试用授权就自动能用TF6250。你要在License设置界面里确认TF6250的授权状态,通常试用授权会把常用功能包一起激活,但正式授权和试用授权要分开看。如果FB_MBUS_SERVER调用后一直报错,多半就是TF6250的授权缺失。
2.4 没有倍福硬件,怎么把程序仿真跑起来
你如果跟我一样手头只有一台普通Windows PC,也没有CX系列嵌入式控制器,那就要使用TwinCAT的本地仿真模式。前提是安装时勾选了Simulation组件。操作上我习惯这样:
- 新建TwinCAT XAE项目后,在“SYSTEM”→“Real Time”里可以切换运行模式。
- 写完PLC程序后,点击“Activate Configuration”,弹出询问“Run Mode”的对话框时选择“Simulated”。
- 激活之后,PLC任务会自动进入Run状态,程序里的变量可以在在线监视里看到。
仿真模式下没有真实IO,但Modbus TCP Server是可以完整的在本机跑起来的——因为它本质是Windows上的一个TCP监听服务,不依赖EtherCAT端子。你甚至可以在同一台电脑上用Modbus Poll连接127.0.0.1的502端口完成整个协议测试,这对前期联调是巨大的便利。
3. Modbus-TCP的核心机制:搞清楚映射与字节序才不会采错数
3.1 一帧Modbus TCP报文拆开看看
Modbus TCP报文比很多人想象中简单,没有复杂的握手过程,就是TCP 502端口上的请求-响应。一帧读保持寄存器请求报文长这样:
00 01 00 00 00 06 01 03 00 00 00 0A拆开解释一下:
| 字段 | 长度 | 值 | 含义 |
|---|---|---|---|
| 事务处理标识符 | 2字节 | 00 01 | 主站的一次请求编号,响应要原样带回 |
| 协议标识符 | 2字节 | 00 00 | Modbus协议固定为0 |
| 长度 | 2字节 | 00 06 | 后面还有6个字节 |
| 单元标识符 | 1字节 | 01 | 从站地址,可以理解为设备ID |
| 功能码 | 1字节 | 03 | 03=读保持寄存器 |
| 起始地址 | 2字节 | 00 00 | 从寄存器地址0开始读 |
| 寄存器数量 | 2字节 | 00 0A | 连续读10个寄存器 |
响应报文则会把寄存器数量和数据拼在后面。也就是说,在TwinCAT侧实现Server,本质上就是监听TCP端口,收到请求后去指定的内存区域取出数据、按大端字节序塞回响应。
3.2 倍福是小端字节序,Modbus是大端字节序,这是第一个坑
Modbus寄存器是16位宽的,协议规定传输时高位字节在前(Big Endian)。但倍福和绝大多数x86 PC一样,内存里是Little Endian。如果你不去处理字节序,主站读到的16位整数很可能是高8位和低8位反过来的。
比如PLC里一个WORD变量值是16#1234,在内存里存储为34 12,Modbus主站按大端解析出来就变成16#3412,这数字谁看了都懵。
解决办法也很直接,在把数据映射到Modbus缓冲区之前,做一次字节交换。TwinCAT ST里可以自己写一个字节交换函数,也可以利用UNION特性来处理32位数据。
3.3 功能码和数据区的对应关系,别把读和写搞混
Modbus TCP常用的功能码就那么几个:
| 功能码 | 名称 | 对应PLC方向 |
|---|---|---|
| 03 | 读保持寄存器 | 主站读PLC数据,最常用 |
| 04 | 读输入寄存器 | 主站读PLC只读数据区 |
| 06 | 写单个寄存器 | 主站写一个寄存器 |
| 16 | 写多个寄存器 | 主站连续写多个寄存器 |
在TwinCAT的TF6250中,Server功能块一般会暴露一块连续的缓冲区。主站读取的寄存器地址,就是这块缓冲区从地址0开始的偏移。所以你要做的实际上就是:把需要对外开放的PLC变量,按顺序摆到一块连续内存里,然后让Server功能块指向这块内存。这个思路跟触摸屏变量表很相似,只是触摸屏由组态软件自动处理地址映射,而TwinCAT这边需要你自己维护。
4. 手把手操作:从创建TwinCAT项目到服务器配置全流程
4.1 创建项目并添加PLC程序
打开TcXaeShell,新建项目时选择“TwinCAT XAE Project (XML format)”,给它起个名字,比如ModbusTCPServer_Demo。然后在解决方案里右键“PLC”,添加一个“Standard PLC Project”。
项目结构里会自动生成一个Main程序,双击进入,这就是你写逻辑的地方。我习惯把Modbus Server相关的调用单独放到一个PRG里,比如PRG_MODBUS_MAP,然后在任务配置里把它挂到主任务下面。这样主程序保持干净,通讯代码独立成块,后期维护也清晰。
4.2 添加TF6250库和FB_MBUS_SERVER功能块
右键PLC项目的“References”,选择“Add Library”,在搜索框里输入Tc2_ModbusSrv(不同Build版本库名可能略有差异,也可能是TwinCAT Modbus TCP Server相关的库名)。添加成功后,在“POUs”或者“References”里能看到功能块定义。
TF6250的核心功能块是FB_MBUS_SERVER(某些版本库可能命名不同)。它的典型使用方法是在PLC变量区实例化一个功能块对象,然后在循环任务里调用它。
4.3 写一个最小可用的Server调用代码
先声明数据区。因为Modbus寄存器是16位宽,我直接用WORD数组来定义保持寄存器区,一个数组下标就是一个Modbus寄存器地址,这样对应关系非常直观:
PROGRAM PRG_MODBUS_MAP VAR fbServer : FB_MBUS_SERVER; aHoldRegs : ARRAY[0..99] OF WORD; // Modbus保持寄存器区,地址0~99 nPort : UDINT := 502; bInit : BOOL := FALSE; fError : BOOL; iErrorId : UINT; bConnected : BOOL; nConns : UDINT; END_VAR然后在循环任务里调用功能块:
IF NOT bInit THEN bInit := TRUE; END_IF fbServer( sIPAddr := '', // 空表示监听所有网卡 nPort := nPort, // 502为Modbus TCP标准端口 cbLen := SIZEOF(aHoldRegs), // 缓冲区字节长度 pBuffer := ADR(aHoldRegs), // 指向数据区的起始地址 bAutoRestart := TRUE, // 连接断开后自动重启监听 tRetryDelay := T#2S ); fError := fbServer.fError; iErrorId := fbServer.iErrorID; bConnected := fbServer.bConnected; nConns := fbServer.nConnections;说明一下功能块引脚的用途:sIPAddr填空字符串表示监听本机所有IP地址,如果只想监听从站网卡也可以指定具体IP;nPort默认502;cbLen和pBuffer决定了主站能访问的数据区大小和位置,这俩就是核心;bAutoRestart置TRUE后,主站断开连接Server会自动重新进入监听状态,避免断一次就死在那。
4.4 编译、激活配置、跑起来
写完代码后,先“Rebuild”PLC项目,确认没有语法错误。然后在TwinCAT总线上右键“Activate Configuration”,如果是仿真环境就选“Simulated”。
激活成功且任务Run起来后,fbServer会进入监听状态。我在开发时一般先在本机用Modbus Poll连一下127.0.0.1:502,功能码选03,起始地址0,数量10。如果能读到数据,说明Server已经工作了。
不过这里马上就会遇到另一个问题:你看到的寄存器数值也许跟PLC里的值对不上,这就是上面说的字节序问题,下一节专门处理。
4.5 修改PLC数据后,主站能实时看到变化吗
可以。因为pBuffer指向的是数组起始地址,主站每次发请求,FB_MBUS_SERVER都会从当前内存地址读取数据。所以只要你在PLC里修改aHoldRegs[0],主站下一次轮询就能读到新值。不需要额外的“刷新”动作。
反过来,如果主站通过06或16功能码写寄存器,FB_MBUS_SERVER会把数据写入同一块内存区,PLC程序里只要周期扫描这个数组,就能发现外部写入的数据。这也是为什么Modbus TCP Server在TwinCAT里实现起来比想象中简单,因为内存映射天然就存在。
5. 数据区映射与PLC程序写法:真正让Server“跑起来”
5.1 把PLC变量同步到Modbus寄存器区
实际项目里不可能让上位机直接面对一串裸的WORD数组,你肯定希望看到的是“温度值在寄存器0,速度在寄存器1,启动命令在寄存器10”这种语义化布局。所以通常在PLC里做一个数据管理功能块,负责把实际变量拷贝到aHoldRegs数组:
// 把生产数据写入保持寄存器区 aHoldRegs[0] := WORD_TO_WORD_REAL? // 注意类型转换温度、速度这类REAL数据,需要先把32位浮点数拆成两个WORD。TwinCAT ST里可以用UNION,也可以直接用地址拷贝的方式处理:
// 假设有结构体 TYPE ST_MACHINE_DATA UNION fTemperature : REAL; aWords : ARRAY[0..1] OF WORD; END_UNION END_TYPE多个数据组合成UNION后,把aWords[0]写到aHoldRegs[0]、aWords[1]写到aHoldRegs[1],主站读两个寄存器,再按Modbus的32位浮点规则拼起来就行。
5.2 处理32位数据的字节序和字顺序
Modbus 32位浮点数的排列顺序有两大流派,业界比较常见的是“两个字,低地址存高字”还是“低地址存低字”,很多设备还不一样。倍福这边,我做项目时的经验是:先按“WORD内部字节序交换,字顺序不变”的方式,然后在Modbus Poll里验证,如果解析出来数值不对,再把两个WORD的位置调换一下。
写一个通用字节交换函数:
FUNCTION FC_SwapWORD : WORD VAR_INPUT value : WORD; END_VAR FC_SwapWORD := SHL(value, 8) OR SHR(value, 8);4字节数据可以用两次SwapWORD组合,或者更直接地逐个BYTE交换。联合体在TwinCAT 3里完全支持,能显著减少手工位运算。
5.3 主站写入的数据怎么反哺到PLC逻辑
主站写的数据同样从aHoldRegs数组读出来,然后赋给PLC内部变量。比如从一个触摸屏上设置目标产量,触摸屏写寄存器地址5,PLC程序每隔几十毫秒把aHoldRegs[5]拷贝到内部变量nTargetOutput:
IF aHoldRegs[5] <> nLastCmd THEN nTargetOutput := aHoldRegs[5]; nLastCmd := aHoldRegs[5]; END_IF为什么要做一次变化检测?因为上位机可能每个扫描周期都在写同一个值,如果直接拷贝可能导致PLC逻辑频繁触发。加一个“上次值”的缓存,只在数值变化时执行一次写入动作,逻辑稳定得多。
5.4 通信状态监控:怎么知道主站是断是连
FB_MBUS_SERVER的引脚里,bConnected表示当前是否存在主站连接,nConnections表示当前连接数。我一般在上位机界面上把这两个值显示出来,并且在PLC逻辑里做断线处理。比如设备处于自动运行模式时,如果Modbus主站掉了,可以给一个“通讯断开”报警,而不是让产线在无人知晓的情况下失联。
还建议在寄存器区里放一个“心跳字”或者“时间戳字”,由PLC每500ms自增一次。上位机读完只要发现这个值在动,就能确认通讯链路是活的,而不只是TCP连接存在。这项经验在排查那些“上位机显示连接正常但数据总是不刷新”的问题时非常管用。
6. 连不上?超时?——Modbus-TCP Server排错排查链路
6.1 先判断是连接层问题还是数据层问题
遇到故障,第一步不是改程序,而是把问题分层。
- 如果Modbus Poll压根连不上,属于连接层问题。
- 如果连上了但数据读出来不对、全0、乱码、报超时,属于数据层问题。
连接层问题优先查网卡、防火墙、进程监听状态、许可证。数据层问题优先查缓冲区长度、地址偏移、字节序、数据长度和轮询频率。
6.2 连接不上的完整排查链路
我按照自己处理现场故障的顺序列个清单,你可以直接照做:
- 确认TwinCAT处于Run状态且激活了配置。很多情况下,你修改了PLC代码但没有“Activate Configuration”,服务器实际还在运行旧程序。
- 确认FB_MBUS_SERVER的fError状态。如果报错,看iErrorID,对照库自带的错误码表。
- 本机回环测试。在同一台开发机上用Modbus Poll连接127.0.0.1:502。如果本机能通、局域网连不上,大概率是防火墙或IP配置问题。
- 检查Windows防火墙。TCP 502端口需要在入站规则里放行。很多人装完系统后防火墙默认拦截所有外部连接,而本机回环不受影响,表现得非常迷惑。
- 用telnet做一个粗暴测试:命令行执行
telnet <PLC的IP> 502,如果出现黑色窗口且光标闪烁,说明端口是通的;如果立即退出或报连接失败,说明服务没监听或被防火墙拦了。 - 检查TF6250许可证状态。如果许可证缺失,FB_MBUS_SERVER可能初始化失败,连接直接拒绝。
6.3 连上了但数据不对,问题出在哪
之前我遇到过一次:主站能连上,寄存器也能读,但读回来的数值跟PLC里显示的完全两样,有些值还特别大。最后发现是数据类型映射错了。我把一个REAL变量直接塞进了WORD数组,相当于把4个字节塞进了2个字节的坑里,数据完全错位。
排查时建议用最笨但也最可靠的方法:先在PLC里给aHoldRegs[0]赋一个固定值,比如16#1234,让上位机读这个地址。如果读到16#1234,字节序没问题;如果读到16#3412,说明需要字节交换。这样就能把“协议配置问题”和“PLC程序问题”快速切开。
6.4 连接一会儿就断开,大概率是网卡节能策略
有段时间我在现场碰到一个奇怪故障:Modbus连上后能正常通讯一两分钟,然后突然断开,过几秒又能自动重连。折腾很久才发现是笔记本电脑网卡的电源管理策略在作怪——Windows默认允许“计算机关闭此设备以节约电源”,当网络空闲时间稍长,系统就自动把网卡休眠了。
解决办法:在“设备管理器”→“网络适配器”→“属性”→“电源管理”里,取消勾选“允许计算机关闭此设备以节约电源”。如果是工控机自带的板载网卡,也要检查驱动里有没有类似的省电策略。这个坑特别隐蔽,因为它不会让你完全断网,而是“通讯一段时间后断一次”,非常容易误判为PLC程序问题。
6.5 抓包是最诚实的手段
如果以上手段都试过还不行,直接上抓包工具。在PLC所在电脑上跑Wireshark,过滤条件写tcp.port == 502,立马就能看到主站发来的请求帧和PLC回应的响应帧。请求帧到了、响应帧没有,问题就在PLC侧;请求帧压根没到,问题就在网络侧。抓一次包,比你在那猜半天快得多,这也是我做工业通讯排查养成的好习惯。
7. 进阶优化:多客户端、字节序开关、性能与安全性
7.1 多客户端同时连接,就得靠多实例
FB_MBUS_SERVER的监听能力一样,一个实例一般能接受一定数量的TCP客户端连接(具体数量看授权和版本,通常在几个到十几个之间)。如果你有多个主站同时要读数据,最简单的做法是实例化多个FB_MBUS_SERVER,每个实例监听不同端口,指向相同或不同的数据区。
我做过最典型的场景是:一套设备,触摸屏走502端口读控制区,MES系统走503端口读产量区。两个Server实例互不干扰,每个端口的数据区可以按权限划分,触摸屏只能读,MES可以写。这种“一个设备、多端口、多数据区”的架构,在现场特别好用。
7.2 性能和扫描周期的平衡
FB_MBUS_SERVER本质上是在PLC任务里执行TCP报文解析和收发,所以它吃的是PLC任务的CPU时间。如果你把它放在1ms任务里跑,而且数据区很大,那PLC的循环周期会被明显拖慢。我的经验是给通讯功能块单独分配一个10ms或50ms的周期任务,跟高速运动控制任务分开。这样做还能避免一个好处:即使Modbus流量突然暴增,也不会影响主轴的插补周期。
Modbus TCP的请求在数据量不大时,10ms周期完全够用。如果上位机要高频采集,考虑调整到5ms,但一定要观察CpuLoad和任务最坏执行时间。
7.3 安全建议:别把502端口裸奔到办公网
Modbus TCP是没有加密和认证的,哪个主站都能读,甚至能写。我见过一个现场,设备PLC的502端口直接暴露在工厂局域网里,结果有一天车间网络里有设备广播风暴,PLC频繁收到乱七八糟的写请求,数据被改得面目全非。
所以给几条实用建议:
- 在PLC程序里,对主站写入的数据做范围校验,超出合理范围就拒绝或恢复到默认值。
- 利用防火墙或交换机ACL,只允许指定上位机IP访问PLC的502端口。
- 如果确实需要跨区域访问,建议通过上位机中转,而不是把PLC直接暴露给办公网。
7.4 一个可靠的寄存器规划方法
最后分享一个我个人习惯用的寄存器规划表,每次做Modbus Server之前我都会先画这样一张表,再动手写代码:
| 寄存器地址 | 数据项 | 数据类型 | 读写权限 | 说明 |
|---|---|---|---|---|
| 0 | 运行状态 | WORD | 只读 | 位0运行中,位1报警 |
| 1-2 | 当前电流 | REAL | 只读 | 32位浮点数,占2个寄存器 |
| 10 | 启动命令 | WORD | 只写 | 主站写1触发启动 |
| 20-21 | 目标产量 | DINT | 只写 | 主站写入设定值 |
| 99 | 心跳字 | WORD | 只读 | PLC自增,用于链路检查 |
有了表格,写PLC映射代码就像填表一样简单,主站工程师只需要拿到这张表就能开始联调,双方沟通成本极低。这套方法比“我把变量都放到一个数组里了你看看”要专业得多,也让后续维护的人省心很多。
我自己在倍福平台上做Modbus TCP Server的次数多了之后,最大的体会是:协议本身并不复杂,复杂的永远是数据规划、字节序处理和异常排查这套组合拳。只要把寄存器地图画得清清楚楚,再按“先回环、再局域网、最后抓包”的顺序排查,这个功能在TwinCAT3里稳稳落地不是难事。如果你正准备在下一个项目里让倍福老老实实当一回Modbus从站,希望这篇能帮你少走几步冤枉路。