前言
在工业自动化系统中,通信是设备间交互的纽带。上位机需要与PLC、传感器、伺服驱动器等设备实时交换数据。理解并熟练掌握各种通信协议,是工控软件开发工程师的核心技能。
TCP/IP通信:工业以太网的基石
TCP/IP是互联网的基础协议,在工业4.0时代,越来越多的工控设备支持以太网通信。
TCP客户端实现
```csharp
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
public class TcpClientHelper
{
private TcpClient _tcpClient;
private NetworkStream _stream;
public async Task<bool> ConnectAsync(string ip, int port)
{
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(IPAddress.Parse(ip), port);
_stream = _tcpClient.GetStream();
return true;
}
catch (Exception ex)
{
Console.WriteLine($"TCP连接失败: {ex.Message}");
return false;
}
}
// 发送数据并接收响应
public async Task<byte[]> SendAndReceiveAsync(byte[] data)
{
try
{
// 发送数据
await _stream.WriteAsync(data, 0, data.Length);
await _stream.FlushAsync();
// 接收响应(等待最多5秒)
byte[] buffer = new byte[1024];
int bytesRead = await ReadWithTimeoutAsync(5000);
return buffer.Take(bytesRead).ToArray();
}
catch (Exception ex)
{
Console.WriteLine($"TCP通信错误: {ex.Message}");
return null;
}
}
private async Task<int> ReadWithTimeoutAsync(int timeoutMs)
{
var readTask = _stream.ReadAsync(buffer, 0, buffer.Length);
var timeoutTask = Task.Delay(timeoutMs);
var completedTask = await Task.WhenAny(readTask, timeoutTask);
if (completedTask == readTask)
{
return await readTask; // 正常读取完成
}
else
{
throw new TimeoutException("读取超时");
}
}
public void Disconnect()
{
_stream?.Close();
_tcpClient?.Close();
}
}
```
实际应用:读取PLC的TCP数据
```csharp
public class SiemensPlcClient
{
private readonly TcpClientHelper _tcp = new TcpClientHelper();
public async Task<bool> ConnectPlc(string ip, int port = 102)
{
return await _tcp.ConnectAsync(ip, port);
}
// 读取PLC DB块数据(西门子S7协议简化示例)
public async Task<short> ReadDbIntAsync(int dbNumber, int byteOffset)
{
// 构建S7协议报文(实际项目建议使用成熟的S7协议库)
byte[] request = BuildS7ReadRequest(dbNumber, byteOffset, 2);
byte[] response = await _tcp.SendAndReceiveAsync(request);
// 解析响应报文
if (response != null && response.Length > 17)
{
// 提取数据(假设数据在第17-18字节)
return BitConverter.ToInt16(response, 17);
}
throw new Exception("读取PLC数据失败");
}
private byte[] BuildS7ReadRequest(int dbNumber, int offset, int length)
{
// 简化版S7协议报文构建
byte[] request = new byte[31];
// ... 填充协议头部信息
return request;
}
}
```
串口通信:经典工业接口
串口(RS232/RS485)是工业现场最常用的通信方式之一,特别是老旧设备或简单传感器。
串口基础类
```csharp
using System.IO.Ports;
public class SerialPortHelper
{
private SerialPort _serialPort;
public async Task<bool> OpenAsync(string portName, int baudRate, Parity parity, int dataBits)
{
try
{
_serialPort = new SerialPort(portName, baudRate, parity, dataBits);
_serialPort.DtrEnable = true; // 启用DTR
_serialPort.RtsEnable = true; // 启用RTS
_serialPort.WriteTimeout = 1000;
_serialPort.ReadTimeout = 1000;
_serialPort.Open();
return true;
}
catch (Exception ex)
{
Console.WriteLine($"串口打开失败: {ex.Message}");
return false;
}
}
// 异步发送数据
public async Task WriteAsync(byte[] data)
{
if (_serialPort?.IsOpen == true)
{
await _serialPort.BaseStream.WriteAsync(data, 0, data.Length);
await _serialPort.BaseStream.FlushAsync();
}
}
// 异步读取数据
public async Task<byte[]> ReadAsync(int bufferSize = 1024)
{
if (_serialPort?.IsOpen == true)
{
byte[] buffer = new byte[bufferSize];
int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, bufferSize);
return buffer.Take(bytesRead).ToArray();
}
return null;
}
public void Close()
{
_serialPort?.Close();
}
}
```
Modbus协议:工业标准
Modbus是应用最广泛的工业现场总线协议,支持RTU(串口)和TCP两种模式。
Modbus RTU实现
```csharp
public class ModbusRtu
{
private readonly SerialPortHelper _serial = new SerialPortHelper();
private byte _slaveAddress = 1; // 从站地址
// 读取保持寄存器(功能码0x03)
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
{
// 构建Modbus RTU请求帧
byte[] request = new byte[8];
request[0] = _slaveAddress; // 从站地址
request[1] = 0x03; // 功能码
request[2] = (byte)(startAddress >> 8); // 起始地址高字节
request[3] = (byte)(startAddress & 0xFF); // 起始地址低字节
request[4] = (byte)(count >> 8); // 寄存器数量高字节
request[5] = (byte)(count & 0xFF); // 寄存器数量低字节
// 添加CRC校验
ushort crc = CalculateCrc16(request, 6);
request[6] = (byte)(crc & 0xFF);
request[7] = (byte)(crc >> 8);
// 发送请求
await _serial.WriteAsync(request);
// 接收响应
byte[] response = await _serial.ReadAsync(5 + count * 2);
// 验证CRC
ushort responseCrc = BitConverter.ToUInt16(response, response.Length - 2);
if (responseCrc != crc)
{
throw new Exception("CRC校验失败");
}
// 解析数据
ushort[] registers = new ushort[count];
for (int i = 0; i < count; i++)
{
registers[i] = (ushort)(response[3 + i * 2] << 8 | response[4 + i * 2]);
}
return registers;
}
// CRC16计算(Modbus CRC-16)
private ushort CalculateCrc16(byte[] data, int length)
{
const ushort poly = 0xA001;
ushort crc = 0xFFFF;
for (int i = 0; i < length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= poly;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
}
```
实际应用:读取变频器数据
```csharp
public class VfdController
{
private readonly ModbusRtu _modbus = new ModbusRtu();
public async Task<bool> ConnectAsync(string portName)
{
return await _serial.OpenAsync(portName, 9600, Parity.None, 8);
}
// 读取当前频率
public async Task<double> ReadFrequencyAsync()
{
// 假设频率值保存在保持寄存器40001中
var registers = await _modbus.ReadHoldingRegistersAsync(0, 1);
// 转换为实际频率(假设单位为0.01Hz)
return registers[0] * 0.01;
}
// 读取输出电流
public async Task<double> ReadCurrentAsync()
{
// 电流保存在40002中,单位为0.1A
var registers = await _modbus.ReadHoldingRegistersAsync(1, 1);
return registers[0] * 0.1;
}
// 设置运行频率(功能码0x06)
public async Task WriteFrequencyAsync(double frequency)
{
ushort value = (ushort)(frequency * 100);
// 发送写单个寄存器命令...
}
}
```
通信异常处理
工控环境复杂,通信中断、数据错误是常有的事,必须robustly处理。
重连机制
```csharp
public class RobustCommClient
{
private int _retryCount = 3;
private int _retryDelay = 1000; // 重试间隔1秒
public async Task<bool> ReadDataWithRetryAsync(Func<Task<byte[]>> readFunc)
{
for (int i = 0; i < _retryCount; i++)
{
try
{
var data = await readFunc();
if (data != null)
return true;
}
catch (Exception ex)
{
Console.WriteLine($"第{i + 1}次读取失败: {ex.Message}");
if (i < _retryCount - 1)
{
await Task.Delay(_retryDelay);
await ReconnectAsync(); // 尝试重连
}
}
}
return false;
}
private async Task ReconnectAsync()
{
// 重新连接设备
await Disconnect();
await Task.Delay(1000);
await ConnectAsync();
}
}
```
总结
工业通信是工控软件的核心:
1. **TCP/IP**:现代工业以太网,高速、可靠,适合大数据量传输。
2. **串口**:简单、成本低,适合老旧设备或简单传感器。
3. **Modbus**:工业标准,跨厂商兼容性好,是必备技能。
4. **异常处理**:必须实现重连、超时、CRC校验等机制确保通信稳定。
在实际项目中,建议使用成熟的通信库(如NModbus、Libnodave等),避免重复造轮子。但理解底层协议原理,对于排查问题和性能优化至关重要。