C#与西门子PLC1500的ModbusTcp服务器通讯4--搭建ModbusTcp客户端 ...

打印 上一主题 下一主题

主题 887|帖子 887|积分 2661

 1、客户端选择

客户端可以是一个程序或一个设备,这里我以C#WINFORM程序来实现客户机与PLC的Modbustcp服务器通讯,开发环境是VS2019,.NET Framework版本是4.7.2
2、创建winform程序




 创建类库

 


编写C#各种类的转换库,该库由我提供,不消费心,文章最后提供。
项目引入这个类库 
 


3、引入Nmodbus4协议

找到项目,找到引用,右键“管理nuget程序”,在下面临话框操纵



 4、界面结构如下:

结构中用到的是下拉框combobox,文本框textbox,按钮button,标签label

 这个IP地址和端口号是与这里对应



 


5、窗体定义两个变量,并引入对应的下令空间

        ModbusIpMaster master = null;//modbus对象
        TcpClient tcpClient = null;//tcp客户端对象


6、毗连按钮代码

  1. private void btnOpen_Click(object sender, EventArgs e)
  2.         {
  3.             string ip = txtIPAddress.Text.Trim();
  4.             bool t = IsIP(ip);
  5.             if (t)
  6.             {
  7.                 try
  8.                 {
  9.                     int port = int.Parse(txtPort.Text.Trim());
  10.                     tcpClient = new TcpClient();
  11.                     tcpClient.Connect(ip, port);//连接到主机
  12.                     master = ModbusIpMaster.CreateIp(tcpClient);//Ip 主站
  13.                     master.Transport.ReadTimeout = 1000;//读超时
  14.                     master.Transport.WriteTimeout = 1000;//写超时
  15.                     master.Transport.Retries = 3;//尝试重复连接次数
  16.                     master.Transport.WaitToRetryMilliseconds = 200;//尝试重复连接间隔
  17.                     lblMessage.Text = "连接成功!";
  18.                     btnOpen.Enabled = false;
  19.                 }
  20.                 catch (Exception ex)
  21.                 {
  22.                     MessageBox.Show("连接失败," + ex.Message);
  23.                 }
  24.             }
  25.             else
  26.             {
  27.                 MessageBox.Show("无效的ip地址!");
  28.             }
  29.         }
复制代码

 7、读取的代码--ushort范例

本例子中只用到了读取保存寄存器这个功能码,即ReadHoldingRegisters(从站地址,开始地址,寄存器数量)
  1.   /// <summary>
  2.         /// 读取
  3.         /// </summary>
  4.         /// <param name="sender"></param>
  5.         /// <param name="e"></param>
  6.         private void myread_Click(object sender, EventArgs e)
  7.         {
  8.             //由于NModbus4读取到寄存器的数据都是ushort类型
  9.             //功能码
  10.             string readType = cboReadTypes.Text.Trim();
  11.             //从站地址
  12.             byte slaveAddr = byte.Parse(txtRSlaveId.Text.Trim());
  13.             //开始地址
  14.             ushort startAddr = ushort.Parse(txtRStartAddress.Text.Trim());
  15.             //读取数量
  16.             ushort readCount = ushort.Parse(txtRCount.Text.Trim());
  17.             switch (readType)
  18.             {
  19.                 case "读线圈":
  20.                     bool[] blVals = master.ReadCoils(slaveAddr, startAddr, readCount);
  21.                     txtReadDatas1.Text = string.Join(",", blVals.Select(b => b ? "1" : "0"));
  22.                     break;
  23.                 case "读输入线圈":
  24.                     bool[] blInputVals = master.ReadInputs(slaveAddr, startAddr, readCount);
  25.                     txtReadDatas1.Text = string.Join(",", blInputVals.Select(b => b ? "1" : "0"));
  26.                     break;
  27.                 case "读保持寄存器":
  28.                     //情况1:ushort到ushort类型:即读取无符号的整数,如23,89,处理方法是:原封不动
  29.                     //ushort[] uDatas = master.ReadHoldingRegisters(slaveAddr, startAddr, readCount);
  30.                     //txtReadDatas.Text = string.Join(",", uDatas);
  31.                     //功能码
  32.                     string dataType = cmddatatype.Text.Trim();
  33.                     switch (dataType)
  34.                     {
  35.                         case "ushort":
  36.                             //利用token循环读取
  37.                             ushortctsRead = new CancellationTokenSource();
  38.                             Task.Run(new Action(() =>
  39.                             {
  40.                                 ReadUshortFromPLC(slaveAddr, startAddr, readCount);
  41.                             }), ushortctsRead.Token);
  42.                             break;
  43.                         case "short":
  44.                             //利用token循环读取
  45.                             shortctsRead = new CancellationTokenSource();
  46.                             Task.Run(new Action(() =>
  47.                             {
  48.                                 ReadShortFromPLC(slaveAddr, startAddr, readCount);
  49.                             }), shortctsRead.Token);
  50.                             break;
  51.                         case "float":
  52.                             //利用token循环读取
  53.                             floatctsRead = new CancellationTokenSource();
  54.                             Task.Run(new Action(() =>
  55.                             {
  56.                                 ReadFloatFromPLC(slaveAddr, startAddr, readCount);
  57.                             }), floatctsRead.Token);
  58.                             break;
  59.                     }  
  60.                     break;
  61.                 case "读输入寄存器":
  62.                     ushort[] uDatas1 = master.ReadInputRegisters(slaveAddr, startAddr, readCount);
  63.                     txtReadDatas1.Text = string.Join(",", uDatas1);
  64.                     break;
  65.             }
  66.         }
