您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

c# MODBUS协议 上位机

bubuko 2022/1/25 19:52:41 dotnet 字数 42381 阅读 1061 来源 http://www.bubuko.com/infolist-5-1.html

C#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构: 采用定时器(Timer控件)为时间片。 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。 把正确接收的数据取出,转换为有特定的结构体中。 数据通过时间片实时刷新。 MODBUS ...

C#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构:

  1. 采用定时器(Timer控件)为时间片。
  2. 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。
  3. 把正确接收的数据取出,转换为有特定的结构体中。
  4. 数据通过时间片实时刷新。
  5. 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,如下图增加 ,修正在无重复指令时,单次指令的次数的正确性。

if (sci.cmd[0].addr == -1)
                return;
技术分享图片

 来源:https://www.cnblogs.com/hiker-blogs/archive/2012/02/15/2353195.html

 
 

c# MODBUS协议 上位机

原文:https://www.cnblogs.com/li-sx/p/12830533.html


如果您也喜欢它,动动您的小指点个赞吧

除非注明,文章均由 laddyq.com 整理发布,欢迎转载。

转载请注明:
链接:http://laddyq.com
来源:laddyq.com
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


联系我
置顶