ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TwinCAT3与C++上位机ADS异步通信实战:原理、代码与调优

TwinCAT3与C++上位机ADS异步通信实战:原理、代码与调优 做工控上位机这几年TwinCAT3和C的组合我几乎每天都会碰。很多刚入门的工程师会有同一个困惑PLC程序跑在实时核上我的Windows上位机程序跑在用户态两边到底怎么高效地换数据直接共享内存在TwinCAT3的体系里想都别想。轮询变量太粗糙延迟和CPU占用都受不了。这个场景里ADS就是那座桥。这篇文章我结合自己做过的产线数据采集、视觉联动和设备对接项目把TwinCAT3实时核与C程序通过ADS进行异步通信的Server/Client模型完整拆一遍覆盖协议原理、工程搭建、代码实现、常见坑和性能调优适合正在做上位机开发、有C基础、又想彻底搞懂ADS通信的工程师参考。先说明一点我不打算写成API手册风格重点是讲清楚思路和决策过程。每个关键配置我都会解释为什么要这么做只有理解了原理遇到文档查不到的错误码时你才不至于抓瞎。1. 先把概念掰清楚实时核、ADS与Server/Client的角色1.1 实时核和Windows为什么“没话说”TwinCAT3从架构上看是把实时任务跑在一个独立于Windows的实时扩展核上。这个实时核由倍福的RT Extension管理任务调度优先级高于Windows所有进程包括系统进程。实时任务能以1ms甚至更小的周期稳定运行抖动通常控制在微秒级别。而Windows普通用户态程序线程可能在运行途中被DPC、中断或高优先级线程抢占一个看似简单的读写操作最坏延迟可能到几十毫秒甚至更高。这就是两个世界。PLC程序不能直接访问Windows进程的内存Windows程序也不能直接操作实时任务里的变量。我记得第一次在TwinCAT安装目录里翻东西时还天真地找过共享内存DLL后来才意识到这条路从一开始就走不通。两个环境之间的数据交换必须经过ADS协议这一层“通关口岸”。ADS翻译成人话就是一套定义好的请求和应答格式负责把Windows这边的数据请求翻译成实时核能理解的指令。1.2 ADS协议到底是什么Server/Client谁是谁ADS全称Automation Device Specification是倍福定义的一套设备间通信协议底层运行在AMSAutomation Message System消息系统之上。通信双方通过AmsNetId和端口号定位。AmsNetId可以理解成工控世界里的IP地址端口号则用于区分设备上的不同服务。PLC Runtime的ADS端口号固定是851NC轴控是501IO模块是301这些端口号在协议栈里是约定俗成的。大家习惯说“TwinCAT是ServerC程序是Client”这个说法在大多数场景下成立。典型情况是C程序主动发起读写请求TwinCAT实时核响应请求此时TwinCAT充当ADS ServerC是ADS Client。但要注意ADS的角色并不是锁死的。PLC程序同样可以主动向C程序发起ADS请求这时候C进程就得充当Server角色。标题里的“Server/Client”本质上描述的是一种对等通信关系谁发起请求谁是Client谁响应请求谁是Server同一个通信链路里双方可以随时角色互换。1.3 同步读写和异步通知的取舍ADS通信有一种很容易踩坑的认知混淆把同步读写和异步通信对立起来。实际上ADS同时支持两种完全不同的数据获取方式。第一种是同步读写C程序调用AdsSyncReadReq这类函数把请求发出去后线程阻塞等待实时核返回结果。这种方式逻辑简单适合偶发性的参数读写、配置下发这类低频操作。缺点是等待期间线程被挂起如果目标设备无响应一次调用可能阻塞几十毫秒在需要同时处理多个变量或多个设备的场景里会很别扭。第二种就是标题强调的异步通信走的是Notification通知机制。C程序先告诉TwinCAT“我关注某个变量变量变化时你要主动把新值推给我”然后C这边该干嘛干嘛变量变化后由TwinCAT实时核主动推送数据C程序在回调函数里接收。这是一种事件驱动的模型延迟低、CPU占用小是生产环境做高速数据采集和实时联动的首选方案。后面我会重点拆这个机制的完整实现链路。2. TwinCAT3侧准备环境、变量与路由检查2.1 安装与激活阶段容易卡住的地方很多刚接触TwinCAT3的人把Download和安装视为最困难的一步实际上安装不难难的是装完以后跑不起来。这里有几个高频问题值得提前注意。Windows版本最好用专业版或企业版家庭版在运行实时核时会因为缺少组策略和内核调试支持出现各种诡异问题。安装之前建议先关闭UEFI安全启动和BitLocker否则安装过程中TwinCAT的驱动签名验证可能被拦。系统里的Visual C Redistributable一定要装齐全TcAdsDll运行时依赖这些运行库少了会直接报缺少VCRUNTIME140.dll之类的错误。另一个容易忽略的点是授权。TwinCAT3安装后默认有一个7天试用期试用期内本地连接没有问题但做远程ADS通信时如果PLC那边的Runtime授权过期或处于试用模式C程序连接时经常会出现超时或拒绝访问的错误。遇到这类问题先别怀疑代码去TwinCAT System Manager里看一眼授权状态更高效。激活配置也常被忽略。TwinCAT工程写完代码后一定要在XAE界面点击Activate Configuration实时核才会真正加载配置并运行。没有激活配置C程序去连接851端口时永远拿不到响应。2.2 变量规划让通信数据更可控规划通信变量是TwinCAT侧最值得花时间的环节这一步直接影响后面C代码的复杂度。我的建议是不要一股脑地把PLC里所有变量都暴露给上位机而是单独建一个全局变量列表GVL里面定义几个结构化变量作为“通信数据包”。比如一个设备状态包、一个工艺参数包、一个视觉结果包每个包用STRUCT组织。这样做的好处有两个第一C端只需要维护对应数量的数据结构代码清爽第二一次ADS请求可以直接读写整个结构体通信效率远高于逐变量读写。变量作用域也要注意。TwinCAT3里PRG程序内部的变量访问路径通常是PRG名.变量名GVL全局变量的路径是GVL名.变量名。如果你的C程序按名称访问变量路径必须和TwinCAT工程里的完全一致大小写敏感。很多“符号不存在”的错误不是变量没定义而是路径前缀写错了。还有一点如果想要在C里通过变量名访问必须在TwinCAT工程中启用符号访问。在System Manager的工程属性里有对应开关默认通常是开启的但如果你拿到的是一台别人配置过的设备最好确认一下。2.3 先用TwinCAT自带工具确认ADS路由写C代码之前强烈建议先在TwinCAT自带的工具里确认ADS路由是通的。TwinCAT XAE界面的System Manager有一个Router页面可以看到本机AMS NetId。默认情况下每台TwinCAT设备都分配了一个类似192.168.0.1.1.1的AMS NetId这个ID不是简单的随机数而是根据本机网卡或静态配置生成的两台机器通信时必须知道对方的AMS NetId。如果你要连接的是本机实时核直接用AdsGetLocalAddress获取本地AMS NetId即可。如果C程序跑在另一台电脑上需要访问PLC所在机器的实时核那么C程序所在电脑的TwinCAT路由表里必须添加PLC的AMS NetId和IP地址PLC侧也要能反向路由到上位机。路由表配置在TwinCAT System Manager的Router界面里操作添加的是AMS NetId和对应的IP地址。一个很实用的验证手段是在TwinCAT的“Choose Target System”对话框里搜索设备。如果能看到目标设备并成功连接说明ADS路由基本通畅再去写C代码就事半功倍。否则先排查防火墙是否放行AMS端口很多跨设备连接失败都是Windows防火墙拦了48898端口导致的。3. C工程搭建从引入TcAdsDll到第一次读变量3.1 工程配置库引用和DLL部署C项目里使用ADS本质上是调用TcAdsDll动态链接库暴露的C接口。这个DLL随TwinCAT安装包一起提供在C:\TwinCAT\ADS\DLL目录下可以找到TcAdsDll.lib和TcAdsDll.dll头文件是AdsDef.h和AdsApi.h。工程配置有三种主流方式。第一种最直接在Visual Studio工程里把TcAdsDll.lib加入附加依赖项代码里#include TcAdsDef.h和#include TcAdsApi.h运行前把TcAdsDll.dll放到exe同级目录或者系统PATH路径下。第二种是用CMake的target_link_libraries直接链接lib文件适合有CMake基础的项目。第三种是用vcpkg安装第三方封装的ADS库但考虑到绝大多数项目只是需要Client功能直接用官方DLL最省事。位数选择是个坑。TcAdsDll.dll同时提供x86和x64版本如果你的C程序是x64编译的就要确保链接的是x64版本的lib部署的是x64版本的DLL。混用会在运行时出现无法解析的外部符号或者DLL加载失败。我的习惯是程序统一用x64和现代Windows发展趋势保持一致。3.2 建立连接端口、NetId与设备信息探测一个最小的ADS连接流程分三步初始化ADS端口、组装目标设备地址、发起一次探测请求验证连接。初始化调用AdsPortOpen这个函数会打开一个本地的ADS通信端口返回一个long类型的错误码0表示成功。整个进程只需要调用一次不需要多次打开。组装地址时用到AmsAddr结构体里面有两个关键字段AmsNetId和Port。连本机就调用AdsGetLocalAddress获取本机AMS NetId连远程设备就手动填充目标设备的AMS NetId。Port要填851对应PLC Runtime 1。如果你是连第二套PLC Runtime那就是852以此类推。我第一次写这个代码时犯过一个低级错误把Port填成了10000那是系统服务的端口不是PLC Runtime的端口结果连接一直超时。排查了很久才发现是端口号的问题。所以这里特别提醒连接PLC读变量端口就是851除非你明确知道自己在做什么。连接建立后建议立即调用AdsSyncReadDeviceInfoReq读取目标设备名称和版本号。这个请求能同时验证三件事AMS路由通不通、目标端口有没有服务监听、当前程序权限是否足够。如果这步成功后续读写基本就顺了。3.3 按名访问变量句柄机制的完整链路在C里访问TwinCAT变量最推荐的方式是“按名访问 句柄机制”。整个链路分两步先通过变量名字符串获取句柄再用句柄读写数据。获取句柄的核心函数是AdsSyncReadWriteReq这是一个组合读写函数请求消息里携带变量名响应消息里返回句柄。调用时需要指定IndexGroup为ADSIGRP_SYM_HNDBYNAME0x0000F003IndexOffset为0示意代码如下#include Windows.h #include TcAdsDef.h #include TcAdsApi.h #include cstdio #include cstring #pragma comment(lib, TcAdsDll.lib) bool GetSymbolHandle(const AmsAddr addr, const char* symName, uint32_t handle) { long nErr AdsSyncReadWriteReq(addr, ADSIGRP_SYM_HNDBYNAME, // 按名称获取句柄的IndexGroup 0, // IndexOffset固定为0 sizeof(handle), // 期望接收的句柄长度 handle, // 接收句柄的缓冲区 (uint32_t)strlen(symName) 1, (void*)symName); // 符号名字符串 return nErr 0; }拿到句柄后读写变量就简单了。读变量使用AdsSyncReadReqIndexGroup换成ADSIGRP_SYM_VALBYHND0x0000F005IndexOffset填句柄值。写变量用AdsSyncWriteReq参数结构类似。不管变量在PLC里是什么类型到了C这一侧都先看成一段字节流长度由你传入的缓冲区大小决定。这里有个重要设计使用句柄而不是直接填偏移地址是为了让C代码不依赖TwinCAT工程里变量的物理内存布局。PLC工程改版后变量偏移可能变化但只要变量名不变句柄机制就能自动适应。用绝对偏移访问是上个时代的做法除非对性能有极端要求否则不建议。4. 异步通信实战Notification通知与批量数据采集4.1 为什么生产环境不能靠轮询有人会觉得既然同步读这么简单那就循环去读变量不就行了在小项目、低频场景下确实可以但生产环境里轮询的问题很快就会暴露。首先是延迟不可控。你用10ms周期轮询一个变量能保证从变量变化到你感知的延迟小于10ms吗不能最坏情况是刚轮询完变量就变了你得等下一个10ms。如果轮询周期加长到50ms关键信号可能就丢了。其次是CPU资源浪费。Windows线程在高频轮询时即使每次读写只有几百微秒累积起来的开销在高负载工控机上依然可观。更麻烦的是轮询会对实时核产生大量ADS请求挤占系统服务的消息处理时间可能反过来影响PLC的任务周期稳定性。异步Notification机制就没有这些问题。它在C程序里注册一个通知TwinCAT实时核在变量满足条件时主动推送数据数据一到C回调立即触发。从变量变化到应用层收到数据中间只有一次事件派发延迟极低而且没有变量变化时通信链路是完全空闲的。4.2 订阅通知三个核心参数决定通信行为注册通知的函数是AdsSyncAddDeviceNotificationReq核心参数全在ADSNOTIFYINFO结构体里。我详细说一下这三个参数的含义。第一个是回调数据长度cbLength。这个字段告诉ADS系统你在关注多大一块数据。比如PLC端是一个64字节的结构体这里就填64。长度不匹配时ADS系统按最小约束处理过小会截断数据过大浪费内存和带宽。第二个是传输模式nTransMode。最常用的有两个值ADSTRANS_SERVERONCHA变化时通知和ADSTRANS_SERVERCYC周期通知。变化通知适合开关量、状态字、命令字这类事件型数据变量值一变就推送。周期通知适合模拟量采集实时核按固定周期把数据推过来不管值变没变。第三个是周期nCycleTime和最大延迟nMaxDelay。周期模式下这个时间决定推送频率最大延迟则是在变化通知模式下做限流用的避免变量在极端频繁变化时把通信链路打爆。实测下来变化通知配合1ms的最大延迟既能保证事件及时性又能过滤高频抖动。注册通知还需要指定目标数据的IndexGroup和IndexOffset。如果用之前拿到的句柄IndexGroup就是ADSIGRP_SYM_VALBYHNDIndexOffset就是句柄值。注册成功后系统返回一个通知句柄之后删除通知时要用它。4.3 回调内部不要做重活队列化处理模型Notification回调是异步通信里最容易写错的地方。很多新手会在回调函数里直接处理业务逻辑比如写日志、更新界面、调用数据库接口。这么做在测试阶段可能没问题一旦数据频率上去程序就会变得卡顿甚至崩溃。原因在于回调函数是在TcAdsDll内部的工作线程上下文里执行的这个线程负责接收所有ADS数据。如果你在回调里做耗时操作阻塞了这个线程后续的ADS通知都会排队等待轻则数据延迟飙升重则消息堆积导致内存暴涨。我踩过一次坑在回调里直接写了一个同步的SQLite插入动作每秒200个通知跑了几分钟程序就出现明显卡顿最后定位到是回调线程堵死了整个ADS通信。正确的做法是生产者-消费者模型。回调函数里只做两件事把数据拷贝到预分配的业务队列然后唤醒业务处理线程。队列用互斥锁或无锁队列保护业务线程在循环里取数据做实际处理。这样ADS通信线程永远是轻量的不管后端业务多复杂都不会影响数据接收的实时性。4.4 一个可运行的异步采集Demo下面是一段基于Notification机制的异步采集核心骨架覆盖了从打开端口到注销通知的完整流程#include Windows.h #include TcAdsDef.h #include TcAdsApi.h #include cstdio #include vector #include queue #include mutex #include thread #pragma comment(lib, TcAdsDll.lib) #pragma pack(push, 1) struct MachineData { int32_t status; // 设备状态 double position; // 当前位置 double speed; // 当前速度 uint8_t reserved[8]; // 预留字节保证与PLC侧结构体完全一致 }; #pragma pack(pop) static std::queueMachineData g_dataQueue; static std::mutex g_queueMutex; // 通知回调只负责拷贝数据和唤醒业务线程 static void __stdcall NotificationCallback(AmsAddr* pAddr, AADSNOTIFHANDLE hNotify, ULONG nUser, ULONG nFlags, ULONG nLen, void* pData) { if (pData nLen sizeof(MachineData)) { MachineData md; memcpy(md, pData, sizeof(MachineData)); { std::lock_guardstd::mutex lock(g_queueMutex); g_dataQueue.push(md); } } } int main() { // 1. 打开ADS端口 long nErr AdsPortOpen(); if (nErr ! 0) { printf(AdsPortOpen failed, error: 0x%08X\n, nErr); return 1; } // 2. 组装本地地址连接PLC Runtime 1 AmsAddr addr; memset(addr, 0, sizeof(addr)); nErr AdsGetLocalAddress(addr); addr.port 851; // 3. 获取变量句柄 const char* symName GVL.stMachineData; uint32_t hVar 0; nErr AdsSyncReadWriteReq(addr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(hVar), hVar, (uint32_t)strlen(symName) 1, (void*)symName); if (nErr ! 0) { printf(Get symbol handle failed: 0x%08X\n, nErr); AdsPortClose(); return 1; } // 4. 注册变化通知 ADSNOTIFYINFO notifyInfo; memset(notifyInfo, 0, sizeof(notifyInfo)); notifyInfo.cbLength sizeof(MachineData); notifyInfo.nTransMode ADSTRANS_SERVERONCHA; // 变化时通知 notifyInfo.nMaxDelay 1; // 最大延迟1ms notifyInfo.nCycleTime 0; uint32_t hNotify 0; nErr AdsSyncAddDeviceNotificationReq(addr, ADSIGRP_SYM_VALBYHND, hVar, notifyInfo, hNotify, NotificationCallback); if (nErr ! 0) { printf(Add notification failed: 0x%08X\n, nErr); AdsPortClose(); return 1; } // 5. 业务处理线程 std::thread worker([]() { while (true) { MachineData md; bool hasData false; { std::lock_guardstd::mutex lock(g_queueMutex); if (!g_dataQueue.empty()) { md g_dataQueue.front(); g_dataQueue.pop(); hasData true; } } if (hasData) { printf(status%d, position%.3f, speed%.3f\n, md.status, md.position, md.speed); } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } }); // 6. 主线程跑一会儿后退出 std::this_thread::sleep_for(std::chrono::seconds(30)); // 7. 清理 AdsSyncDelDeviceNotificationReq(addr, hNotify); AdsPortClose(); worker.detach(); return 0; }这段代码里最需要注意的就是结构体布局。PLC侧的结构体和C侧必须保持完全一致字段顺序、类型、长度都不能错。我用#pragma pack(push, 1)是为了消除对齐填充带来的差异CRC校验或跨平台通信时这个细节经常让人抓狂。如果你不确认对齐规则建议不要轻易用pack(1)先在PLC侧设置相同的pragma pack试试。5. 反向链路让C扮演Server角色的可行方案5.1 业务场景PLC需要主动把数据交给上位机时读到这里你可能已经会用C主动去读PLC变量了。但工业现场经常有相反的需求PLC逻辑运行到某个节点时需要主动通知上位机“我要某个结果”。比如视觉检测流程相机拍完一张产品图PLC希望上位机立刻开始处理图像并把结果返回。这个场景如果完全依赖C轮询PLC的命令字延迟不可控体验很差。理想的方案是PLC主动发指令给C程序C程序收到指令后执行计算再把结果写回PLC。在这个对话链路里PLC变成了ClientC变成了Server。ADS协议本身支持这种方向但实现方式比单向C读PLC要复杂一些。5.2 方案一命令信箱模式推荐稳定可靠我做了多个项目后发现真正在生产环境里稳定运行的反向通信方案往往是“命令信箱模式”。这个方案不需要C真的实现一个ADS Server而是在PLC侧定义一组命令通信变量C侧通过Notification订阅命令变量通过同步写返回结果。从PLC的逻辑视角看就像在给上位机发命令、等回执一样。具体实现分三步。第一步在PLC的GVL里定义一个命令结构体包含命令ID、参数区、状态字和返回值。第二步C程序订阅这个命令结构体的变化通知。第三步PLC程序在需要服务时先写命令ID和参数再置位“命令有效”标志C程序收到通知后解析命令、执行业务逻辑通过同步AWS写入方式把结果填回结构体并清除“命令有效”标志PLC程序检测到标志位清除就知道命令被处理完了。这个模式本质上还是C作为ADS Client但在业务语义上实现了双向服务调用。它的优势十分明显不依赖额外的Server实现不容易出协议层的问题断线重连也只需重新订阅一次。缺点是实时性受限于变化通知的触发条件但对于大多数视觉检测、Mes上报、参数下发场景延迟完全够用。我在三个产线项目里用了这个方案运行稳定性比直接做ADS Server要强很多。5.3 方案二TcAdsDll的Server端API与真实ADS服务端如果你确实需要C进程绑定一个ADS端口让远程PLC像访问一个独立ADS设备一样直接读写C内存那需要启用TcAdsDll里的Server端API。这套API以AdsSrv为前缀功能包括设置本地AMS地址、注册设备、管理连接等。说实话这套API文档较少用起来不如Client端顺手我平时不太推荐。原因是它需要你对ADS内部机制有很深理解而且授权、路由表和防火墙配置都比单纯使用Client方式复杂得多。另一个现实问题是很多工控现场的防火墙策略不允许开放额外端口ADS的48898端口已经暴露再开更多端口会引入安全隐患。如果确实需要强大的ADS Server能力更靠谱的路线是使用倍福官方的TwinCAT Server扩展模块或者自己基于TCP协议实现ADS数据包的解析与响应。后者适合对协议有深入研究需求的场景开发周期较长前期一定要评估清楚。从成本和稳定性角度权衡我个人的选择优先级总是命令信箱模式优先Server端API次之自己实现协议垫底。6. 踩坑实录错误码速查与性能调优6.1 常见ADS错误码与排查思路ADS错误码是long类型十六进制显示为0x9811开头的形式。我把实际项目中遇见过的高频错误整理成了速查表遇到问题可以直接对照。错误码含义排查方向0x98110003Router未启动检查TwinCAT系统服务状态、授权是否过期0x98110006目标端口未找到确认PLC Runtime是否已激活端口号是否填对0x98110008目标无响应/超时路由表配置、网线连接、防火墙是否拦截48898端口0x98110019无效参数检查调用参数长度、句柄是否为0、变量数据长度是否匹配0x9811001B符号不存在变量路径写错确认是GVL.xxx还是PRG.xxx0x98110020目标设备拒绝权限不足、授权限制、变量属性为只读0x98110032内存不足或长度超限确认接收缓冲区大小、结构体长度配置最坑的一种情况是错误码是0但数据就是不对。比如读出来的数值明显错误或者结构体字段错位。这种问题十有八九是结构体布局不一致我建议先在PLC的Online监控里确认变量值再用C读一次对比两边数据。如果只是个别字段错位几乎可以断定是对齐或字节序问题。6.2 性能调优从数据结构到CPU调度ADS通信性能优化数据结构设计排第一。能打包成一个结构体的数据绝不要拆成多个变量单独访问。一次ADS请求传输几百字节的开销远低于十次请求每次传几十字节的开销。通信频率越高这个差距越明显。PLC侧和C侧的结构体定义要保持一致。推荐使用固定长度的整型、浮点和布尔类型避免使用STRING类型在不同编译器下造成的长度差异。如果PLC侧引用了标准库里的数据类型C侧也要用对应的类型映射。拿不准时全部用定长字符数组或显示字节填充。CPU调度方面处理ADS通知的业务线程可以适当提高优先级但不要绑定到TwinCAT正在使用的实时核上。实时核是TwinCAT RT Extension独占的资源你的线程绑上去会跟实时任务抢CPU严重时会导致PLC任务周期抖动甚至看门狗触发。在双核机器上我一般用SetThreadPriority把业务处理线程设为THREAD_PRIORITY_ABOVE_NORMAL再观察CPU占用效果不错。通知周期也要审慎设置。周期通知模式下nCycleTime设为1ms意味着每秒会有一千次通知这个频率已经非常高了。普通的数据采集场景10ms到50ms的周期完全够用没必要追求极致。变化通知模式下nMaxDelay用来限流我会根据业务容忍度设置合理值避免高频变量把通信链路占满。6.3 工程化经验断线重连、日志与版本兼容产线上运行的上位机程序断线重连是刚需。PLC重启、网线松动、TwinCAT配置重新激活都会导致ADS连接断开。我的做法是在程序里维护一个连接状态标志业务线程定期发送一次AdsSyncReadDeviceInfoReq做健康检查检测到错误码后主动删除所有通知、关闭端口、重新打开端口并重新订阅变量。重连时必须重新获取变量句柄旧句柄在实时核重新激活后已经失效。日志记录也是工程化的重要环节。不建议在ADS回调里直接写日志建议只记录统计信息比如每秒收到的通知数量、平均延迟、错误码变化。把详细的变量值变化记录放到业务处理线程去做异步落盘或批量缓冲。这样既保留下故障排查线索又不会拖累通信性能。版本兼容这个坑往往在项目交付后期才暴露。TwinCAT3不同Build版本之间ADS服务的实现细节偶尔会有差异最典型的是Notification参数在不同版本下的默认行为不同。TcAdsDll在使用前建议检查版本号如果现场TwinCAT版本较老尽量使用相同版本的TcAdsDll部署到上位机。我个人在实际操作中的体会是ADS通信代码本身不难难的是对整个通信链路的建模思考。先想清楚哪些数据需要实时推送哪些数据按需读写然后选择对应的通信模式代码自然水到渠成。如果一开始就混用一堆API后期调试会非常痛苦。最后分享一个小技巧调试ADS通信时不要急着写业务代码先用TwinCAT自带的System Manager在线监控验证变量名和值的正确性再用C写一个最小程序做连接测试链路通了你再往上盖业务逻辑这样能省掉大量排查时间。这个习惯我一直保持到现在也算是我交给新人的一个“传家宝”。
返回列表