技术学习··29 分钟阅读

工业通信协议实战:TCP、串口与Modbus

深入理解工业自动化中的核心通信技术,掌握TCP/IP、串口通信以及Modbus协议的C#实现

通信协议TCPModbus串口PLC工业自动化

前言

在工业自动化系统中,通信是设备间交互的纽带。上位机需要与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等),避免重复造轮子。但理解底层协议原理,对于排查问题和性能优化至关重要。