当前位置:   article > 正文

C#使用欧姆龙PLC的Fins协议读写PLC地址(基本封装)_c# finsudp协议

c# finsudp协议

FINS通讯概述

FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令/响应系统。运用 FINS指令可实现各种网络间的无缝通信,通过编程发送FINS指令,上位机或PLC就能够读写另一个PLC数据区的内容,甚至控制其运行状态,从而简化了用户程序。FINS协议支持工业以太网,这就为OMRON PLC与上位机以太网通信的实现提供了途径。

FINS-TCP需要握手命令成功后方可真正连接上PLC。

以下命令均为十六进制 字节数据流:

1. 握手指令

1.1. 发送

46494E53 0000000C 00000000 00000000 00000018

46494E53:ASCII编码:FINS;

0000000C:指后面跟的字节长度;12个字节

00000000:固定命令;

00000000:错误代码;

00000018:PC节点IP,当设置为0时,会自动获取节点IP。

1.2. 反馈

46494E53 00000010 00000001 00000000 00000018 00000017

46494E53:ASCII编码:FINS;

00000010:指后面跟的字节长度;16个字节

00000001:固定命令;

00000000:错误代码;

00000018:本机电脑节点IP;

00000017:PLC节点IP。

2. 读取指令

读D100开始的2个地址,注:一次最多读1000个地址【以字为单位,也就是2000个字节】。

2.1. 发送

46494E53 0000001A 00000002 00000000 80 00 02 001700 001800 FF 0101 82 006400 0002

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;26个字节

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0101:读指令;

82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

006400:起始地址;

0002:读个数。读取字【WORD】个数,也就是【WORD*2】个字节

2.2. 反馈

46494E53 0000001A 00000002 00000000 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0101:读指令;

0000:读取成功标识;

AABB CCDD:读到的数据。

3. 写入指令

往W10,W11写入AABB,CCDD

3.1. 发送

46494E53 0000001E 00000002 00000000 80 00 02 001700 001800 FF 0102 B1 000A00 0002 AABBCCDD

46494E53:ASCII编码:FINS;

0000001E:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0102:写指令;

B1:写地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

000A00:起始地址;【2的24次方-1】,十进制范围【0~16777215】

0002:写个数;写入的字【WORD】个数,也就是【WORD*2】个字节

AABBCCDD:写入数据。 

3.2. 反馈

46494E53 00000016 00000002 00000000 C0 00 02 001800 001700 FF 0102 0000

46494E53:ASCII编码:FINS;

00000016:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0102:写指令;

0000:写入成功标识。

【原封装代码有误,这次重新更新 斯内科 20211116】

下面C#封装程序【部分类FinsTcpUtil.cs】,代码如下:

PLC寄存器区域类型枚举类【OmronAddressType.cs】,源程序如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace OmronFinsDemo
  7. {
  8. /// <summary>
  9. /// Omron PLC地址类型【存储区域类别】
  10. /// </summary>
  11. public enum OmronAddressType
  12. {
  13. /// <summary>
  14. /// C区:I/O继电器区 【Input Output Area
  15. /// 输入区Input 1600 bits=100 word 范围【CIO0~CIO99
  16. /// 输出区Output 1600 bits=100 word 范围【CIO100~CIO199
  17. /// CPU Bus Unit Area 6400 bits=400Word 范围【CIO1500~CIO1899
  18. /// </summary>
  19. CIO = 0,
  20. /// <summary>
  21. /// W区:工作继电器区 【Work Relay Area
  22. /// 8192 bits = 512 words 范围【W0~W511
  23. /// </summary>
  24. WR = 1,
  25. /// <summary>
  26. /// D区:动态数据存储区,仅可由字(16位Word)进行存取【Data Memory Area
  27. /// 32768Words 范围【D0~D32767
  28. /// </summary>
  29. DM = 2,
  30. /// <summary>
  31. /// 保持继电器区 【Hold Relay】
  32. /// 8192 bits = 512 words 范围【H0~H511
  33. /// </summary>
  34. HR = 3,
  35. /// <summary>
  36. /// 定时器区 【Timer】
  37. /// PVs 4096Words 范围【T0~T4095
  38. /// CompletionFlag 4096bits 范围【T0~T4095
  39. /// </summary>
  40. TIM = 4,
  41. /// <summary>
  42. /// 特殊辅助继电器区 【Auxiliary Relay Area
  43. /// ReadOnly 7168 bits=448Words 范围【A0~A447
  44. /// Read-Write 8192 bits=512Words 范围【A448~A959
  45. /// </summary>
  46. AR = 5,
  47. /// <summary>
  48. /// 计数器区 【Counter】
  49. /// PVs 4096Words 范围【C0~C4095
  50. /// CompletionFlag 4096bits 范围【C0~C4095
  51. /// </summary>
  52. CNT = 6
  53. }
  54. }

