第一次用C#通过串口控制松下PLC时,我盯着屏幕上的通讯超时错误整整两小时。后来才发现是波特率设成了19200,而PLC默认是9600。这种看似简单的参数匹配问题,恰恰是工业通信中最容易踩的坑。本文将分享如何用C#的SerialPort类构建稳定的PLC通信系统,涵盖从硬件接线到协议解析的全流程。
松下FP-X系列PLC在小型自动化设备中应用广泛,其串口通信采用标准的Modbus-RTU协议。与西门子、三菱等品牌不同,松下PLC的通信帧格式有特殊要求——数据区需要反转字节顺序。这意味着即使你熟悉Modbus协议,直接套用常规代码也会导致通信失败。
推荐使用USB转RS232转换器(建议选用FTDI芯片的可靠型号),通过SC09编程电缆连接PLC的编程口。实测中,某宝30元的CH340芯片转换器在连续工作时会出现数据丢包,而FTDI芯片的转换器稳定性更好。接线时注意:
重要提示:带电插拔串口线可能损坏PLC通信芯片,务必在断电状态下操作
安装Visual Studio时需勾选.NET桌面开发工作负载,特别注意添加NuGet包:
bash复制Install-Package NModbus.Serial
这个库封装了Modbus协议处理,比直接操作SerialPort更可靠。新建Windows Forms项目时,建议目标框架选.NET 6+,其串口通信性能较.NET Framework有显著提升。
标准Modbus-RTU请求帧为:
code复制[设备地址][功能码][起始地址Hi][起始地址Lo][数据长度Hi][数据长度Lo][CRC校验]
但松下PLC要求:
例如读取D100-D101的代码实现:
csharp复制byte[] BuildReadRequest(byte slaveId, ushort startAddress, ushort count)
{
var request = new byte[8];
request[0] = slaveId; // 设备地址
request[1] = 0x03; // 功能码
// 地址处理(松下特殊要求)
request[2] = (byte)((startAddress + 1) >> 8);
request[3] = (byte)((startAddress + 1) & 0xFF);
// 数据长度
request[4] = (byte)(count >> 8);
request[5] = (byte)(count & 0xFF);
// CRC计算(需自行实现)
ushort crc = CalcCRC(request, 6);
request[6] = (byte)(crc & 0xFF);
request[7] = (byte)(crc >> 8);
return request;
}
工业现场电磁干扰严重,必须实现通信容错:
csharp复制int retryCount = 0;
while(retryCount < 3)
{
try
{
serialPort.Write(request, 0, request.Length);
Thread.Sleep(50); // 等待响应
int bytesToRead = serialPort.BytesToRead;
if(bytesToRead > 0)
{
byte[] buffer = new byte[bytesToRead];
serialPort.Read(buffer, 0, bytesToRead);
return ValidateResponse(buffer);
}
}
catch(TimeoutException)
{
retryCount++;
Thread.Sleep(100 * retryCount);
}
}
throw new Exception("通信失败");
创建PlcHelper类处理常用操作:
csharp复制public class PanasonicPlcHelper
{
private SerialPort _port;
private byte _slaveId;
public PanasonicPlcHelper(string portName, int baudRate, byte slaveId)
{
_port = new SerialPort(portName, baudRate, Parity.Even, 7, StopBits.One);
_port.Handshake = Handshake.RequestToSend;
_slaveId = slaveId;
}
public bool[] ReadCoils(ushort address, ushort count)
{
// 实现线圈读取(功能码01)
}
public void WriteSingleRegister(ushort address, short value)
{
// 处理字节序转换
byte[] data = BitConverter.GetBytes(value);
Array.Reverse(data); // 松下要求字节交换
var request = new byte[8];
// 构建写入请求帧(功能码06)
}
}
通过后台线程实现周期轮询:
csharp复制private CancellationTokenSource _cts;
private Task _monitorTask;
public void StartMonitoring(ushort startAddress, int interval)
{
_cts = new CancellationTokenSource();
_monitorTask = Task.Run(async () =>
{
while(!_cts.IsCancellationRequested)
{
var values = ReadHoldingRegisters(startAddress, 10);
OnDataReceived?.Invoke(this, values);
await Task.Delay(interval);
}
}, _cts.Token);
}
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
| 通信超时 | 波特率不匹配 | 检查PLC参数C252(默认9600) |
| 返回错误码0x84 | 功能码不支持 | 确认是否启用Modbus功能(PLC参数C251=2) |
| CRC校验失败 | 字节序错误 | 检查是否处理了高低字节交换 |
| 间歇性通信中断 | 电磁干扰 | 改用屏蔽电缆,远离变频器线路 |
调试时建议先用串口调试助手确认物理层通信正常。我曾遇到一个诡异问题:PLC能接收但从不响应,最后发现是客户修改了通信协议使能参数(C251=0表示禁用Modbus)。
csharp复制await _port.BaseStream.WriteAsync(request, 0, request.Length);
await _port.BaseStream.ReadAsync(response, 0, response.Length);
实测在.NET 6环境下,优化后的代码可以达到每秒50次以上的稳定读写,完全满足大多数工业场景需求。对于更高要求的场合,可以考虑改用松下专用的MEWTOCOL协议(需购买通信模块)。
最后分享一个调试技巧:在PLC端用MOV指令将通信数据复制到D寄存器区,通过编程软件在线监控,可以直观看到通信过程中的数据变化。这个方法帮我定位过多个协议解析问题。