基于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}");
            }
        }
    }
}

六、性能优化建议

  1. 连接池管理:维护多个TCP连接实例提高并发性能

  2. 异步通信

    public async Task<byte[]> AsyncSendCommand(byte[] command)
    {
        await stream.WriteAsync(command, 0, command.Length);
        return await stream.ReadAsync(new byte[1024], 0, 1024);
    }
    
  3. 数据缓存:对频繁读取的寄存器建立本地缓存

  4. 心跳机制:定期发送空指令保持连接活跃


七、调试工具代码推荐

  1. Wireshark:抓包分析通信数据
  2. Modbus Poll:验证基础通信功能
  3. PLC模拟器:基恩士官方仿真工具
  4. 代码: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型号调整指令格式和校验方式。

posted @ 2025-12-12 11:23  chen_yig  阅读(42)  评论(0)    收藏  举报