复制代码
这里要注意,
NModbus4读取到寄存器的数据都是ushort范例
NModbus4读取到寄存器的数据都是ushort范例
代码中用到ReadUshortFromPLC方法,ReadShortFromPLC方法,ReadFloatFromPLC方法在本文最后链接都会提供
运行程序,毗连成功,读取数据

 注意这里,从站地址一般都是1,除非你改了,开始地址是0,表示寄存器的起始地址,数量是3,表示读取3个寄存器数量,也就是前面3个变量,m1-speed,m1-duaror,m1-level

 这里为什么数量不能是4,因为第4个变量是real,它占2个寄存器,即占4个字节,它不是ushort范例,这里地址也不能是%DB3.DBW4这种写法,这不是S7协议读取变量,是MODBUS读取寄存器,两者不一样的,别糊涂了,各位长老。
8、读取的代码--float范例


 许多人搞不清楚这个开始地址和数量,这个开始地址是Modbus的地址,Modbus地址编号从0开始,因此8个变量的地址就是0,1,2,3,4,5,6,7,数量是指要读取的寄存器个数,word占一个,real占2个,这里很难理解,比较绕比较晕,一个是PLC地址,一个是MODBUS地址
我们要读的温度是第3个寄存器,它是real范例,占2个寄存器数量
如果要读取“摩头2温度”,怎么读了?

 
 各人想想,为什么开始地址是8?
