技术学习··40 分钟阅读

C#上位机通讯实战之三:PLC 专用协议 (S7、MC)

PLC 专用协议 协议详解

上位机通讯PLCS7MC

私有协议就是厂家(如西门子、三菱)为了商业护城河和极致性能,自己发明的一种不对外公开的“内部语言”。

作为上位机,和 PLC 通讯,用厂家的私有协议是最快、最稳、功能最全的选择。

 

一、私有协议的历史

 

先有私有协议,才有 Modbus

 

当 1968 年第一台 PLC (Modicon 084) 诞生时,大家用的都是两根线的串口

西门子、AB (Rockwell)、三菱这些巨头造出 PLC 后,为了让你**“买了我的 PLC,就必须买我的触摸屏,必须用我的编程软件”,他们刻意发明了极其复杂且不公开**的通讯规则。都是为了垄断。

 

Modicon (现在的施耐德) 公司觉得这样太乱了,于是公开了 Modbus 协议。它是第一个完全公开、免费、简单的协议。

 

以太网 (Ethernet) / Socket 技术开始进入工厂时。

你以为大家会商量出一个统一的新协议,结果却是:大家把自家的“老私有协议”,直接打包塞进了 Socket 里。

西门子做出了 S7Comm (S7 over ISO-on-TCP),三菱做出了 MC Protocol。AB 做出了 EtherNet/IP 。

 
 
 

为什么 Modbus 和 Socket 没能干掉私有协议?

 

Socket 这么方便,Modbus 这么通用,为什么大家统一?还要搞 S7、MC 这种私有协议?

性能功能

A. 功能上的碾压

Modbus (简陋): 只能读写寄存器 (0-65535)。它不懂什么是“程序块”,什么是“系统时间”,什么是“诊断日志”。

私有协议 (全能):

  • S7 协议: 可以直接读写 PLC 的 CPU 状态(RUN/STOP),可以上传下载程序,可以读取变量名。
  • 上位机需求: 你的博图软件 (TIA Portal) 为什么能给 PLC 下载程序?因为它用的就是 S7 私有协议!Modbus 根本做不到这点。

B. 效率上的碾压

  • Modbus: 问一句答一句。读 100 个分散的地址,要问 100 次。
  • 私有协议: 支持**“智能打包”**。比如 S7 协议的一个 PDU (协议数据单元) 可以一次性塞进去很多条不连续的读取请求,一次 Socket 通讯就把活干完了。

 

发展现状:两极分化

 

派系一:更封闭、更智能 (标签化)

代表:AB (EtherNet/IP), 西门子 S7-1500 (优化的 S7)

以前我们读地址:DB1.DBD0

现在他们推崇读**“标签 (Tag)”**:直接读变量名 "Motor_Speed"

目的: 为了防破解,为了防第三方库(像 S7.Net)。西门子 S7-1500 默认开启了“优化块访问”,实际上是把协议加密了,导致很多老驱动连不上,逼着你买官方授权。

 

派系二:大一统的尝试 (OPC UA)

代表:工业 4.0 联盟

大家发现私有协议太难搞了,于是推出了 OPC UA

这是一种跨平台、跨品牌的超级协议。西门子、三菱、倍福的新款 PLC 都原生支持 OPC UA。

愿景: 以后没有 S7、没有 MC,大家全用 OPC UA 说话。

缺点: 协议太重,速度慢(基于 XML/Binary 复杂结构),目前还取代不了 S7/MC 在高速控制中的地位。

 
 
 

PLC 专用协议核心天梯榜

大部分工厂的设备正好是 S7 协议MC 协议 的天下。掌握 S7.Net 和 HslCommunication 是重点。

