协议说明
HostLink C-mode可以直接通过PC连接欧姆龙PLC,可以直接读取/写入欧姆龙PLC寄存器的协议。
其中分为1对1,以及1对N模式,1对1表示1台PC只能连接一个PLC,1对N表示1台PC可以通过协议连接多个PLC。而1:1与1:N在数据帧上也有所不同,其中1:1不需要带有PLC站号,这点比较好理解,毕竟只有一个PLC就无所谓区别是哪一个PLC了。连接图示如下所示:
命令发送和响应帧描述
数据帧一般包含:
1、起始符 '@' 1byte
2、站号 BCD格式 0-31的数 2byte
3、头部 一般为命令的类型 2byte
4、内容 命令的参数
5、FCS 校验码 对FCS之前的字节数组进行异或 2byte
6、结束码 PLC回复时带有,表示是否有异常
7、结束符 "*/r" 2byte 注意'/r'是回车符,与'/n'是不同的
Command Frame Format
Response Frame Format
C-MODE协议关于分帧的规定
当发送或接受的数据帧超过131字节时,将会对发送和结束的数据帧进行分割,会分割多个不同的帧来进行发送。最后如果不是最后一帧,每个帧的最后一个字符为'r',用于分割不同的帧。结束的帧与正常的帧一样结尾"*r"。因为1:1和1:N发送的帧有所不同,故在分帧上发送的字数也不尽相同。我们可以知道1:1有可能会多发一个数。
未发送完成的,即没有Terminator,只有'r',那么回应方需要先发一个'r'才会继续发送下一个帧。直到发送了Terminator或接收到Terminator。
PC发送数据时产生分帧时的规定,如下图所示:
PC发送数据时产生分帧时的规定,如下图所示:
C-MODE可以读写的寄存器
C-MODE读取寄存器时的具体协议
读取规则除了RG和RE之外,其他的Memory读取协议都是一样的。这两个的读取可查阅官方文档。
C-MODE寄存器写 -Monitor模式下才能正确写入
常用的寄存器除了WE之外其他都差不多的帧内容
C#实现协议
读取寄存器部分代码
目前不支持RE,RG。方法主体使用泛型来编写,返回值设计了一个泛型类型表示通讯是否成功、通讯时间、以及读取到的寄存器内容。返回的类型设计为:
1
2
3
4
5
6public class HostLinkResult<T> where T : unmanaged { public bool IsSuccessful { get; set; } public IList<T> Results { get; set; } public long CommunicationTime { get; set; } }
方法主体,分为发送命令和解析返回帧。返回帧为16进制的多段字,可以先将及写入Ilist<short>中,在全部写入IList<T>中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public HostLinkResult<T> Read<T>(int begin, int length, RegisterType readType) where T : unmanaged { Stopwatch sw = new Stopwatch(); sw.Restart(); HostLinkResult<T> result = new HostLinkResult<T>(); var size = Marshal.SizeOf(default(T)); string commandStr = ""; commandStr += _firstChar; if (_unitNumber >= 0) { commandStr += _unitNumber.ToString("X2"); } commandStr += $"{GetReadHeaderCode(readType)}"; commandStr += $"{begin:0000}"; var registerNumber = (size / 16 * length); if (registerNumber < 1) registerNumber = 1; //寄存器数量必须大于1 commandStr += $"{registerNumber:0000}"; Tuple<bool, List<short>> r = SendReadCommand(commandStr, readType); result.IsSuccessful = r.Item1; result.CommunicationTime = sw.ElapsedMilliseconds; if (!r.Item1) return result; byte[] bytes = BytesHelper.StructsToBytes(r.Item2); result.Results = BytesHelper.BytesToStructs<T>(bytes); result.CommunicationTime = sw.ElapsedMilliseconds; return result; }
读取寄存器帧的命令,发送字符串的生成方法SendReadCommand如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90/// <summary> /// 处理读取命令 /// </summary> /// <param name="commandStr"></param> /// <returns></returns> private Tuple<bool, List<short>> SendReadCommand(string commandStr, RegisterType readType) { commandStr += $"{GetChecksum(commandStr)}"; commandStr += "*r"; string resultStr = ""; List<short> result = new List<short>(); #if DEBUG WriteToLog($"PC=>PLCt:{commandStr}"); #endif _serialPort.Write(commandStr); try { string curFrame = ""; while (true) { curFrame = _serialPort.ReadTo("r"); //接收到一帧 读后就没有r了 #if DEBUG WriteToLog($"PLC=>PCt:{curFrame}"); #endif var checksum = GetChecksum(curFrame.Substring(0, curFrame.Length - 3)); var fcs = curFrame.Substring(curFrame.Length - 3, 2); if (checksum != fcs)//检测校验码 { _lastErrorMessage = "The received data FCS error"; #if DEBUG WriteToLog($"Errort:{_lastErrorMessage}"); #endif return new Tuple<bool, List<short>>(false, result); } if (curFrame.IndexOf("*") == -1) //判断是否是结束符 当前不是结束符 { #if DEBUG WriteToLog($"Infot:Current frame isn't end."); #endif resultStr += curFrame.Substring(0, curFrame.Length - 3); //去掉FCS和Terminator fcs效验过留着没用了 } else { WriteToLog($"Infot:Current frame is end."); resultStr += curFrame.Substring(0, curFrame.Length - 3); break; //这里退出 } _serialPort.Write("r"); //发送一个回车 } string errCode = ""; if (_unitNumber >= 0) { errCode = resultStr.Substring(5, 2); if (!CheckErrorCode(errCode)) return new Tuple<bool, List<short>>(false, result); resultStr = resultStr.Remove(0, 7); //已经验证过ErrCode 可以把头部去掉 } else { errCode = resultStr.Substring(3, 2); if (!CheckErrorCode(errCode)) return new Tuple<bool, List<short>>(false, result); resultStr = resultStr.Remove(0, 5); //已经验证过ErrCode 可以把都去掉 } #if DEBUG WriteToLog($"resultStr ist:{resultStr}"); #endif if (readType == RegisterType.TC_Status) { foreach (var c in resultStr) { result.Add(c == '0' ? (short)0 : (short)1); } } else { for (int i = 0; i <= resultStr.Length - 4; i += 4) { result.Add(short.Parse(resultStr.Substring(i, 4), System.Globalization.NumberStyles.HexNumber)); } } return new Tuple<bool, List<short>>(true, result); } catch (Exception exp) //一般是超时的异常 { _lastErrorMessage = exp.StackTrace; #if DEBUG WriteToLog($"Errort:{_lastErrorMessage}"); #endif return new Tuple<bool, List<short>>(false, result); } }
C-MODE写入寄存器的具体代码
写入代码返回比较简单,主要是验证是否正确的写入。具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public bool Write<T>(int begin, IList<T> datas, RegisterType writeType) where T : unmanaged { Stopwatch sw = new Stopwatch(); sw.Restart(); string commandStr = ""; commandStr += _firstChar; if (_unitNumber >= 0) { commandStr += _unitNumber.ToString("X2"); } commandStr += $"{GetWriteHeaderCode(writeType)}"; commandStr += $"{begin:0000}"; var bytes = BytesHelper.StructsToBytes(datas); var words = BytesHelper.BytesToStructs<short>(bytes); foreach (var word in words) { commandStr += $"{word:X4}"; } if (!SendWriteCommand(commandStr, writeType)) return false; else return true; }
读取寄存器帧的命令,发送字符串的生成方法SendWriteCommand如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105private bool SendWriteCommand(string commandStr, RegisterType writeType) { commandStr += GetChecksum(commandStr); //添加验证码 string reply = ""; //欧姆龙回复的字符串 string replyCheckSum = ""; string errorCode = ""; if (commandStr.Length <= 129) { commandStr += "*r"; //所有要发送的数据 _serialPort.Write(commandStr); #if DEBUG WriteToLog($"PC=>PLCt:{commandStr}"); #endif reply = _serialPort.ReadTo("r"); #if DEBUG WriteToLog($"PLC=>PCt:{reply}"); #endif replyCheckSum = reply.Substring(reply.Length - 3, 2); if (replyCheckSum != GetChecksum(reply.Substring(0, reply.Length - 3))) { _lastErrorMessage = "The received data FCS error"; #if DEBUG WriteToLog($"Errort:{_lastErrorMessage}"); #endif return false; } errorCode = ""; if (_unitNumber >= 0) errorCode = reply.Substring(5, 2); else errorCode = reply.Substring(3, 2); #if DEBUG WriteToLog($"Error codet:{errorCode}r"); #endif if (!CheckErrorCode(errorCode)) return false; return true; } else { string firstFrame = ""; string residueStr = ""; if (_unitNumber > 0) { firstFrame = commandStr.Substring(0, 127); //写入头部和前30个字 residueStr = commandStr.Remove(0, 127); } else { firstFrame = commandStr.Substring(0, 125); //写入头部和前30个字 residueStr = commandStr.Remove(0, 125); } firstFrame += GetChecksum(firstFrame + "r"); _serialPort.Write(firstFrame); #if DEBUG WriteToLog($"PC=>PLCt:{firstFrame}r"); #endif while (true) { reply = _serialPort.ReadTo("r"); if (reply.IndexOf("*") != -1) //已经是结束帧 其他的帧只是回车符 { replyCheckSum = reply.Substring(reply.Length - 3, 2); if (replyCheckSum != GetChecksum(reply.Substring(0, reply.Length - 3))) { _lastErrorMessage = "The received data FCS error"; #if DEBUG WriteToLog($"Errort:{_lastErrorMessage}r"); #endif return false; } errorCode = ""; if (_unitNumber >= 0) errorCode = reply.Substring(5, 2); else errorCode = reply.Substring(3, 2); #if DEBUG WriteToLog($"Error codet:{errorCode}r"); #endif if (!CheckErrorCode(errorCode)) return false; return true; } else //不是结束帧 只回复回车。 没有FCS { string sendCurFrame = "";//需要发送的当前帧 if (residueStr.Length / 4 <= 31) //需要发送的最后一帧 31 * 4 = 124 { sendCurFrame = residueStr; //全部发送 sendCurFrame += GetChecksum(sendCurFrame); _serialPort.Write(sendCurFrame + "*r"); WriteToLog($"PC=>PLCt:{sendCurFrame}"); } else { sendCurFrame = residueStr.Substring(0, 124); //发送31字 residueStr.Remove(0, 124); sendCurFrame += GetChecksum(sendCurFrame); _serialPort.Write(sendCurFrame + "r"); WriteToLog($"PC=>PLCt:{sendCurFrame}"); } } } } }
NOTE
由于本人没有欧姆龙PLC做验证,只是在朋友的帮助下验证了几个命令。所以C#代码仅供参考。
最后
以上就是满意钢笔最近收集整理的关于欧姆龙PLC HostLink通讯 C-MODE格式的全部内容,更多相关欧姆龙PLC内容请搜索靠谱客的其他文章。
发表评论 取消回复