ARTICLE DETAIL

资讯详情

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

台达PLC的RS232通信C#实战:从串口接线到Modbus RTU报文轮询

台达PLC的RS232通信C#实战:从串口接线到Modbus RTU报文轮询 简介这份资源是一套面向PLC及上位机开发者的台达PLC RS232通信测试软件源码基于C#实现主要解决如何快速查看与验证台达PLC输入输出状态的问题。无论刚接触工控通信的新手还是需要二次开发的工程师都可以通过它快速上手串口通信与PLC交互逻辑。压缩包共37个文件包含9个C#源文件、可执行exe、pdb调试文件、工程配置与资源文件等形成完整的项目结构压缩后仅269KB轻量便于部署。该资源已有299人学习体量虽小但结构清楚既提供可直接运行的exe用于即时测试也保留了Form1.cs、Program.cs等源码以及配置文件便于阅读串口参数设置、通信报文和界面逻辑适合作为学习PLC串口通信的入门范例或实际项目的移植基础。1. 台达PLC的RS232 C#通信测试先解决三根线再谈报文调试台达PLC时最容易被忽略的不是波特率而是对方型号的通信模式。给台达DVP系列接上RS232线用串口助手发01 03 00 00 00 02 C4 0B十次有九次等不到应答。换线、换USB转串口、换软件都没用最后发现是站号不对或者是PLC的COM2口被设置成了Modbus ASCII模式。RS232通信测试软件的核心不是把数据发出去而是把台达PLC侧的实际参数、报文格式、校验方式和C#代码逐一对应上。下面这套源码骨架按串口参数、Modbus帧构造、CRC校验、轮询和日志几个模块拆开直接改端口号和寄存器地址就能用在真机上。2. 台达PLC的RS232接线与通信参数选择2.1 RS232交叉接法才是第一道关卡台达DVP系列PLC的COM2口通常引出DB9公头或者通过编程电缆转成DB9。PC侧一般是USB转RS232线驱动装好后映射成COM3、COM4这类端口。RS232是单端电平两机通信必须交叉PLC侧TXD接PC侧RXDPLC侧RXD接PC侧TXD地线接GND。很多现场用的所谓“RS232直通线”其实已经是交叉线再接一个交叉头就会把收发对调表现出来的故障是发送方能看到数据接收方永远收不到完整帧。不建议只看接头外形判断用万用表蜂鸣档量一下两端针脚更可靠。将串口头两端分别量TXD、RXD、GND三根线的通断关系确认是2-3交叉、3-2交叉、5-5直连再插到设备上。USB转RS232线靠近PC端是公头还是母头也会影响接线稳妥做法是先接一个串口延长头再把PLC侧线缆插上避免反复拔插PLC端子。提示RS232线长超过15米或经过变频器柜时优先加磁环并让信号线远离动力线。乱码不一定来自波特率先怀疑线缆屏蔽。2.2 台达PLC的RS232通信参数怎么定最稳台达大多数DVP系列支持标准Modbus RTU和Modbus ASCII。在C#上位机里推荐用RTU原因很直接RTU用CRC16校验ASCII用LRC校验且每字节拆成两个ASCII字符帧长翻倍测试工具处理起来更麻烦。参数表如下参数项测试工具推荐值说明波特率9600对短距离调试足够抗干扰比115200好产线大量轮询时可改为38400或更高数据位8Modbus RTU固定8位ASCII才需要配合7位停止位1常用默认不需要设2位校验位NoneRTU本身带CRC帧错误能检出如PLC侧强制Even两边必须一致站号1台达PLC站号通过工程参数或特殊寄存器设置常见默认是1超时1000ms测试阶段给1秒足够连续轮询时建议降到200ms注意台达部分机型存在“用COM2跑内部专用协议”的设置一旦打开标准Modbus帧会被忽略。测试前应进入PLC编程软件确认COM2协议选的是MODBUS RTU而不是LINK或出厂默认的专用格式。站号也和PC机不同不是自动识别的C#发出去的地址必须和PLC侧一致。2.3 在PLC侧确认通信配置的落地步骤用台达编程软件打开PLC工程找到串行口设置页面选择COM2。将通信协议设为MODBUS RTU波特率设为9600数据格式设为8-N-1。站号设为1和测试工具发送地址保持一致。写回PLC后重新上电让通信参数重新加载。不经过编程软件直接发Modbus帧只能做被动测试改不了PLC侧参数。修改通信参数后没有重新上电是调试串口通信最容易踩的坑表现为参数明明保存了却始终不响应。上电后先观察PLC上的通信指示灯有数据收发时会闪烁完全不闪大概率是RS232线没通。3. C#串口库SerialPort的封装与线程安全3.1 打开台达PLC串口的最小稳定代码C#里操作RS232最直接的是System.IO.Ports.SerialPort。测试工具不需要引入第三方组件.NET Framework 4.x或者.NET 6都可以跑通。打开串口的代码要处理两个问题端口已经打开、端口被其他程序占用。下面这段封装可以直接放到窗体类里using System; using System.IO.Ports; using System.Windows.Forms; public class PlcSerial { private SerialPort _sp; public bool Open(string portName, int baudRate) { if (_sp ! null _sp.IsOpen) Close(); _sp new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One) { ReadTimeout 1000, WriteTimeout 1000 }; try { _sp.Open(); return _sp.IsOpen; } catch (UnauthorizedAccessException) { MessageBox.Show(串口被占用请关闭其他串口助手); return false; } catch (IOException ex) { MessageBox.Show(串口不存在或已被拔出 ex.Message); return false; } } public void Close() { if (_sp null) return; if (_sp.IsOpen) _sp.Close(); _sp.Dispose(); _sp null; } }ReadTimeout和WriteTimeout分别控制读写等待时间测试阶段设为1000ms避免PLC无响应时界面卡死。UnauthorizedAccessException代表串口被串口助手或组态软件占用IOException则多出现在USB转串口线拔掉或驱动异常时。不要把Open()包在using里因为上位机工具要长时间保持端口打开。3.2 DataReceived事件与轮询线程的取舍很多人会直接在SerialPort.DataReceived事件里处理应答这个做法在纯手动发送场景没问题。一旦做连续轮询PLC每帧应答到达时间不固定事件触发和下一次请求发送会竞争同一个串口容易出现半包。常见做法是放弃事件驱动改成专用轮询线程线程里先发请求再同步读应答读完后再发下一帧整个收发过程串行化。如果非要用DataReceived也要注意事件回调运行在线程池线程不能直接修改TextBox和DataGridView需要BeginInvoke切换到UI线程。测试工具是给人看的日志和界面更新频率控制在每100ms一次就够了不需要在每次应答里刷新所有控件。3.3 关闭串口时最容易崩溃的隐藏问题关闭串口时有一种很隐蔽的异常轮询线程还在Read()里阻塞UI线程调用Close()线程会抛出InvalidOperationException或ObjectDisposedException。正确顺序是先取消轮询再关闭串口。可以用一个bool标志位让轮询循环退出或者用CancellationToken。调试时为了让工具更健壮可以在轮询方法里catch掉InvalidOperationException避免偶发崩溃try { _sp.Write(frame, 0, frame.Length); _sp.Read(buffer, 0, buffer.Length); } catch (InvalidOperationException) { // 串口已关闭轮询线程直接退出即可 }这段代码让测试工具在关闭串口时不会弹异常。注意catch里什么都不做是为了让线程干净退出如果后续还要上报状态可以设置一个枚举字段标记通信状态。4. C#构造台达PLC报文CRC校验与解析4.1 台达PLC的RS232串口协议报文解析一帧Modbus RTU在C#里发Modbus RTU帧前先明确RTU帧结构。读D区寄存器最常见的就是功能码03读取台达D0开始的连续寄存器。帧格式如下字段长度示例值说明站号1字节0x01对应PLC侧COM2站号功能码1字节0x0303是读保持寄存器起始地址2字节0x0000寄存器地址高字节在前寄存器数量2字节0x0002一次最多建议125个CRC162字节0xC40B低字节在前发送一个完整的读请求是01 03 00 00 00 02 C4 0B。很多网友问为什么不是0B C4这是Modbus RTU的固化规则CRC先传低字节再传高字节。字节序错了台达PLC会丢弃整帧。4.2 CRC16-Modbus逐位计算实现CRC16-Modbus使用多项式x^16 x^15 x^2 1的反射形式0xA001初始值为0xFFFF。逐位计算比查表法代码更短学习阶段方便验证实测在串口速率下性能差可以忽略。C#实现如下static byte[] GetCrc(byte[] data) { ushort crc 0xFFFF; foreach (byte b in data) { crc ^ b; for (int i 0; i 8; i) { if ((crc 0x0001) ! 0) crc (ushort)((crc 1) ^ 0xA001); else crc 1; } } return new byte[] { (byte)(crc 0xFF), (byte)(crc 8) }; }返回值就是按Modbus顺序排好的低字节和高字节。需要注意crc 1的结果默认是int类型乘位运算必须强转回ushort否则连续移动8位后高位数据可能被截断。计算CRC时只计算除CRC后的所有字节把CRC追加到帧尾即可static byte[] BuildFrame(byte station, byte func, ushort addr, ushort count) { byte[] body { station, func, (byte)(addr 8), (byte)(addr 0xFF), (byte)(count 8), (byte)(count 0xFF) }; Listbyte frame new Listbyte(body); frame.AddRange(GetCrc(body)); return frame.ToArray(); }addr 8取高8位addr 0xFF取低8位完全按Modbus高位在前的顺序。寄存器地址从0x0000铺开对应台达D0寄存器0x0001对应D1依次类推。功能码03读的是保持寄存器台达D区一般映射到这里。4.3 报文应答解析与异常码处理PLC正常应答格式是站号 功能码 字节数 数据 CRC。例如读2个保持寄存器成功应答可能是01 03 04 12 34 AB CD 校验其中04表示后面跟随4个字节数据。解析时先校验CRC再按两个字节合成一个寄存器值static Listushort ParseHoldRegisters(byte[] resp) { if (resp.Length 5) return new Listushort(); int dataLen resp[2]; var values new Listushort(); for (int i 3; i 3 dataLen; i 2) { values.Add((ushort)((resp[i] 8) | resp[i 1])); } return values; }如果响应长度只有3个字节表示PLC返回了异常帧比如01 83 02。其中83是功能码03加上最高位0x80得到的02是异常码。C#工具应该把异常码单独显示而不是直接当超时处理。常见异常码含义如下异常码含义调试方向0x01非法功能码当前站不支持该功能码0x02非法数据地址寄存器地址超出范围0x03非法数据值写入值超限或数量为00x04从站设备故障PLC内部通信处理器未就绪0x06从站忙重新发送上一帧台达部分旧型号对超出范围的寄存器地址会静默丢弃表现为完全无响应而不是返回异常码这在测试时要区分无响应优先查站号和地址范围返回异常码则说明通路已经打通。5. 台达PLC通信测试软件源码的轮询、界面与日志5.1 测试工具界面布局与控件规划一套可运行的上位机测试界面至少要包含四个区域串口参数区、报文显示区、连续轮询区和日志区。不要试图把所有参数塞到一个窗体里测试工具的定位是快速验证界面越直接越好。控件规划可以参考下表控件类型控件名用途ComboBoxcmbPort枚举本机COM口ComboBoxcmbBaud波特率选择ButtonbtnOpen打开/关闭串口NumericUpDownnudStationPLC站号NumericUpDownnudRegAddress起始寄存器地址NumericUpDownnudCount读取寄存器个数ButtonbtnReadOnce手动读取一次ButtonbtnStartPolling启动或停止连续轮询DataGridViewdgvValues显示每个寄存器的地址和值TextBoxtxtLog显示收发十六进制报文枚举串口用SerialPort.GetPortNames()把返回的字符串数组填充到cmbPort中。注意拔掉USB转串口后再插入端口号可能变化每次打开串口前都重新刷新一次列表。5.2 连续轮询的完整代码骨架连续轮询不要在Button点击事件里用while(true)那样UI线程会被阻塞。简单做法是用Task.Run在线程池执行用CancellationToken退出。下面的轮询逻辑包含发帧、读响应、解析、更新界面能直接放在源码主体里private async void btnStartPolling_Click(object sender, EventArgs e) { if (_pollTask ! null !_pollTask.IsCompleted) return; var cts new CancellationTokenSource(); _pollTask Task.Run(() PollLoop(cts.Token)); } private void PollLoop(CancellationToken ct) { byte station (byte)nudStation.Value; ushort regAddr (ushort)nudRegAddress.Value; ushort count (ushort)nudCount.Value; var frame BuildFrame(station, 0x03, regAddr, count); while (!ct.IsCancellationRequested) { try { byte[] resp RequestResponse(frame); Listushort values ParseHoldRegisters(resp); BeginInvoke(new ActionListushort(UpdateGrid), values); } catch (TimeoutException) { BeginInvoke(new Actionstring(AppendLog), 读取超时); } ct.WaitHandle.WaitOne(200); } }RequestResponse内部先写请求帧再根据应答头读取完整响应。这里给每条请求留200ms周期适合9600波特率下的常规调试。如果PLC现场有多个从站且每个站响应时间不同轮询间隔应通过界面暴露给用户调节不要写死在代码里。用BeginInvoke更新DataGridView和TextBox比直接调用线程安全的第三方控件更可控。提示Count参数在C#的NumericUpDown里是int类型转成ushort时要检查是否超过125超过要提示用户拆成多次读取。一次读125个寄存器长度491字节已经接近RS232在9600波特率下的极限稳定帧长。5.3 报文日志记录把每次收发写进文件日志是排查RS232乱码和丢帧的抓手。测试软件里最常见的是把发送和收到的原始十六进制追加到一个文本文件加时间戳。实现如下private void AppendLog(string direction, byte[] data) { string hex BitConverter.ToString(data); string line ${DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {direction}: {hex}; File.AppendAllText(plc_comm.log, line Environment.NewLine, Encoding.ASCII); BeginInvoke(new Actionstring(s txtLog.AppendText(s Environment.NewLine)), line); }写文件时用Encoding.ASCII而不是默认UTF8可以避免换行符被多写字节。BitConverter.ToString会把字节数组转成01-03-00-02这种格式和Modbus调试助手看到的十六进制格式一致复制到串口助手里可以直接对比。每次发送和接收都调用AppendLog一帧请求配一帧响应日志行数的对应关系就一目了然。如果测试时间很长建议对日志文件做大小限制比如超过5MB就重命名成plc_comm_old.log再新建新文件避免单个文件过大导致写入越来越慢。6. 真机联调中的RS232乱码与报文解析排错技巧6.1 先用回环测试排除串口线和驱动问题接上台达PLC之前先把RS232线对侧的TXD和RXD短接在C#工具里发送一帧AA 55正常能原样收到AA 55。收不到说明串口驱动、COM口号或线缆方向有问题和PLC无关。如果收到的是乱码比如3F 3F大多是波特率不匹配或USB转串口芯片异常。这一步可以把排查范围缩小一半不要跳过。6.2 一表对照RS232乱码、超时和异常响应联调时的常见现象和处理方向集中在下面这张表里现象最可能原因排查动作完全无响应站号不匹配、PLC侧协议不是Modbus RTU用编程软件确认COM2配置乱码波特率、数据位、校验位不一致把测试工具和PLC侧参数逐项对比偶发超时RS232线未屏蔽、线太长缩短线缆间距或换带屏蔽层串口线响应CRC错误帧头粘包、USB串口缓存断帧每次请求前清空接收缓冲区返回非法数据地址寄存器地址超出该系列PLC范围换一个已知存在的D区再试其中“清空接收缓冲区”在C#里先调用_sp.DiscardInBuffer()再发送请求能解决不少USB转串口带来的残留数据问题。注意发送应答之间的间隔要大于RTU要求的静默时间通常软件里加10ms到20ms的Thread.Sleep就足够。6.3 连续轮询时的周期和寄存器数量调优台达PLC对单帧读取数量有限制一次读125个寄存器是最稳妥的上限。数量超过上限时PLC会拒绝处理或者只返回一部分数据。调试工具里如果一次要读几百个寄存器正确的做法是分成多个帧每帧读128个地址范围以内。轮询周期从200ms起步先确认每帧都稳定应答再慢慢降到20ms观察是否有丢帧。当发现丢帧时不要只调波特率先看日志里的时间戳间隔如果两次请求间隔小于PLC实际响应时间说明上位机发送频率超过了从站处理能力。把响应时间和周期加在一起才是合理的轮询间隔。本文还有配套的精品资源点击获取
返回列表