【部分类FinsTcpUtil.cs】

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace OmronFinsDemo
  9. {
  10. /// <summary>
  11. /// FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令-响应系统。
  12. /// 我们只使用欧姆龙PLC的FINS-TCP协议,FINS协议可以读写位(Bit)、字(WORD)操作
  13. /// 斯内科
  14. /// </summary>
  15. public partial class FinsTcpUtil
  16. {
  17. /// <summary>
  18. /// 是否已连接
  19. /// </summary>
  20. private bool connected;
  21. /// <summary>
  22. /// PLC的IP地址
  23. /// </summary>
  24. private string serverIP;
  25. /// <summary>
  26. /// PLC的端口,默认9600
  27. /// </summary>
  28. private int serverPort;
  29. /// <summary>
  30. /// 连接PLC的客户端对象
  31. /// </summary>
  32. TcpClient msender;
  33. /// <summary>
  34. /// 发送命令 和 接收反馈的套接字对象
  35. /// </summary>
  36. Socket msock;
  37. /// <summary>
  38. /// 工控机【上位机PC】的IP地址的尾节点
  39. /// </summary>
  40. private byte clientIpTail;
  41. /// <summary>
  42. /// 服务端【欧姆龙PLC】的IP地址的尾节点
  43. /// </summary>
  44. private byte serverIpTail;
  45. /// <summary>
  46. /// 记录FINS的发送和接收数据包事件
  47. /// 第一个参数是发送的数据包,第二个参数是响应的数据包,第三个参数的获取到响应数据包所花费的时间(ms)
  48. /// </summary>
  49. public event Action<byte[], byte[], double> RecordDataEvent;
  50. /// <summary>
  51. /// 是否已连接上欧姆龙PLC【握手成功后才连接成功】
  52. /// </summary>
  53. public bool IsConnected
  54. {
  55. get
  56. {
  57. return connected;
  58. }
  59. }
  60. /// <summary>
  61. /// 断开连接
  62. /// </summary>
  63. public void Disconnect()
  64. {
  65. if (connected)
  66. {
  67. msender.Close();
  68. connected = false;
  69. }
  70. }
  71. /// <summary>
  72. /// 初始化TCP,并发送握手协议命令,尝试连接欧姆龙PLC
  73. /// </summary>
  74. /// <param name="ip">PLC的IP地址</param>
  75. /// <param name="port">端口号,默认为9600</param>
  76. /// <param name="frame"></param>
  77. /// <returns></returns>
  78. public bool ConnectPlc(string ip, int port = 9600, int frame = 0)
  79. {
  80. serverIP = ip;
  81. serverPort = port;
  82. if (!connected)
  83. {
  84. msender = new TcpClient(ip, port);
  85. msender.ReceiveTimeout = 3000;
  86. msock = msender.Client;
  87. //握手协议 共20个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节】
  88. byte[] handshakeProtocol = new byte[20];
  89. //标识头命令:固定FINS
  90. handshakeProtocol[0] = 0x46;//F
  91. handshakeProtocol[1] = 0x49;//I
  92. handshakeProtocol[2] = 0x4E;//N
  93. handshakeProtocol[3] = 0x53;//S
  94. //数据长度:4个字节
  95. handshakeProtocol[4] = 0;
  96. handshakeProtocol[5] = 0;
  97. handshakeProtocol[6] = 0;
  98. handshakeProtocol[7] = 0x0C;//Length长度:后面跟的字节长度:12个字节
  99. //00000000:固定命令;【索引 8~11
  100. //00000000:错误代码;【索引 12~15
  101. handshakeProtocol[16] = 0;
  102. handshakeProtocol[17] = 0;
  103. handshakeProtocol[18] = 0;
  104. handshakeProtocol[19] = (byte)frame;//FINS Frame (工控机IP节点的最后一个字节,如 192.168.1.139 就填入139),frame为0时服务端为客户端自动分配IP尾号
  105. msock.Send(handshakeProtocol);
  106. //【握手的】反馈结果 共24个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节,服务端节点地址4个字节】
  107. /*
  108. * 46494E53:ASCII编码:FINS;
  109. * 00000010:指后面跟的字节长度;16个字节
  110. * 00000001:固定命令;
  111. * 00000000:错误代码;
  112. * 00000018:本机电脑节点IP;
  113. * 00000017:PLC节点IP。
  114. */
  115. byte[] feedbackBuffer = new byte[24];//反馈结果
  116. msock.Receive(feedbackBuffer, SocketFlags.None);
  117. if (handshakeProtocol[0] == feedbackBuffer[0] && handshakeProtocol[1] == feedbackBuffer[1] && handshakeProtocol[2] == feedbackBuffer[2] && handshakeProtocol[3] == feedbackBuffer[3]
  118. && feedbackBuffer[7] == 0x10 && feedbackBuffer[15] == 0x00 && (handshakeProtocol[19] == 0 || feedbackBuffer[19] == handshakeProtocol[19]))
  119. {
  120. //&& feedbackBuffer[11] == 0x01
  121. //反馈结果 需满足 按照FINS开头,字节长度一定是16,错误代码为0
  122. //客户端IP尾号frame为0时服务端将为客户端自动分配IP尾号
  123. clientIpTail = feedbackBuffer[19];
  124. serverIpTail = feedbackBuffer[23];
  125. connected = true;
  126. }
  127. }
  128. return connected;
  129. }
  130. /// <summary>
  131. /// 获取欧姆龙地址类型对应的 FINS通信标识
  132. /// </summary>
  133. /// <param name="omronAddressType">欧姆龙地址类型枚举</param>
  134. /// <param name="isBitProcess">是否位处理。true:位处理,false:字处理</param>
  135. /// <returns></returns>
  136. private byte GetAreaTypeFlag(OmronAddressType omronAddressType, bool isBitProcess)
  137. {
  138. if (isBitProcess)
  139. {
  140. //位处理
  141. switch (omronAddressType)
  142. {
  143. case OmronAddressType.CIO:
  144. return 0x30;
  145. case OmronAddressType.WR:
  146. return 0x31;
  147. case OmronAddressType.DM:
  148. return 0x02;
  149. case OmronAddressType.HR:
  150. return 0x32;
  151. case OmronAddressType.TIM:
  152. return 0x09;
  153. case OmronAddressType.AR:
  154. return 0x33;
  155. case OmronAddressType.CNT:
  156. return 0x09;
  157. default:
  158. return 0x00;
  159. }
  160. }
  161. else
  162. {
  163. //字处理
  164. switch (omronAddressType)
  165. {
  166. case OmronAddressType.CIO:
  167. return 0xB0;
  168. case OmronAddressType.WR:
  169. return 0xB1;
  170. case OmronAddressType.DM:
  171. return 0x82;
  172. case OmronAddressType.HR:
  173. return 0xB2;
  174. case OmronAddressType.TIM:
  175. return 0x89;
  176. case OmronAddressType.AR:
  177. return 0xB3;
  178. case OmronAddressType.CNT:
  179. return 0x89;
  180. default:
  181. return 0x00;
  182. }
  183. }
  184. }
  185. /// <summary>
  186. /// 用于排他锁,确保在多线程调用该接口时,不会同时调用。确保在处理当前命令时,其他命令请等待
  187. /// </summary>
  188. static int lockedValue = 0;
  189. /// <summary>
  190. /// 发送命令并解析反馈【关键方法】,需要进行加锁操作,防止同时操作一个地址时出现脏读等异常情况
  191. /// 当位操作时,只读取或写入一位
  192. /// </summary>
  193. /// <param name="omronAddressType">欧姆龙PLC存储区域类别枚举</param>
  194. /// <param name="startAddress">起始地址</param>
  195. /// <param name="bitIndexOrWordLength">要读写的位索引 或者 字长度。读字时,一次最多读1000个字【也就是2000个字节】.如果是写入Word时,该参数无意义,直接按(writeData.Length+1)/2处理</param>
  196. /// <param name="isBitProcess">是否位处理。true:位处理,false:字【WORD】处理</param>
  197. /// <param name="isRead">读内存区域还是写内存区域 true:读,false:写</param>
  198. /// <param name="receiveData">读取时反馈的数据流,写入操作时该参数无意义</param>
  199. /// <param name="errMsg">处理时的异常错误信息,默认为空</param>
  200. /// <param name="writeData">需要写入的连续字节流,读取操作时该参数无意义,写入操作【isRead为false】时该参数不能为空</param>
  201. /// <returns>返回的错误号,0代表操作成功</returns>
  202. private int SendCommandAndParseFeedback(OmronAddressType omronAddressType, int startAddress, int bitIndexOrWordLength, bool isBitProcess, bool isRead, ref byte[] receiveData, out string errMsg, byte[] writeData = null)
  203. {
  204. errMsg = string.Empty;
  205. if (!IsConnected)
  206. {
  207. errMsg = $"【未建立连接】尚未连接PLC成功或者握手协议失败【{serverIP}:{serverPort}】,请检查配置和网络,当前起始地址【{startAddress}】";
  208. return 1000;
  209. }
  210. if (!isRead && (writeData == null || writeData.Length == 0 || writeData.Length > 2000))
  211. {
  212. errMsg = $"【参数非法】写入操作时,需要写入的数据为空 或者 写入数据流的字节长度不在【1~2000】之间,当前起始地址【{startAddress}】";
  213. return 1001;
  214. }
  215. if (startAddress < 0 || startAddress > 65535)
  216. {
  217. errMsg = $"【参数非法】欧姆龙PLC的FINS协议的起始地址必须在0~65535之间,当前起始地址【{startAddress}】";
  218. return 1002;
  219. }
  220. //读保持寄存器0x03读取的寄存器数量的范围为 1~1000。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~2000
  221. if (isRead && bitIndexOrWordLength > 1000)
  222. {
  223. errMsg = $"【参数非法】读取的位索引 或者 字长度不能超过1000,当前起始地址【{startAddress}】,读取长度【{bitIndexOrWordLength}】";
  224. return 1003;
  225. }
  226. //写入WORD时,直接使用 写入的字节长度除以2【因 一个字相当于两个字节 1WORD=2Byte】
  227. if (!isRead && !isBitProcess)
  228. {
  229. bitIndexOrWordLength = (writeData.Length + 1) / 2;
  230. }
  231. byte serviceId = 0x15;//SID:SID用于标识数据发送的过程。【服务标识】,可以任意指定,返回数据包的SID和发送包的SID一致
  232. int commandLength = 34;//发送命令的长度【读取命令 固定34位】
  233. if (!isRead)
  234. {
  235. commandLength = 34 + writeData.Length;
  236. }
  237. byte[] sendBytes = new byte[commandLength];
  238. //标识头命令:固定FINS
  239. sendBytes[0] = 0x46;//F
  240. sendBytes[1] = 0x49;//I
  241. sendBytes[2] = 0x4E;//N
  242. sendBytes[3] = 0x53;//S
  243. //数据长度:4个字节
  244. sendBytes[4] = 0;
  245. sendBytes[5] = 0;
  246. sendBytes[6] = 0;
  247. sendBytes[7] = 0x1A;//Length长度:后面跟的字节长度:26个字节
  248. if (!isRead)
  249. {
  250. sendBytes[7] = (byte)(26 + writeData.Length);
  251. }
  252. //命令码 固定 00 00 00 02
  253. sendBytes[8] = 0;
  254. sendBytes[9] = 0;
  255. sendBytes[10] = 0;
  256. sendBytes[11] = 0x02;//frame command
  257. //【索引12~15】错误码 00 00 00 00
  258. sendBytes[16] = 0x80;//ICF
  259. sendBytes[17] = 0x00;//RSV
  260. sendBytes[18] = 0x02;//GCT, less than 8 network layers
  261. sendBytes[19] = 0x00;//DNA, local network
  262. sendBytes[20] = serverIpTail;//DA1 PLC的IP节点尾号
  263. sendBytes[21] = 0x00;//DA2, CPU unit
  264. sendBytes[22] = 0x00;//SNA, local network
  265. sendBytes[23] = clientIpTail;//SA1 工控机IP节点尾号
  266. sendBytes[24] = 0x00;//SA2, CPU unit
  267. sendBytes[25] = serviceId;//SID:SID用于标识数据发送的过程。 【服务标识】
  268. //SID能够设置为00到FF十六进制的任何数字。SID用于检测响应请求是否正确,当发送节点与响应节点的SID值相同,表明响应的数据是请求的数据,不相同,表明响应的数据非请求数据。
  269. if (isRead)
  270. {
  271. //读命令 01 01
  272. sendBytes[26] = 0x01;
  273. sendBytes[27] = 0x01;
  274. }
  275. else
  276. {
  277. //写命令 01 02
  278. sendBytes[26] = 0x01;
  279. sendBytes[27] = 0x02;
  280. }
  281. //地址区域标识
  282. sendBytes[28] = GetAreaTypeFlag(omronAddressType, isBitProcess);
  283. byte[] addressParts = BitConverter.GetBytes((ushort)startAddress);
  284. //起始地址【索引 29~30
  285. sendBytes[29] = addressParts[1];
  286. sendBytes[30] = addressParts[0];
  287. //读取的字【Word】的长度
  288. byte[] lengthParts = BitConverter.GetBytes((ushort)bitIndexOrWordLength);
  289. if (isBitProcess)
  290. {
  291. //位处理时,表示读取的位的索引,只读取 或者 写入 一位
  292. sendBytes[31] = lengthParts[0];
  293. sendBytes[32] = 0x00;
  294. sendBytes[33] = 0x01;//每次只读取一位
  295. /*
  296. * 读取位操作【从索引26开始】0101+1字节存储区代码+3字节开始地址+2字节数量
  297. * 示例 0101 31 010A 01 000A
  298. * 0101 代表读操作 0x31代表WR区的位操作 010A代表起始地址266 01代表起始位 000A代表读取位的个数:10
  299. * 写入位操作【从索引26开始】 0102+1字节存储区代码+3字节开始地址+2字节数量+1字节第1位值+1字节第2位值+
  300. * 示例 0102 31 00D4 01 0002 01 00
  301. * 31是W位代码,0x00D401=212.01, 00D4代表起始地址,01代表位索引 0002写入2个位,0100 第一个位W212.01写入1,第二个位W212.02写入0
  302. *
  303. */
  304. }
  305. else
  306. {
  307. //字处理时,按零处理
  308. sendBytes[31] = 0x00;//字处理时,起始地址的位索引,按零处理如 D1234.0
  309. sendBytes[32] = lengthParts[1];
  310. sendBytes[33] = lengthParts[0];
  311. }
  312. //要写入的字节流数据,写入位时writeData只考虑第一个
  313. if (!isRead)
  314. {
  315. Array.Copy(writeData, 0, sendBytes, 34, writeData.Length);
  316. }
  317. //添加锁
  318. while (Interlocked.Exchange(ref lockedValue, 1) != 0) { }
  319. System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
  320. try
  321. {
  322. msock.Send(sendBytes, SocketFlags.None);
  323. }
  324. catch (SocketException ex)
  325. {
  326. connected = false;
  327. errMsg = $"发送数据时出现套接字异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.SocketErrorCode}】,{ex.Message}";
  328. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  329. return 1004;
  330. }
  331. catch (Exception ex)
  332. {
  333. connected = false;
  334. errMsg = $"发送数据时出现异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";
  335. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  336. return 1005;
  337. }
  338. byte[] buffer = new byte[2048];
  339. byte[] receiveByts = null;//接收的有效字节流
  340. int rcvCount = 0;//收到的字节数
  341. try
  342. {
  343. rcvCount = msock.Receive(buffer);
  344. receiveByts = new ArraySegment<byte>(buffer, 0, rcvCount).ToArray();
  345. stopwatch.Stop();
  346. RecordDataEvent?.Invoke(sendBytes, receiveByts, stopwatch.Elapsed.TotalMilliseconds);
  347. /*
  348. * 反馈数据流
  349. * 46494E53 0000001A 00000002 00000000 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD XXXX XXXX
  350. * 标识头 后面跟的字节长度 固定 错误代码 ICF RSV GCT 工控机IP PLC的IP SID 读命令 成功标识 实际读取到的数据,索引30开始
  351. */
  352. }
  353. catch (SocketException ex)
  354. {
  355. connected = false;
  356. errMsg = $"【网络异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】,套接字错误【{ex.SocketErrorCode}】,【{ex.Message}】";
  357. //释放锁
  358. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  359. return 1006;
  360. }
  361. catch (Exception ex)
  362. {
  363. connected = false;
  364. errMsg = $"【处理异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";
  365. //释放锁
  366. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  367. return 1007;
  368. }
  369. if (receiveByts == null || receiveByts.Length < 30)
  370. {
  371. errMsg = $"接收数据为空,或者低于30个字节,接收数据为【{(receiveByts == null ? "" : string.Join(",", receiveByts))}】";
  372. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  373. return 1008;
  374. }
  375. if (receiveByts[0] != sendBytes[0] || receiveByts[1] != sendBytes[1] || receiveByts[2] != sendBytes[2] || receiveByts[3] != sendBytes[3])
  376. {
  377. errMsg = $"接收数据头非法,不是从【FINS】开始,接收数据为【{string.Join(",", receiveByts)}】";
  378. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  379. return 1009;
  380. }
  381. if (receiveByts[15] != 0)//if (receiveByts[11] != 2 || receiveByts[15] != 0)
  382. {
  383. errMsg = $"数据非法,解析时出现错误,错误号【{receiveByts[15]}】,接收数据为【{string.Join(",", receiveByts)}】";
  384. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  385. return 1010;
  386. }
  387. //if (receiveByts[25] != serviceId)
  388. //{
  389. // errMsg = $"服务标识非法,发送的SID【{serviceId}】与接收的SID【{receiveByts[25]}】不一致,接收数据为【{string.Join(",", receiveByts)}】";
  390. // Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  391. // return 1011;
  392. //}
  393. if (receiveByts[26] != sendBytes[26] || receiveByts[27] != sendBytes[27])
  394. {
  395. errMsg = $"读写命令不匹配,发送命令【{sendBytes[26].ToString("X2")}{sendBytes[27].ToString("X2")}】,反馈命令【{receiveByts[26].ToString("X2")}{receiveByts[27].ToString("X2")}】";
  396. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  397. return 1012;
  398. }
  399. if (receiveByts[28] != 0 || receiveByts[29] != 0)
  400. {
  401. errMsg = GetErrorMessage(receiveByts[28], receiveByts[29]);
  402. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  403. return 1013;
  404. }
  405. //抓取读取到的实际字节流 【一个字WORD 相当于 两个字节】
  406. if (isRead)
  407. {
  408. //读取位时,只读取一位
  409. receiveData = new byte[isBitProcess ? 1 : (bitIndexOrWordLength * 2)];
  410. Array.Copy(receiveByts, 30, receiveData, 0, receiveData.Length);
  411. }
  412. //释放锁
  413. Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
  414. return 0;
  415. }
  416. /// <summary>
  417. /// 高低字节转化,也就是相邻的两个字节交换位置。【索引0与索引1的值交换位置,索引2与索引3的值交换位置,索引4与索引5的值交换位置...】
  418. /// 注意奇数个数组长度时,强行补零按偶数处理【一个字 相当于 两个字节】
  419. /// </summary>
  420. /// <param name="sourceData"></param>
  421. /// <returns></returns>
  422. private byte[] HightLowChange(byte[] sourceData)
  423. {
  424. if (sourceData.Length % 2 == 1)
  425. {
  426. byte[] dres = new byte[sourceData.Length];
  427. for (int i = 0; i < sourceData.Length - 1; i += 2)
  428. {
  429. dres[i] = sourceData[i + 1];
  430. dres[i + 1] = sourceData[i];
  431. }
  432. dres[sourceData.Length - 1] = sourceData[sourceData.Length - 1];
  433. return dres;
  434. }
  435. int len = (sourceData.Length % 2 == 1 ? sourceData.Length + 1 : sourceData.Length);
  436. byte[] res = new byte[len];
  437. for (int i = 0; i < sourceData.Length; i += 2)
  438. {
  439. res[i] = sourceData[i + 1];
  440. res[i + 1] = sourceData[i];
  441. }
  442. return res;
  443. }
  444. /// <summary>
  445. /// 获得反馈指定错误消息描述
  446. /// </summary>
  447. /// <param name="errCode1"></param>
  448. /// <param name="errCode2"></param>
  449. /// <returns></returns>
  450. private string GetErrorMessage(byte errCode1, byte errCode2)
  451. {
  452. string msg = $"错误代码【{errCode1.ToString("X2")} {errCode2.ToString("X2")}】";
  453. switch (errCode1)
  454. {
  455. case 0x00:
  456. if (errCode2 == 0x01)
  457. msg += "service canceled";
  458. break;
  459. case 0x01:
  460. switch (errCode2)
  461. {
  462. case 0x01: msg += "Ip配置错误:local node not in network"; break;
  463. case 0x02: msg += "权限出错:token timeout"; break;
  464. case 0x03: msg += "retries failed"; break;
  465. case 0x04: msg += "too many send frames"; break;
  466. case 0x05: msg += "node address range error"; break;
  467. case 0x06: msg += "node address duplication"; break;
  468. }
  469. break;
  470. case 0x02:
  471. switch (errCode2)
  472. {
  473. case 0x01: msg += "destination node not in network"; break;
  474. case 0x02: msg += "unit missing"; break;
  475. case 0x03: msg += "third node missing"; break;
  476. case 0x04: msg += "destination node busy"; break;
  477. case 0x05: msg += "response timeout"; break;
  478. }
  479. break;
  480. case 0x03:
  481. switch (errCode2)
  482. {
  483. case 0x01: msg += "communications controller error"; break;
  484. case 0x02: msg += "CPU unit error"; break;
  485. case 0x03: msg += "控制器错误:controller error"; break;
  486. case 0x04: msg += "unit number error"; break;
  487. }
  488. break;
  489. case 0x04:
  490. switch (errCode2)
  491. {
  492. case 0x01: msg += "未定义的指令:undefined command"; break;
  493. case 0x02: msg += "不支持的模式:not supported by model/version"; break;
  494. }
  495. break;
  496. case 0x05:
  497. switch (errCode2)
  498. {
  499. case 0x01: msg += "destination address setting error"; break;
  500. case 0x02: msg += "no routing tables"; break;
  501. case 0x03: msg += "routing table error"; break;
  502. case 0x04: msg += "too many relays"; break;
  503. }
  504. break;
  505. case 0x10:
  506. switch (errCode2)
  507. {
  508. case 0x01: msg += "指令太长:command too long"; break;
  509. case 0x02: msg += "command too short"; break;
  510. case 0x03: msg += "数据与长度不匹配:elements/data don't match"; break;
  511. case 0x04: msg += "command format error"; break;
  512. case 0x05: msg += "header error"; break;
  513. }
  514. break;
  515. case 0x11:
  516. switch (errCode2)
  517. {
  518. case 0x01: msg += "area classification missing"; break;
  519. case 0x02: msg += "access size error"; break;
  520. case 0x03: msg += "address range error"; break;
  521. case 0x04: msg += "address range exceeded"; break;
  522. case 0x06: msg += "program missing"; break;
  523. case 0x09: msg += "relational error"; break;
  524. case 0x0a: msg += "duplicate data access"; break;
  525. case 0x0b: msg += "response too long"; break;
  526. case 0x0c: msg += "parameter error"; break;
  527. }
  528. break;
  529. case 0x20:
  530. switch (errCode2)
  531. {
  532. case 0x02: msg += "protected"; break;
  533. case 0x03: msg += "table missing"; break;
  534. case 0x04: msg += "data missing"; break;
  535. case 0x05: msg += "program missing"; break;
  536. case 0x06: msg += "file missing"; break;
  537. case 0x07: msg += "data mismatch"; break;
  538. }
  539. break;
  540. case 0x21:
  541. switch (errCode2)
  542. {
  543. case 0x01: msg += "read-only"; break;
  544. case 0x02: msg += "protected,cannot write data link table"; break;
  545. case 0x03: msg += "cannot register"; break;
  546. case 0x05: msg += "program missing"; break;
  547. case 0x06: msg += "file missing"; break;
  548. case 0x07: msg += "file name already exists"; break;
  549. case 0x08: msg += "cannot change"; break;
  550. }
  551. break;
  552. case 0x22:
  553. switch (errCode2)
  554. {
  555. case 0x01: msg += "not possible during execution"; break;
  556. case 0x02: msg += "not possible while running"; break;
  557. case 0x03: msg += "wrong PLC mode"; break;
  558. case 0x04: msg += "wrong PLC mode"; break;
  559. case 0x05: msg += "wrong PLC mode"; break;
  560. case 0x06: msg += "wrong PLC mode"; break;
  561. case 0x07: msg += "specified node not polling node"; break;
  562. case 0x08: msg += "step cannot be executed"; break;
  563. }
  564. break;
  565. case 0x23:
  566. switch (errCode2)
  567. {
  568. case 0x01: msg += "file device missing"; break;
  569. case 0x02: msg += "memory missing"; break;
  570. case 0x03: msg += "clock missing"; break;
  571. }
  572. break;
  573. case 0x24:
  574. if (errCode2 == 0x01) msg += "table missing";
  575. break;
  576. case 0x25:
  577. switch (errCode2)
  578. {
  579. case 0x02: msg += "memory error"; break;
  580. case 0x03: msg += "I/O setting error"; break;
  581. case 0x04: msg += "too many I/O points"; break;
  582. case 0x05: msg += "CPU bus error"; break;
  583. case 0x06: msg += "I/O duplication"; break;
  584. case 0x07: msg += "CPU bus error"; break;
  585. case 0x09: msg += "SYSMAC BUS/2 error"; break;
  586. case 0x0a: msg += "CPU bus unit error"; break;
  587. case 0x0d: msg += "SYSMAC BUS No. duplication"; break;
  588. case 0x0f: msg += "memory error"; break;
  589. case 0x10: msg += "SYSMAC BUS terminator missing"; break;
  590. }
  591. break;
  592. case 0x26:
  593. switch (errCode2)
  594. {
  595. case 0x01: msg += "no protection"; break;
  596. case 0x02: msg += "incorrect password"; break;
  597. case 0x04: msg += "protected"; break;
  598. case 0x05: msg += "service already executing"; break;
  599. case 0x06: msg += "service stopped"; break;
  600. case 0x07: msg += "no execution right"; break;
  601. case 0x08: msg += "settings required before execution"; break;
  602. case 0x09: msg += "necessary items not set"; break;
  603. case 0x0a: msg += "number already defined"; break;
  604. case 0x0b: msg += "error will not clear"; break;
  605. }
  606. break;
  607. case 0x30:
  608. if (errCode2 == 0x01)
  609. msg += "无访问权限:no access right";
  610. break;
  611. case 0x40:
  612. if (errCode2 == 0x01)
  613. msg += "服务未开启:service aborted";
  614. break;
  615. }
  616. return msg;
  617. }
  618. }
  619. }

