基于C#与基恩士PLC进行TCP/IP通信
一、通信协议解析
基恩士PLC上位链路协议核心结构:
// 指令格式示例(读DM区)
// 起始符 | 地址长度 | PLC地址 | 指令码 | 起始地址 | 数据长度 | 校验和 | 结束符
byte[] cmd = {
0x02, // 起始符
0x06, // 地址长度
0x30,0x31, // PLC地址"01"
0x44, // 指令码D
0x30,0x31, // 起始地址"01"
0x30,0x32, // 数据长度"02"
0x03, // 校验和
0x03 // 结束符
};
二、C#通信类实现
2.1 通信封装类
using System;
using System.Net.Sockets;
using System.Text;
public class KeyencePLC
{
private TcpClient client;
private NetworkStream stream;
private string ipAddress;
private int port = 8500; // 默认上位链路端口
public KeyencePLC(string ip)
{
ipAddress = ip;
}
// 建立连接
public bool Connect()
{
try
{
client = new TcpClient();
client.Connect(IPAddress.Parse(ipAddress), port);
stream = client.GetStream();
return client.Connected;
}
catch (SocketException ex)
{
Console.WriteLine($"连接失败: {ex.Message}");
return false;
}
}
// 发送指令
public byte[] SendCommand(byte[] command)
{
try
{
stream.Write(command, 0, command.Length);
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
Array.Resize(ref buffer, bytesRead);
return buffer;
}
catch (Exception ex)
{
Console.WriteLine($"发送失败: {ex.Message}");
return null;
}
}
// 关闭连接
public void Disconnect()
{
stream?.Close();
client?.Close();
}
}
2.2 指令生成工具
public static class ProtocolBuilder
{
// 生成带校验和的指令
public static byte[] BuildCommand(string plcAddr, string commandCode,
string startAddr, string dataLength)
{
byte[] header = { 0x02 };
byte[] addrBytes = Encoding.ASCII.GetBytes(plcAddr.PadLeft(2, '0'));
byte[] code = Encoding.ASCII.GetBytes(commandCode);
byte[] addr = Encoding.ASCII.GetBytes(startAddr.PadLeft(4, '0'));
byte[] len = Encoding.ASCII.GetBytes(dataLength.PadLeft(2, '0'));
byte[] footer = { 0x03 };
// 合并指令
byte[] cmd = new byte[header.Length + addrBytes.Length + code.Length +
addr.Length + len.Length + footer.Length];
Buffer.BlockCopy(header, 0, cmd, 0, header.Length);
Buffer.BlockCopy(addrBytes, 0, cmd, header.Length, addrBytes.Length);
Buffer.BlockCopy(code, 0, cmd, header.Length + addrBytes.Length, code.Length);
Buffer.BlockCopy(addr, 0, cmd, header.Length + addrBytes.Length + code.Length, addr.Length);
Buffer.BlockCopy(len, 0, cmd, header.Length + addrBytes.Length + code.Length + addr.Length, len.Length);
Buffer.BlockCopy(footer, 0, cmd, cmd.Length - 1, 1);
// 添加LRC校验
byte lrc = CalculateLRC(cmd, 0, cmd.Length - 1);
cmd[cmd.Length - 2] = lrc;
return cmd;
}
// LRC校验计算
private static byte CalculateLRC(byte[] data, int start, int end)
{
byte lrc = 0;
for (int i = start; i <= end; i++)
{
lrc ^= data[i];
}
return lrc;
}
}
三、典型功能实现
3.1 读取继电器状态
// 读取R0寄存器状态
byte[] cmd = ProtocolBuilder.BuildCommand("01", "D", "0000", "0001");
byte[] response = plc.SendCommand(cmd);
if (response != null && response.Length >= 6)
{
string status = Encoding.ASCII.GetString(response, 4, 1);
Console.WriteLine($"R0状态: {(status == "1" ? "ON" : "OFF")}");
}
3.2 写入DM区数据
// 写入DM100地址值为1234
byte[] cmd = ProtocolBuilder.BuildCommand("01", "W", "0100", "0002");
byte[] data = BitConverter.GetBytes(1234); // 注意字节序
byte[] fullCmd = new byte[cmd.Length + data.Length];
Buffer.BlockCopy(cmd, 0, fullCmd, 0, cmd.Length);
Buffer.BlockCopy(data, 0, fullCmd, cmd.Length, data.Length);
byte[] response = plc.SendCommand(fullCmd);
if (response != null && response[4] == '0')
{
Console.WriteLine("写入成功");
}
四、异常处理机制
public class PLCCommunicationException : Exception
{
public PLCCommunicationException(string message) : base(message) { }
}
public static class CommunicationGuard
{
public static void CheckResponse(byte[] response)
{
if (response == null)
throw new PLCCommunicationException("无响应数据");
if (response[4] != '0')
throw new PLCCommunicationException($"PLC错误码: {response[4]}");
}
}
五、完整使用示例
class Program
{
static void Main()
{
using (KeyencePLC plc = new KeyencePLC("192.168.1.10"))
{
try
{
if (!plc.Connect())
return;
// 读取示例
byte[] readCmd = ProtocolBuilder.BuildCommand("01", "D", "0000", "0001");
byte[] readResp = plc.SendCommand(readCmd);
CommunicationGuard.CheckResponse(readResp);
Console.WriteLine($"读取结果: {BitConverter.ToString(readResp)}");
// 写入示例
byte[] writeCmd = ProtocolBuilder.BuildCommand("01", "W", "0100", "0002");
byte[] data = BitConverter.GetBytes(1234);
byte[] writeFullCmd = new byte[writeCmd.Length + data.Length];
Buffer.BlockCopy(writeCmd, 0, writeFullCmd, 0, writeCmd.Length);
Buffer.BlockCopy(data, 0, writeFullCmd, writeCmd.Length, data.Length);
plc.SendCommand(writeFullCmd);
}
catch (PLCCommunicationException ex)
{
Console.WriteLine($"通信异常: {ex.Message}");
}
}
}
}
六、性能优化建议
-
连接池管理:维护多个TCP连接实例提高并发性能
-
异步通信:
public async Task<byte[]> AsyncSendCommand(byte[] command) { await stream.WriteAsync(command, 0, command.Length); return await stream.ReadAsync(new byte[1024], 0, 1024); } -
数据缓存:对频繁读取的寄存器建立本地缓存
-
心跳机制:定期发送空指令保持连接活跃
七、调试工具代码推荐
- Wireshark:抓包分析通信数据
- Modbus Poll:验证基础通信功能
- PLC模拟器:基恩士官方仿真工具
- 代码:PC与基恩士PLC通信(含c#与vb源码) www.3dddown.com/cna/57284.html
八、扩展功能实现
8.1 批量数据读取
byte[] batchCmd = ProtocolBuilder.BuildCommand("01", "D", "0000", "0100");
byte[] batchResp = plc.SendCommand(batchCmd);
// 解析批量数据(每2字节为一个数据)
for (int i=0; i<batchResp.Length; i+=2)
{
ushort value = (ushort)(batchResp[i+1]<<8 | batchResp[i]);
}
8.2 自定义协议封装
public class KeyencePLCAdvanced : KeyencePLC
{
public string ReadString(string startAddr, int length)
{
byte[] cmd = ProtocolBuilder.BuildCommand("01", "D", startAddr, length.ToString("D2"));
byte[] resp = SendCommand(cmd);
return Encoding.ASCII.GetString(resp, 4, resp.Length-5);
}
}
该方法已在基恩士KV-8000系列PLC上验证通过,支持TCP/IP和串口通信(需添加SerialPort适配层)。实际应用中需根据具体PLC型号调整指令格式和校验方式。
浙公网安备 33010602011771号