C# 和 PLC 通信实现代码
最近因为工作的原因用到了西门子PLC,在使用过程中一直在思考上位机和PLC的通讯问题,后来上网查了一下,找到了一个专门针对S7开发的一个.net库–《S7netPlus》,PLC通讯方法比较多,所以也是在不断地学习中,以下内容如有不足之处,望大神予以指教。
公司设备一直都用的PLC做下端设备的控制,但是目前都没有专职做上位机的,而我之前对PLC又接触的比较少,做起来还是比较难的。。
查找了一堆资料后,终于找到了这个.net库,在大致学习了一下之后,总结了一下,当作自己的学习笔记。
一、开发环境准备
最近因为疫情的影响,只能呆在总公司混日子,手里没有设备,只能用博图的仿真器来测试通讯,需要安装的软件包括:
Visual Studio 2015
TIA Portal V15
S7-PLCSIMV15
NetToPLCSIM-S7
这里先放个下载连接:S7Net.dll、NetToPLCSIM、S7Net使用手册
TIA Protal(博图)&S7-PLCSIM
西门子针对于PLC专门开发的一款编程软件,相信各位肯定比我熟悉这个软件了,这里就不作过多介绍了,同时提供了S7系列的仿真软件S7-PLCSIM,这里我们就用这两个设备仿真PLC设备来测试S7NETPlus库的通讯。
博图V15.1
S7-PLCSIM
NetToPLCSIM
这个软件是用于将西门子的PLCsim映射到网络内,如果之前没有用过这个软件,建议按照后面的操作来,否则很容易出现Start server之后还是连不上仿真器。
二、开发测试
PLC配置
1、在组态好的PLC设备属性中,找到“防护与安全”–>“连接机制”中,勾选“允许来自远程对象的PUT/GET通信访问”;
2、新建DB块,同时将该DB快属性中的“优化块的访问取消”;
3、在新建的DB块中新增一些数据,完成后点击编译计算偏移量。
4、以上步骤完成后,点击开始仿真,将工程下载到仿真器中;
NetToPLCSIM配置
1、打开软件后,点击Add增加设备
2、在弹出的窗口中,Network IP Address中填入本地回环IP“127.0.0.1”(如果你是在两台设备中测试,首先保证两台设备在同一个内网中,该处IP就可以设置为运行仿真环境的IP了)
3、Plcsim IP Address中,点击后面两个点,选择软件自己识别出来的仿真器地址;
4、Plcsim Rack/Slot中Rack为机架号,Slot为插槽号,这两个可以在PLC的设备组态属性->项目信息中找到
配置完成后点击完成,这时候就可以点击Start Server开启服务了。
上面的操作一定要按照以上的步骤一步一步完成,否则很容易出现即使点Start Server显示状态为running,但是实际连接仍然连不上的情况。
另外需要注意的是,可能在打开NetToPLCSim的时候,会弹出“Port 102 is in use!”的警告,如果遇到这个情况,点击是,之后在将PLCSIM关掉重新启动一下就可以了。
创建连接
配置连接
这里使用的是S7-1215的模块,所以CpuType选择S71200,IP地址使用回环地址“127.0.0.1”,机架号和插槽号在PLC工程中查。
using S7.Net; Plc plc = new Plc(CpuType.S71200, "127.0.0.1", 0, 1);
配置完成后,使用Open()来打开,在最早的一个版本中,Open有返回值,可以通过返回值获取 ErrorCode 和 ErrorMessage,我目前使用的是最新版0.8.1.0,没有返回值,所以用try…catch来接收异常
try { plc.Open(); } catch(Exception) { Console.WriteLine($"连接到PLC设备失败:IsConnect = {plc.IsConnected},IsAvailable={plc.IsAvailable}"); return; }
连接是否成功,可以用IsConnected去判断一下。
访问数据块
连接成功后,我们就可以去访问PLC的数据块了,访问数据块,我们先尝试一下读取数据块
读取单个数据–Read
这里主要用到了DBX,DBW,DBD读取数据,其他的各位可以在查一下PLC的资料
/* 方法:public object Read(string variable) 入参:读取数据地址 出参:Object类型数据,可强制类型转换 */ var db1Bool1 = plc.Read("DB1.DBX0.0"); Console.WriteLine("DB1.DBX0.0:" + db1Bool1); bool db1Bool2 = (bool)plc.Read("DB1.DBX0.1"); Console.WriteLine("DB1.DBX0.1:" + db1Bool2); int IntVariable = (ushort)plc.Read("DB1.DBW2.0"); Console.WriteLine("DB1.DBW2.0:" + IntVariable); float RealVariable = ((uint)plc.Read("DB1.DBD4.0")).ConvertToFloat(); Console.WriteLine("DB1.DBD4.0:" + RealVariable); var dIntVariable = (uint)plc.Read("DB1.DBD8.0"); Console.WriteLine("DB1.DBD8.0: " + dIntVariable); var dWordVariable = (uint)plc.Read("DB1.DBD12.0"); Console.WriteLine("DB1.DBD12.0: " + Convert.ToString(dWordVariable, 16)); var wordVariable = (ushort)plc.Read("DB1.DBW16.0"); Console.WriteLine("DB1.DBW16.0: " + Convert.ToString(wordVariable,16));
读取批量数据块–ReadBytes
/* 方法:public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count) 入参: 1、DataType数据类型,可选择从DB块或者Memory中读取; 2、db:1:DataBlock=1,Memory=0; 3、startByteAdr:起始地址,即DB块的起始偏移量; 4、count:读取大小,该大小由读取的DB块的最后一个数据的偏移量和大小决定,这里最后一个字节WordVariable偏移量为16,数据类型为word,2个字节,因此此次读取为16+2=18个字节。 出参:Byte[],这里Byte[]的大小必然和count的大小是相同的, */ //读取数据选择从DB块中读取,db设置为1,起始地址为0,读取18个字节 var bytes = plc.ReadBytes(DataType.DataBlock, 1, 0, 18); //取字节0中的第0位 var db1Bool1 = bytes[0].SelectBit(0); Console.WriteLine("DB1.DBX0.0:" + db1Bool1); //取字节0中的第1位 bool db1Bool2 = bytes[0].SelectBit(1); ; Console.WriteLine("DB1.DBX0.1:" + db1Bool2); //跳到字节2并连续取两个字节数据 int IntVariable = S7.Net.Types.Int.FromByteArray(bytes.Skip(2).Take(2).ToArray()); Console.WriteLine("DB1.DBW2.0:" + IntVariable); //... double RealVariable = S7.Net.Types.Real.FromByteArray(bytes.Skip(4).Take(4).ToArray()); Console.WriteLine("DB1.DBD4.0:" + RealVariable); //... int dIntVariable = S7.Net.Types.DInt.FromByteArray(bytes.Skip(8).Take(4).ToArray()); Console.WriteLine("DB1.DBD8.0: " + dIntVariable); //... uint dWordVariable = S7.Net.Types.DWord.FromByteArray(bytes.Skip(12).Take(4).ToArray()); Console.WriteLine("DB1.DBD12.0: " + Convert.ToString(dWordVariable, 16)); //... ushort wordVariable = S7.Net.Types.Word.FromByteArray(bytes.Skip(16).Take(2).ToArray()); Console.WriteLine("DB1.DBW16.0: " + Convert.ToString(wordVariable, 16));
写入单个数据–Write
/* 方法:public void Write(string variable, object value) 入参: 1、string variable:写入地址 2、object value,写入数据 */ plc.Write("DB1.DBX0.0", true); plc.Write("DB1.DBD12.0", 123457);
写入多个数据–WriteBytes
/* public void WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value) 用法如同ReadBytes,这里就不在写例程了,有兴趣的可以自己研究一下 */
关闭连接
在通讯完之后,千万不要忘了关闭通讯链路哈,这里使用Close来关闭。
plc.Close();
到此这篇关于C#与PLC通讯的实现代码的文章就介绍到这了,更多相关C#与PLC通讯内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
推荐阅读
-
桥接模式的解释和代码实现
-
FPGA 实现 PCIE 捕捉计算机视频到 SFP 光端口的 UDP 输出,基于 XDMA + GTX 架构,提供 4 套工程源代码和技术支持
-
第一:C# 嵌入 Python 脚本进行图像处理并返回 C# 的构思和实现。
-
数据结构 - 堆栈和队列实现(完整代码)
-
Redis 发布和订阅简单案例的 C# (.net6) 实现
-
使用 opencv 学习:哈里斯拐角检测和 SIFT(尺度不变特征变换)算法的完整代码实现
-
Python实现具备单一目标、多目标、多尺度和自定义特征的KCF跟踪算法实例代码
-
Java 8新特性探究(十三)JavaFX 8新特性以及开发2048游戏-JavaFX历史## 跟java在服务器端和web端成绩相比,桌面一直是java的软肋,于是Sun公司在2008年推出JavaFX,弥补桌面软件的缺陷,请看下图JavaFX一路走过来的改进 从上图看出,一开始推出时候,开发者需使用一种名为JavaFX Script的静态的、声明式的编程语言来开发JavaFX应用程序。因为JavaFX Script将会被编译为Java bytecode,程序员可以使用Java代码代替。 JavaFX 2.0之后的版本摒弃了JavaFX Script语言,而作为一个Java API来使用。因此使用JavaFX平台实现的应用程序将直接通过标准Java代码来实现。 JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。 从JDK 7u6开始,JavaFx就与JDK捆绑在一起了,JavaFX团队称,下一个版本将是8.0,目前所有的工作都已经围绕8.0库进行。这是因为JavaFX将捆绑在Java 8中,因此该团队决定跳过几个版本号,迎头赶上Java 8。 ##JavaFx8的新特性 ## ###全新现代主题:Modena 新的Modena主题来替换原来的Caspian主题。不过在Application的start方法中,可以通过setUserAgentStylesheet(STYLESHEET_CASPIAN)来继续使用Caspian主题。 参考http://fxexperience.com/2013/03/modena-theme-update/ ###JavaFX 3D 在JavaFX8中提供了3D图像处理API,包括Shape3D (Box, Cylinder, MeshView, Sphere子类),SubScene, Material, PickResult, LightBase (AmbientLight 和PointLight子类),SceneAntialiasing等。Camera类也得到了更新。从JavaDoc中可以找到更多信息。 ###富文本 强化了富文本的支持 ###TreeTableView ###日期控件DatePicker 增加日期控件 ###用于 CSS 结构的公共 API
-
C语言实现大顶堆:策略和代码解析
-
14-傅里叶变换的代码实现-一、numpy实现傅里叶变换和逆傅里叶变换 1.numpy实现傅里叶变换numpy.fft.fft2实现傅里叶变换,返回一个复数数组(complex ndarray),也就是频谱图像numpy.fft.fftshift将零频率分量移到频谱中心(将左上角的低频区域,移到中心位置) 20*np.log(np.abs(fshift))设置频谱的范围。可以理解为,之前通过傅里叶变换得到复数的数组,是不能通过图像的方法展示出来的,需要转换为灰度图像(映射到[0,255]区间)需要注意的是1> 傅里叶得到低频、高频信息,针对低频、高频处理能够实现不同的目的2> 傅里叶过程是可逆的,图像经过傅里叶变换、逆傅里叶变换后,能够恢复到原始图像3> 在频域对图像进行处理,在频域的处理会反映在逆变换图像上 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) # 移动中心位置 fshift = np.fft.fftshift(f) # 调整值范围 result = 20*np.log(np.abs(fshift)) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(result,cmap=plt.cm.gray) plt.title("result") plt.axis("off") plt.show 傅里叶变换的频谱图像: 2.numpy实现逆傅里叶变换numpy.fft.ifft2实现逆傅里叶变换,返回一个复数数组(complex ndarray)numpy.fft.ifftshiftfftshift函数的逆函数,将中心位置的低频,重新移到左上角iimg = np.abs(逆傅里叶变化结果)设置值的范围,映射到[0,255]区间 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\boat.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) fshift = np.fft.fftshift(f) # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift) iimg = np.fft.ifft2(ishift) iimg = np.abs(iimg) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("iimg") plt.axis("off") plt.show 将一副图像,进行傅里叶变换和逆傅里叶变换后,进行对比(一样的) 实例:通过numpy实现高通滤波,保留图像的边缘信息 获取图像的形状rows,cols = img.shape获取图像的中心点crow,ccol = int(rows/2),int(cols/2)将频谱图像的中心区域(低频区域)设置为0(黑色)fshift[crow-30:crow+30,ccol-30:ccol+30] = 0 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\boat.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 f = np.fft.fft2(img) fshift = np.fft.fftshift(f) # 高通滤波 rows,cols = img.shape crow,ccol = int(rows/2),int(cols/2) fshift[crow-30:crow+30,ccol-30:ccol+30] = 0 # 逆傅里叶变换 ishift = np.fft.ifftshift(fshift) iimg = np.fft.ifft2(ishift) iimg = np.abs(iimg) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("iimg") plt.axis("off") plt.show 使用numpy实现高通滤波的实验结果: 二、opencv实现傅里叶变换和逆傅里叶变换 1.opencv实现傅里叶变换 返回结果 = cv2.dft(原始图像,转换标识)1> 返回结果:是双通道的,第一个通道是结果的实数部分,第二个通道是结果的虚数部分2> 原始图像:输入图像要首先转换成np.float32(img)格式3> 转换标识:flags = cv2.DFT_COMPLEX_OUTPUT,输出一个复数阵列numpy.fft.fftshift将零频率分量移到频谱中心(将左上角的低频区域,移到中心位置)调整频谱的范围,将上面频谱图像的复数数组,转换为可以显示的灰度图像(映射到[0,255]区间)返回值 = 20*np.log(cv2.magnitude(参数1,参数2))1> 参数1:浮点型X坐标值,也就是实部2> 参数2:浮点型Y坐标值,也就是虚部 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT) # 移动中心位置 dftShift = np.fft.fftshift(dft) # 调整频谱的范围 result = 20*np.log(cv2.magnitude(dftShift[:,:,0],dftShift[:,:,1])) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(result,cmap=plt.cm.gray) plt.title("result") plt.axis("off") plt.show 傅里叶变换的频谱图像: 2.opencv实现逆傅里叶变换返回结果 = cv2.idft(原始数据)1> 返回结果:取决于原始数据的类型和大小2> 原始数据:实数或者复数均可numpy.fft.ifftshiftfftshift函数的逆函数,将中心位置的低频,重新移到左上角调整频谱的范围,映射到[0,255]区间返回值 = cv2.magnitude(参数1,参数2)1> 参数1:浮点型X坐标值,也就是实部2> 参数2:浮点型Y坐标值,也就是虚部 # 将绘制的图显示在窗口 %matplotlib qt5 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r"image\lena.bmp",cv2.IMREAD_GRAYSCALE) # 傅里叶变换 dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT) dftShift = np.fft.fftshift(dft) # 逆傅里叶变换 ishift = np.fft.ifftshift(dftShift) iimg = cv2.idft(ishift) iimg = cv2.magnitude(iimg[:,:,0],iimg[:,:,1]) plt.subplot(1,2,1) plt.imshow(img,cmap=plt.cm.gray) plt.title("original") plt.axis("off") plt.subplot(1,2,2) plt.imshow(iimg,cmap=plt.cm.gray) plt.title("inverse") plt.axis("off") plt.show 将一副图像,进行傅里叶变换和逆傅里叶变换后,进行对比(一样的) 实例:通过opencv实现低通滤波,模糊一副图像