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;
