C#实现Modbus协议与PLC通信

本文介绍了如何使用C#编程,通过Modbus/TCP协议与台达DELTADVP系列PLC进行通信,控制伺服电机的启停。项目中,利用开源库NModbus实现与PLC的连接,并详细解析了PLC的地址表和通信协议,特别是针对M30地址的启停控制。代码示例展示了如何启动和停止电机,以及通信数据帧的结构和解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目需要用C#写一个上位机,用Modbus/TCP协议与PLC通信,控制伺服电机的启停、转速等。D:\Code\C#\ConsoleApp1

1. 获取PLC的IP地址

待续。。。

2.

“启动”的代码如下,btn_stop_Click 是“启动”这个按钮对应的响应函数,启动一个新线程,实际的代码在StopEnginer函数中实现

        private void btn_stop_Click(object sender, EventArgs e) //控制电机停止
        {
            r_timer.Stop();
            t_timer.Stop();
            Thread t = new Thread(StopEnginer) { IsBackground = true };
            t.Start();
        }
private void StopEnginer()
        {
            string cmd_str = "0105081E0000";
            //01从站地址salve address
            //05功能码 写入单个线圈函数WriteSingleCoil(开源通讯库Nmodbus)
            //Nmodbus 地址https://github.com/NModbus/NModbus
            //081E是写入单个线圈地址,参考台达PLC应用手册161页DVP装置通讯地址和x工给的excel表
            //081E = M30是控制启停的地址
            //0000后四位是写入的值
            // string cmd_str = (tbx_cmdString.Text).ToUpper();

            ushort[] returnValues = new ushort[30];
            //将字符串cmd_str切割成地址、功能码、写入地址、写入值
            byte address = Convert.ToByte(cmd_str.Substring(0, 2), 16);
            byte Function = Convert.ToByte(cmd_str.Substring(2, 2), 16);
            ushort start = Convert.ToUInt16(cmd_str.Substring(4, 4), 16);
            ushort values = Convert.ToUInt16(cmd_str.Substring(8, 4), 16);
            Console.WriteLine("这是故障台停止程调用");
            try
            {
                bool value_TrueOrFalse = Convert.ToUInt16(values) == 0xFF00;
           
                //调用WriteSingleCoil函数
                master.WriteSingleCoil(address, start, value_TrueOrFalse);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

代码解释如下,需要Modbus协议、PLC通信协议的相关知识,以及PLC地址表。项目用的PLC型号为台达DELTA DVP系列

1. PLC地址表

名称对应的台达DVP系列PLC地址
转速设定D520
扭矩设定D502
实时转速D46
实时扭矩D26
启停M30
扭力计量程D410
转速量程D414
加载器量程D422
PLC数字量D418
转速系数D426
伺服加速时间D504
伺服减速时间D506
加减速系数D508
其中,控制电机启停的PLC地址是M30。台达DVP系列PLC中,S, X, D, M等表示PLC装置(应该与PLC内部结构有关??分别代表不同的线圈区域?不是很确定),总之知道地址M30就OK了。
现在去查M30对应的十六进制编码地址是多少,因为用C给PLC发送信号时,需要以十六进制数形式发送。下表来自台达PLC应用手册(厚达743页)中的第161页,可以看出,M30000~255这一区域,该区域的编码以十六进制08开头,进一步,30的十六进制为1e,所以,PLC地址M30的十六进制地址是0x081e,其中0x表示十六进制。类似的,256~511这一区域的地址,其编码以十六进制09开头,256~511分别对应0x00 ~ 0xFF。例如,M267,它的十六进制地址计算方式为 :267-256=11,11的十六进制为0b,因此M267的十六进制地址为0x090b
在这里插入图片描述
本项目PLC与上位机的通讯用到了一个开源通讯库NModbus ,下载地址https://github.com/NModbus/NModbus。该通信库官方提供的手册NModbus
在VS2017中,可以通过以下方式下载安装:
在这里插入图片描述
在这里插入图片描述

下载后,在相应的处理业务的类中定义成员变量

private NModbus.IModbusMaster master;

建立连接,用NModbus.ModbusFactory.CreateMaster创建主站,返回IModbusMaster类对象,对PLC地址的读写主要通过该类提供的方法实现

    public bool connect(string ip,int port)
    {
        if (hasConnected) return true;
       
        var tcp=new TcpClient();
        var result=tcp.BeginConnect(ip,port,null,null);
        result.AsyncWaitHandle.WaitOne(5000);
        if (result.IsCompleted)
        {
            var factory = new NModbus.ModbusFactory();
            master = factory.CreateMaster(tcp);
            hasConnected = true;
        }
        else
        {
            throw new TimeoutException("连接超时!");
        }
        return hasConnected;
    }

之后,调用IModbusMaster的成员函数就可以通信了,可以看到IModbusMaster 的部分常用API函数如下。
在这里插入图片描述
本例中,将使用WriteSingleCoil函数往M30地址中写入数据,控制电机的启动和停止。WriteSingleCoil的函数原型为void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);,三个参数,slaveAddress,coilAddress分别表示从站地址和线圈地址,类型分别为byteushortvlaue为布尔型,其值为trueorfalse。给PLC的M30地址发送信号启动电机的代码如下。

public void start() //给PLC的M30地址发送信号启动电机
    {
        check();
        byte slaveAddress = 1; //单元标识符
        ushort coilAddress = 0x81e; //起始地址
        bool value = true;
        master.WriteSingleCoil(slaveAddress, coilAddress, value);
        // void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);函数原型
    }

2. DELTA DVP系列PLC通信协议详解

翻译自 DELTA DVP系列PLC官方文档
通信数据框架(Communication Data Frame)如下:
在这里插入图片描述
解释如下:

  • STX:起始字符,为 “:”
  • ADR1 和 ADR0: 通信地址,由2个ASCII码组成,有效的通信地址范围为0…31。
  • CMD1和CMD0:命令码,由2个ASCII码组成,用来告诉PLC该执行什么任务。该系列PLC的命令码取了Modbus协议的部分功能码,Modbus协议如下表所示。完整的Modbus协议见xxxx。表中的Description 是PLC对应的部件

Code | Name | Translation | Function | Description

  • | :-: | -: | -: | -:
    01 | Read Coil Status| 读取线圈状态 | 取得一组逻辑线圈的当前状态(on/off) | S, Y, M, T, C
    02 | Read Input Status | 读取输入状态 | 取得一组开关输入的当前状态(on/off) | S,X,Y,M,T,C
    03 | Read Holding Registers | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制值 | T,C,D
    05 | Force Single Coil | 强制单线圈| 强制一个逻辑线圈的通断状态| S,Y,M,T,C
    06| Preset Signle Register | 预置单寄存器|把具体二进制装入一个保持器| T,C,D
    15|Force Multiple Coils | 强置多线圈 | 强置一串连续逻辑线圈的通断 | S,Y,M,T,C
    16|Preset Multiple Register | 预置多寄存器|把具体的二进制值装入一串连续的保持 | T,C,D
    17 | Report Slave ID | None

  • DATA(0)~DATA(n-1): 所要发送的数据内容,DATA字符的格式取决于功能码。数据的内容包括n个8位数据,由2n个ASCII码组成(一个8-bit的数据由2个ASCII码表示),n<=37,因此DATA最大可包含74个ASCII码

例子:Reading Coils T20~T27 from slave device 01,从从站设备地址01开始读取线圈T20-T27中的内容,一共8个字节(8 words)
PC发给PLC的代码
PC→PLC “:01 03 06 14 00 08 DA CR LF”
代码解析如下:

Field NameExample(Hex)
Heading3A
Slave Address01
Command code03
Starting Address Hi06
Starting Address Lo14
Number of Points Hi00
Number of points Lo08
Error Check (LRC)DA
End character 1CR
End character 2LF

PLC发送给PC的代码:
:01 03 10 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 B8 CR LF
代码解析如下:

Field NameExample(Hex)
Slave Address01
Command code03
Bytes Count10
Data Hi(T20)00
Data Lo (T20)01
Data Hi (T21)00
Data Lo (T21)02
Data Hi (T22)00
Data Lo (T22)03
Data Hi (T23)00
Data Lo (T23)04
Data Hi (T24)00
Data Lo (T24)05
Data Hi (T25)00
Data Lo (T25)06
Data Hi (T26)00
Data Lo (T26)07
Data Hi (T27)00
Data Lo (T27)08
Error Check (LRC)C8

LRC(Longitudinal Redundancy Check)纵向冗余度检查,

DELTA DVP系列的PLC的设备地址

/// /// 读保持寄存器03 /// /// 数据读取延迟 /// 设备从站地址 /// 数据起始地址 /// 寄存器数量 /// 返回的寄存器数值 /// 返回异常描述 /// 是否读取成功 public bool ReadHoldReg(int timeout, byte slaveAddress, ushort startAddress, ushort regCountIn, out ushort[] holdRegs, out ModbusException ex) /// /// 读输入寄存器04 /// /// 数据读取延迟 /// 设备从站地址 /// 数据地址 /// 寄存器数量 /// 返回的寄存器数值 /// 返回异常描述 /// 是否读取成功 public bool ReadInputReg(int timeout, byte slaveAddress, ushort startAddress, ushort regCountIn, out ushort[] InputRegs, out ModbusException ex) /// /// 写单寄存器(06功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteSingleReg(int timeout, byte slaveAddress, ushort regAdr, ushort regValue, out ModbusException ex) /// /// 写单个线圈(05功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteSingleCoil(int timeout, byte slaveAddress, ushort regAdr, ushort ONorOFF, out ModbusException ex) /// /// 写多寄存器(10功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器起始地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteMutilReg(int timeout, byte slaveAddress, ushort regAdr, ushort[] regValue, out ModbusException ex) ....................
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值