Files
ClientServerProject/MelsecReadMe.md
2017-05-25 08:52:11 +08:00

337 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 三菱PLC的详细的数据访问功能
<ul>
<li>技术支持QQ群<strong>592132877</strong></li>
<li>邮箱:<strong>hsl200909@163.com</strong></li>
<li>概述:接下来详细讲解读写数据及字符串的代码</li>
</ul>
#### 配置三菱PLC的以太网模块
<strong>环境</strong>此处以GX Works2为示例添加以太网模块型号为<strong>QJ71E71-100</strong>,组态里添加完成后进行以太网的参数配置,此处需要注意的是:<strong style="color:#ff0000">参数的配置对接下来的代码中配置参数要一一对应</strong>
![](https://github.com/dathlin/C-S-/raw/master/img/plc_melsec_1.jpg)
<span style="font-weight:bold;color:#ff0000">注意在PLC的以太网模块的配置中无法设置网络号为0也无法设置站号为0 所以此处均设置为1在C#程序中也使用上述的配置在代码中均配置为0如果您自定义设置为网络2 站号8那么在代码中就要写对应的数据。如果仍然通信失败重新测试00。</span>
<strong>打开设置</strong>在上图中的打开设置选项进行其他参数的配置下图只是举了一个例子开通了4个端口来支持读写操作
![](https://github.com/dathlin/C-S-/raw/master/img/plc_melsec_2.jpg)
<strong>端口号设置规则:</strong>
<ul>
<li>为了不与原先存在的系统发生冲突,您在添加自己的端口时尽量使用您自己的端口。</li>
<li>如果读写都需要,尽可能的将读取端口和写入端口区分开来,这样做比较高性能。</li>
<li>如果您的网络状态不是特别稳定读取端口使用2个一个受阻切换另一个读取可以提升系统的稳定性。</li>
</ul>
本文档仅作组件的测试,所以只用了一个端口作为读写。如果你的程序也使用了一个端口,那么你在读取数据时候, 刚好也在写入(异步操作可能发生这样的情况),那么写入会失败!
三菱PLC的数据主要由两类数据组成位数据和字数据在位数据中例如X,Y,M,L都是位数据字数据例如D,W。 两类的数据在读取解码上存在一点小差别。事实上也可以先将16个M先赋值给一个D读取D数据再进行解析 在读取M的数量比较多的时候这样操作效率更高
#### 初始化访问PLC对象
如果想使用本组件的数据读取功能,必须先初始化数据访问对象,根据实际情况进行数据的填入。 下面仅仅是测试中的数据:
<pre>
<code>
private MelsecNet melsec_net = new MelsecNet();
private void Form1_Load(object sender, EventArgs e)
{
//初始化
melsec_net.PLCIpAddress = System.Net.IPAddress.Parse("192.168.0.7");//IP
melsec_net.PortRead = 6000;//端口
melsec_net.PortReadBackup = 6001;//备用读端口,也可以不指定,默认负数,不会切换负数端口
melsec_net.PortWrite = 6000;//写入端口,也可以和读取一样
melsec_net.NetworkNumber = 0;//网络号
melsec_net.NetworkStationNumber = 0;//网络站号
melsec_net.ConnectTimeout = 500;//连接超时时间
      }
</code>
</pre>
说明对象应该放在窗体类下面此处仅仅针对读取一台设备的plc也可以在访问的方法中实例化局部对象 初始化数据然后读取该对象几乎不损耗内存内存垃圾由CLR进行自动回收。此处测试方便窗体的多个按钮均连接同一台PLC 设备,所以本窗体实例化一个对象即可。
#### X,Y,M,L位数据的读写说明
本小节将展示四种位数据的读取虽然更多的时候只是读取D数据即可或者是将位数据批量挪到D数据中 但是在此处仍然进行介绍单独的读取X,Y,M,L由于这四种读取手法一致故针对M数据进行介绍其他的您可以自己测试。
如下方法演示读取了M200-M209这10个M的值注意读取长度必须为偶数即时写了奇数也会补齐至偶数<strong style="color:#ff0000">读取和写入的最大长度为7168否则报错。如需实际需求确实大于7168的请分批次读取。</strong><br />
返回值解析如果读取正常则共返回10个字节的数据以下示例数据
<pre>
<code>
private void button100_Click(object sender, EventArgs e)
{
//后台循环读取PLC数据 M200开始10个字 也即是M200-M209
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.M, 200, 10);
if (read.IsSuccess)
{
textBox2.Text = "M200:" + (read.Content[0] == 1 ? "通" : "断");
//textBox2.Text = "M201:" + (read.Content[1] == 1 ? "通" : "断");
//textBox2.Text = "M202:" + (read.Content[2] == 1 ? "通" : "断");
//textBox2.Text = "M203:" + (read.Content[3] == 1 ? "通" : "断");
//textBox2.Text = "M204:" + (read.Content[4] == 1 ? "通" : "断");
//textBox2.Text = "M205:" + (read.Content[5] == 1 ? "通" : "断");
//textBox2.Text = "M206:" + (read.Content[6] == 1 ? "通" : "断");
//textBox2.Text = "M207:" + (read.Content[7] == 1 ? "通" : "断");
//textBox2.Text = "M208:" + (read.Content[8] == 1 ? "通" : "断");
//textBox2.Text = "M209:" + (read.Content[9] == 1 ? "通" : "断");
}
else
{
//失败读取,显示失败信息
MessageBox.Show(read.ToMessageShowString());
}
}
private void button5_Click(object sender, EventArgs e)
{
//此处写入后M200:通 M201:断 M202:断 M203:通
bool[] values = new bool[] { true, false, false, true };
OperateResultBytes write = melsec_net.WriteIntoPLC(MelsecDataType.M, 200, values);
if(write.IsSuccess)
{
textBox2.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
</code>
</pre>
错误说明:有可能因为站号网络号没有配置正确返回有错误代号没有错误信息, 也有可能因为网络问题导致没有连接上,此时会有连接不上的错误信息。
下面展示的是后台线程循环读取的情况,事实上在实际的使用过程中经常会碰见的情况。下面的方法需要 放到单独的线程中同理访问D数据时也是按照下面循环就行此处不再赘述。
<pre>
<code>
//后台循环读取PLC数据 M200开始10个字 也即是M200-M209
while (true)
{
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.M, 200, 10);
if (read.IsSuccess)
{
//成功读取,委托显示
textBox2.BeginInvoke(new Action(delegate
{
textBox2.Text = "M201:" + (read.Content[1] == 1 ? "通" : "断");
}));
}
else
{
//失败读取,应该对失败信息进行日志记录,不应该显示,测试访问时才适合显示错误信息
LogHelper.save(read.ToMessageShowString());
}
System.Threading.Thread.Sleep(1000);//决定了访问的频率
}
</code>
</pre>
#### D,W字数据的读写操作
此处读取针对中间存在整数数据的情况,因为两者读取方式相同,故而只演示一种数据读取, <strong style="color:#ff0000">使用本组件读取数据一次最多读取或写入960个字超出则失败。 如果读取的长度确实超过限制,请考虑分批读取。</strong>
<pre>
<code>
private void button1_Click(object sender, EventArgs e)
{
//读取PLC数据 D6000开始21个字 也即是D6000-D6020 最大长度980
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.D, 6000, 21);
if(read.IsSuccess)
{
//成功读取
textBox2.Text = "D6000:" + melsec_net.GetShortFromBytes(read.Content, 0);
//textBox2.Text = "D6001:" + melsec_net.GetShortFromBytes(read.Content, 1);
//textBox2.Text = "D6002:" + melsec_net.GetShortFromBytes(read.Content, 2);
//textBox2.Text = "D6003:" + melsec_net.GetShortFromBytes(read.Content, 3);
//textBox2.Text = "D6004:" + melsec_net.GetShortFromBytes(read.Content, 4);
//================================================================================
//这两种方式一样的,
//textBox2.Text = "D6000:" + BitConverter.ToInt16(read.Content, 0);
//textBox2.Text = "D6001:" + BitConverter.ToInt16(read.Content, 2);
//textBox2.Text = "D6002:" + BitConverter.ToInt16(read.Content, 4);
//textBox2.Text = "D6003:" + BitConverter.ToInt16(read.Content, 6);
//textBox2.Text = "D6004:" + BitConverter.ToInt16(read.Content, 8);
}
else
{
//失败读取
MessageBox.Show(read.ToMessageShowString());
}
}
private void button2_Click(object sender, EventArgs e)
{
short[] values = new short[4] { 1335, 8765, 1234, 4567 };//决定了写多少长度的D
//写入PLC数据 D6000为1234,D6001为8765,D6002为1234,D6003为4567
OperateResultBytes write = melsec_net.WriteIntoPLC(MelsecDataType.D, 6000, values);
if(write.IsSuccess)
{
textBox2.Text = "写入成功";
}
else
{
              MessageBox.Show(write.ToMessageShowString());//显示失败原因
          }
}
</code>
</pre>
#### ASCII字符串数据的读写
在实际项目中有可能会碰到PLC存储了规格数据或是条码数据这些数据是以ASCII编码形式存在 我们需要把数据进行读取出来用于显示保存等操作。下面演示读取指定长度的条码数据数据的数据存放在D2000-D2004中 长度应该为存储条码的最大长度也即是占用了5个D<strong style="color:#ff0000">一个D可以存储2个ASCII码字符</strong>
<pre>
<code>
private void button7_Click(object sender, EventArgs e)
{
//读取字符串数据共计10个字节长度
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.D, 2000, 5);
if (read.IsSuccess)
{
//成功读取
textBox2.Text = Encoding.ASCII.GetString(read.Content);
}
else
{
//失败读取
MessageBox.Show(read.ToMessageShowString());
}
}
private void button8_Click(object sender, EventArgs e)
{
//写字符串如果写入K12345678这9个字符读取出来时末尾会补0
OperateResultBytes write = melsec_net.WriteAsciiStringIntoPLC(MelsecDataType.D, 2000, "K123456789");
if (write.IsSuccess)
{
textBox2.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
</code>
</pre>
需要注意的是如果第一次在D2000-D2004中写入了"K123456789",第二次写入了"K6666"那么读取D2000-D2004的条码数据会读取到 K666656789如果要避免这种情况<strong style="color:#ff0000">则需要在写入条码的时候,指定总长度,该长度必须为偶数, 不然也会自动补0小于该长度时自动补零大于该长度时自动截断数据</strong>具体的使用方法如下:
<pre>
<code>
private void button8_Click(object sender, EventArgs e)
{
//写字符串本次写入指定了10个长度的字符其余的D的数据将被清空是一种安全的写入方式
OperateResultBytes write = melsec_net.WriteAsciiStringIntoPLC(MelsecDataType.D, 2000, "K6666", 10);
if (write.IsSuccess)
{
textBox2.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
</code>
</pre>
#### 中文及特殊字符的读写
在需要读写复杂的字符数据时上述的ASCII编码已经不能满足要求虽然使用读写的基础方法可以实现任意数据的读写 但是此处为了方便还是提供了一个方便的方法来读写中文数据采用Unicode编码的字符 该编码下的一个字符占用一个D或W来存储。如下将演示读写方法基本用途和上述 ASCII编码的读写一致。
<pre>
<code>
private void button9_Click(object sender, EventArgs e)
{
//读中文存储在D3000-D3009
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.D, 3000, 10);
if (read.IsSuccess)
{
//解析数据
textBox2.Text = Encoding.Unicode.GetString(read.Content);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
private void button10_Click(object sender, EventArgs e)
{
//写中文 D3000-D3009该10含义为中文字符数
OperateResultBytes write = melsec_net.WriteUnicodeStringIntoPLC(MelsecDataType.D, 3000, "测试数据test", 10);
if (write.IsSuccess)
{
textBox2.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
</code>
</pre>
#### 一个实际中复杂的例子演示
实际中可能碰到的情况会很复杂,一台设备中需要上传的数据包含了温度,压力,产量,规格等等信息,在一串数据中 会包含各种各样的不同的数据上述的读取D读取M读取条码的方式不太好用所以此处做一个完整示例的演示假设我们需要读取 D4000-D4009的数据假设D4000存放了温度数据55.1℃在D中为551D4001存放了压力数据1.23MPa在D中存放为123D4002存放了 设备状态0为停止1为运行D4003存放了产量1000就是指1000个D4004备用D4005-D4009存放了规格以下代码演示如何去解析数据
<pre>
<code>
private void button29_Click(object sender, EventArgs e)
{
//解析复杂数据
OperateResultBytes read = melsec_net.ReadFromPLC(MelsecDataType.D, 4000, 10);
if (read.IsSuccess)
{
double 温度 = BitConverter.ToInt16(read.Content, 0) / 10d;//索引很重要
double 压力 = BitConverter.ToInt16(read.Content, 2) / 100d;
bool IsRun = BitConverter.ToInt16(read.Content, 4) == 1;
int 产量 = BitConverter.ToInt16(read.Content, 6);
string 规格 = Encoding.ASCII.GetString(read.Content, 10, 10);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
</code>
</pre>
#### 高并发时的高性能访问方法
实际中可能会碰到需要读取100台设备的PLC上的数据读取的数据地址是一致的如果选择开100条线程去读取将会极大的浪费系统资源且又效率低下即使使用了线程池技术也无法保证所有的数据进行同步处理。所以本系统提供了一个类来高性能的访问多台设备<strong style="color:#ff0000">注意:该类只针对访问相同数据块的设备。</strong>
<pre>
<code>
/*************************************************************************************************
*
* 以下展示一个高性能访问多台PLC数据的类即使同时访问100台设备性能也是非常高
*
* 该类没有仔细的在现场环境测试过,不保证完全可用
*
*************************************************************************************************/
HslCommunication.Profinet.MelsecNetMultiAsync MelsecMulti { get; set; }
private void MelsecNetMultiInnitialization()
{
List<System.Net.IPEndPoint> IpEndPoints = new List<System.Net.IPEndPoint>();
//增加100台需要访问的三菱设备指定所有设备IP和端口注意顺序很重要
for (int i = 1; i < 100; i++)
{
IpEndPoints.Add(new System.Net.IPEndPoint(System.Net.IPAddress.Parse("192.168.10." + i), 6000));
}
//每隔1秒钟访问一次
MelsecMulti = new HslCommunication.Profinet.MelsecNetMultiAsync(0, 0, HslCommunication.Profinet.MelsecDataType.D, 6000, 20, 700, 1000, IpEndPoints.ToArray());
MelsecMulti.OnReceivedData += MelsecMulti_OnReceivedData;//所有机台的数据都返回时触发
}
private void MelsecMulti_OnReceivedData(byte[] object1)
{
/*********************************************************************************************
*
* 正常情况下一秒触发一次object1包含了所有机台读取到的数据
* 比如每台设备读取D6000开始20个D如上述指令所示
* 那么每台设备数据长度为20*2+2=42个byte100台设备就是4200字节长度
* 也就是说object1的0-41字节是第一台设备的以此类推
* 每台设备的前两个字节都为0才说明本次数据访问正常为0x00,0x01说明连接失败其他说明说明三菱本身的异常
*
********************************************************************************************/
for (int i = 0; i < 100; i++)
{
int startIndex = i * 42;
ushort netState = BitConverter.ToUInt16(object1, startIndex);
//为0说明数据正常不为0说明网络访问失败或是指令出错
}
}
</code>
</pre>