简介:这份资源是面向C#开发人员与工控领域学习者的倍福PLC通信实例源码,基于TwinCAT.Ads.dll库实现上位机与倍福PLC之间的ADS数据交互,适合新手入门及有一定经验的开发者借鉴参考。压缩包共61个文件,约1.51MB,以12个cs源码文件为核心,辅以csproj、sln工程文件、resx资源文件、dll动态库与exe可执行程序,并附带docx技术说明文档、PLC侧的dfr、sdb、tpy等配置文件及bmp示意图,覆盖从代码到PLC工程的完整链路。资源中配有完整代码、技术文档与注释,读者可据此理解ADS读写变量的调用方式、连接建立流程与工程组织思路,并对照说明文档排查通信异常。目前已有1520人学习下载,适合需要快速搭建C#与倍福PLC通信Demo、借鉴工程结构或补充ADS通信知识点的开发者。
1. 从一份 C# 与倍福 PLC 通信源码说起:TwinCAT.Ads.dll 到底能解决什么
产线上位机要读倍福 PLC 的变量,很多人第一反应是走 Modbus 或者 OPC UA,结果发现倍福自家的 ADS 协议才是延迟最低、配置最省事的那条路。这份 C# 通过 TwinCAT.Ads.dll 与倍福 PLC 通信的实例源码,解决的就是「上位机怎么用原生协议直接读写 PLC 变量」这件事。它适合两类人:一是做 C# 上位机、需要和倍福控制器打交道的工控开发者;二是手里有 CX 系列或 TwinCAT 3 环境、想快速验证通信链路的调试人员。源码本身不复杂,但把连接、读变量、写变量、句柄管理这几条主线串起来了,照着改就能落到自己的项目里。
2. TwinCAT.Ads.dll 的通信模型与源码结构拆解
2.1 ADS 协议为什么比 Modbus 更适合倍福场景
倍福的控制器底层跑的是 TwinCAT 实时核,ADS(Automation Device Specification)是它对外暴露的原生寻址协议。和 Modbus 相比,ADS 不需要你手动维护寄存器地址映射表,它直接按「变量名」寻址——你在 PLC 里声明了MAIN.nSpeed,上位机就能用这个名字去读写,省掉了一层地址换算。这也是这份源码选择 ADS 而不是 Modbus 的根本原因。
ADS 的通信模型分两层:底层是 AMS 网络,每个设备有唯一的 AMS NetId(形如192.168.1.10.1.1),端口号区分服务类型(PLC 运行时通常是 851)。上层才是 ADS 命令,读、写、读句柄、释放句柄都是独立的命令码。TwinCAT.Ads.dll 把这套东西封装成了 .NET 类库,你不需要自己拼 AMS 报文,调TcAdsClient或AdsClient就行。
这里有个选型细节值得说清楚:老版本库用的是TcAdsClient,新版本(TwinCAT.ADS 5.x 以后)改成了AdsClient,命名空间也从TwinCAT.Ads内部做了调整。这份源码用的是哪个版本,直接决定了你引用 dll 后能不能编译通过。常见做法是先在 NuGet 里搜Beckhoff.TwinCAT.Ads,看版本号再决定 API 写法,别拿着旧教程硬套新库。
2.2 源码文件构成与依赖关系
拿到压缩包解压后,典型结构是解决方案文件加一个主项目,核心逻辑集中在一两个.cs文件里。你需要关注的是这几块:
| 文件/模块 | 作用 | 需要改的地方 |
|---|---|---|
| 主窗体或主类 | 初始化 AdsClient、建立连接 | AMS NetId、端口号 |
| 变量读写方法 | 封装 ReadAny / WriteAny | 变量名、数据类型 |
| 句柄管理逻辑 | 变量句柄的申请与释放 | 一般不用改,但要理解 |
| 配置文件 | 存 IP、NetId 等参数 | 按现场实际填 |
依赖上,项目必须引用TwinCAT.Ads.dll。这个 dll 有两个来源:一是装了 TwinCAT 3 开发环境后,在安装目录的Components\Ads下能找到;二是直接通过 NuGet 包引入。我一般推荐 NuGet,因为版本可控,换机器不用重新找 dll。引用之后还要注意目标框架——如果项目是 .NET Framework 4.x,选对应的包版本;如果是 .NET 6/8,得用支持 .NET Standard 的那版。
2.3 连接建立与变量读写的代码骨架
下面这段是连接和读写的核心骨架,参数我按现场最常见的配置填了,你对着自己的环境改 NetId 和变量名即可:
using TwinCAT.Ads; // 创建客户端实例,using 确保连接释放 using (var client = new AdsClient()) { // AmsNetId:目标 PLC 的 AMS 地址,格式为 IP.1.1 // 端口 851 是 TwinCAT 3 PLC 运行时的标准端口 client.Connect("192.168.1.10.1.1", 851); // 按变量名申请句柄,避免每次读写都做名称解析 int handle = client.CreateVariableHandle("MAIN.nSpeed"); // 读:ReadAny 返回 object,需按 PLC 侧声明类型强转 short speed = (short)client.ReadAny(handle, typeof(short)); // 写:类型必须与 PLC 变量声明一致,否则会抛异常 client.WriteAny(handle, (short)500); // 句柄用完必须释放,否则长时间运行会耗尽 PLC 侧资源 client.DeleteVariableHandle(handle); }逻辑说明:Connect的第一个参数是 AMS NetId,不是普通 IP,末尾的.1.1不能省;第二个参数是端口,PLC 运行时固定 851。CreateVariableHandle把变量名解析成句柄,后续读写都用句柄,比每次传字符串快得多。ReadAny和WriteAny的类型参数必须和 PLC 里声明的类型严格对应,short对INT,int对DINT,float对REAL,错一个就报类型不匹配。
参数怎么改:NetId 换成你 PLC 实际的,可以在 TwinCAT 系统托盘图标右键看 AmsNetId;变量名换成你 PLC 工程里真实存在的符号,注意大小写敏感。如果读的是结构体或数组,ReadAny要传对应的 .NET 类型,这块源码里如果有示例就照着抄,没有的话建议先用简单类型跑通再上复杂结构。
3. 把源码跑起来:环境配置与分步调试
3.1 TwinCAT 侧的前置配置
上位机代码写得再对,PLC 侧没配好照样连不上。第一步是确认 PLC 上电且 TwinCAT 处于 Run 模式,Config 模式下 ADS 端口是不响应的。第二步是检查 AMS 路由——上位机和 PLC 不在同一台机器时,必须在 TwinCAT 的「路由配置」里把上位机的 AMS NetId 加进去,否则 PLC 会直接拒绝连接请求。
具体操作:打开 TwinCAT 系统托盘,右键选 Router,再选 Edit Routes,在 Static Routes 里添加上位机的 AmsNetId 和 IP。上位机的 AmsNetId 一般是它自己的 IP 加.1.1。这一步是血泪经验,很多人代码没问题但一直超时,最后发现就是路由没加。
还有一点,如果上位机和 PLC 在同一台机器上(比如本机装了 TwinCAT 又跑 C# 程序),NetId 可以用127.0.0.1.1.1或者本机实际的 AmsNetId,端口还是 851。这种情况不需要配路由,但要注意 TwinCAT 服务得是启动状态。
3.2 C# 项目引用 dll 与编译
引用 dll 有两种方式,我分别说下。NuGet 方式:在 Visual Studio 里右键项目 → 管理 NuGet 程序包 → 搜索Beckhoff.TwinCAT.Ads→ 安装。装完后using TwinCAT.Ads;就能用。手动引用方式:找到 TwinCAT 安装目录下的TwinCAT.Ads.dll,右键项目 → 添加引用 → 浏览到该文件。手动引用的坑在于 dll 可能依赖其他运行时组件,换机器容易缺文件,所以能用 NuGet 就别手动。
编译时如果报「找不到类型 TcAdsClient」,八成是版本问题——新库把类名改成了AdsClient。解决办法是查你装的包版本,5.x 以上用AdsClient,4.x 及以下用TcAdsClient。另外目标框架要匹配,.NET Framework 4.7.2和.NET 6引的包不一样,别混。
# 如果用 dotnet CLI,直接加包 dotnet add package Beckhoff.TwinCAT.Ads --version 5.4.0 # 版本号按你实际需要的填,不确定就先不加 --version 装最新稳定版3.3 分步验证:从连接测试到变量读写
不要一上来就跑完整业务逻辑,按这个顺序验证,出问题好定位:
第一步,只测连接。把Connect单独拎出来,外面包 try-catch,看是否抛AdsErrorException。如果抛,看错误码,0x20一般是路由问题,0x745是端口没开。
第二步,测句柄申请。连接通了之后调CreateVariableHandle,如果变量名写错会返回错误,这时候去 PLC 工程里确认变量是不是在MAIN下、有没有拼错。
第三步,测读。先读一个已知值的变量,比如一个常量或者当前速度,看返回值对不对。类型转换错误在这一步会暴露。
第四步,测写。写一个非关键变量,写完回读确认。注意有些变量 PLC 侧是只读的,写会报错,换一个可写的测。
try { client.Connect("192.168.1.10.1.1", 851); Console.WriteLine("连接成功"); } catch (AdsErrorException ex) { // 错误码是排查的关键,别只看 Message Console.WriteLine($"ADS 错误码: 0x{ex.ErrorCode:X}"); }这段的价值在于把错误码打出来。ADS 的异常信息有时候很笼统,但错误码是精确的,拿着码去查倍福的 ADS 错误码表,基本能定位到根因。
4. 避坑与常见问题排查
4.1 连接超时或报 0x20 错误
现象:Connect调用后等很久然后抛异常,错误码0x20或0x745。原因:最常见的是 AMS 路由没配,PLC 不认识上位机的 NetId,请求被丢弃。其次是 PLC 不在 Run 模式,Config 模式下 ADS 端口不监听。解决:先在 TwinCAT 路由配置里加上位机 NetId,确认 PLC 切到 Run,再用 ping 确认网络通。如果同机测试,检查 TwinCAT 服务是否启动。
4.2 变量句柄申请失败
现象:CreateVariableHandle抛异常或返回无效句柄。原因:变量名拼写错误、大小写不对、变量不在MAIN程序下、或者变量被 PLC 工程设为不可外部访问。解决:在 TwinCAT 工程里用「符号搜索」确认变量全名,注意MAIN.前缀不能少。如果变量在功能块实例里,路径会更长,比如MAIN.fbMotor.nSpeed,得写全。
4.3 读写类型不匹配导致异常
现象:ReadAny或WriteAny抛类型转换异常。原因:C# 侧类型和 PLC 侧声明不一致,比如 PLC 是DINT(32 位)你用了short(16 位),或者 PLC 是REAL你用了double。解决:对照 PLC 变量声明表,INT→short、DINT→int、REAL→float、LREAL→double、BOOL→bool。结构体的话两边字段顺序和类型都要对齐。
4.4 长时间运行后通信中断
现象:程序跑几个小时或几天后突然读写失败。原因:句柄没释放,PLC 侧句柄资源被耗尽;或者连接对象没正确 Dispose,底层 socket 泄漏。解决:每次CreateVariableHandle配一个DeleteVariableHandle,用using包住AdsClient,确保异常路径也能释放。如果变量是频繁读写的,建议启动时申请一次句柄,全程复用,而不是每次读写都申请释放。
4.5 多线程访问冲突
现象:多个线程同时调ReadAny时报错或数据错乱。原因:AdsClient不是线程安全的,并发调用会出问题。解决:加锁,或者每个线程用独立的 client 实例。我一般会在 client 外面包一层 lock,简单粗暴但有效。如果读写频率很高,考虑用异步 API 或者把读写集中到一个线程里做。
5. 进阶:句柄复用、批量读写与异常重连的工程化写法
把 demo 跑通只是第一步,真正上产线得考虑稳定性和效率。句柄复用是最直接的优化——启动时把所有要用的变量句柄一次性申请好,存到字典里,运行期只做读写不做句柄操作。这样单次读写耗时会明显下降,因为省掉了名称解析那一步。
批量读写方面,ADS 支持ReadAny传结构体,把多个变量打包成一个结构体一次读完,比逐个读快得多。前提是 PLC 侧这些变量在内存上连续,或者你定义一个结构体类型两边对齐。这块源码里如果没涉及,可以自己扩展,思路是用Marshal把结构体和字节数组互转。
异常重连是产线程序的标配。网络抖动、PLC 重启都会导致连接断开,程序不能直接崩。常见做法是包一个重连循环:捕获AdsErrorException,判断是不是连接类错误,是的话 Dispose 旧 client,等几秒重新 Connect,重建句柄。重连次数要设上限,避免死循环。
private AdsClient client; private Dictionary<string, int> handles = new Dictionary<string, int>(); private void EnsureConnected() { if (client != null && client.IsConnected) return; // 断线后重建连接和句柄 client?.Dispose(); client = new AdsClient(); client.Connect("192.168.1.10.1.1", 851); foreach (var name in handles.Keys.ToList()) { handles[name] = client.CreateVariableHandle(name); } }这段的关键是IsConnected判断和句柄重建。注意重连后旧句柄全部失效,必须重新申请,不能沿用。handles字典的 key 存变量名,value 存句柄,重连时遍历重建。
验证方法上,我习惯用 TwinCAT 自带的变量监视窗口对照——C# 程序写进去的值,在 PLC 侧监视窗口里能不能实时看到变化,能对上就说明链路没问题。反过来 PLC 侧强制改一个值,C# 读出来是不是新值,双向验证才算跑通。
从那以后我每次拿到这类通信源码,都强制先在同机环境用127.0.0.1.1.1跑一遍最小连接测试,确认库版本和 API 对得上,再上现场网络。这一步花五分钟,能省掉后面半小时的抓瞎排查。希望帮到你。
本文还有配套的精品资源,点击获取