梯队 & 推荐度协议名称对应品牌 (典型型号)**核心特点 **默认端口C# 开发首选库 (不造轮子)
🏆 T0 (必学) (市场占有率 80%)S7 协议 (S7Comm)西门子 Siemens (S7-1200, 1500, 300, Smart)1. 无需配置:不用映射地址,直接读 DB 块。 2. 基于 TCP:底层是 ISO-on-TCP。 3. 如果不买授权:记得在 PLC 侧勾选“允许来自远程对象的 PUT/GET 访问”。1021. S7.Net (开源免费,新手首选) 2. HslCommunication (国产最强) 3. Snap7 (性能怪兽)
🏆 T0 (必学) (日系老大)MC 协议 (Qna-3E Binary)三菱 Mitsubishi (FX5U, Q系列, L系列)1. 二进制最快:推荐配置为 Binary 模式,比 ASCII 快且包更小。 2. 半公开:协议格式规整,手写 Socket 难度中等。 3. 灵活性:支持按位(M)、按字(D)批量读写。6000 (或 5002)1. HslCommunication (对三菱支持极好)
T1 (常用) (中高端/美资)FINS TCP欧姆龙 Omron (CJ, CP, NX/NJ)1. 握手复杂:连接前要先发握手包申请节点号,非常繁琐。 2. 地址分级:网络号-节点号-单元号。 3. 大端序:注意字节高低位反转。96001. HslCommunication 2. libplctag
T1 (常用) (标签访问)EtherNet/IP (CIP 协议)罗克韦尔 AB (Rockwell) (ControlLogix, Compact)1. 标签 (Tag) 访问唯一一个不用记地址的! 直接读变量名 "Speed"。 2. 协议极其复杂:千万别尝试手写底层 Socket。448181. libplctag (C语言库的C#封装,稳定) 2. EEIP
T2 (特定)(PC-Based)ADS 协议倍福 Beckhoff (TwinCAT 控制器)1. 路由通讯:不光靠 IP,还要靠 AMS Net ID (如 1.2.3.4.1.1)。 2. 极快:专为 PC 控制设计。 3. 官方支持好:倍福直接提供 DLL。48898TwinCAT.Ads (官方免费 DLL)
T2 (简单)(小型机)Mewtocol松下 Panasonic (FP 系列)1. 文本流:类似 ASCII 码流,比较古老但简单。 2. 易调试:可以用网络调试助手直接发指令测试。自定义1. HslCommunication 2. 手写 Socket (很容易)

 
 
 

二、上位机和 PLC 通讯的原因

 

在进入实战之前,先聊一下这个问题。

 

PLC 里有什么

想象一下,PLC 内部有一个巨大的 Excel 表格(内存区),这个表格里的每一个格子,都实时反映着外面的世界。

现实世界的零件PLC 里的动作 (硬件接线)PLC 内存地址 (你的代码读写的目标)C# 代码的含义
绿色启动按钮按钮按下 -> 24V电传给 PLCI0.0 (输入点 0.0)上位机读它:判断工人是不是按了启动?
红色急停拍钮按钮拍下 -> 断电I0.1 (输入点 0.1)上位机读它:如果为 True,软件界面马上弹红色报警窗!
温度传感器测到 85.5℃DB1.DBD0 (浮点数)上位机读它:把 85.5 显示在电脑屏幕的仪表盘上。
流水线电机PLC 输出 24V -> 继电器吸合Q0.0 (输出点 0.0)上位机它:软件点“强制启动”,PLC 就让电机转。
产量计数器每过一个产品加 1DB1.DBW10 (整数)上位机读它:存入数据库,老板要看今天做了多少个。

所以, Read("DB1.DBD0"),本质上就是在问 PLC:“喂,现在的温度是多少?”

 

PLC 自己干不行吗

 

既然 PLC 自己就可以控制,为什么还需要上位机?

简单的控制,确实不需要。但是复杂的现代工厂里,PLC 需要和上位机互补配合。

具体可以概括为 4 大核心需求

  1. 监控:PLC 没有显示屏。比如现在的温度是 80℃,PLC 知道,但通知不了工人。上位机可以读取地址得到数值,然后在屏幕上显示出来。一目了然。
  2. **数据追溯:PLC 的状态和数值都只能保存很短时间,你要问上周的温度是多少,它会打不了。上位机可以将这些数据都存到数据库里,随时读取。
  3. **配方下发:今天生产设备需要温度是 80℃,明天可能是 70℃。PLC 要改参数,要么电工去调传感器,要么拿电脑去改代码,太麻烦。上位机可以直接将参数写入 PLC,操作员在上位机软件点击就行。
  4. 逻辑握手:相机拍了一张照,要识别产品有没有问题。PLC 处理不了图片。上位机可以,识别图片之后,通过地址告诉 PLC ,PLC 再做下一步处理。

 

所以, PLC 通讯很重要,下面就来进入实战环节。

 

三、西门子 S7 协议 (S7.Net)

 

3.1 环境准备

 

  • NuGet 安装: S7.Net (目前最流行的免费开源 C# 驱动)。
  • 硬件模拟: 没有 PLC,使用 Snap7 Server 模拟。(把电脑伪装成一台西门子 PLC,IP 就是本机 IP (127.0.0.1)。)

 

3.2 关键概念

“机架 0,插槽 1,数据都在 DB 里。”

西门子连接需要 4 个核心参数,不像 Modbus 只有 IP 和端口:

  1. IP 地址:
  2. CPU 型号: S7-1200 还是 S7-300?(代码里要选)
  3. Rack (机架号): 绝大多数情况都是 0
  4. Slot (插槽号): 这是最大的坑!
    • S7-1200 / 1500:插槽号通常是 1
    • S7-300 / 400:插槽号通常是 2

 

3.3 实战代码

 

using S7.Net; // 1. 引用库
using System;

class Program
{
    static void Main(string[] args)
    {
        // ==========================================
        // 1. 创建 PLC 对象 (配置连接参数)
        // ==========================================
        // 参数顺序:CPU型号, IP地址, 机架号(Rack), 插槽号(Slot)
        // 如果你是模拟器或者 S7-1200,Slot 通常填 1;如果是 S7-300,填 2。
        Plc plc = new Plc(CpuType.S71200, "192.168.1.10", 0, 1);

        try
        {
            // ==========================================
            // 2. 打开连接
            // ==========================================
            Console.WriteLine("正在连接 PLC...");
            plc.Open(); // 这一步如果不报错,恭喜你,网通了!
            Console.WriteLine("连接成功!");

            // ==========================================
            // 3. 读数据 (Read) —— 最爽的字符串寻址
            // ==========================================
            
            // 场景 A: 读取 DB1 块里的第 0 个地址 (假设是温度,浮点数)
            // 语法解析: "DB1.DBD0"
            // DB1 = 1号数据块
            // DBD = Double Word (4字节,对应 C# float/int)
            // 0   = 偏移量
            var temp = plc.Read("DB1.DBD0");
            // 注意:S7.Net 读回来的是 object,通常需要转一下类型
            float temperature = ((uint)temp).ConvertToFloat(); 
            Console.WriteLine($"当前温度: {temperature} ℃");

            // 场景 B: 读取 M 区的一个开关 (M100.0)
            // bool 类型
            bool isRunning = (bool)plc.Read("M100.0");
            Console.WriteLine($"设备运行状态: {isRunning}");

            // ==========================================
            // 4. 写数据 (Write)
            // ==========================================
            
            // 场景 C: 修改温度设定值为 50.5
            // 注意:写入时不需要转换类型,库会自动处理
            plc.Write("DB1.DBD0", 50.5f);
            Console.WriteLine("温度设定值已修改为 50.5");

            // 场景 D: 启动设备 (把 M100.0 置为 true)
            plc.Write("M100.0", true);
            Console.WriteLine("启动指令已发送");

        }
        catch (Exception ex)
        {
            Console.WriteLine($"发生错误: {ex.Message}");
        }
        finally
        {
            // ==========================================
            // 5. 断开连接 (养成好习惯)
            // ==========================================
            plc.Close();
        }

        Console.ReadKey();
    }
}

 

详解:

DB1.DBD0 是 S7 寻址字符串。

字符串写法含义对应 C# 类型字节长度备注
DB1.DBX0.0DB1块,第0个字节的第0位bool1位X 代表 Bit (位)
DB1.DBB0DB1块,第0个字节byte1字节B 代表 Byte (字节)
DB1.DBW0DB1块,起始0,读2个字节ushort / short2字节W 代表 Word (字)
DB1.DBD0DB1块,起始0,读4个字节uint / float4字节D 代表 Double Word (双字)

读开关/信号 -> 用 DBX (或者 M10.0)

读整数 (short) -> 用 DBW

读小数 (float) -> 用 DBD

 
 
 

四、三菱 MC 协议 (MC Protocol)

 

4.1 关键概念

 

“Qna-3E 帧,二进制 (Binary) 模式。”

  • 3E:三菱的协议分为 A系列 (1E)Q/L/FX5U系列 (3E/4E),现在主流是用 3E。
  • Binary:因为二进制传输数据量小,速度快。ASCII 模式是把数据转成文本发,慢且笨。
  • 端口号: 西门子固定 102,但三菱没有固定端口!通常是 60005002,这完全取决于电气工程师。

坑点提示: 三菱 PLC 需要在软件(GX Works)里配置“以太网端口设置”,开启 MC 协议并设置为 二进制 模式,否则连不上。

 

4.2 库的选择

 

  • HslCommunication:国内最强,但收费(有学生版/试用版)。代码极其优雅。
  • 手写简易版:因为 MC 协议比较规整,很多教程会教你手拼报文。

 

4.3 实战代码

 

安装 Nuget:搜索 HslCommunication 并安装。

HslCommunication 自带的 demo 程序来模拟一个三菱服务器。

using HslCommunication;
using HslCommunication.Profinet.Melsec; // 引用三菱专用命名空间
using System;

class Program
{
    static void Main(string[] args)
    {
        // ==========================================
        // 1. 创建对象 (选对协议!)
        // ==========================================
        // MelsecMcNet 代表:三菱(Melsec) MC协议(Mc) 网络版(Net)
        // 这是一个“Qna-3E 二进制”客户端
        MelsecMcNet mitsubishi = new MelsecMcNet("192.168.1.10", 6000);

        // ==========================================
        // 2. 连接 PLC
        // ==========================================
        OperateResult connect = mitsubishi.ConnectServer();
        if (connect.IsSuccess)
        {
            Console.WriteLine("连接三菱 PLC 成功!");
        }
        else
        {
            Console.WriteLine($"连接失败: {connect.Message}");
            return; // 连不上就别往下走了
        }

        // ==========================================
        // 3. 读写数据 (地址写法是关键)
        // ==========================================

        // 场景 A: 读取 D 寄存器 (相当于西门子的 DBW)
        // D100 是一个 short (16位整数)
        short d100 = mitsubishi.ReadInt16("D100").Content;
        Console.WriteLine($"D100 的值: {d100}");

        // 场景 B: 读取 M 软元件 (相当于西门子的 M点)
        // M100 是一个开关 (bool)
        bool m100 = mitsubishi.ReadBool("M100").Content;
        Console.WriteLine($"M100 状态: {m100}");

        // 场景 C: 写入数据
        // 把 12345 写入 D200
        mitsubishi.Write("D200", (short)12345);
        Console.WriteLine("已写入 D200");

        // 场景 D: 启动设备 (置位 M200)
        mitsubishi.Write("M200", true);
        Console.WriteLine("已启动设备 (M200 = ON)");
        
        // ==========================================
        // 4. 批量读取 (提高效率的法宝)
        // ==========================================
        // 一次读 D100 到 D109 (共10个)
        // 也就是 20 个字节 (每个 short 占 2 字节)
        byte[] buffer = mitsubishi.Read("D100", 10).Content;
        // 自己处理 byte[]...
        
        // 5. 断开
        mitsubishi.ConnectClose();
        Console.ReadKey();
    }
}

 

代码详解:

 

代号全称作用对应 C# 类型读写特性
DData Register (数据寄存器)存数字 (温度、产量、速度)short / ushort可读可写 (最常用)
MInternal Relay (中间继电器)存状态 (开关、标志位)bool可读可写 (最常用)
XInput (输入点)物理按钮、传感器信号bool只读 (不能写!)
YOutput (输出点)控制电机、灯泡bool可读可写
WLink Register (字链接)也是存数字的 (稍微少用)short可读可写

注意:

  • 如果你想读 32位整数 (int)浮点数 (float) 怎么办?
  • 三菱没有专门的 float 地址。它占用 2 个连续的 D。比如 float 值存在 D100,其实它是占用了 D100 和 D101。代码写法:mitsubishi.ReadFloat("D100")

 

五、总结

 

通讯的目的,就是为了把孤立的机器,变成智能的系统。私有协议,让通讯更快、更透明。