9、写入的代码--ushort范例

  1. /// <summary>
  2.         /// 写入
  3.         /// </summary>
  4.         /// <param name="sender"></param>
  5.         /// <param name="e"></param>
  6.         private void btnWrite_Click(object sender, EventArgs e)
  7.         {
  8.             //功能码
  9.             string writeType = cboWriteTypes.Text.Trim();
  10.             //从站地址
  11.             byte slaveAddr = byte.Parse(txtWSlaveId.Text.Trim());
  12.             //开始地址
  13.             ushort startAddr = ushort.Parse(txtWStartAddress.Text.Trim());
  14.             //数量
  15.             //实际数量
  16.             string objWriteVals = "";
  17.             string dataType = cmddatatype2.Text.Trim();
  18.             switch (dataType)
  19.             {
  20.                 case "ushort":
  21.                     objWriteVals = txtWriteDatas1.Text.Trim();
  22.                     break;
  23.                 case "short":
  24.                     objWriteVals = txtWriteDatas2.Text.Trim();
  25.                     break;
  26.                 case "float":
  27.                     objWriteVals = txtWriteDatas3.Text.Trim();
  28.                     break;
  29.             }
  30.             ushort writeCount = ushort.Parse(txtWCount.Text.Trim());
  31.             ushort objWCount = (ushort)objWriteVals.Split(',').Length;
  32.             //实际数量与要求数量不一致,不允许操作
  33.             if (writeCount != objWCount)
  34.             {
  35.                 MessageBox.Show("写入值的数量不正确!");
  36.                 return;
  37.             }
  38.             string vals = objWriteVals;
  39.             switch (writeType)
  40.             {
  41.                 case "写单线圈":
  42.                     bool blVal = vals == "1" ? true : false;
  43.                     try
  44.                     {
  45.                         master.WriteSingleCoil(slaveAddr, startAddr, blVal);
  46.                         MessageBox.Show("【单线圈】写入成功!");
  47.                     }
  48.                     catch (Exception ex)
  49.                     {
  50.                         MessageBox.Show(ex.Message);
  51.                     }
  52.                     break;
  53.                 case "写单保持寄存器":
  54.                     ushort uVal01 = ushort.Parse(vals);
  55.                     try
  56.                     {
  57.                         master.WriteSingleRegister(slaveAddr, startAddr, uVal01);
  58.                         MessageBox.Show("【单保持寄存器】写入成功!");
  59.                     }
  60.                     catch (Exception ex)
  61.                     {
  62.                         MessageBox.Show(ex.Message);
  63.                     }
  64.                     break;
  65.                 case "写多线圈":
  66.                     bool[] blVals = vals.Split(',').Select(s => s == "1" ? true : false).ToArray();//bool数组
  67.                     try
  68.                     {
  69.                         master.WriteMultipleCoils(slaveAddr, startAddr, blVals);
  70.                         MessageBox.Show("【多线圈】写入成功!");
  71.                     }
  72.                     catch (Exception ex)
  73.                     {
  74.                         MessageBox.Show(ex.Message);
  75.                     }
  76.                     break;
  77.                 case "写多保持寄存器":
  78.                     try
  79.                     {
  80.                         //功能码
  81.                         //string dataType = cmddatatype2.Text.Trim();
  82.                         switch (dataType)
  83.                         {
  84.                             case "ushort":
  85.                                 情况1:写入无符号的整数,即写入ushort数据,如写入33,44
  86.                                 ushort[] uVals01 = vals.Split(',').Select(s => ushort.Parse(s)).ToArray();
  87.                                 master.WriteMultipleRegisters(startAddr, uVals01);
  88.                                 break;
  89.                             case "short":
  90.                                 //情况2:写入有符号的整数,即写入short数据,如写入-133,-65,98等,处理方法是:short[]=>byte[]=>ushort[],情况2包括了情况1
  91.                                 short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
  92.                                 byte[] y2 = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
  93.                                 ushort[] ushorts2 = UShortLib.GetUShortArrayFromByteArray(y2);
  94.                                 master.WriteMultipleRegisters(startAddr, ushorts2);
  95.                                 MessageBox.Show("【short类型数据】写入成功!");
  96.                                 break;
  97.                             case "float":
  98.                                 //情况3:写入有符号的小数,即写入float数据,如写入-6.3,-2.65,56.893,51,-465等,处理方法是:float[]=>byte[]=>ushort[],情况3包括了情况2和情况1
  99.                                 float[] uVals03 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
  100.                                 byte[] y3 = ByteArrayLib.GetByteArrayFromFloatArray(uVals03);
  101.                                 ushort[] ushorts3 = UShortLib.GetUShortArrayFromByteArray(y3);
  102.                                 master.WriteMultipleRegisters(startAddr, ushorts3);
  103.                                 MessageBox.Show("【float类型数据】写入成功!");
  104.                                 break;
  105.                         }
  106.                         情况2:写入有符号的整数,即写入short数据,如写入-133,-65,98等,处理方法是:short[]=>byte[]=>ushort[],情况2包括了情况1
  107.                         //short[] uVals02 = vals.Split(',').Select(s => short.Parse(s)).ToArray();
  108.                         //byte[] y = ByteArrayLib.GetByteArrayFromShortArray(uVals02);
  109.                         //ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
  110.                         //master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);
  111.                         情况3:写入有符号的小数,即写入float数据,如写入-6.3,-2.65,56.893,51,-465等,处理方法是:float[]=>byte[]=>ushort[],情况3包括了情况2和情况1
  112.                         //float[] uVals02 = vals.Split(',').Select(s => float.Parse(s)).ToArray();
  113.                         //byte[] y = ByteArrayLib.GetByteArrayFromFloatArray(uVals02);
  114.                         //ushort[] ushorts = UShortLib.GetUShortArrayFromByteArray(y);
  115.                         //master.WriteMultipleRegisters(slaveAddr, startAddr, ushorts);
  116.                         MessageBox.Show("【多保持寄存器】写入成功!");
  117.                     }
  118.                     catch (Exception ex)
  119.                     {
  120.                         MessageBox.Show(ex.Message);
  121.                     }
  122.                     break;
  123.             }
  124.         }
复制代码

 写入成功,同时读取的也是刚才写的值,在博途的监控表中看到

10、写入的代码--float范例


 

写入负数


 我们向“摩头2温度”这个寄存器写入数据

再看博途中的数据
 



11、小结

客户端创建tcp client对象,然后modbus利用tcp对象创建modbus通讯,然后通过不同数据范例读写PLC数据,成功了
代码链接:
链接:https://pan.baidu.com/s/1aCqv3eSX-7SXAdGtrGNpTw 
提取码:kyqo  






免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表