基于C#的三菱PLC串口通信实现方案

一、通信协议选择与配置

推荐采用MELSEC通信协议(MC协议),支持串口/以太网通信,兼容FX3U/Q系列PLC。核心参数配置如下:

// 串口参数配置(需与PLC设置一致)
SerialPort serialPort = new SerialPort();
serialPort.PortName = "COM1";      // 波特率
serialPort.BaudRate = 9600;        // 数据位
serialPort.DataBits = 8;           // 停止位
serialPort.StopBits = StopBits.One;// 校验位
serialPort.Parity = Parity.None;   // 流控
serialPort.Handshake = Handshake.None;

二、核心通信类实现

public class MitsubishiPLC
{
    private SerialPort _serialPort;
    private const byte STX = 0x02;
    private const byte ETX = 0x03;
    private const byte ENQ = 0x05;
    private const byte ACK = 0x06;
    private const byte NAK = 0x15;

    public MitsubishiPLC(string portName, int baudRate)
    {
        _serialPort = new SerialPort(portName, baudRate);
        _serialPort.DataReceived += SerialPort_DataReceived;
    }

    // 打开串口
    public bool Open()
    {
        try
        {
            if (!_serialPort.IsOpen)
            {
                _serialPort.Open();
                return true;
            }
            return false;
        }
        catch (Exception ex)
        {
            Log($"串口打开失败: {ex.Message}");
            return false;
        }
    }

    // 关闭串口
    public void Close()
    {
        if (_serialPort.IsOpen) _serialPort.Close();
    }

    // 发送指令并接收响应
    private byte[] SendCommand(byte[] command)
    {
        _serialPort.Write(command, 0, command.Length);
        Thread.Sleep(100); // 等待响应

        if (_serialPort.BytesToRead > 0)
        {
            byte[] response = new byte[_serialPort.BytesToRead];
            _serialPort.Read(response, 0, response.Length);
            return response;
        }
        return null;
    }

    // 读取保持寄存器(D区)
    public ushort[] ReadD(string address, int count)
    {
        byte[] cmd = BuildReadCommand('D', address, count);
        byte[] response = SendCommand(cmd);
        return ParseResponse(response);
    }

    // 写入保持寄存器(D区)
    public bool WriteD(string address, ushort[] values)
    {
        byte[] cmd = BuildWriteCommand('D', address, values);
        byte[] response = SendCommand(cmd);
        return CheckAck(response);
    }

    // 构建读取指令
    private byte[] BuildReadCommand(char type, string addr, int count)
    {
        string cmdStr = $"{(char)ENQ}00FF{type}{addr.PadLeft(4,'0')}{count.ToString("X4")}03";
        byte[] data = Encoding.ASCII.GetBytes(cmdStr);
        byte checksum = CalculateChecksum(data);
        return data.Concat(new byte[] { checksum, ETX }).ToArray();
    }

    // 构建写入指令
    private byte[] BuildWriteCommand(char type, string addr, ushort[] values)
    {
        string valueHex = string.Join("", values.Select(v => v.ToString("X4")));
        string cmdStr = $"{(char)ENQ}00FF{type}{addr.PadLeft(4,'0')}{values.Length.ToString("X2")}{valueHex}";
        byte[] data = Encoding.ASCII.GetBytes(cmdStr);
        byte checksum = CalculateChecksum(data);
        return data.Concat(new byte[] { checksum, ETX }).ToArray();
    }

    // 校验和计算
    private byte CalculateChecksum(byte[] data)
    {
        byte sum = 0;
        foreach (byte b in data)
        {
            sum += b;
        }
        return (byte)(0 - sum);
    }

    // 解析响应数据
    private ushort[] ParseResponse(byte[] response)
    {
        if (response[0] != STX || response[1] != 0x30) return null;
        int length = response[2] - 0x30;
        ushort[] data = new ushort[length / 2];
        for (int i = 0; i < length; i += 2)
        {
            data[i / 2] = (ushort)(response[3 + i] << 8 | response[4 + i]);
        }
        return data;
    }

    // 校验ACK响应
    private bool CheckAck(byte[] response)
    {
        return response != null && response[0] == ACK;
    }
}

三、关键功能实现示例

1. 读取D0-D9寄存器
MitsubishiPLC plc = new MitsubishiPLC("COM1", 9600);
if (plc.Open())
{
    ushort[] data = plc.ReadD("0000", 10);
    for(int i=0; i<data.Length; i++)
    {
        Console.WriteLine($"D{i}: {data[i]}");
    }
}
2. 写入单个D寄存器
ushort[] writeData = { 0x1234 };
plc.WriteD("0000", writeData);
3. 批量写入M区位元件
// 写入M0-M15(16位)
byte[] cmd = new byte[] { 0x05, 0x30, 0x30, 0x46, 0x46, 0x42, 0x57, 0x41, 0x4D, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32 };
_serialPort.Write(cmd, 0, cmd.Length);

四、异常处理与调试

  1. 超时处理

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (DateTime.Now - _lastRecvTime > TimeSpan.FromSeconds(2))
        {
            Log("通信超时");
            _serialPort.Close();
        }
    }
    
  2. 日志记录

    private void Log(string message)
    {
        File.AppendAllText("plc_log.txt", $"{DateTime.Now}: {message}\r\n");
    }
    
  3. 错误码解析

    错误码 含义 解决方案
    0x0101 站号错误 检查PLC站号设置
    0x0102 指令不支持 确认协议版本兼容性
    0x0105 数据校验错误 检查校验和计算逻辑

五、高级功能扩展

  1. Modbus RTU从站模式

    通过RS指令实现从站功能,需配置PLC的通信参数:

    LD M8000
    RS D1000 D101F 0A 03  // 从站地址03,接收区D1000-D101F,长度10
    RS D2000 D201F 0A 03  // 发送区D2000-D201F,长度10
    
  2. 多线程通信

    private Thread _commThread;
    private void StartComm()
    {
        _commThread = new Thread(() =>
        {
            while (true)
            {
                ReadDataPeriodically();
                Thread.Sleep(1000);
            }
        });
        _commThread.Start();
    }
    

参考代码 通过串口与三菱PLC进行通讯,能读取、写入各个软元件。 www.youwenfan.com/contentcnp/93723.html

六、调试工具推荐

  1. GX Works2模拟器 通过GX Works2的仿真功能测试通信逻辑,无需物理PLC设备。
  2. 串口调试助手 使用SecureCRT或Putty进行原始数据监控,验证报文格式。
  3. Wireshark抓包 分析串口通信的完整数据帧结构。

七、完整项目结构

PLC_Communication/
├── MitsubishiPLC.cs        // 核心通信类
├── SerialPortConfig.cs     // 串口配置管理
├── ProtocolParser.cs       // 报文解析工具
├── ExceptionHandler.cs     // 异常处理模块
└── TestApp/                // 测试程序
    ├── MainForm.cs         // 主界面
    └── DeviceManager.cs    // 设备管理器

八、注意事项

  1. 通信参数一致性:确保PLC与上位机的波特率、数据位、停止位、校验位完全一致
  2. 数据缓冲区管理:批量读写时注意PLC的接收缓冲区大小限制(通常≤256字节)
  3. 实时性要求:关键控制指令建议采用中断接收方式
  4. 安全防护:工业环境中需增加光电隔离和防浪涌电路
posted @ 2026-01-04 16:18  chen_yig  阅读(12)  评论(0)    收藏  举报