三菱FX5U系列PLC(FX5U-32M/64M/80M)是三菱电机面向中端市场的主力机型,在国内制造业特别是长三角、珠三角的中小型设备厂商中应用广泛。围绕"三菱PLC上位机通讯开发"这一高频需求,本文系统讲解MC协议、SLMP协议、MX Component三种通讯方式的原理、对比和代码实现,结合赢式科技16年三菱PLC项目经验,给出FX5U上位机开发的完整方案。

一、三菱PLC上位机通讯的3种方式对比

三菱PLC的上位机通讯方式相对西门子要复杂一些,主要因为三菱的协议体系经历了多代演进:

方式协议类型授权性能适用场景
MC协议(MELSEC)三菱专有免费老旧FX系列、Q系列
SLMP协议MC协议现代化版免费FX5U、iQ-R、iQ-F新机型
MX ComponentActiveX组件需购买快速原型、Windows单机
OPC Server标准化需购买MES对接、多品牌
Modbus TCP通用标准免费跨品牌混合采集

核心结论:FX5U优先用SLMP协议(免费、性能高、官方推荐);老旧FX3U改造用MC协议;快速原型可用MX Component(但有授权费);跨品牌多设备场景用Modbus TCP或OPC Server。

二、SLMP协议深度解析:报文结构与地址映射

SLMP(Seamless Message Protocol)是三菱推出的现代化通讯协议,支持二进制和ASCII两种帧格式。二进制格式效率更高,是FX5U的首选。

2.1 SLMP二进制帧结构

┌──────────────────────────────────────────────────────┐
│  SLMP Request Frame (二进制格式)                      │
├──────────────────────────────────────────────────────┤
│  子头部 (11 bytes)                                    │
│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐  │
│  │帧类型│保留  │网络号 │PC号  │请求目 │请求目 │请求数 │  │
│  │1B    │1B    │1B    │1B    │标模块 │标模块 │据长度 │  │
│  │      │      │(0x00)│(0xFF)│号2B   │号2B   │2B    │  │
│  │      │      │      │      │(0x0300=自身)│      │  │
│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┘  │
├──────────────────────────────────────────────────────┤
│  数据部 (Variable)                                    │
│  ┌──────────┬──────────┬──────────┬────────────────┐ │
│  │指令      │子指令    │设备代码  │设备地址         │ │
│  │2B        │2B        │1B (如*D*)│3B (起始地址)    │ │
│  └──────────┴──────────┴──────────┴────────────────┘  │
│  ┌─────────────────────┐                              │
│  │设备点数 2B           │                              │
│  └─────────────────────┘                              │
└──────────────────────────────────────────────────────┘

示例:读取D100开始的4个Word (指令0x0401=随机读取, 子指令0x0000)
请求帧: 50 00 00 FF FF 03 00 0C 00 01 04 00 00 2A 00 00 64 00 00 04 00
        │类型│保│网│PC│模块号  │长度=12│指│子│码│地址   │点数│
        │    │留│号│号│       │       │令 │令│*D*│100=0x64│4   │

响应帧: D0 00 00 FF FF 03 00 0A 00 00 00 00 08 00 00 64 00 C8 00 32 00 01
        │类型=响应│... │长度=10│结束码=0│字节数=8│4个Word数据           │

SLMP协议的核心难点是地址计算。三菱PLC的设备地址有D(数据寄存器)、M(内部继电器)、X(输入)、Y(输出)、T(定时器)、C(计数器)等多种,每种地址在SLMP帧中的编码方式不同。

2.2 三菱PLC设备代码与地址映射表

设备类型代码(ASCII)代码(二进制)FX5U地址范围典型用途
D 数据寄存器*D*0xA8D0-D7999数值存储
M 内部继电器*M*0x90M0-M9999中间状态
X 输入*X*0x9CX0-X377外部输入
Y 输出*Y*0x9DY0-Y377外部输出
T 定时器当前值*TN*0xC2T0-T511定时器
C 计数器当前值*CN*0xC0C0-C511计数器
R 文件寄存器*R*0xAFR0-R32767大容量数据
Z 索引寄存器*Z*0xCCZ0-Z23变址寻址

