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