项目需要用C#写一个上位机,用Modbus/TCP协议与PLC通信,控制伺服电机的启停、转速等。D:\Code\C#\ConsoleApp1
待续。。。
“启动”的代码如下,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系列
名称 | 对应的台达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页,可以看出,M30
在000~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分别表示从站地址和线圈地址,类型分别为byte
和ushort
,vlaue
为布尔型,其值为true
orfalse
。给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);函数原型
}
翻译自 DELTA DVP系列PLC官方文档
通信数据框架(Communication Data Frame)如下:
解释如下:
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 |
例子
: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 Name | Example(Hex) |
---|---|
Heading | 3A |
Slave Address | 01 |
Command code | 03 |
Starting Address Hi | 06 |
Starting Address Lo | 14 |
Number of Points Hi | 00 |
Number of points Lo | 08 |
Error Check (LRC) | DA |
End character 1 | CR |
End character 2 | LF |
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 Name | Example(Hex) |
---|---|
Slave Address | 01 |
Command code | 03 |
Bytes Count | 10 |
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的设备地址