三菱PLC通信实战:从字节流到C#代码的A-1E协议深度解析

第一次尝试用C#与三菱PLC通信时,看着工具抓取的十六进制报文就像面对天书——那些排列组合的0x01、0xFF究竟代表什么?为什么同样的地址在代码和报文中呈现完全不同?本文将用最直观的方式,带您逐字节拆解A-1E协议,无需真实PLC硬件,仅需网络调试工具和模拟器就能掌握工业通信的核心调试技巧。

1. 实验环境搭建与工具链配置

工欲善其事必先利其器。我们选择HSL通信组件作为虚拟PLC服务器,它完美模拟了三菱FX系列PLC的通信行为。同时准备以下工具:

  • TCP/UDP测试工具 (推荐使用开源的PacketSender或商业版HslCommunication)
  • 十六进制转换计算器 (Windows自带的计算器切换为程序员模式即可)
  • Wireshark网络抓包工具 (用于高级故障诊断)

配置HSL模拟器的关键参数如下表:

参数项 示例值 说明
PLC类型 FX5U 模拟FX5U系列PLC的通信行为
IP地址 192.168.1.10 建议使用固定IP避免频繁变更
端口号 6000 三菱MC协议默认端口
协议版本 A-1E 二进制通信模式

注意:模拟器启动后需在防火墙中放行对应端口,否则会出现连接超时错误

2. A-1E协议帧结构精解

2.1 读取操作报文解剖

以读取D100开始的2个WORD数据为例,完整请求报文为:

01 FF 0A 00 64 00 00 00 20 44 02 00

用C#构造该报文的典型代码:

byte[] BuildReadRequest(ushort address, ushort length)
{
    var buffer = new List<byte>();
    buffer.Add(0x01);  // 功能码:批量字读取
    buffer.Add(0xFF);  // PLC站号
    buffer.AddRange(BitConverter.GetBytes((ushort)10)); // 超时2.5秒
    buffer.AddRange(BitConverter.GetBytes(address).Reverse()); // 小端地址
    buffer.AddRange(new byte[] { 0x20, 0x44 }); // 存储区标识
    buffer.AddRange(BitConverter.GetBytes(length).Reverse()); // 读取长度
    return buffer.ToArray();
}

关键字段解析:

  • 小端序处理 :三菱PLC采用低位在前的字节序,这与PC默认的大端序相反。例如地址100(0x64)在报文中呈现为 64 00 00 00
  • 存储区编码 :D寄存器对应 0x4420 ,但需转换为 20 44 放入报文
  • 长度字段 :读取2个WORD数据时,实际传输的是 02 00 而非 00 02

2.2 写入操作报文设计

向D20写入数值34和45的请求报文:

03 FF 0A 00 14 00 00 00 20 44 02 00 22 00 2D 00

对应的C#构造方法:

void BuildWriteRequest(ushort startAddress, params ushort[] values)
{
    var buffer = new List<byte>();
    buffer.Add(0x03);  // 功能码:批量字写入
    buffer.Add(0xFF);  // PLC站号
    buffer.AddRange(BitConverter.GetBytes((ushort)10)); // 超时
    buffer.AddRange(BitConverter.GetBytes(startAddress).Reverse());
    buffer.AddRange(new byte[] { 0x20, 0x44 });
    buffer.AddRange(BitConverter.GetBytes((ushort)values.Length).Reverse());
    
    foreach(var val in values) {
        buffer.AddRange(BitConverter.GetBytes(val).Reverse());
    }
    
    return buffer.ToArray();
}

3. 网络调试实战技巧

3.1 报文捕获与比对

使用TCP调试工具发送请求后,典型响应报文如下:

81 00 19 00 26 00

解析步骤:

  1. 状态检查 :首字节 0x81 表示读取响应,次字节 0x00 表示操作成功
  2. 数据提取 :后续字节为实际数据,需按小端序重组:
    • 19 00 0x0019 → 十进制25
    • 26 00 0x0026 → 十进制38

3.2 浮点数处理技巧

读取D102的float值(24.5)时,响应报文为:

81 00 33 33 35 42

C#解码方法:

byte[] response = { 0x33, 0x33, 0x35, 0x42 };
float value = BitConverter.ToSingle(response, 0);
Console.WriteLine(value); // 输出45.3

关键点:工业协议中浮点数通常采用IEEE 754标准,但字节顺序需特别注意

4. 常见故障排查指南

4.1 连接建立失败

  • 现象 :TCP连接超时
  • 排查步骤
    1. 确认模拟器IP和端口配置正确
    2. 使用ping测试网络连通性
    3. 检查防火墙设置
    4. 通过Wireshark确认是否有SYN包发出

4.2 数据读写异常

  • 典型错误 :返回状态码非零
  • 解决方案对照表
状态码 含义 处理建议
0x01 非法功能码 检查功能码是否在支持范围内
0x02 地址越界 确认软元件地址是否有效
0x03 数据长度超限 单次读写数量不超过128个WORD
0x04 权限不足 检查PLC的通信权限设置

4.3 字节序混淆问题

当发现读取数值与预期不符时,大概率是字节序处理错误。例如:

  • 预期读取100(0x64),但收到0x6400
  • 解决方案:在C#中使用 Array.Reverse() 调整字节顺序
ushort ReadUInt16(byte[] data, int offset)
{
    var bytes = new byte[2];
    Array.Copy(data, offset, bytes, 0, 2);
    Array.Reverse(bytes); // 小端转大端
    return BitConverter.ToUInt16(bytes, 0);
}

5. 高级应用:自定义协议分析器

为提升调试效率,可以开发简易协议分析器:

class A1EAnalyzer
{
    public static void ParseResponse(byte[] data)
    {
        byte funcCode = (byte)(data[0] & 0x7F);
        bool isError = (data[0] & 0x80) != 0;
        
        Console.WriteLine($"功能码: 0x{funcCode:X2}");
        Console.WriteLine(isError ? "操作失败" : "操作成功");
        
        if(!isError && funcCode == 0x01) 
        {
            int wordCount = (data.Length - 2) / 2;
            for(int i=0; i<wordCount; i++) 
            {
                ushort value = ReadUInt16(data, 2 + i*2);
                Console.WriteLine($"数据[{i}]: {value}");
            }
        }
    }
}

实际项目中遇到的典型问题:某次读取温度传感器值时始终得到0,最终发现是地址偏移计算错误——PLC中配置的起始地址与程序中的偏移量未对齐。通过逐字节对比请求报文与PLC配置参数,最终定位到D寄存器地址应增加8个偏移量。

更多推荐