☰
HslCommunication 11.3.2在.NET 4.5下读写三菱与西门子PLC指南
2026/10/14 4:07:20 网站建设 项目流程

简介:HslCommunication 是基于 .NET Framework 4.5 的工业自动化通信库,版本号为 11.3.2,可应用于机械设备、生产线的上位机与 PLC 之间的数据采集和控制。该资源包已内置授权信息,使用者在获得压缩包后,只需解压并提取 HslCommunication.dll 并引用到工程中,就能直接调用功能,无需再手动设置激活码,也不受二十四小时试用期的限制。整个压缩包体积约为 5.69MB,共包含 10 个文件,其中有核心的动态链接库、用于快速验证功能的演示程序、描述接口用法的可扩展标记语言文档、备份文件与文本说明,以及所依赖的第三方组件,可帮助开发者在离线环境下顺利编译和运行。当前已有 3812 人浏览学习,特别适合正在评估该通信框架或需要快速完成工业设备对接项目的软件工程师,借助演示程序和接口文档能够大幅缩短前期调研和联调时间,并可直接基于免激活版本进行二次开发与部署。

1. HslCommunication 11.3.2 和 .NET 4.5:这是老上位机项目的起点

如果你的工控机还在用 .NET Framework 4.5,上位机程序又是早就没人敢动的老工程,HslCommunication 11.3.2 会是你接触到的第一个“能用、能改、能跑”的通讯库选择。它的定位很直接:给上位机提供与 PLC 通讯的能力,三菱、西门子、Modbus 这类常见设备协议都覆盖,而 11.3.2 这个版本另一个关键属性是目标框架正好落在 .NET 4.5 上,意味着老项目不用升框架就能直接引用。这篇文章就是围绕这个组合展开的:版本怎么选、DLL 怎么引、最小读写代码怎么写、现场连不上设备时先查哪里。维护老系统的工程师、刚接手设备通讯的新人,都能按着步骤落地,少走几趟弯路。

2. 为什么 11.3.2 常配 .NET 4.5:版本选型与 DLL 最小引入流程

2.1 老框架项目还守着 .NET 4.5,通讯库该怎么选

常见的上位机项目有两种处境:一种是新项目,从 .NET 6 或 .NET 8 起步,通讯库随便选;另一种是产线上跑了好几年的老程序,基于 .NET Framework 4.5/4.6 写的,改框架的风险远大于改代码本身。后一种处境下,通讯库的版本选型就变得非常敏感。新版通讯库普遍往 .NET Standard 2.0 和 .NET Core 迁移,拿一个高版本 DLL 强行塞进 .NET 4.5 工程,经常碰到“加载不了 System.Runtime”或某些 API 缺失的问题,编译能过,运行起来才报错,极其折磨。

11.3.2 这个版本在社区和现场里被反复使用,一个重要原因是它的目标框架就是 .NET 4.5,依赖的程序集少,不像新版库还要额外引入一串兼容包。实际使用中你会发现,引用这个 DLL 后,bin 目录里不需要铺一整套第三方依赖,通讯核心功能都在一个文件里。从工程维护角度看,这也是巨大的便利:后期把整个项目从一台工控机复制到另一台,只要 DLL 跟着 exe 走,通讯功能基本不会因环境差异而翻车。

选型时我一般会做一张对比表,帮自己和同事快速决策:

对比项11.3.2(.NET 4.5 目标)新版本(.NET Standard / .NET Core 目标)
.NET 4.5 老工程引用直接引用,依赖简单可能出现 API 缺失,需验证
三菱 MC、西门子 S7、Modbus 常规读写完全覆盖覆盖更全,还带更多新协议
现场离线部署单个 DLL 拷贝即可可能依赖 NuGet 包运行库
长期维护版本不再更新,但足够稳定持续迭代,新设备支持更好
适合场景老项目改造、无外网工控机新项目、跨平台、新设备接入

结论很明确:如果你的项目框架已经定在 .NET 4.5,不要冒险用新版本,直接用 11.3.2 起步是最稳妥的。这个判断基于一个事实——现场设备通讯最怕的不是功能少,而是在你改完代码之后程序启动不了。

2.2 用本地 DLL 把 HslCommunication 11.3.2 引进来