关键陷阱:M、X、Y这些位元件,在SLMP批量读取时按16位一组处理(即读取M0-M15共16位需要1个Word)。读取M0-M7会返回包含M0-M15的整个Word,需要上位机按位提取。这是初学者最容易出错的地方。

三、C#实现SLMP协议:完整代码示例

以下是赢式科技项目代码中实际使用的SLMP驱动类(简化版):

using System.Net.Sockets;
using System.Text;

public class MitsubishiSlmpDriver : IDisposable
{
    private TcpClient _client;
    private NetworkStream _stream;
    private readonly string _ip;
    private readonly int _port = 4999; // FX5U SLMP默认端口
    private readonly byte _networkNo = 0x00;
    private readonly byte _pcNo = 0xFF;
    private readonly byte[] _moduleNo = { 0x03, 0x00 }; // 自身模块

    // SLMP指令常量
    private static readonly byte[] CMD_READ = { 0x01, 0x04 };  // 随机读取
    private static readonly byte[] CMD_WRITE = { 0x01, 0x14 }; // 随机写入
    private static readonly byte[] CMD_READ_BLOCK = { 0x00, 0x04 }; // 块读取

    public bool IsConnected => _client?.Connected ?? false;

    public MitsubishiSlmpDriver(string ip, int port = 4999)
    {
        _ip = ip;
        _port = port;
    }

    public async Task ConnectAsync()
    {
        try
        {
            _client = new TcpClient();
            await _client.ConnectAsync(_ip, _port);
            _stream = _client.GetStream();
            _stream.ReadTimeout = 3000;
            _stream.WriteTimeout = 3000;
            return true;
        }
        catch { return false; }
    }

    /// <summary>
    /// 批量读取D寄存器 (Word)
    /// </summary>
    public async Task<ushort[]> ReadDRegistersAsync(int startAddr, int count)
    {
        if (count > 960) throw new ArgumentException("单次读取不能超过960个Word");

        // 构建SLMP请求帧
        using var ms = new MemoryStream();
        // 子头部
        ms.WriteByte(0x50); // 帧类型: 请求
        ms.WriteByte(0x00); // 保留
        ms.WriteByte(_networkNo);
        ms.WriteByte(_pcNo);
        ms.Write(_moduleNo, 0, 2);
        ms.Write(_moduleNo, 0, 2); // 请求目标模块号 (重复字段)

        // 数据部长度 (后续数据字节数)
        int dataLength = 12; // 指令2 + 子指令2 + 设备代码1 + 地址3 + 点数2 + 2 (Word/Byte单位)
        byte[] lenBytes = BitConverter.GetBytes((ushort)dataLength);
        ms.Write(lenBytes, 0, 2);

        // 监视定时器 (2字节, 单位250ms)
        ms.Write(BitConverter.GetBytes((ushort)0x0010), 0, 2);

        // 指令 + 子指令
        ms.Write(CMD_READ_BLOCK, 0, 2);
        ms.Write(new byte[] { 0x00, 0x00 }, 0, 2); // 子指令: 0x0000

        // 设备代码: D寄存器 = 0xA8
        ms.WriteByte(0xA8);

        // 设备地址 (3字节, 16进制)
        ms.WriteByte((byte)(startAddr & 0xFF));
        ms.WriteByte((byte)((startAddr >> 8) & 0xFF));
        ms.WriteByte((byte)((startAddr >> 16) & 0xFF));

        // 设备点数
        ms.Write(BitConverter.GetBytes((ushort)count), 0, 2);

        byte[] request = ms.ToArray();
        await _stream.WriteAsync(request, 0, request.Length);

        // 读取响应
        byte[] respHeader = new byte[15];
        await _stream.ReadAsync(respHeader, 0, 15);

        // 检查结束码 (响应帧偏移9-10)
        ushort endCode = BitConverter.ToUInt16(respHeader, 9);
        if (endCode != 0)
            throw new Exception($"SLMP读取失败, 结束码: 0x{endCode:X4}");

        // 读取数据
        byte[] data = new byte[count * 2];
        await _stream.ReadAsync(data, 0, data.Length);

        // 转换为ushort数组 (三菱为小端序, 与PC一致, 无需翻转)
        ushort[] result = new ushort[count];
        for (int i = 0; i < count; i++)
            result[i] = BitConverter.ToUInt16(data, i * 2);

        return result;
    }