部分类FinsTcpUtil.Partial.cs,源程序如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace OmronFinsDemo
  7. {
  8. public partial class FinsTcpUtil
  9. {
  10. /// <summary>
  11. /// 写基本数据类型到PLC,如bool,short,int,uint,float等
  12. /// </summary>
  13. /// <typeparam name="T"></typeparam>
  14. /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如D32000</param>
  15. /// <param name="value"></param>
  16. /// <param name="msg"></param>
  17. /// <returns></returns>
  18. public int WriteValue<T>(OmronAddressType omronAddressType, int startAddress, T value, out string msg) where T : struct
  19. {
  20. bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】
  21. byte[] datas = new byte[0];
  22. if (typeof(T) == typeof(bool))
  23. {
  24. //位操作 只考虑0
  25. datas = BitConverter.GetBytes(Convert.ToBoolean(value));
  26. isBitProcess = true;
  27. }
  28. else if (typeof(T) == typeof(sbyte))
  29. {
  30. datas = new byte[1] { (byte)Convert.ToSByte(value) };
  31. }
  32. else if (typeof(T) == typeof(byte))
  33. {
  34. datas = new byte[1] { Convert.ToByte(value) };
  35. }
  36. else if (typeof(T) == typeof(short))
  37. {
  38. datas = BitConverter.GetBytes(Convert.ToInt16(value));
  39. }
  40. else if (typeof(T) == typeof(ushort))
  41. {
  42. datas = BitConverter.GetBytes(Convert.ToUInt16(value));
  43. }
  44. else if (typeof(T) == typeof(int))
  45. {
  46. datas = BitConverter.GetBytes(Convert.ToInt32(value));
  47. }
  48. else if (typeof(T) == typeof(uint))
  49. {
  50. datas = BitConverter.GetBytes(Convert.ToUInt32(value));
  51. }
  52. else if (typeof(T) == typeof(long))
  53. {
  54. datas = BitConverter.GetBytes(Convert.ToInt64(value));
  55. }
  56. else if (typeof(T) == typeof(ulong))
  57. {
  58. datas = BitConverter.GetBytes(Convert.ToUInt64(value));
  59. }
  60. else if (typeof(T) == typeof(float))
  61. {
  62. datas = BitConverter.GetBytes(Convert.ToSingle(value));
  63. }
  64. else if (typeof(T) == typeof(double))
  65. {
  66. datas = BitConverter.GetBytes(Convert.ToDouble(value));
  67. }
  68. else
  69. {
  70. //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
  71. msg = $"写Fins数据暂不支持其他类型:{value.GetType()}";
  72. return -1;
  73. }
  74. byte[] rcvBuffer = new byte[0];
  75. int bitIndexOrWordLength = 0;//位操作 默认处理0
  76. if (!isBitProcess)
  77. {
  78. bitIndexOrWordLength = (datas.Length + 1) / 2;
  79. }
  80. datas = HightLowChange(datas);
  81. return SendCommandAndParseFeedback(omronAddressType, startAddress, bitIndexOrWordLength, isBitProcess, false, ref rcvBuffer, out msg, datas);
  82. }
  83. /// <summary>
  84. /// 写入连续的字节数组
  85. /// </summary>
  86. /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如MW32000</param>
  87. /// <param name="buffer"></param>
  88. /// <param name="msg"></param>
  89. /// <returns></returns>
  90. public int WriteValue(OmronAddressType omronAddressType, int startAddress, byte[] buffer, out string msg)
  91. {
  92. byte[] rcvBuffer = new byte[0];
  93. buffer = HightLowChange(buffer);
  94. return SendCommandAndParseFeedback(omronAddressType, startAddress, (buffer.Length + 1) / 2, false, false, ref rcvBuffer, out msg, buffer);
  95. }
  96. /// <summary>
  97. /// 写指定长度的字符串(比如条码)到汇川PLC,最大240个字符
  98. /// 将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后重新填充
  99. /// </summary>
  100. /// <param name="startAddress"></param>
  101. /// <param name="length">字符串的最大长度【范围1~240】,超过240个字符将直接返回错误</param>
  102. /// <param name="barcode">实际字符串,长度可能小于最大长度length</param>
  103. /// <returns></returns>
  104. public int WriteString(OmronAddressType omronAddressType, int startAddress, int length, string barcode, out string msg)
  105. {
  106. if (barcode == null)
  107. {
  108. barcode = string.Empty;
  109. }
  110. //将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后填充
  111. //防止出现 上一次写 【ABCD】,本次写 【12】, 读取字符串 结果是【12CD】 的问题
  112. barcode = barcode.PadRight(length, '\0');
  113. return WriteValue(omronAddressType, startAddress, Encoding.ASCII.GetBytes(barcode), out msg);
  114. }
  115. /// <summary>
  116. /// 写长字符串
  117. /// 一个寄存器地址可以存放两个字节【两个字符】,设定每次最多写入200个字符,
  118. /// 分多次写入,每一次写入的起始寄存器地址偏移100
  119. /// </summary>
  120. /// <param name="startAddress">起始地址</param>
  121. /// <param name="longString">长字符串</param>
  122. /// <param name="msg"></param>
  123. /// <returns></returns>
  124. public int WriteLongString(OmronAddressType omronAddressType, int startAddress, string longString, out string msg)
  125. {
  126. msg = string.Empty;
  127. if (longString == null)
  128. {
  129. longString = string.Empty;
  130. }
  131. int cycleCount = 200;//20个字符就进行分段
  132. int maxLength = longString.Length;
  133. int pageSize = (maxLength + cycleCount - 1) / cycleCount;
  134. int errorCode = -1;
  135. for (int i = 0; i < pageSize; i++)
  136. {
  137. int writeLength = cycleCount;
  138. if (i == pageSize - 1)
  139. {
  140. //最后一次
  141. writeLength = maxLength - i * cycleCount;
  142. }
  143. //分段字符串,每次最多200
  144. string segment = longString.Substring(i * cycleCount, writeLength);
  145. //寄存器地址是字,所以需要 字节个数 除以2
  146. errorCode = WriteString(omronAddressType, startAddress + (i * cycleCount / 2), writeLength, segment, out msg);
  147. if (errorCode != 0)
  148. {
  149. return errorCode;
  150. }
  151. }
  152. return errorCode;
  153. }
  154. /// <summary>
  155. /// 读取基本数据类型
  156. /// </summary>
  157. /// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam>
  158. /// <param name="startAddress"></param>
  159. /// <param name="value"></param>
  160. /// <param name="msg"></param>
  161. /// <returns></returns>
  162. public int ReadValue<T>(OmronAddressType omronAddressType, int startAddress, out T value, out string msg) where T : struct
  163. {
  164. bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】
  165. value = default(T);
  166. int length = 0;//读取的连续字节个数
  167. if (typeof(T) == typeof(bool))
  168. {
  169. isBitProcess = true;//位操作时,只考虑0
  170. }
  171. else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte))
  172. {
  173. length = 1;
  174. }
  175. else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
  176. {
  177. length = 2;
  178. }
  179. else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float))
  180. {
  181. length = 4;
  182. }
  183. else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double))
  184. {
  185. length = 8;
  186. }
  187. else
  188. {
  189. //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
  190. msg = $"读Fins数据暂不支持其他类型:{value.GetType()}";
  191. return -1;
  192. }
  193. byte[] rcvBuffer = new byte[0];
  194. int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, isBitProcess, true, ref rcvBuffer, out msg);
  195. rcvBuffer = HightLowChange(rcvBuffer);
  196. if (errorCode != 0)
  197. {
  198. return errorCode;
  199. }
  200. if (typeof(T) == typeof(bool))
  201. {
  202. value = (T)(object)Convert.ToBoolean(rcvBuffer[0]);
  203. }
  204. else if (typeof(T) == typeof(sbyte))
  205. {
  206. value = (T)(object)(sbyte)rcvBuffer[0];
  207. }
  208. else if (typeof(T) == typeof(byte))
  209. {
  210. value = (T)(object)rcvBuffer[0];
  211. }
  212. else if (typeof(T) == typeof(short))
  213. {
  214. value = (T)(object)BitConverter.ToInt16(rcvBuffer, 0);
  215. }
  216. else if (typeof(T) == typeof(ushort))
  217. {
  218. value = (T)(object)BitConverter.ToUInt16(rcvBuffer, 0);
  219. }
  220. else if (typeof(T) == typeof(int))
  221. {
  222. value = (T)(object)BitConverter.ToInt32(rcvBuffer, 0);
  223. }
  224. else if (typeof(T) == typeof(uint))
  225. {
  226. value = (T)(object)BitConverter.ToUInt32(rcvBuffer, 0);
  227. }
  228. else if (typeof(T) == typeof(long))
  229. {
  230. value = (T)(object)BitConverter.ToInt64(rcvBuffer, 0);
  231. }
  232. else if (typeof(T) == typeof(ulong))
  233. {
  234. value = (T)(object)BitConverter.ToUInt64(rcvBuffer, 0);
  235. }
  236. else if (typeof(T) == typeof(float))
  237. {
  238. value = (T)(object)BitConverter.ToSingle(rcvBuffer, 0);
  239. }
  240. else if (typeof(T) == typeof(double))
  241. {
  242. value = (T)(object)BitConverter.ToDouble(rcvBuffer, 0);
  243. }
  244. return 0;
  245. }
  246. /// <summary>
  247. /// 从起始地址开始,读取一段字节流
  248. /// </summary>
  249. /// <param name="startAddress">起始寄存器地址</param>
  250. /// <param name="length">读取的字节个数</param>
  251. /// <param name="values">返回的字节流数据</param>
  252. /// <returns>true:读取成功 false:读取失败</returns>
  253. public int ReadValue(OmronAddressType omronAddressType, int startAddress, int length, out byte[] values, out string msg)
  254. {
  255. values = new byte[length];
  256. int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, false, true, ref values, out msg);
  257. if (errorCode != 0)
  258. {
  259. return errorCode;
  260. }
  261. values = HightLowChange(values);
  262. return errorCode;
  263. }
  264. /// <summary>
  265. /// 读取(长)字符串,设定每次最多读取200个字符,
  266. /// 分多次读取,每一次读取的起始寄存器地址偏移100
  267. /// </summary>
  268. /// <param name="startAddress"></param>
  269. /// <param name="length"></param>
  270. /// <param name="longString"></param>
  271. /// <param name="msg"></param>
  272. /// <returns></returns>
  273. public int ReadLongString(OmronAddressType omronAddressType, int startAddress, int length, out string longString, out string msg)
  274. {
  275. msg = string.Empty;
  276. longString = string.Empty;
  277. int cycleCount = 200;
  278. int pageSize = (length + cycleCount - 1) / cycleCount;
  279. int errorCode = -1;
  280. for (int i = 0; i < pageSize; i++)
  281. {
  282. int readLength = cycleCount;
  283. if (i == pageSize - 1)
  284. {
  285. //最后一次
  286. readLength = length - i * cycleCount;
  287. }
  288. byte[] values;
  289. errorCode = ReadValue(omronAddressType, startAddress + (i * cycleCount / 2), readLength, out values, out msg);
  290. if (errorCode != 0)
  291. {
  292. return errorCode;
  293. }
  294. longString += Encoding.ASCII.GetString(values);
  295. }
  296. return errorCode;
  297. }
  298. }
  299. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/309562
推荐阅读
相关标签
  

闽ICP备14008679号