引入方式我强烈建议走“本地 DLL”而不是 NuGet。原因有二:现场工控机基本没有外网,NuGet 还原依赖经常失败;生产环境不允许为了一个包去升级整条工具链。本地 DLL 的方式在任何离线机器上都能复制。

第一步,把 HslCommunication.dll 放到项目根目录的 libs 文件夹里,保持文件纯净,不要和源码混在一起。第二步,在 Visual Studio 中右键项目,选择“添加引用 → 浏览”,定位到该 DLL,勾选确认。第三步,在引用列表中选中它,把“复制本地”属性设为 True,这样每次生成都会自动输出到 bin 目录,部署时直接拷 bin 就行。

引入之后,最直观的验证方式是在代码里把命名空间用起来。注意命名空间分两级,根命名空间是 HslCommunication,三菱和西门子各自的协议类分别放在不同的子命名空间下。下面这段是最小自检代码,不读真实设备数据,只验证程序集加载和命名空间解析是否正常:

using System; using HslCommunication; using HslCommunication.Profinet.Siemens; class Program { static void Main(string[] args) { // 实例化一个西门子 S7-1200 连接对象,IP 先填实验室里的 PLC var siemens = new SiemensS7Net(SiemensPLCS.S1200, "192.168.0.10"); // 连接超时设成 3 秒,避免设备不在线时干等很久 siemens.ConnectTimeOut = 3000; // 尝试读取 DB1 的第一个字(DBW0),这步会触发真正的网络请求 var result = siemens.ReadInt16("DB1.DBW0"); if (result.IsSuccess) { Console.WriteLine("当前值:" + result.Content); } else { Console.WriteLine("连接或读取失败:" + result.Message); } } }

这段代码的逻辑很直白:创建对象、设超时、读一个地址、判断结果。关键在result.IsSuccess,这是 HslCommunication 统一的返回约定,所有读写方法都返回OperateResult<T>类型,判断成功后再取Content,不要直接访问读取结果,否则设备不在线时会拿一个无效值去用,问题很难查。参数方面,SiemensPLCS.S1200告诉库你要连的是哪种机型,不同机型协议细节不同;"192.168.0.10"是 PLC 的 IP;ConnectTimeOut以毫秒为单位,3000 就是 3 秒,现场调试我习惯先设 2000 到 3000,快速暴露问题。

2.3 先跑通最小连接自检,再谈读写

很多新手拿到库以后第一步就去写读 D100、读 DB1 的完整业务代码,结果连不上,代码又长,根本分不清是地址写错还是网络不通。我自己的习惯是:任何新项目接入这个库,先跑一遍最小自检,只做一件事——连上设备并做一次最简单的读取。自检通过,再往正式项目里搬。

最小自检的好处是能把故障域缩得很小。如果自检失败,要么是环境问题(防火墙、IP 网段、运行库缺失),要么是引用问题(DLL 没复制、版本选错),和业务逻辑完全无关。如果是三菱设备,就把上面的SiemensS7Net换成MelsecMcNet,IP 和端口改掉,同样读一个地址。自检通了,库的接入工作就算完成了一半。

自检代码还有一个隐藏作用:验证目标框架兼容性。如果项目框架伪装成 .NET 4.5,实际却开了“首选 32 位”等特殊配置,自检会直接暴露程序集加载异常,而不是等业务跑到一半才崩。这一步做扎实,后面调三菱和西门子的读写时,你能把大部分精力集中在地址格式和协议差异上,而不是反复怀疑环境。

3. 三菱 PLC 读写:MelsecMcNet 的地址格式与实测命令

3.1 MC 协议地址该怎么写:D、M、X/Y 的关系

三菱 PLC 是以太网 MC 协议最典型的场景,Q 系列、L 系列、iQ-R 系列基本都走这条路。HslCommunication 里对应的类是MelsecMcNet。地址格式上,D 是数据寄存器,M 是中间继电器,X/Y 是输入输出,看起来简单,实际坑都在细节里。

D 地址直接写 "D100" 就是第 100 个寄存器,数据类型对应 16 位;想读 32 位数据,要占两个连续寄存器,比如 "D100" 和 "D101",库里的ReadInt32和ReadFloat会自动处理这个组合,不需要你手动拼两个 16 位再算。M 地址和 D 不一样,它按位寻址,一个 M 地址就是 1 个 bit,读出来是 bool。X/Y 更特殊,它们是八进制地址,也就是说字符串里不会出现数字 8 和 9,比如 "X000" 后面接的是 "X001"、"X002"…… "X007" 之后直接跳到 "X010"。第一次接触的人经常按十进制思维写 "X009",结果读出来的数据完全是错的,或者直接报地址格式异常。

在做点表映射时我一般按这张表格来对照:

软元件地址示例数据宽度备注
D 数据寄存器"D100"16 位32 位读数自动占用 D100+D101
M 中间继电器"M100"1 位位读写,用 ReadBool/Write
X 输入"X000"1 位八进制地址,无 8/9
Y 输出"Y000"1 位八进制地址,无 8/9
R 文件寄存器"R100"16 位部分 L 系列使用,需确认机型支持

还有一个容易出错的地方:FX 系列。FX 5U 走的是 SLMP 协议,FX 3U 等老机型走 FX 专用协议,跟 Q/L 系列的 MC 协议字段定义不同。不能拿着MelsecMcNet去连所有三菱 PLC,要先确认设备到底支持哪种以太网协议。最常见的做法是看设备手册里的“以太网通信”章节,找到协议名称再决定类。

3.2 最小读写代码:读 D100、写 D110

演示代码我用最常用的三菱 Q 系列作为假设设备,IP 是 192.168.3.10,端口 6000。这个端口是 MC 协议常用的,但也见过设备被改过,所以最好和电气确认。

using System; using HslCommunication.Profinet.Melsec; class Program { static void Main(string[] args) { // 三菱 MC 协议,IP 和端口按现场实际修改 var plc = new MelsecMcNet("192.168.3.10", 6000); // 超时设 5 秒,现场偶尔会有响应慢的情况 plc.ConnectTimeOut = 5000; // 读 D100,一个 16 位寄存器,返回值是 short var read = plc.ReadInt16("D100"); if (read.IsSuccess) { Console.WriteLine("D100 = " + read.Content); } else { Console.WriteLine("读取失败:" + read.Message); } // 写 D110,把 1234 这个值写入寄存器 var write = plc.Write("D110", (short)1234); if (!write.IsSuccess) { Console.WriteLine("写入失败:" + write.Message); } } }

这段代码里有几个关键点。第一,ReadInt16返回的是OperateResult<short>,IsSuccess为 true 时才去拿Content。第二,写入方法Write的第二个参数我强制转成了short,因为 D 寄存器是 16 位的,如果你直接传一个 int 类型的变量,重载解析会出问题,编译不过。第三,读写分离是刻意为之——读失败不影响后面尝试写,这样现场排查时可以快速定位是上行还是下行链路有问题。

关于超时参数,我需要多说一句。ConnectTimeOut是连接阶段的超时,不是每次读写的超时。实际读写等待时间由库内部的会话超时决定,现场如果发现某台设备偶尔超时,优先检查的是网络丢包和 PLC 扫描周期,而不是调大ConnectTimeOut。这个参数调得再大也解决不了读写超时,误判很容易让人浪费时间。

3.3 没有真实 PLC 时的联调与验证思路

现场项目经常遇到“上位机做好了,PLC 还没到场”的情况。这时候不建议直接写死逻辑等设备到位,而是想办法验证通讯代码本身。常见做法是:如果项目里同时有 Modbus TCP 设备,可以先用一份模拟从站工具把代码调通;如果没有,就只能靠抓包和回环测试做基础验证。

抓包的思路是这样:电脑上启动一个临时 TCP Server 监听 6000 端口,把MelsecMcNet的 IP 指向本机 127.0.0.1,运行读 D100 的代码。如果代码发出请求,监听端口就能收到一帧 MC 协议的报文,看到报文头的格式、地址编码方式,至少证明代码流程是完整的。没有抓包工具的话,也可以用简单的 TCP 转发脚本把报文打出来看。这一步的价值在于,它能区分“代码写错了”和“设备没到位”两种状态。

必须提醒的是,模拟验证只能证明通讯链路通了,不等于协议完全没问题。三菱 MC 协议的帧格式在不同型号之间仍有细微差别,尤其是 FX 系列和 Q 系列,最终必须拿着同一套代码去真实设备上复测一遍。我的习惯是把模拟验证当成“编译检查”,真实设备联调才是验收。

4. 西门子 S7 通讯:SiemensS7Net 的 DB 块读写与配置核对

4.1 S7 协议与 MC 完全不是一回事:先分清三件事

西门子的 S7 协议和三菱的 MC 协议差别非常大,从地址格式上就能看出来。三菱是 "D100",西门子是 "DB1.DBX0.0" 或 "DB1.DBD0" 这种带 DB 块编号的结构。DB 块是数据块,里面按偏移地址组织数据,读哪个数据必须知道它在哪个 DB 块、从哪个字节开始、是什么数据类型,三者缺一不可。

SiemensS7Net类允许你在实例化时指定机型,这个参数影响报文结构和参数配置。S7-300 和 S7-1200/1500 之间有几个关键差异,我用表格整理出来:

对比项S7-300 / S7-400S7-1200 / S7-1500
常见 Rack / SlotRack=0,Slot=2Rack=0,Slot=1
远程 PUT/GET 访问默认支持,一般无需改动需在组态软件中显式开启
DB 块访问非优化访问,直接可读默认优化访问,需要改设置
常见端口102102

第一件事是确认机型和槽号。很多人 S7-300 的程序用到 1200 上,通讯持续超时,其实就是 Rack 和 Slot 参数不对。第二件事是确认 PLC 侧是否允许远程访问,1200/1500 默认不开这个口。第三件事是 DB 块是否优化,这个下面单独展开。

4.2 最小读写代码:读 DB1 里的 Float 和 Word

下面这段代码以 S7-1200 为例,IP 192.168.0.10,读写 DB1 里的数据。地址规划是:DB1.DBW0 是一个 16 位 Word,DB1.DBD2 是一个 32 位浮点数。

using System; using HslCommunication.Profinet.Siemens; class Program { static void Main(string[] args) { // 机型选 S1200,IP 改为现场 PLC 地址 var s7 = new SiemensS7Net(SiemensPLCS.S1200, "192.168.0.10"); // 连接超时 3 秒 s7.ConnectTimeOut = 3000; // 读取 DB1 偏移 0 开始的 16 位整数 var wordValue = s7.ReadInt16("DB1.DBW0"); if (wordValue.IsSuccess) { Console.WriteLine("DB1.DBW0 = " + wordValue.Content); } // 读取 DB1 偏移 2 开始的 32 位浮点数 var floatValue = s7.ReadFloat("DB1.DBD2"); if (floatValue.IsSuccess) { Console.WriteLine("DB1.DBD2 = " + floatValue.Content); } // 写入 DB1.DBW4,一个 short 值 var write = s7.Write("DB1.DBW4", (short)100); if (!write.IsSuccess) { Console.WriteLine("写入失败:" + write.Message); } } }

重点说明几个容易误解的地址规则。DB1.DBW0里的 W 是 Word,表示从这个偏移读取 16 位数据;DB1.DBD2里的 D 是 Double Word,读取 32 位。偏移单位是字节,所以 DBD2 是从第 2 个字节开始连续读 4 个字节。如果浮点数占用 4 字节,下一个数据的偏移至少要从 6 开始,不然后面写的地址会盖住前面的数据尾巴。位地址写成DB1.DBX0.0,第一位是字节偏移,第二位是位编号,取值范围 0 到 7。

浮点数的字节顺序也要留意。西门子 PLC 遵循大端序,高位字节在前,HslCommunication 内部已经按 S7 协议做了转换,正常情况下不需要你手动干预。但如果你自己写程序解析原始字节,或者用其他工具抓包对比数据,一定要清楚这个差异,否则读出来的浮点数经常是科学计数法的一大串,明显不符合实际值。

4.3 连得上但读不到数据:重点检查 3 处配置

设备能 Ping 通,连接也建立了,却读不到数据,这是西门子现场最折磨人的情况。我遇到这类问题,基本只查三处配置。

第一处是最容易被忽略的安全设置。S7-1200/1500 在组态软件里默认不开放远程 PUT/GET 通信,需要在“防护与安全”的“连接机制”中勾选“允许来自远程对象的 PUT/GET 通信访问”,编译下载后重启设备,再测试通讯。这句话在项目里经常被电气工程师漏掉,你代码写得再对也没用。

第二处是 DB 块的“优化的块访问”属性。这个属性在 DB 块属性里,默认启用。开启状态下,数据不再通过固定偏移寻址,而是按符号名寻址,外部设备直接按偏移地址读就会失败或读到错误值。解决方法是右键 DB 块,取消勾选“优化的块访问”,然后重新编译下载。这一步同样需要停机操作,提前和产线沟通好。

第三处是槽号和机架的核对。S7-300 常见 Slot=2,S7-1200/1500 常见 Slot=1,但如果你面对的是一套特殊组态,比如机架上有多个 CPU,或者通过扩展机架连接,槽号就得实际查看组态。有个笨办法但很有效:把 Slot 从 1 到 5 逐个试一遍,每次改完重启程序看能否连接成功。虽然粗暴,但在现场没有组态文档时,比干等电气工程师回复快得多。

5. 排查避坑:.NET 4.5 下碰到的 6 个高频问题

5.1 目标框架不对,DLL 引进了,编译报一堆类型不存在

现象:项目引用列表里明明有 HslCommunication.dll,代码里也写了 using,编译却报“未能找到类型或命名空间”,而且错误列表刷屏。

原因:最常见是把 .NET Framework 4.5 的 DLL 强行加进了一个目标框架为 .NET Core 或 .NET 5+ 的项目里。新版项目引用了兼容程序集,但在运行时 API 存在差异,编译期就看不出来,生成了目标框架不匹配的程序集。还有一种情况是项目目标框架写着 4.5,实际安装的 SDK 只支持更高版本,引用解析失败。

解决:右键项目检查“目标框架”,确认是 .NET Framework 4.5 或兼容版本。如果是新式项目格式(SDK Style),在 csproj 里明确写<TargetFramework>net45</TargetFramework>。不要在这个环境下追求跨框架,老项目就按老环境跑,能减少大量编译层面的噪音。

5.2 能 Ping 通却连接超时,防火墙和双网卡在作怪

现象:电脑能 Ping 通 PLC,但程序报连接超时或读取超时,用其他通讯工具也连不上。

原因:Ping 走 ICMP,通讯库走 TCP 端口。Windows 防火墙默认会拦截入站 TCP 连接,ICMP 却常常被放行,所以 Ping 通不代表端口通。另一个常见原因是工控机装了两块网卡,一张连外网、一张连设备网,系统路由表不稳定时,发往 PLC 的报文走了错误的网卡。

解决:先关闭 Windows 防火墙验证一次,如果关了就通,说明要放行 TCP 102 或 TCP 6000 端口,按端口加“入站规则”。双网卡场景则在命令行里用route add把 PLC 网段固定指向设备网卡的网关,然后再跑自检代码。这问题看着玄学,查到最后基本都是路由和防火墙。

5.3 三菱读出来超时,多半是协议类和端口对不上

现象:三菱 PLC,IP 是通的,程序连接也没报错,但一读地址就超时,反复试都一样。

原因:用了MelsecMcNet连一台不支持 QnA 兼容 3E 帧的 FX 机型,或者设备端口实际不是 6000。FX 系列有其专用协议类,而 Q/L 系列走 MC 协议,类选对了端口不对照样连不上。设备的端口号有时被前一个项目改过,不要默认一定是 6000。

解决:先和设备手册对照,确认机型以及对应协议。一般 Q 系列用MelsecMcNet连 6000 端口,FX 5U 用MelsecFx5U,FX 3U 以太网模块用 FX 协议类。拿不准时,把 PLC 侧通讯参数截图拿来,逐一核对接线图和端口配置,不要靠猜。

5.4 西门子 S7-1200 优化 DB 块,读了一手长度错误

现象:连接正常,读 DB1.DBW0 返回成功,但值明显不合理,或者读 DB1.DBD0 报长度错误。

原因:DB 块启用了“优化的块访问”。启用后 DB 块内部布局不完全按偏移排布,你按 DBW0 硬读,拿到的可能是符号名背后的别的数据。这个问题在 S7-1200/1500 系列上尤其常见。

解决:在组态软件里打开 DB 块属性,取消“优化的块访问”,重新编译下载。注意这会导致 DB 块内所有符号名对应的地址重新排布,程序里访问符号名的地方不受影响,但外部通讯的地址表要重新核对。如果产线不能停机,退而求其次,在 PLC 内部程序里用 MOVE 指令把关键数据复制到专门的非优化 DB 区域,再让上位机去读。

5.5 界面线程调 Read,程序直接“假死”

现象:点击窗体上的“读取”按钮,界面卡住不动,标题栏显示“无响应”,过几秒才恢复。

原因:同步方法Read在调用线程上阻塞等待网络响应,而 UI 线程被这个消息循环卡住,窗体自然就冻住了。设备响应越慢,卡顿越明显。很多老工程在 .NET 4.5 上依然用同步写法,遇到设备偶发超时就是一场灾难。

解决:把读写操作移到后台线程,或者调用库的异步方法。最简单的做法是套一层Task.Run,但这还不够,涉及 UI 更新时要小心跨线程访问。稳妥的写法如下:

private async void btnRead_Click(object sender, EventArgs e) { // 用 Task.Run 把同步读取放到线程池,避免阻塞 UI 线程 var result = await Task.Run(() => plc.ReadInt16("D100")); if (result.IsSuccess) { txtValue.Text = result.Content.ToString(); } else { txtValue.Text = "读取失败:" + result.Message; } }

逻辑说明:Task.Run把ReadInt16放到线程池执行,await让 UI 线程在等待期间保持消息响应,用户不会感到界面卡死。参数上没有额外调优,核心是异步化。这个习惯在 .NET 4.5 下尤其重要,因为老工控机性能有限,同步阻塞的杀伤力比新机器大得多。

5.6 现场工控机缺 .NET 4.5 运行库,程序启动即崩

现象:程序在开发机上正常,拷到现场工控机双击运行就报“未处理的异常”或“未能加载文件或程序集”,事件查看器里能看到 .NET 运行时错误。

原因:工控机系统是精简版或长期未更新,缺少 .NET Framework 4.5 运行时。通讯库本身在 DLL 里,但 CLR 跑不起来,一切都白搭。

解决:先在目标机器上装 .NET Framework 4.5 或 4.6 运行库,装完再跑自检程序。如果现场无法联网安装,就在开发机上把安装包拷到 U 盘带过去。装完还报程序集加载失败,用fuslogvw程序集绑定日志查看器看具体加载路径,通常是 HslCommunication.dll 没在 exe 同目录下。这个环境问题的排查顺序必须靠前,否则后面照着代码调半天协议,结果根本不是协议的事。

6. 最后一个建议:把 PLC 连接封装成统一访问层再上线

如果你已经用 11.3.2 把三菱和西门子都调通了,下一步别急着往业务代码里到处写plc.ReadInt16。我见过太多项目,点表地址散落在十几个窗体里,换一台设备型号就全局搜索替换,痛苦至极。正确的做法是在通讯库之上包一层统一的访问接口,业务层只认“设备标签”这个概念。

public static class PlcService { private static SiemensS7Net _s7; private static MelsecMcNet _mc; private static bool _useSiemens = true; public static void Init(bool useSiemens, string ip, int port) { _useSiemens = useSiemens; if (_useSiemens) { _s7 = new SiemensS7Net(SiemensPLCS.S1200, ip); _s7.ConnectTimeOut = 3000; } else { _mc = new MelsecMcNet(ip, port); _mc.ConnectTimeOut = 3000; } } public static OperateResult<short> ReadInt16(string tag) { return _useSiemens ? _s7.ReadInt16(tag) : _mc.ReadInt16(tag); } public static OperateResult WriteTag(string tag, short value) { return _useSiemens ? _s7.Write(tag, value) : _mc.Write(tag, value); } }

这个封装的核心价值是把“设备类型”和“业务数据”彻底解耦。业务层只调用PlcService.ReadInt16("D100")或PlcService.ReadInt16("DB1.DBW0"),至于底层是西门子还是三菱,由初始化时的一次配置决定。换设备时只改Init里的参数,不需要动任何业务代码。超时处理、自动重连、日志记录也都可以收敛在这一层,而不是散落各处。

我自己的习惯是:每次拿到新设备,先花十分钟写一个最小 Demo 跑通读写,确认没问题再往这种访问层里填地址。这个动作看起来慢,实际上能帮你避开上面讲的大部分坑——引用对不对、环境缺不缺、地址格式是否符合设备型号,在最小 Demo 里一眼就能看出来。这个习惯是从一次深刻的现场教训里学来的:当时直接在大项目里改地址,改完下载到工控机,通讯彻底断了,排查了一下午,最后发现只是 X/Y 地址的八进制写错了。自那以后,凡是通讯改动都先过一遍最小 Demo,再进正式工程。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询