C#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构:
- 采用定时器(Timer控件)为时间片。
- 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。
- 把正确接收的数据取出,转换为有特定的结构体中。
- 数据通过时间片实时刷新。
- MODBUS协议(这里不介绍了,网上有很多的权威资料)。
串口接收问题
这里采用的是MODBUS_RTU协议,是没有回车等明显的结束符的哈。所以在C#也不可以用serialPort1.ReadLine来读取。我用的是serialPort1.BytesToRead先读缓冲区中的数据个数,再通过个数据读数据。这样在用串口软件测试的时候确实很有用,再随之问题又出现了。下位机传上来的数据长度高出8个,就会分断接收。即接收到的两次的长度,第一次是8个,然后再接收到后面的。 原因是因为软件没有接收完一整帧数据后就进行了中断。解决方法:在中断中加入线程阻塞方法,然后再读取串口中的数据。
发送读数据和发送写数据的结构
写了多个MODBUS协议的上位机后,总结了些经验,并将这部分程序封装在一个类中。
使用时只需对其接口函数调用即可,有很强的移植性。在写软件时不用再在协议这部分花太多的时间。
基本的使用方法在注释中。程序总体感觉 可能过于臃肿,希望各位大神批评指点。
以下是源代码:
1 /*
2 * MODBUS协议
3 *
4 *
5 * 介绍:
6 * 此modbus上位机 协议类 具有较强的通用性
7 * 本协议类最主要的思想是 把所有向下位机发送的指令 先存放在缓冲区中(命名为管道)
8 * 再将管道中的指令逐个发送出去。
9 * 管道遵守FIFO的模式。管道中所存放指令的个数 在全局变量中定义。
10 * 管道内主要分为两部分:1,定时循环发送指令。2,一次性发送指令。
11 * 定时循环发送指令:周期性间隔时间发送指令,一般针对“输入寄存器”或“输入线圈”等实时更新的变量。
12 * 这两部分的长度由用户所添加指令个数决定(所以自由性强)。
13 * 指令的最大发送次数,及管道中最大存放指令的个数在常量定义中 可进行设定。
14 *
15 * 使用说明:
16 * 1,首先对所定义的寄存器或线圈进行分组定义,并定义首地址。
17 * 2,在MBDataTable数组中添加寄存器或线圈所对应的地址。 注意 寄存器:ob = new UInt16()。线圈:ob = new byte()。
18 * 3,对所定义的地址 用属性进行定义 以方便在类外进行访问及了解所对应地址的含义。
19 * 4,GetAddressValueLength函数中 对使用说明的"第一步"分组 的元素个数进行指定。
20 * 5,在主程序中调用MBConfig进行协议初始化(初始化内容参考函数)。
21 * 6,在串口中断函数中调用MBDataReceive()。
22 * 7,定时器调用MBRefresh()。(10ms以下)
23 * 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次 指令发送间隔为50ms。
24 * 8,在主程序初始化中添加固定实时发送的指令操作 用MBAddRepeatCmd函数。
25 * 9,在主程序运行过程中 根据需要添加 单个的指令操作(非固定重复发送的指令)用MBAddCmd函数。
26 *
27 *
28 * 作者:王宏强
29 * 时间:2012.7.2
30 *
31 *
32 *
33 *
34 *
35 *
36 */
37
38 using System;
39 using System.Collections.Generic;
40 using System.ComponentModel;
41 using System.Data;
42 using System.Drawing;
43 using System.Text;
44 using System.Windows.Forms;
45 using System.IO.Ports;
46
47 namespace WindowsApplication1
48 {
49
50 public class Modbus
51 {
52 #region 所用结构体
53 /// <summary>
54 /// 地址对应表元素单元
55 /// </summary>
56 public struct OPTable{
57 public volatile int addr;
58 public volatile byte type;
59 public volatile object ob;
60 };
61 /// <summary>
62 /// 当前的指令
63 /// </summary>
64 public struct MBCmd
65 {
66 public volatile int addr; //指令首地址
67 public volatile int stat; //功能码
68 public volatile int len; //所操作的寄存器或线圈的个数
69 public volatile int res; //返回码的状态, 0:无返回,1:正确返回
70 };
71 /// <summary>
72 /// 当前操作的指令管道
73 /// </summary>
74 public struct MBSci
75 {
76 public volatile MBCmd[] cmd; //指令结构体
77 public volatile int index; //当前索引
78 public volatile int count; //当前功能码执行的次数
79 public volatile int maxRepeatCount; //最大发送次数
80 public volatile int rtCount; //实时读取的指令各数(无限间隔时间读取)
81 };
82 #endregion
83
84 #region 常量定义
85 public const byte MB_READ_COILS = 0x01; //读线圈寄存器
86 public const byte MB_READ_DISCRETE = 0x02; //读离散输入寄存器
87 public const byte MB_READ_HOLD_REG = 0x03; //读保持寄存器
88 public const byte MB_READ_INPUT_REG = 0x04; //读输入寄存器
89 public const byte MB_WRITE_SINGLE_COIL = 0x05; //写单个线圈
90 public const byte MB_WRITE_SINGLE_REG = 0x06; //写单寄存器
91 public const byte MB_WRITE_MULTIPLE_COILS = 0x0f; //写多线圈
92 public const byte MB_WRITE_MULTIPLE_REGS = 0x10; //写多寄存器
93
94 private const int MB_MAX_LENGTH = 120; //最大数据长度
95 private const int MB_SCI_MAX_COUNT = 15; //指令管道最大存放的指令各数
96 private const int MB_MAX_REPEAT_COUNT = 3; //指令最多发送次数
97 #endregion
98
99 #region 全局变量
100 private static volatile bool sciLock = false; //调度器锁 true:加锁 false:解锁
101 private static volatile byte[] buff = new byte[MB_MAX_LENGTH]; //接收缓冲器
102 private static volatile int buffLen = 0;
103 private static volatile byte[] rBuff = null; //正确接收缓冲器
104 private static volatile byte[] wBuff = null; //正确发送缓冲器
105 public static MBSci gMBSci = new MBSci() { cmd = new MBCmd[MB_SCI_MAX_COUNT], index = 0, maxRepeatCount = MB_MAX_REPEAT_COUNT, rtCount = 0, count = 0 };
106 private static SerialPort comm = null;
107 private static int mbRefreshTime = 0;
108 #endregion
109
110 #region MODBUS 地址对应表
111 //modbus寄存器和线圈分组 首地址定义
112 public const int D_DIO = 0x0000;
113 public const int D_BASE = 0x0014;
114 public const int D_RANGE = 0x0018;
115 public const int D_PWM = 0x001A;
116 public const int D_PID = 0x001E;
117
118 /// <summary>
119 /// 变量所对应的地址 在此位置
120 /// </summary>
121 public static volatile OPTable[] MBDataTable =
122 {
123 new OPTable(){addr = D_DIO, type = MB_READ_INPUT_REG, ob = new UInt16()}, //0
124 new OPTable(){addr = D_DIO + 1, type = MB_READ_INPUT_REG, ob = new UInt16()},
125 new OPTable(){addr = D_DIO + 2, type = MB_READ_INPUT_REG, ob = new UInt16()},
126 new OPTable(){addr = D_DIO + 3, type = MB_READ_INPUT_REG, ob = new UInt16()},
127 new OPTable(){addr = D_DIO + 4, type = MB_READ_INPUT_REG, ob = new Int16()},
128 new OPTable(){addr = D_DIO + 5, type = MB_READ_INPUT_REG, ob = new Int16()},
129
130 new OPTable(){addr = D_BASE, type = MB_READ_HOLD_REG, ob = new Int16()}, //6
131 new OPTable(){addr = D_BASE + 1, type = MB_READ_HOLD_REG, ob = new Int16()},
132 new OPTable(){addr = D_BASE + 2, type = MB_READ_HOLD_REG, ob = new Int16()},
133 new OPTable(){addr = D_BASE + 3, type = MB_READ_HOLD_REG, ob = new Int16()},
134
135 new OPTable(){addr = D_RANGE, type = MB_READ_HOLD_REG, ob = new Int16()}, //10
136 new OPTable(){addr = D_RANGE + 1, type = MB_READ_HOLD_REG, ob = new Int16()},
137
138 new OPTable(){addr = D_PWM, type = MB_READ_HOLD_REG, ob = new Int16()}, //12
139 new OPTable(){addr = D_PWM + 1, type = MB_READ_HOLD_REG, ob = new Int16()},
140 new OPTable(){addr = D_PWM + 2, type = MB_READ_HOLD_REG, ob = new Int16()},
141 new OPTable(){addr = D_PWM + 3, type = MB_READ_HOLD_REG, ob = new Int16()},
142
143 new OPTable(){addr = D_PID, type = MB_READ_HOLD_REG, ob = new UInt16()}, //16
144 new OPTable(){addr = D_PID + 1, type = MB_READ_HOLD_REG, ob = new UInt16()},
145 new OPTable(){addr = D_PID + 2, type = MB_READ_HOLD_REG, ob = new UInt16()},
146 new OPTable(){addr = D_PID + 3, type = MB_READ_HOLD_REG, ob = new UInt16()},
147 new OPTable(){addr = D_PID + 4, type = MB_READ_HOLD_REG, ob = new UInt16()},
148 new OPTable(){addr = D_PID + 5, type = MB_READ_HOLD_REG, ob = new UInt16()},
149
150 };
151 public static UInt16 gDioX { get { return Convert.ToUInt16(MBDataTable[0].ob); } set { MBDataTable[0].ob = value; } }
152 public static UInt16 gDioY { get { return Convert.ToUInt16(MBDataTable[1].ob); } set { MBDataTable[1].ob = value; } }
153 public static UInt16 gDioZ { get { return Convert.ToUInt16(MBDataTable[2].ob); } set { MBDataTable[2].ob = value; } }
154 public static UInt16 gDioD { get { return Convert.ToUInt16(MBDataTable[3].ob); } set { MBDataTable[3].ob = value; } }
155 public static Int16 gDioXx { get { return (Int16)Convert.ToInt32(MBDataTable[4].ob); } set { MBDataTable[4].ob = value; } }
156 public static Int16 gDioXy { get { return (Int16)Convert.ToInt32(MBDataTable[5].ob); } set { MBDataTable[5].ob = value; } }
157
158 public static Int16 gBaseF1 { get { return (Int16)Convert.ToInt32(MBDataTable[6].ob); } set { MBDataTable[6].ob = value; } }
159 public static Int16 gBaseF2 { get { return (Int16)Convert.ToInt32(MBDataTable[7].ob); } set { MBDataTable[7].ob = value; } }
160 public static Int16 gBaseF3 { get { return (Int16)Convert.ToInt32(MBDataTable[8].ob); } set { MBDataTable[8].ob = value; } }
161 public static Int16 gBaseF4 { get { return (Int16)Convert.ToInt32(MBDataTable[9].ob); } set { MBDataTable[9].ob = value; } }
162
163 public static Int16 gRangeMax { get { return (Int16)Convert.ToInt32(MBDataTable[10].ob); } set { MBDataTable[10].ob = value; } }
164 public static Int16 gRangeMin { get { return (Int16)Convert.ToInt32(MBDataTable[11].ob); } set { MBDataTable[11].ob = value; } }
165
166 public static Int16 gPwmF1 { get { return (Int16)Convert.ToInt32(MBDataTable[12].ob); } set { MBDataTable[12].ob = value; } }
167 public static Int16 gPwmF2 { get { return (Int16)Convert.ToInt32(MBDataTable[13].ob); } set { MBDataTable[13].ob = value; } }
168 public static Int16 gPwmF3 { get { return (Int16)Convert.ToInt32(MBDataTable[14].ob); } set { MBDataTable[14].ob = value; } }
169 public static Int16 gPwmF4 { get { return (Int16)Convert.ToInt32(MBDataTable[15].ob); } set { MBDataTable[15].ob = value; } }
170
171 public static float gP
172 {
173 get
174 {
175 int tmp = (Convert.ToInt32(MBDataTable[16].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[17].ob) & 0xFFFF) << 16);
176 byte[] arr = BitConverter.GetBytes(tmp);
177 return BitConverter.ToSingle(arr, 0);
178 }
179 set
180 {
181 byte[] val = BitConverter.GetBytes(value);
182 MBDataTable[16].ob = BitConverter.ToUInt16(val, 0);
183 MBDataTable[17].ob = BitConverter.ToUInt16(val, 2);
184 }
185 }
186 public static float gI
187 {
188 get
189 {
190 int tmp = (Convert.ToInt32(MBDataTable[18].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[19].ob) & 0xFFFF) << 16);
191 byte[] arr = BitConverter.GetBytes(tmp);
192 return BitConverter.ToSingle(arr, 0);
193 }
194 set
195 {
196 byte[] val = BitConverter.GetBytes(value);
197 MBDataTable[18].ob = BitConverter.ToUInt16(val, 0);
198 MBDataTable[19].ob = BitConverter.ToUInt16(val, 2);
199 }
200 }
201 public static float gD
202 {
203 get
204 {
205 int tmp = (Convert.ToInt32(MBDataTable[20].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[21].ob) & 0xFFFF) << 16);
206 byte[] arr = BitConverter.GetBytes(tmp);
207 return BitConverter.ToSingle(arr, 0);
208 }
209 set
210 {
211 byte[] val = BitConverter.GetBytes(value);
212 MBDataTable[20].ob = BitConverter.ToUInt16(val, 0);
213 MBDataTable[21].ob = BitConverter.ToUInt16(val, 2);
214 }
215 }
216
217 public static UInt16 gNode = 100;
218 public static UInt16 gBaud = 38400;
219 /// <summary>
220 /// 获取寄存器或线圈 分组后的成员各数
221 /// </summary>
222 /// <param name="addr">首地址</param>
223 /// <returns>成员各数</returns>
224 private static int GetAddressValueLength(int addr)
225 {
226 int res = 0;
227 switch (addr)
228 {
229 case D_DIO: res = 6; break;
230 case D_BASE: res = 4; break;
231 case D_RANGE: res = 2; break;
232 case D_PWM: res = 4; break;
233 case D_PID: res = 6; break;
234 default: break;
235 }
236 return res;
237 }
238 /// <summary>
239 /// 获取地址所对应的数据
240 /// </summary>
241 /// <param name="addr">地址</param>
242 /// <param name="type">类型</param>
243 /// <returns>获取到的数据</returns>
244 private static object GetAddressValue(int addr, byte type)
245 {
246 switch (type) //功能码类型判断
247 {
248 case MB_READ_COILS:
249 case MB_READ_DISCRETE:
250 case MB_READ_HOLD_REG:
251 case MB_READ_INPUT_REG: break;
252 case MB_WRITE_SINGLE_COIL:
253 case MB_WRITE_MULTIPLE_COILS: type = MB_READ_DISCRETE; break;
254 case MB_WRITE_SINGLE_REG:
255 case MB_WRITE_MULTIPLE_REGS: type = MB_READ_HOLD_REG; break;
256 default: return null;
257 }
258
259 for (int i = 0; i < MBDataTable.Length; i++)
260 {
261 if (MBDataTable[i].addr == addr)
262 {
263 if (MBDataTable[i].type == type)
264 {
265 return MBDataTable[i].ob;
266 }
267 }
268 }
269 return null;
270 }
271 /// <summary>
272 /// 设置地址所对应的数据
273 /// </summary>
274 /// <param name="addr">地址</param>
275 /// <param name="type">类型</param>
276 /// <param name="data">数据</param>
277 /// <returns>是否成功</returns>
278 private static object SetAddressValue(int addr, byte type, object data)
279 {
280 for (int i = 0; i < MBDataTable.Length; i++)
281 {
282 if (MBDataTable[i].addr == addr)
283 {
284 if (MBDataTable[i].type == type)
285 {
286 MBDataTable[i].ob = data;
287 return true;
288 }
289 }
290 }
291 return null;
292 }
293 /// <summary>
294 /// 获取一连串数据
295 /// </summary>
296 /// <param name="addr">首地址</param>
297 /// <param name="type">功能码</param>
298 /// <param name="len">长度</param>
299 /// <returns>转换后的字节数组</returns>
300 private static byte[] GetAddressValues(int addr, byte type, int len)
301 {
302 byte[] arr = null;
303 object obj;
304 byte temp;
305 int temp2;
306
307 switch (type)
308 {
309 case MB_WRITE_MULTIPLE_COILS:
310 arr = new byte[(len % 8 == 0) ? (len / 8) : (len / 8 + 1)];
311 for (int i = 0; i < arr.Length; i++)
312 {
313 for (int j = 0; j < 8; j++)
314 { //获取地址所对应的数据 并判断所读数据 是否被指定,有没被指定的数据 直接返回null
315 obj = GetAddressValue(addr + i * 8 + j, MB_READ_COILS);
316 if (obj == null)
317 return null;
318 else
319 temp = Convert.ToByte(obj);
320 arr[i] |= (byte)((temp == 0? 0 : 1) << j);
321 }
322 }
323 break;
324 case MB_WRITE_MULTIPLE_REGS:
325 arr = new byte[len * 2];
326 for (int i = 0; i < len; i++)
327 {
328 obj = GetAddressValue(addr + i, MB_READ_HOLD_REG);
329 if (obj == null)
330 return null;
331 else
332 temp2 = Convert.ToInt32(obj);
333 arr[i * 2] = (byte)(temp2 >> 8);
334 arr[i * 2 + 1] = (byte)(temp2 & 0xFF);
335 }
336 break;
337 default: break;
338 }
339 return arr;
340 }
341 #endregion
342
343 #region 校验
344 private static readonly byte[] aucCRCHi = {
345 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
346 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
347 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
348 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
349 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
350 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
351 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
352 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
353 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
354 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
355 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
356 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
357 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
358 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
359 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
360 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
361 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
362 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
363 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
364 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
365 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
366 0x00, 0xC1, 0x81, 0x40
367 };
368 private static readonly byte[] aucCRCLo = {
369 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
370 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
371 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
372 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
373 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
374 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
375 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
376 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
377 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
378 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
379 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
380 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
381 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
382 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
383 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
384 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
385 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
386 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
387 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
388 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
389 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
390 0x41, 0x81, 0x80, 0x40
391 };
392 /// <summary>
393 /// CRC效验
394 /// </summary>
395 /// <param name="pucFrame">效验数据</param>
396 /// <param name="usLen">数据长度</param>
397 /// <returns>效验结果</returns>
398 public static int Crc16(byte[] pucFrame, int usLen)
399 {
400 int i = 0;
401 byte ucCRCHi = 0xFF;
402 byte ucCRCLo = 0xFF;
403 UInt16 iIndex = 0x0000;
404
405 while (usLen-- > 0)
406 {
407 iIndex = (UInt16)(ucCRCLo ^ pucFrame[i++]);
408 ucCRCLo = (byte)(ucCRCHi ^ aucCRCHi[iIndex]);
409 ucCRCHi = aucCRCLo[iIndex];
410 }
411 return (ucCRCHi << 8 | ucCRCLo);
412 }
413
414 #endregion
415
416 #region 发送指命操作
417 /// <summary>
418 /// 首部分数据 node:节点
419 /// </summary>
420 /// <param name="addr">寄存器地址</param>
421 /// <param name="len">数据长度,或单个数据</param>
422 /// <param name="stat"></param>
423 /// <returns></returns>
424 private static byte[] SendTrainHead(int node, int addr, int len, byte stat)
425 {
426 byte[] head = new byte[6];
427
428 head[0] = Convert.ToByte(node);
429 head[1] = stat;
430 head[2] = (byte)(addr >> 8);
431 head[3] = (byte)(addr & 0xFF);
432 head[4] = (byte)(len >> 8);
433 head[5] = (byte)(len & 0xFF);
434
435 return head;
436 }
437 /// <summary>
438 /// 计算数据长度 并在0x0f,0x10功能下 加载字节数
439 /// </summary>
440 /// <param name="arr"></param>
441 /// <param name="len"></param>
442 /// <param name="stat"></param>
443 /// <returns></returns>
444 private static byte[] SendTrainBytes(byte[] arr, ref int len, byte stat)
445 {
446 byte[] res;
447 switch (stat)
448 {
449 default: len = 0; break;
450
451 case MB_READ_COILS:
452 case MB_READ_DISCRETE:
453 case MB_READ_HOLD_REG:
454 case MB_READ_INPUT_REG:
455 case MB_WRITE_SINGLE_COIL:
456 case MB_WRITE_SINGLE_REG:
457 len = 0;
458 break;
459
460 case MB_WRITE_MULTIPLE_COILS:
461 len = (len % 8 == 0) ? (len / 8) : (len / 8 + 1);
462 res = new byte[arr.Length + 1];
463 arr.CopyTo(res, 0);
464 res[arr.Length] = (byte)(len);
465 arr = res;
466 break;
467
468 case MB_WRITE_MULTIPLE_REGS:
469 len *= 2;
470 res = new byte[arr.Length + 1];
471 arr.CopyTo(res, 0);
472 res[arr.Length] = (byte)len; //把字节写入数据最后位置
473 arr = res;
474 break;
475
476 }
477 return arr;
478 }
479 /// <summary>
480 /// 主控方式 发送指令模板
481 /// </summary>
482 /// <param name="node">节点</param>
483 /// <param name="data">数据</param>
484 /// <param name="addr">地址</param>
485 /// <param name="con">变量各数</param>
486 /// <param name="stat">功能码</param>
487 /// <returns></returns>
488 private static byte[] SendTrainCyclostyle(int node, byte[] data, int addr, int con, byte stat)
489 {
490 int crcVal = 0;
491 byte[] headData = SendTrainHead(node, addr, con, stat); //写首部分数据
492 byte[] headDataLen = SendTrainBytes(headData, ref con, stat); //计算数据的长度,有字节则写入。
493 byte[] res = new byte[headDataLen.Length + con + 2];
494
495 headDataLen.CopyTo(res, 0);
496
497 if ((stat == MB_WRITE_MULTIPLE_REGS) || (stat == MB_WRITE_MULTIPLE_COILS))
498 Array.Copy(data, 0, res, headDataLen.Length, con); //把数据复制到数据中
499
500 crcVal = Crc16(res, res.Length - 2);
501 res[res.Length - 2] = (byte)(crcVal & 0xFF);
502 res[res.Length - 1] = (byte)(crcVal >> 8);
503
504 return res;
505 }
506 /// <summary>
507 /// 封装发送数据帧
508 /// </summary>
509 /// <param name="node">从机地址</param>
510 /// <param name="cmd">指令信息</param>
511 /// <returns></returns>
512 private static byte[] SendPduPack(int node, MBCmd cmd)
513 {
514 byte[] res = null;
515 switch (cmd.stat)
516 {
517 case MB_READ_COILS:
518 case MB_READ_DISCRETE:
519 case MB_READ_HOLD_REG:
520 case MB_READ_INPUT_REG:
521 case MB_WRITE_SINGLE_COIL:
522 case MB_WRITE_SINGLE_REG:
523 res = SendTrainCyclostyle(node, null, cmd.addr, cmd.len, (byte)cmd.stat); break;
524
525 case MB_WRITE_MULTIPLE_COILS:
526 case MB_WRITE_MULTIPLE_REGS:
527 byte[] data = GetAddressValues(cmd.addr, (byte)cmd.stat, cmd.len);
528 res = SendTrainCyclostyle(node, data, cmd.addr, cmd.len, (byte)cmd.stat); break;
529 }
530 return res;
531 }
532 #endregion
533
534 #region 回传数据操作
535 /// <summary>
536 /// 存储回传的线圈
537 /// </summary>
538 /// <param name="data">回传的数组</param>
539 /// <param name="addr">首地址</param>
540 /// <returns>存储是否正确</returns>
541 private static bool ReadDiscrete(byte[] data, int addr)
542 {
543 bool res = true;
544 int len = data[2];
545
546 if (len != (data.Length - 5)) //数据长度不正确 直接退出
547 return false;
548
549 for (int i = 0; i < len; i++)
550 {
551 for (int j = 0; j < 8; j++)
552 {
553 if (SetAddressValue(addr + i * 8 + j, data[1], data[i + 3] & (0x01 << j)) == null)
554 {
555 return false;
556 }
557 }
558 }
559 return res;
560 }
561 /// <summary>
562 /// 读回传的寄存器
563 /// </summary>
564 /// <param name="data">回传的数组</param>
565 /// <param name="addr">首地址</param>
566 /// <returns>存储是否正确</returns>
567 private static bool ReadReg(byte[] data, int addr)
568 {
569 bool res = true;
570 int len = data[2];
571
572 if (len != (data.Length - 5)) //数据长度不正确 直接退出
573 return false;
574
575 for (int i = 0; i < len; i += 2)
576 {
577 if (SetAddressValue(addr + i / 2, data[1], (data[i + 3] << 8) | data[i + 4]) == null)
578 {
579 res = false;
580 break;
581 }
582 }
583 return res;
584 }
585 /// <summary>
586 /// 回传的数据处理
587 /// </summary>
588 /// <param name="buff">回传的整帧数据</param>
589 /// <param name="addr">当前所操作的首地址</param>
590 /// <returns></returns>
591 private static bool ReceiveDataProcess(byte[] buff, int addr)
592 {
593 if (buff == null)
594 return false;
595 if (buff.Length < 5) //回传的数据 地址+功能码+长度+2效验 = 5字节
596 return false;
597
598 bool res = true;
599 switch (buff[1])
600 {
601 case MB_READ_COILS: ReadDiscrete(buff, addr); break;
602 case MB_READ_DISCRETE: ReadDiscrete(buff, addr); break;
603 case MB_READ_HOLD_REG: ReadReg(buff, addr); break;
604 case MB_READ_INPUT_REG: ReadReg(buff, addr); break;
605 case MB_WRITE_SINGLE_COIL:
606 case MB_WRITE_SINGLE_REG:
607 case MB_WRITE_MULTIPLE_COILS:
608 case MB_WRITE_MULTIPLE_REGS: break;
609 default: res = false; break;
610 }
611 return res;
612 }
613 #endregion
614
615 #region 收发调度
616 /// <summary>
617 /// 添加重复操作指令
618 /// </summary>
619 /// <param name="sci">待发送的指命管道</param>
620 /// <param name="addr">所添加指令的首地址</param>
621 /// <param name="len">所添加指令的寄存器或线圈个数</param>
622 /// <param name="stat">所添加指令的功能码</param>
623 private static void SciAddRepeatCmd(ref MBSci sci, int addr, int len, int stat)
624 {
625 if (sci.rtCount >= MB_SCI_MAX_COUNT - 1) //超出指令管道最大长度 直接退出
626 return;
627 if (len == 0) //地址的数据长度为空 直接退出
628 return;
629
630 sci.cmd[sci.rtCount].addr = addr;
631 sci.cmd[sci.rtCount].len = len;
632 sci.cmd[sci.rtCount].stat = stat;
633 sci.cmd[sci.rtCount].res = 0;
634 sci.rtCount++;
635 }
636 /// <summary>
637 /// 添加一次性操作指令
638 /// </summary>
639 /// <param name="sci">待发送的指命管道</param>
640 /// <param name="addr">所添加指令的首地址</param>
641 /// <param name="len">所添加指令的寄存器或线圈个数</param>
642 /// <param name="stat">所添加指令的功能码</param>
643 private static void SciAddCmd(ref MBSci sci, int addr, int len, int stat)
644 {
645 if (len == 0) //地址的数据长度为空 直接退出
646 return;
647
648 for (int i = sci.rtCount; i < MB_SCI_MAX_COUNT; i++)
649 {
650 if (sci.cmd[i].addr == -1) //把指令载入到空的管道指令上
651 {
652 sci.cmd[i].addr = addr;
653 sci.cmd[i].len = len;
654 sci.cmd[i].stat = stat;
655 sci.cmd[i].res = 0;
656 break;
657 }
658 }
659 }
660 /// <summary>
661 /// 清空重复读取指令集
662 /// </summary>
663 /// <param name="sci">待发送的指命管道</param>
664 private static void SciClearRepeatCmd(ref MBSci sci)
665 {
666 sci.rtCount = 0;
667 }
668 /// <summary>
669 /// 清空一次性读取指令集
670 /// </summary>
671 /// <param name="sci">待发送的指命管道</param>
672 private static void SciClearCmd(ref MBSci sci)
673 {
674 for (int i = sci.rtCount; i < MB_SCI_MAX_COUNT; i++)
675 {
676 sci.cmd[i].addr = -1;
677 sci.cmd[i].len = 0;
678 sci.cmd[i].res = 0;
679 }
680 }
681 /// <summary>
682 /// 跳到下一个操作指令
683 /// </summary>
684 /// <param name="sci">待发送的指命管道</param>
685 private static void SciJumbNext(ref MBSci sci)
686 {
687 if (sci.index >= sci.rtCount) //非实时读取地址会被清除
688 {
689 sci.cmd[sci.index].addr = -1;
690 sci.cmd[sci.index].len = 0;
691 sci.cmd[sci.index].stat = 0;
692 }
693
694 do{
695 sci.index++;
696 if (sci.index >= MB_SCI_MAX_COUNT) //超出指令最大范围
697 {
698 sci.index = 0;
699 if (sci.rtCount == 0) //如果固定实时读取 为空 直接跳出
700 break;
701 }
702
703 } while (sci.cmd[sci.index].addr == -1);
704 sci.cmd[sci.index].res = 0; //本次返回状态清零
705 }
706 /// <summary>
707 /// 发送指令调度锁定
708 /// </summary>
709 public static void SciSchedulingLock()
710 {
711 sciLock = true;
712 }
713 /// <summary>
714 /// 发送指令调度解锁
715 /// </summary>
716 public static void SciSchedulingUnlock()
717 {
718 sciLock = false;
719 }
720 /// <summary>
721 /// 待发送的指令管道调度
722 /// </summary>
723 /// <param name="sci">待发送的指命管道</param>
724 /// <param name="rBuf">收到正确的回传数据</param>
725 /// <param name="wBuf">准备发送的指令数据</param>
726 private static void SciScheduling(ref MBSci sci, ref byte[] rBuf, ref byte[] wBuf)
727 {
728 if (sciLock) //如果被加锁 直接退出
729 return;
730
731 if ((sci.cmd[sci.index].res != 0) || (sci.count >= sci.maxRepeatCount))
732 {
733 sci.count = 0; //发送次数清零
734 if (sci.cmd[sci.index].res != 0) //如果收到了正常返回
735 {
736 ReceiveDataProcess(rBuf, sci.cmd[sci.index].addr); //保存数据
737 rBuf = null; //清空当前接收缓冲区的内容, 以防下次重复读取
738 }
739 else
740 {
741 //参数操作失败
742 }
743
744 SciJumbNext(ref sci);
745 }
746 wBuf = SendPduPack((int)gNode, sci.cmd[sci.index]); //发送指令操作
747 sci.count++; //发送次数加1
748 }
749 /// <summary>
750 /// 快速刷新 处理接收到的数据 建议:10ms以下
751 /// </summary>
752 /// <returns>所正确回传数据的功能码, null:回传不正确</returns>
753 private static int MBQuickRefresh()
754 {
755 int res = -1;
756 if (rBuff != null)
757 {
758 SciSchedulingLock();
759 if (ReceiveDataProcess(rBuff, gMBSci.cmd[gMBSci.index].addr) == true)
760 {
761 gMBSci.cmd[gMBSci.index].res = 1; //标记 所接收到的数据正确
762 res = gMBSci.cmd[gMBSci.index].stat;
763 }
764 rBuff = null;
765 SciSchedulingUnlock();
766 }
767 return res;
768 }
769 /// <summary>
770 /// 调度间隔时间刷新 建议:50ms以上
771 /// </summary>
772 /// <returns>封装好的协议帧</returns>
773 private static void MBSchedRefresh()
774 {
775 SciScheduling(ref gMBSci, ref rBuff, ref wBuff);
776 if (wBuff != null)
777 comm.Write(wBuff, 0, wBuff.Length);
778 }
779
780 #endregion
781
782 #region 接口函数
783 /// <summary>
784 /// 清空存放一次性的指令空间
785 /// </summary>
786 public static void MBClearCmd()
787 {
788 SciClearCmd(ref gMBSci);
789 }
790 /// <summary>
791 /// 添加固定刷新(重复) 操作指令
792 /// </summary>
793 /// <param name="addr">地址</param>
794 /// <param name="stat">功能码</param>
795 public static void MBAddRepeatCmd(int addr, byte stat)
796 {
797 for (int i = 0; i < GetAddressValueLength(addr); i++ )
798 if (GetAddressValue(addr, stat) == null) //如果所添加的指令没有在MODBUS对应表中定义 直接退出
799 return;
800 SciAddRepeatCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
801 }
802 /// <summary>
803 /// 添加一次性 操作指令
804 /// </summary>
805 /// <param name="addr"></param>
806 /// <param name="stat"></param>
807 public static void MBAddCmd(int addr, byte stat)
808 {
809 for (int i = 0; i < GetAddressValueLength(addr); i++)
810 if (GetAddressValue(addr, stat) == null) //如果所添加的指令没有在MODBUS对应表中定义 直接退出
811 return;
812 SciAddCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
813 }
814 /// <summary>
815 /// 串口参数配置
816 /// </summary>
817 /// <param name="commx">所用到的串口</param>
818 /// <param name="node"></param>
819 /// <param name="baud"></param>
820 public static void MBConfig(SerialPort commx, UInt16 node, UInt16 baud)
821 {
822 gBaud = baud;
823 gNode = node;
824 comm = commx;
825 SciClearRepeatCmd(ref gMBSci);
826 SciClearCmd(ref gMBSci);
827 }
828 /// <summary>
829 /// 读取串口中接收到的数据
830 /// </summary>
831 /// <param name="comm">所用到的串口</param>
832 public static void MBDataReceive()
833 {
834 if (comm == null) //如果串口没有被初始化直接退出
835 return;
836 SciSchedulingLock();
837 System.Threading.Thread.Sleep(20); //等待缓冲器满
838
839 buffLen = comm.BytesToRead; //获取缓冲区字节长度
840 if (buffLen > MB_MAX_LENGTH) //如果长度超出范围 直接退出
841 {
842 SciSchedulingUnlock();
843 return;
844 }
845 comm.Read(buff, 0, buffLen); //读取数据
846 if (gMBSci.cmd[gMBSci.index].stat == buff[1])
847 {
848 if (Crc16(buff, buffLen) == 0)
849 {
850 rBuff = new byte[buffLen];
851 Array.Copy(buff, rBuff, buffLen);
852 }
853 }
854 SciSchedulingUnlock();
855 }
856 /// <summary>
857 /// MODBUS的实时刷新任务,在定时器在实时调用此函数
858 /// 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次 指令发送间隔为50ms。
859 /// </summary>
860 /// <returns>返回当前功能读取指令回传 的功能码</returns>
861 public static int MBRefresh()
862 {
863 if (sciLock) //如果被加锁 直接退出
864 return 0;
865
866 mbRefreshTime++;
867 if (mbRefreshTime > 10)
868 {
869 mbRefreshTime = 0;
870 MBSchedRefresh();
871 }
872 return MBQuickRefresh();
873 }
874 #endregion
875
876
877 }
878
879 }
下面是自己开发的一个小控制软件及原代码:
原文件上传到我的网盘中:
http://115.com/file/dp4vm8c7#CopterSoftware.rar
提示:这个小软件用了第三方插件Developer Express v2011。确认安装此插件方能正常打开。
下面这个小工具是用modbus发送 大块数据的样例:
http://pan.baidu.com/share/link?shareid=157523&uk=118334538
日志 BUG修改:
1,如下图增加 ,修正在无重复指令时,单次指令的次数的正确性。
return;
![](https://i-blog.csdnimg.cn/blog_migrate/65f87bcd69ace24172e80d2c4c148e29.png)
转载请注明本文地址: c# MODBUS协议 上位机