c# 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;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM