两年前有个做设备维护的朋友发我一段源码,标题写的就是"C#读取,写入1200控制西门子V90源代码,博途V13C#源代码VS2013"。他说在网上找了好久才下下来,结果在博途V13里折腾了三天都没跑通。我远程帮他看了半小时,代码本身没什么问题,问题出在几个最容易忽略的PLC侧配置和报文理解上。这套代码在网上流传挺广,但真正能把它用起来的人不多,因为会抄代码不代表懂通讯。
这篇文章我就把这套C#读写S7-1200并控制V90伺服的技术链路完整拆开讲一遍。从S7通讯原理、PLC组态设置、C#源码结构到V90报文控制逻辑,再到联调过程中的真实踩坑记录,一次性说透。不管你是刚接触上位机开发的学生,还是已经在做设备维护想转型的工程师,按这篇文章的思路走一遍,你也能把这套代码跑起来,并且知道每一步为什么要这样做。
1. 这套源码背后的技术链路:C#上位机如何连上S7-1200再指挥V90
1.1 三层架构,先搞清楚数据从哪里走到哪里
在双击Visual Studio里的.sln文件之前,得先把整个系统的通讯拓扑想明白。这套系统里其实有三层设备:
- 最上层是C#上位机,跑在普通电脑上,负责显示数据、下发指令、记录日志
- 中间层是S7-1200 PLC,作为整个系统的控制核心,既接收上位机指令,也执行实际逻辑
- 最底层是西门子V90伺服驱动器,通过PROFINET总线挂在S7-1200下面,驱动电机运转
三层之间用的是两种完全不同的协议。C#和S7-1200之间走的是S7协议,基于TCP/IP,默认端口102。S7-1200和V90之间走的是PROFINET工业以太网协议,PLC周期性给伺服发送控制字和速度给定值,再从伺服读回状态字和实际速度。
很多初学者搞混的一点是:你以为C#直接控制V90,实际上不是。C#只是在跟PLC的存储区打交道——往DB块、M区写值,从DB块、M区读值。PLC再通过自己的扫描周期把DB块里的控制字、速度值转发给V90。也就是说,V90的控制报文被PLC封装在程序里,C#只是那个"幕后推手"。
理解这个三层架构后,你再看网上那些源代码就不会发懵了。C#程序里必然有连接PLC的代码、读写数据块的代码、界面刷新的代码;PLC程序里必然有接收上位机指令的DB块,以及把DB块数据映射到V90报文的逻辑。两者是配合关系,缺一不可。
1.2 选对S7通讯库:Sharp7依然是这类源码的主流选择
这段源码采用的主流通讯库是Sharp7,一个开源免费的S7协议通讯库。为什么不是S7.Net Plus?为什么不是自己用Socket写S7报文?这是有历史原因的。
Sharp7是意大利工程师Davide Nardella开发的,从S7-200到S7-1500全系列支持,底层是纯C#实现,没有额外的运行时依赖,性能相当不错。S7.Net Plus封装更友好,适合快速开发,但它依赖.Net Framework的某些特性,遇到老版本的VS2013反而容易出现兼容问题。而这套源码是围绕VS2013写的,用Sharp7是最稳妥的。
我自己用过这两个库做对比,在同样的PLC上做毫秒级轮询,Sharp7的耗时大约比S7.Net Plus少三分之一左右。但对于学习项目来说,性能差异不是关键,关键是你得理解Sharp7的API设计逻辑。它的命名很直白,S7Client负责连接,DBRead/DBWrite负责数据块读写,ABRead/ABWrite负责I/O区,MBRead/MBWrite负责M区。代码读起来一目了然。
还有一类做法是自己用Socket直接发S7报文,我以前也干过这事。S7协议请求帧的组装是有固定格式的,例如头部8字节的TICKET、PDU参数区、数据区,一不小心字节拼错PLC就给你报错。自己实现的好处是彻底掌握协议,坏处是开发周期长、调试难度大。学习阶段直接用Sharp7跑通流程,等把S7协议的帧结构理解清楚了再去尝试自己封装,是效率最高的路径。
2. 连接PLC前必须做对的四个关键设置
2.1 防护与安全:允许PUT/GET远程访问
这是最经典的"代码没问题但死活连不上"的原因之一。S7-1200从博途V13开始默认是禁止外部设备对它进行无授权读写访问的,即使你IP能ping通,C#程序连上也会被拒绝读取数据。
需要在博途V13的组态界面左侧树形结构里找到设备的"防护与安全",点进去找到"连接机制",勾选"允许来自远程对象的PUT/GET通信访问"。这个选项翻译得有点绕,通俗讲就是"允许电脑端的程序直接读写我的内存区"。
我遇到过不少现场工程师,拿着以前调试S7-300的思路来调试S7-1200,结果S7-300不需要勾这些,S7-1200默认不打开,于是傻眼了。所以拿到这套源码,第一步不是打开VS2013,而是先检查PLC组态里这个勾选有没有打上。
2.2 DB块必须取消"优化的块访问"
这是另一个隐蔽的坑。博途从V13开始,新建的DB块默认都是"优化的块访问",意味着DB块内部地址是编译器自动分配的,外部程序无法按绝对偏移地址(如DB1.DBB0)来访问,只能通过符号名访问。而C#端的Sharp7是按绝对地址来读写的,比如DBRead读取DB1的第0个字节开始的数据,如果DB1开了优化访问,C#根本拿不到数据。
取消优化的路径是:在博途V13里右键点击需要访问的DB块,选择"属性",打开"属性"对话框后,取消勾选"优化的块访问",然后重新编译下载。注意,修改这个属性可能会导致DB块内部的绝对地址重新分配,如果PLC程序里已经有V90报文映射或者MOVE指令依赖这些地址,改完后要仔细检查是否错位。
这个问题排查起来相当费时间,因为PLC程序和C#程序单独看都没毛病,但就是读不到数据。我建议所有新建DB块时就顺手关闭优化访问,尤其是在有上位机参与的项目里,这是行业惯例。
2.3 CPU硬件地址与软件地址的关系
S7-1200在PROFINET网络中的设备名称和IP地址要在博途的设备组态中设置。PLC的IP地址默认为192.168.0.1,如果你的电脑用网线直连PLC,需要给电脑的以太网卡配置一个同网段的静态IP,比如192.168.0.10,子网掩码255.255.255.0,否则物理链路都通不了。
C#代码中连接PLC时要用到的具体参数是"IP地址+机架号+插槽号"。S7-1200固定是机架0、插槽1。这部分很多人会从S7-300的习惯顺过来填机架0插槽2,一填就错。下面这张表可以收藏备用:
| PLC型号 | IP | 机架号Rack | 插槽号Slot |
|---|---|---|---|
| S7-1200 | 192.168.0.1 | 0 | 1 |
| S7-1500 | 192.168.0.1 | 0 | 1 |
| S7-300 | 192.168.0.1 | 0 | 2 |
| S7-400 | 192.168.0.1 | 0 | 3 |
2.4 C#连接代码的写法
Sharp7连接PLC的代码极简,核心就两行:
using Sharp7; S7Client plc = new S7Client(); int result = plc.ConnectTo("192.168.0.1", 0, 1); if (result == 0) { // 连接成功,0表示无错误 } else { // 连接失败,result为错误码 MessageBox.Show("连接失败,错误码:" + result); }ConnectTo的返回值为0表示成功,非0是S7错误码,每个错误码对应的含义在Sharp7文档里都能查到。最常见的几个:1是客户端已连接,2是TCP连接出错,3是对方拒绝连接,7是PDU长度协商失败。如果碰到PDU长度协商失败,多半是PLC版本太老或固件设置问题。
在VS2013的WinForms项目里使用Sharp7,只需要把Sharp7.dll引用到项目里,然后在代码文件顶部用using Sharp7引入命名空间就行。这里的连接动作建议放在窗体Load事件或专门的连接按钮里,避免在UI线程做耗时操作导致界面假死。
3. 读取PLC数据的C#实现:字节序、类型转换和UI刷新
3.1 从DB块读取数据并转换成C#变量
数据读取是这套源代码的核心功能之一。Sharp7最常用的读取方法是DBRead,它把DB块中指定地址、指定长度的原始字节一次性读到C#的byte数组中,然后再通过S7类的静态方法把这些字节转换成int、float、bool等具体类型。
下面是一段典型代码,假设PLC侧DB1中存储了设备的状态和速度数据:
byte[] buffer = new byte[100]; // 缓冲区,大小要根据实际数据长度定义 int dbNumber = 1; // DB块号 int startByte = 0; // 从DB1.DBB0开始读 int size = 100; // 读取100个字节 int result = plc.DBRead(dbNumber, startByte, size, buffer); if (result == 0) { // 从buffer中取DInt(双整数)类型,起始字节偏移0 int actualSpeed = S7.GetDIntAt(buffer, 0); // 从buffer中取Real(浮点数)类型,起始字节偏移4 float temperature = S7.GetRealAt(buffer, 4); // 从buffer中取Bool类型,字节偏移8,第3位(从0开始计数) bool isRunning = S7.GetBitAt(buffer, 8, 3); }代码里的S7.GetDIntAt、S7.GetRealAt、S7.GetBitAt都是Sharp7自带的类型转换工具。GetBitAt接受三个参数:字节数组、字节编号、位编号。位编号从0到7,对应一个字节内部的8个bit。很多初学者看到这个函数不太理解"第3位"是啥意思,其实就是字节0b00000000里从右往左数的第几位。
3.2 西门子大端字节序的陷阱
这是所有C#上位机开发绕不过去的坎。西门子PLC内部数据存储遵循大端字节序(Big Endian),即高字节在前,低字节在后。而C#的BitConverter和直接内存操作是基于小端字节序(Little Endian)的,即低字节在前。
举个例子。PLC中一个WORD类型的值0x1234,在PLC内存里的排列是0x12、0x34。你用C#的直接转换会得到0x3412,完全反了。Sharp7提供的S7.GetIntAt、S7.GetRealAt等方法内部已经做了字节序转换,所以直接用这些方法取数就对了。
但如果你自己拼接 bytes,比如用BitConverter.ToInt32把4字节转成int,就一定要做字节序翻转。我见过不少人在这一步吃亏,读出来的数值要么大得出格,要么完全不合理。
// 错误示例:小端直接转换 int wrongValue = BitConverter.ToInt32(rawBytes, 0); // 结果不对 // 正确示例:先翻转再转换 Array.Reverse(rawBytes, 0, 4); int rightValue = BitConverter.ToInt32(rawBytes, 0);如果你不想记住哪一段要翻转,最简单的规则就是:所有从PLC读出的字节数据,一律通过S7类的静态转换方法来解析;所有要写入PLC的C#数据,一律通过S7类的打包方法来构造。让库帮你处理字节序,不要自己搞。
3.3 定时采集与UI刷新的性能优化
网络热词里有个提问很典型:"C#循环数据采集和UI刷新卡顿"。这个卡顿问题在WinForms里很常见,根源在于很多人把数据采集和UI刷新放在了UI线程里,然后还用了个永真循环或者高频Timer。当PLC响应延迟时,UI线程被卡住,界面就"未响应"了。
正确的做法是数据采集跑在后台线程里,UI刷新通过Invoke或BeginInvoke回到UI线程。简单的实现是使用System.Windows.Forms.Timer做低频刷新,或者用BackgroundWorker。但更推荐的做法是一个后台线程循环采集数据,然后以100~200毫秒的周期通过BeginInvoke把最新数据推送到界面。
private void timer_Read_Tick(object sender, EventArgs e) { timer_Read.Enabled = false; // 防止重入 try { byte[] buffer = new byte[100]; int result = plc.DBRead(1, 0, 100, buffer); if (result == 0) { float speed = S7.GetRealAt(buffer, 0); // 更新界面控件 label_Speed.BeginInvoke(new Action(() => { label_Speed.Text = speed.ToString("0.00"); })); } } catch (Exception ex) { // 记录日志,不要弹窗 } finally { timer_Read.Enabled = true; } }一个实用经验是:读取频率不要超过20Hz,也就是间隔不要小于50毫秒。S7-1200的CPU处理上位机的读写请求是有负载的,高频轮询会显著增加PLC的扫描周期,影响设备控制精度。工业现场做数据监控,100~500毫秒的轮询间隔基本够用。真需要高速数据,就得换方案了,比如用S7-1200的TSEND_C通讯指令主动往上位机推数据。
4. 写入控制与V90报文:从布尔开关到伺服速度给定
4.1 写入DB块数据:先打包再写入
写入比读取容易出错,因为你得先把C#的数据类型转换成PLC能识别的字节数组。Sharp7提供了S7.SetDIntAt、S7.SetRealAt、S7.SetBitAt等一系列打包方法。
// 写入一个BOOL变量到DB1.DBX2.0 byte[] buffer = new byte[10]; S7.SetBitAt(ref buffer, 2, 0, true); int result = plc.DBWrite(1, 0, 10, buffer); // 写入一个INT值(速度给定)到DB1.DBW4 S7.SetIntAt(ref buffer, 4, 1500); // 1500转/分 result = plc.DBWrite(1, 0, 10, buffer);这里最容易犯的错误是写入长度和缓冲区大小不匹配。比如你要写一个16位的INT,但你的buffer定义得太小,SetIntAt写入时越界,底层就会报错。我建议为每个DB块定义足够大的buffer,比如100字节,只修改特定偏移位置的数据,然后整块写回。这个思路虽然会多一些网络传输量,但逻辑简单可靠。
还有一点:不要频繁写入。写入操作比读取更耗PLC的CPU资源,而且每次写入都相当于对PLC存储区做一次修改操作,只应在数据变化时写入,而不是在每个扫描周期里疯狂写相同的数据。
4.2 V90的控制原理:PLC通过PROFINET报文控制电机
S7-1200控制V90伺服通常有两种模式:一种是通过PROFINET总线发送标准报文控制V90,另一种是通过PTO脉冲/模拟量控制。后者是低端方案,前者的动态响应和控制精度更好,也是这套代码采用的方式。
V90的PROFINET控制报文结构里,最核心的是"标准报文1",也叫速度控制报文。它的组成简单说就是:
- 控制字1(STW1):16位,控制伺服启停、使能、急停等
- 速度设定值(NSOLL):16位,以16384对应100%额定转速
- 状态字1(ZSW1):16位,反馈伺服当前状态
- 实际速度(NIST_A):16位,反馈当前实际速度
C#程序通过改变PLC中DB块的STW1和NSOLL,PLC再把这些值通过报文周期性地发送给V90驱动器,从而实现远程控制。反过来,V90反馈的ZSW1和NIST_A通过PLC读入DB块,C#上位机再从DB块读取显示。
4.3 控制字的使能逻辑
V90的控制流程有先后顺序,一个典型的启动过程是这样的:
- 第一步:把控制字设为0x047E。二进制展开是0000 0100 0111 1110,它使"1号位:准备接通"和"2号位:运行使能"等关键开关位动作,相当于让伺服驱动器"上电准备"
- 第二步:等待几毫秒,再把控制字设为0x047F。打开最后的"运行允许"位,这时伺服驱动器使能完成,电机处于待命状态
- 第三步:写速度给定值,比如16384对应100%额定转速
如果用一句话总结:0x047E是"预备"信号,0x047F是"开始运行"信号。很多初学者直接一把梭把控制字改成0x047F,发现电机不动,就是因为没有先经过0x047E这个"预备"状态,驱动器的内部状态机不认。
停止的过程则相反:先把速度给定值设为0,再把控制字从0x047F改回0x047E,最后设0。急停则直接发送0x0000或者对应的急停控制字。
4.4 速度给定值的标准化计算
V90报文里的速度给定值是16位整数,单位是"二进制补码值/16384对应额定转速的100%"。也就是说:
给定值 = (目标转速 / 额定转速) × 16384举个例子,假设V90配的电机额定转速是3000rpm,你想让电机以1500rpm转动,那么:
给定值 = (1500 / 3000) × 16384 = 8192负数代表反转,比如-8192就表示以1500rpm反转。
在C#代码里,这个计算就是一行的事:
short speedSetpoint = (short)((targetSpeed / ratedSpeed) * 16384);注意目标转速和额定转速的单位要一致。这里用short类型是因为16位有符号整数。如果计算出的数值超出short范围(-32768~32767),说明你给的速度超出了允许范围,程序应该做一次限幅处理。
4.5 状态字的解析:判断V90现在到底什么状态
能写控制字只是第一步,一个完整的控制系统一定要会读状态字。V90的ZSW1状态字各个位的含义如下(只看关键位):
| 位 | 含义 | 说明 |
|---|---|---|
| 位0 | 准备接通 | 1表示驱动器主回路已就绪 |
| 位1 | 运行就绪 | 1表示驱动器已准备运行 |
| 位2 | 运行使能 | 1表示电机已使能 |
| 位3 | 故障 | 1表示当前有故障 |
| 位6 | 接通禁止 | 1表示禁止接通 |
| 位7 | 报警 | 1表示有报警但没有跳闸 |
C#端判断方法仍然是S7.GetBitAt:
bool isFault = S7.GetBitAt(statusBuffer, 0, 3); // 故障位 bool isEnabled = S7.GetBitAt(statusBuffer, 0, 2); // 使能位调试时只要把状态字各bit位显示在界面上,就能很直观地看到伺服当前卡在哪一步。我习惯在调试界面上做一排LED状态灯,绿色表示就绪,红色表示故障,比看十六进制数字快得多。
5. 博途V13 + VS2013联调:真实踩坑记录与排查方案
5.1 版本匹配问题
博途V13是西门子TIA Portal比较古老的版本,对应S7-1200的固件版本一般是4.x。如果你手里的PLC固件版本高于V13支持的版本,博途V13打开项目时会直接报错或者无法下载。这个时候只能升级博途版本,或者降级PLC固件——但降固件有风险,一般不建议。
VS2013对应的是.Net Framework 4.5,Sharp7库对这个版本兼容良好。但要注意的是,如果你的电脑装了VS2019或VS2022,双击打开VS2013项目会出现迁移提示,绝大多数情况下直接"不升级"也能正常编译。如果编译报错,优先检查目标框架是否被篡改成4.6以上,改回4.5再编译。
5.2 连不上PLC的几种典型原因
结合我自己的排查经验,C#连接S7-1200失败时,按下面这个顺序排查效率最高:
| 现象 | 原因 | 处理办法 |
|---|---|---|
| ping不通PLC IP | 网线/防火墙/IP配置错误 | 检查本机IP和PLC IP是否同网段,关闭Windows防火墙 |
| ping通但连不上端口102 | PLC未开启PUT/GET | 勾选"允许来自远程对象的PUT/GET通信访问" |
| 连接成功但读不到DB数据 | DB块开了优化访问 | 取消DB属性中的"优化的块访问",重新编译下载 |
| 报PDU长度错误 | 固件/库版本不匹配 | 尝试Sharp7设置PDU长度协商 |
| 偶发断线 | 网线质量差/干扰 | 换工业级别的网线,避免与动力线平行走线 |
有一个细节要特别提醒:Win10/Win11系统默认防火墙会拦截PLC通讯端口102。很多人在工程现场测试时都是先关掉防火墙再调试,但这不是长久之计。正确做法是在防火墙高级设置中添加入站规则,允许TCP端口102通信,给PLC通讯程序放行。
5.3 V90通讯异常:掉使能和周期发送
V90通过PROFINET和S7-1200通讯时,有一个"看门狗"机制:如果PLC没有在设定的时间内周期性发送报文,V90会判定通讯中断,触发故障停机。这造成一个很常见的现场问题:上位机界面上电机明明显示运行中,但实际已经掉使能停了。
这个问题的根源往往是PLC程序里发送报文的OB块(如循环中断OB30)没有被正确调用,或者报文的发送条件被人为地加了一个开关条件。做V90通讯程序时,我的习惯是单独建立一个定时中断OB块(OB30,设定周期为10ms或20ms),在OB30里调用PROFINET报文发送功能块(如MC_Mover或SINA_SPEED),确保报文无条件、周期性地发送。上位机只管改DB块里的数据,不要直接参与报文的发送节奏,否则任何一处UI卡顿都会引起伺服抖动。
5.4 上位机断线重连
C#上位机运行过程中,PLC重启、网线松脱、交换机掉电都会造成连接中断。如果不处理断线,程序会一直卡在读数据的等待中,界面显示旧数据,给操作人员造成严重误导。
一个稳健的上位机程序应该具备两种机制:一是通讯超时机制,二是自动重连机制。
plc.SetConnectionParams("192.168.0.1", 0, 1); // 设置连接参数 plc.SetTimeout(2000); // 超时时间2秒,这个很关键 private void Reconnect() { if (plc != null && plc.Connected == false) { try { int result = plc.Connect(); if (result == 0) { // 重连成功,可以加一个标志位 } } catch (Exception ex) { // 记录日志 } } }重连逻辑放在定时器里,每隔几秒检测一次plc.Connected状态,如果断开了就尝试重新建立连接。另外数据读取方法外层一定要包try-catch,因为S7客户端在极端情况下会抛出异常,不捕获的话整个程序会崩溃。
6. 把源码变成你自己的东西:学习路径与二次开发方向
6.1 拿到源代码后先干什么
网上下载的源代码,不要直接双击运行,先做三件事:
第一,通读一遍代码结构。把Form1.cs(或者主窗体文件)打开,把每个按钮的Click事件、每个Timer的Tick事件都看一遍,心里大概有个地图,知道哪个控件调哪个方法。
第二,把PLC侧的变量表整理出来。在博途V13里打开项目,找到全局DB块,把变量名、偏移地址、数据类型列一张表。这张表就是C#代码和PLC程序之间的"翻译对照表"。没有这张表,你改了C#里的某个偏移量,PLC端根本不知道你要操作哪个变量。
第三,小步验证。用Sharp7自带的一个简单连接测试程序(或者自己写一个只读一个变量的测试窗口),先把数据读通,再逐步扩展到写入和V90控制。一次验证一个点,永远比一次改一堆代码然后不知道错在哪里要好。
6.2 从速度控制扩展到位置控制
这套源码的基础是速度控制模式,也就是你给电机一个速度值,它就一直转。但如果你的应用场景是定位、送料、摆臂这类需要精确停位的设备,速度模式就不够了,必须要用EPOS位置控制模式。
在EPOS模式下,S7-1200通过SINA_POS功能块或MC_MoveAbsolute指令控制V90,报文中除了控制字和状态字,还要有目标位置、速度、加减速时间等参数。从速度模式切换成位置模式,需要修改V90驱动器的P29003参数(报文宏定义),同时PLC侧的报文配置也要随之调整。这是一个比较复杂的升级路径,但学会了就比速度模式值钱得多。
我的建议是:先把速度模式跑熟,理解控制字-状态字的交互逻辑,再研究位置控制。因为底层逻辑是一脉相承的,位置模式无非是在"控制字+给定值"的基础上增加了定位参数。
6.3 从"能跑"到"好用"的架构改造
网上这套源码"能跑",但它演示的是单人开发的小项目逻辑。真正上产线前,你应该至少做三处改造:
第一,把通讯代码和界面代码分离。开一个独立的类库项目放S7通讯逻辑,界面项目只负责显示和交互。这样后续换PLC型号、加数据记录功能,都不会动到界面代码。
第二,增加日志系统。每一帧读写请求、每一次异常、每一次重连都要记录时间和结果。产线上的故障分析,全靠日志。别指望用MessageBox记录,那既影响运行又没法回溯。
第三,增加数据校验。比如在写入关键控制字前做二次确认,接收到速度反馈后判断是否在合理范围内,超出就报警。工业控制里,上位机写了一个错误的数值,轻则报警停机,重则撞机损坏设备。
这块相当重要。我曾经在一个项目里,因为数据绑定错误,UI上的输入框绑定错了字段名,操作员输入一个2000转,结果C#代码把2000当作给定值直接写进去了。V90的实际转速除以一个错误的增益值,电机输出转速直接冲到额定转速外,要不是伺服本身的限幅保护发故障,机械设备就撞了。加一道限幅检查,这类问题可以被提前拦截。
6.4 下一步:可以怎么扩展
当你能熟练读写S7-1200,并且掌握了V90的报文控制逻辑后,就可以往这几个方向扩展:
一是加入配方管理。设备需要频繁切换产品参数时,上位机通过数据块批量写入配方参数,比操作员一个个改数值高效得多,也减少出错概率。
二是接入数据库和MES系统。把PLC采集的产量、报警信息记录到SQL Server或MySQL中,让数据能够追溯和分析。这也是工业4.0和数字化车间的基础。
三是多PLC联动控制。一套上位机同时连接多台S7-1200,通过设备编号区分不同线体。Sharp7支持同一个客户端对象只能连接一个PLC,因此每台PLC需要单独的S7Client实例,程序结构要做成PLC连接管理器。
根据我个人的实际项目体会,C#与西门子PLC通讯的技术门槛不在于语言本身,而在于对PLC数据存储机制和通讯协议的深层次理解。你把这份源代码跑通了,S7协议的原理、报文结构、类型转换这些核心知识就都打通了,后面遇到什么型号的PLC、什么类型的伺服,思路都是相通的,只是换了通讯接口函数而已。