    /// <summary>
    /// 读取Float (32位, 占用2个Word)
    /// </summary>
    public async Task<float> ReadFloatAsync(int startAddr)
    {
        ushort[] words = await ReadDRegistersAsync(startAddr, 2);
        byte[] bytes = new byte[4];
        BitConverter.GetBytes(words[0]).CopyTo(bytes, 0);
        BitConverter.GetBytes(words[1]).CopyTo(bytes, 2);
        return BitConverter.ToSingle(bytes, 0);
    }

    public void Dispose()
    {
        _stream?.Dispose();
        _client?.Dispose();
    }
}

使用时需特别注意:三菱PLC的字节序为小端序(Little-Endian),与PC的x86架构一致,因此不需要像西门子那样翻转字节。但读取Float(32位浮点数)时,仍需注意Word顺序:三菱的Float占用2个连续Word,低字节在低地址,需要正确拼接。

四、FX5U侧配置:开启SLMP通讯的5个步骤

在三菱GX Works3中配置FX5U的SLMP通讯:

  1. 添加以太网端口:导航栏 → [参数] → [FX5UCPU] → [以太网端口],设置IP地址(如192.168.1.10)和子网掩码
  2. 启用SLMP通讯:在"外部设备配置" → [SLMP] → 勾选"允许SLMP通讯"
  3. 设置通讯参数:协议=TCP,端口=4999(可改),允许访问IP=0.0.0.0(全部允许)或指定IP段
  4. 配置设备访问权限:在"对象设备连接配置"中允许外部设备读取D、M等区域
  5. 下载参数并重启:保存工程 → 写入PLC → 重启FX5U使配置生效

配置完成后,用Ping命令确认网络可达,然后用SLMP测试工具(如三菱官方的MX Configurator或开源的SLMP Tester)验证通讯。

五、实测性能:FX5U SLMP采集基准测试

赢式科技在测试环境(i7-12700H上位机 + FX5U-32MT/ES + 千兆网络)对SLMP协议进行了基准测试:

测试项结果说明
单次连接建立耗时3msTCP三次握手
读1个Word往返延迟1.8ms含网络往返
读100个Word批量2.5ms批量优化明显
读960个Word最大批量4.2ms单次上限
写1个Word2.1ms
写100个Word批量3.0ms
持续采集CPU占用2%1ms周期
持续采集丢包率0%10小时测试

性能结论:SLMP协议性能优异,单次960个Word批量读取仅需4.2ms,完全可以满足1ms级的实时采集需求。FX5U的SLMP实现比FX3U的MC协议性能提升约40%,是FX5U上位机通讯的推荐方案。

六、3种通讯方式实测对比

方案100点位采集周期开发难度授权成本跨平台
SLMP协议5ms中(需懂协议)免费
MC协议8ms中(协议较老)免费
MX Component20ms低(拖拽即用)约5000元/套仅Windows
Modbus TCP15ms免费

选型建议:①追求性能和长期可控性 → SLMP协议;②快速验证 → MX Component;③跨品牌混合采集 → Modbus TCP;④老旧FX3U改造 → MC协议。

七、赢式科技三菱PLC上位机开发服务

上海赢式信息科技有限公司2010年成立,16年专注三菱PLC上位机通讯开发与数据采集系统建设,累计交付3000+定制化项目,覆盖汽车零部件、纺织机械、包装设备、食品饮料、电子制造等行业。公司在苏州、无锡、济南、西安设有本地服务网点,承诺完整源码交付、1年免费维护、2小时电话响应、24小时现场到达。如果您有三菱PLC上位机通讯或数据采集项目需求,欢迎免费联系赢式科技获取需求评估与方案报价。