紫外工控论坛

 找回密码
 立即注册

扫一扫,访问微社区

QQ登录

只需一步,快速开始

搜索
查看: 2451|回复: 0

[C/VC] c# MODBUS协议 上位机

[复制链接]
冰糖 发表于 2014-12-24 10:28:49 | 显示全部楼层 |阅读模式
本文转自 http://www.cnblogs.com/hiker-blo ... /02/15/2353195.html

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. using System;
  38. using System.Collections.Generic;
  39. using System.ComponentModel;
  40. using System.Data;
  41. using System.Drawing;
  42. using System.Text;
  43. using System.Windows.Forms;
  44. using System.IO.Ports;

  45. namespace WindowsApplication1
  46. {

  47.     public class Modbus
  48.     {
  49.         #region 所用结构体
  50.         ///
  51.         /// 地址对应表元素单元
  52.         ///
  53.         public struct OPTable{
  54.             public volatile int addr;
  55.             public volatile byte type;
  56.             public volatile object ob;
  57.         };
  58.         ///
  59.         /// 当前的指令
  60.         ///
  61.         public struct MBCmd
  62.         {
  63.             public volatile int addr;           //指令首地址
  64.             public volatile int stat;           //功能码
  65.             public volatile int len;            //所操作的寄存器或线圈的个数
  66.             public volatile int res;            //返回码的状态, 0:无返回,1:正确返回
  67.         };
  68.         ///
  69.         /// 当前操作的指令管道
  70.         ///
  71.         public struct MBSci
  72.         {
  73.             public volatile MBCmd[] cmd;             //指令结构体
  74.             public volatile int index;               //当前索引
  75.             public volatile int count;               //当前功能码执行的次数
  76.             public volatile int maxRepeatCount;      //最大发送次数
  77.             public volatile int rtCount;             //实时读取的指令各数(无限间隔时间读取)
  78.         };
  79.         #endregion

  80.         #region 常量定义
  81.         public const byte MB_READ_COILS = 0x01;             //读线圈寄存器
  82.         public const byte MB_READ_DISCRETE = 0x02;          //读离散输入寄存器
  83.         public const byte MB_READ_HOLD_REG = 0x03;          //读保持寄存器
  84.         public const byte MB_READ_INPUT_REG = 0x04;         //读输入寄存器
  85.         public const byte MB_WRITE_SINGLE_COIL = 0x05;      //写单个线圈
  86.         public const byte MB_WRITE_SINGLE_REG = 0x06;       //写单寄存器
  87.         public const byte MB_WRITE_MULTIPLE_COILS = 0x0f;   //写多线圈
  88.         public const byte MB_WRITE_MULTIPLE_REGS = 0x10;    //写多寄存器

  89.         private const int MB_MAX_LENGTH = 120;               //最大数据长度
  90.         private const int MB_SCI_MAX_COUNT = 15;             //指令管道最大存放的指令各数
  91.         private const int MB_MAX_REPEAT_COUNT = 3;           //指令最多发送次数
  92.         #endregion

  93.         #region 全局变量
  94.         private static volatile bool sciLock = false;                       //调度器锁 true:加锁  false:解锁
  95.         private static volatile byte[] buff = new byte[MB_MAX_LENGTH];      //接收缓冲器
  96.         private static volatile int buffLen = 0;
  97.         private static volatile byte[] rBuff = null;                  //正确接收缓冲器
  98.         private static volatile byte[] wBuff = null;                     //正确发送缓冲器
  99.         public static MBSci gMBSci = new MBSci() { cmd = new MBCmd[MB_SCI_MAX_COUNT], index = 0, maxRepeatCount = MB_MAX_REPEAT_COUNT, rtCount = 0, count = 0 };
  100.         private static SerialPort comm = null;
  101.         private static int mbRefreshTime = 0;
  102.         #endregion

  103.         #region MODBUS 地址对应表
  104.         //modbus寄存器和线圈分组 首地址定义
  105.         public const int D_DIO = 0x0000;
  106.         public const int D_BASE = 0x0014;
  107.         public const int D_RANGE = 0x0018;
  108.         public const int D_PWM = 0x001A;
  109.         public const int D_PID = 0x001E;

  110.         ///
  111.         /// 变量所对应的地址 在此位置
  112.         ///
  113.         public static volatile OPTable[] MBDataTable =
  114.         {
  115.             new OPTable(){addr = D_DIO,         type = MB_READ_INPUT_REG,      ob = new UInt16()},      //0
  116.             new OPTable(){addr = D_DIO + 1,     type = MB_READ_INPUT_REG,      ob = new UInt16()},
  117.             new OPTable(){addr = D_DIO + 2,     type = MB_READ_INPUT_REG,      ob = new UInt16()},
  118.             new OPTable(){addr = D_DIO + 3,     type = MB_READ_INPUT_REG,      ob = new UInt16()},
  119.             new OPTable(){addr = D_DIO + 4,     type = MB_READ_INPUT_REG,      ob = new Int16()},
  120.             new OPTable(){addr = D_DIO + 5,     type = MB_READ_INPUT_REG,      ob = new Int16()},

  121.             new OPTable(){addr = D_BASE,        type = MB_READ_HOLD_REG,      ob = new Int16()},        //6
  122.             new OPTable(){addr = D_BASE + 1,    type = MB_READ_HOLD_REG,      ob = new Int16()},
  123.             new OPTable(){addr = D_BASE + 2,    type = MB_READ_HOLD_REG,      ob = new Int16()},
  124.             new OPTable(){addr = D_BASE + 3,    type = MB_READ_HOLD_REG,      ob = new Int16()},

  125.             new OPTable(){addr = D_RANGE,       type = MB_READ_HOLD_REG,      ob = new Int16()},        //10
  126.             new OPTable(){addr = D_RANGE + 1,   type = MB_READ_HOLD_REG,      ob = new Int16()},

  127.             new OPTable(){addr = D_PWM,         type = MB_READ_HOLD_REG,      ob = new Int16()},        //12
  128.             new OPTable(){addr = D_PWM + 1,     type = MB_READ_HOLD_REG,      ob = new Int16()},
  129.             new OPTable(){addr = D_PWM + 2,     type = MB_READ_HOLD_REG,      ob = new Int16()},
  130.             new OPTable(){addr = D_PWM + 3,     type = MB_READ_HOLD_REG,      ob = new Int16()},

  131.             new OPTable(){addr = D_PID,         type = MB_READ_HOLD_REG,      ob = new UInt16()},        //16
  132.             new OPTable(){addr = D_PID + 1,     type = MB_READ_HOLD_REG,      ob = new UInt16()},
  133.             new OPTable(){addr = D_PID + 2,     type = MB_READ_HOLD_REG,      ob = new UInt16()},
  134.             new OPTable(){addr = D_PID + 3,     type = MB_READ_HOLD_REG,      ob = new UInt16()},
  135.             new OPTable(){addr = D_PID + 4,     type = MB_READ_HOLD_REG,      ob = new UInt16()},
  136.             new OPTable(){addr = D_PID + 5,     type = MB_READ_HOLD_REG,      ob = new UInt16()},

  137.         };
  138.         public static UInt16 gDioX { get { return Convert.ToUInt16(MBDataTable[0].ob); } set { MBDataTable[0].ob = value; } }
  139.         public static UInt16 gDioY { get { return Convert.ToUInt16(MBDataTable[1].ob); } set { MBDataTable[1].ob = value; } }
  140.         public static UInt16 gDioZ { get { return Convert.ToUInt16(MBDataTable[2].ob); } set { MBDataTable[2].ob = value; } }
  141.         public static UInt16 gDioD { get { return Convert.ToUInt16(MBDataTable[3].ob); } set { MBDataTable[3].ob = value; } }
  142.         public static Int16 gDioXx { get { return (Int16)Convert.ToInt32(MBDataTable[4].ob); } set { MBDataTable[4].ob = value; } }
  143.         public static Int16 gDioXy { get { return (Int16)Convert.ToInt32(MBDataTable[5].ob); } set { MBDataTable[5].ob = value; } }

  144.         public static Int16 gBaseF1 { get { return (Int16)Convert.ToInt32(MBDataTable[6].ob); } set { MBDataTable[6].ob = value; } }
  145.         public static Int16 gBaseF2 { get { return (Int16)Convert.ToInt32(MBDataTable[7].ob); } set { MBDataTable[7].ob = value; } }
  146.         public static Int16 gBaseF3 { get { return (Int16)Convert.ToInt32(MBDataTable[8].ob); } set { MBDataTable[8].ob = value; } }
  147.         public static Int16 gBaseF4 { get { return (Int16)Convert.ToInt32(MBDataTable[9].ob); } set { MBDataTable[9].ob = value; } }

  148.         public static Int16 gRangeMax { get { return (Int16)Convert.ToInt32(MBDataTable[10].ob); } set { MBDataTable[10].ob = value; } }
  149.         public static Int16 gRangeMin { get { return (Int16)Convert.ToInt32(MBDataTable[11].ob); } set { MBDataTable[11].ob = value; } }

  150.         public static Int16 gPwmF1 { get { return (Int16)Convert.ToInt32(MBDataTable[12].ob); } set { MBDataTable[12].ob = value; } }
  151.         public static Int16 gPwmF2 { get { return (Int16)Convert.ToInt32(MBDataTable[13].ob); } set { MBDataTable[13].ob = value; } }
  152.         public static Int16 gPwmF3 { get { return (Int16)Convert.ToInt32(MBDataTable[14].ob); } set { MBDataTable[14].ob = value; } }
  153.         public static Int16 gPwmF4 { get { return (Int16)Convert.ToInt32(MBDataTable[15].ob); } set { MBDataTable[15].ob = value; } }

  154.         public static float gP
  155.         {
  156.             get
  157.             {
  158.                 int tmp = (Convert.ToInt32(MBDataTable[16].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[17].ob) & 0xFFFF) << 16);
  159.                 byte[] arr = BitConverter.GetBytes(tmp);
  160.                 return BitConverter.ToSingle(arr, 0);
  161.             }
  162.             set
  163.             {
  164.                 byte[] val = BitConverter.GetBytes(value);
  165.                 MBDataTable[16].ob = BitConverter.ToUInt16(val, 0);
  166.                 MBDataTable[17].ob = BitConverter.ToUInt16(val, 2);
  167.             }
  168.         }
  169.         public static float gI
  170.         {
  171.             get
  172.             {
  173.                 int tmp = (Convert.ToInt32(MBDataTable[18].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[19].ob) & 0xFFFF) << 16);
  174.                 byte[] arr = BitConverter.GetBytes(tmp);
  175.                 return BitConverter.ToSingle(arr, 0);
  176.             }
  177.             set
  178.             {
  179.                 byte[] val = BitConverter.GetBytes(value);
  180.                 MBDataTable[18].ob = BitConverter.ToUInt16(val, 0);
  181.                 MBDataTable[19].ob = BitConverter.ToUInt16(val, 2);
  182.             }
  183.         }
  184.         public static float gD
  185.         {
  186.             get
  187.             {
  188.                 int tmp = (Convert.ToInt32(MBDataTable[20].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[21].ob) & 0xFFFF) << 16);
  189.                 byte[] arr = BitConverter.GetBytes(tmp);
  190.                 return BitConverter.ToSingle(arr, 0);
  191.             }
  192.             set
  193.             {
  194.                 byte[] val = BitConverter.GetBytes(value);
  195.                 MBDataTable[20].ob = BitConverter.ToUInt16(val, 0);
  196.                 MBDataTable[21].ob = BitConverter.ToUInt16(val, 2);
  197.             }
  198.         }

  199.         public static UInt16 gNode = 100;
  200.         public static UInt16 gBaud = 38400;
  201.         ///
  202.         /// 获取寄存器或线圈 分组后的成员各数
  203.         ///
  204.         /// 首地址
  205.         /// 成员各数
  206.         private static int GetAddressValueLength(int addr)
  207.         {
  208.             int res = 0;
  209.             switch (addr)
  210.             {
  211.                 case D_DIO: res = 6; break;
  212.                 case D_BASE: res = 4; break;
  213.                 case D_RANGE: res = 2; break;
  214.                 case D_PWM: res = 4; break;
  215.                 case D_PID: res = 6; break;
  216.                 default: break;
  217.             }
  218.             return res;
  219.         }
  220.         ///
  221.         /// 获取地址所对应的数据
  222.         ///
  223.         /// 地址
  224.         /// 类型
  225.         /// 获取到的数据
  226.         private static object GetAddressValue(int addr, byte type)
  227.         {
  228.             switch (type)       //功能码类型判断
  229.             {
  230.                 case MB_READ_COILS:
  231.                 case MB_READ_DISCRETE:
  232.                 case MB_READ_HOLD_REG:
  233.                 case MB_READ_INPUT_REG: break;
  234.                 case MB_WRITE_SINGLE_COIL:
  235.                 case MB_WRITE_MULTIPLE_COILS: type = MB_READ_DISCRETE; break;
  236.                 case MB_WRITE_SINGLE_REG:
  237.                 case MB_WRITE_MULTIPLE_REGS: type = MB_READ_HOLD_REG; break;
  238.                 default: return null;
  239.             }

  240.             for (int i = 0; i < MBDataTable.Length; i++)
  241.             {
  242.                 if (MBDataTable[i].addr == addr)
  243.                 {
  244.                     if (MBDataTable[i].type == type)
  245.                     {
  246.                         return MBDataTable[i].ob;
  247.                     }
  248.                 }
  249.             }
  250.             return null;
  251.         }
  252.         ///
  253.         /// 设置地址所对应的数据
  254.         ///
  255.         /// 地址
  256.         /// 类型
  257.         /// 数据
  258.         /// 是否成功
  259.         private static object SetAddressValue(int addr, byte type, object data)
  260.         {
  261.             for (int i = 0; i < MBDataTable.Length; i++)
  262.             {
  263.                 if (MBDataTable[i].addr == addr)
  264.                 {
  265.                     if (MBDataTable[i].type == type)
  266.                     {
  267.                         MBDataTable[i].ob = data;
  268.                         return true;
  269.                     }
  270.                 }
  271.             }
  272.             return null;
  273.         }
  274.         ///
  275.         /// 获取一连串数据
  276.         ///
  277.         /// 首地址
  278.         /// 功能码
  279.         /// 长度
  280.         /// 转换后的字节数组
  281.         private static byte[] GetAddressValues(int addr, byte type, int len)
  282.         {
  283.             byte[] arr = null;
  284.             object obj;
  285.             byte temp;
  286.             int temp2;

  287.             switch (type)
  288.             {
  289.                 case MB_WRITE_MULTIPLE_COILS:
  290.                     arr = new byte[(len % 8 == 0) ? (len / 8) : (len / 8 + 1)];
  291.                     for (int i = 0; i < arr.Length; i++)
  292.                     {
  293.                         for (int j = 0; j < 8; j++)
  294.                         {   //获取地址所对应的数据 并判断所读数据 是否被指定,有没被指定的数据 直接返回null
  295.                             obj = GetAddressValue(addr + i * 8 + j, MB_READ_COILS);
  296.                             if (obj == null)
  297.                                 return null;
  298.                             else
  299.                                 temp = Convert.ToByte(obj);
  300.                             arr[i] |=  (byte)((temp == 0? 0 : 1) << j);
  301.                         }
  302.                     }
  303.                     break;
  304.                 case MB_WRITE_MULTIPLE_REGS:
  305.                     arr = new byte[len * 2];
  306.                     for (int i = 0; i < len; i++)
  307.                     {
  308.                         obj = GetAddressValue(addr + i, MB_READ_HOLD_REG);
  309.                         if (obj == null)
  310.                             return null;
  311.                         else
  312.                             temp2 = Convert.ToInt32(obj);
  313.                         arr[i * 2] = (byte)(temp2 >> 8);
  314.                         arr[i * 2 + 1] = (byte)(temp2 & 0xFF);
  315.                     }
  316.                     break;
  317.                 default: break;
  318.             }
  319.             return arr;
  320.         }
  321.         #endregion

  322.         #region 校验
  323.         private static readonly byte[] aucCRCHi = {
  324.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  325.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  326.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  327.             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  328.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  329.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  330.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  331.             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  332.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  333.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  334.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  335.             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  336.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  337.             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  338.             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  339.             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  340.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  341.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  342.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  343.             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  344.             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  345.             0x00, 0xC1, 0x81, 0x40
  346.         };
  347.         private static readonly byte[] aucCRCLo = {
  348.             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
  349.             0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
  350.             0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
  351.             0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
  352.             0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  353.             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
  354.             0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
  355.             0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
  356.             0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
  357.             0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  358.             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
  359.             0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
  360.             0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
  361.             0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
  362.             0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  363.             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
  364.             0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
  365.             0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
  366.             0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
  367.             0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  368.             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
  369.             0x41, 0x81, 0x80, 0x40
  370.         };
  371.         ///
  372.         /// CRC效验
  373.         ///
  374.         /// 效验数据
  375.         /// 数据长度
  376.         /// 效验结果
  377.         public static int Crc16(byte[] pucFrame, int usLen)
  378.         {
  379.             int i = 0;
  380.             byte ucCRCHi = 0xFF;
  381.             byte ucCRCLo = 0xFF;
  382.             UInt16 iIndex = 0x0000;

  383.             while (usLen-- > 0)
  384.             {
  385.                 iIndex = (UInt16)(ucCRCLo ^ pucFrame[i++]);
  386.                 ucCRCLo = (byte)(ucCRCHi ^ aucCRCHi[iIndex]);
  387.                 ucCRCHi = aucCRCLo[iIndex];
  388.             }
  389.             return (ucCRCHi << 8 | ucCRCLo);
  390.         }

  391.         #endregion

  392.         #region 发送指命操作
  393.         ///
  394.         /// 首部分数据 node:节点
  395.         ///
  396.         /// 寄存器地址
  397.         /// 数据长度,或单个数据
  398.         ///
  399.         ///
  400.         private static byte[] SendTrainHead(int node, int addr, int len, byte stat)
  401.         {
  402.             byte[] head = new byte[6];

  403.             head[0] = Convert.ToByte(node);
  404.             head[1] = stat;
  405.             head[2] = (byte)(addr >> 8);
  406.             head[3] = (byte)(addr & 0xFF);
  407.             head[4] = (byte)(len >> 8);
  408.             head[5] = (byte)(len & 0xFF);

  409.             return head;
  410.         }
  411.         ///
  412.         /// 计算数据长度 并在0x0f,0x10功能下 加载字节数
  413.         ///
  414.         ///
  415.         ///
  416.         ///
  417.         ///
  418.         private static byte[] SendTrainBytes(byte[] arr, ref int len, byte stat)
  419.         {
  420.             byte[] res;
  421.             switch (stat)
  422.             {
  423.                 default: len = 0; break;

  424.                 case MB_READ_COILS:
  425.                 case MB_READ_DISCRETE:
  426.                 case MB_READ_HOLD_REG:
  427.                 case MB_READ_INPUT_REG:
  428.                 case MB_WRITE_SINGLE_COIL:
  429.                 case MB_WRITE_SINGLE_REG:
  430.                     len = 0;
  431.                     break;

  432.                 case MB_WRITE_MULTIPLE_COILS:
  433.                     len = (len % 8 == 0) ? (len / 8) : (len / 8 + 1);
  434.                     res = new byte[arr.Length + 1];
  435.                     arr.CopyTo(res, 0);
  436.                     res[arr.Length] = (byte)(len);
  437.                     arr = res;
  438.                     break;

  439.                 case MB_WRITE_MULTIPLE_REGS:
  440.                     len *= 2;
  441.                     res = new byte[arr.Length + 1];
  442.                     arr.CopyTo(res, 0);
  443.                     res[arr.Length] = (byte)len;      //把字节写入数据最后位置
  444.                     arr = res;
  445.                     break;

  446.             }
  447.             return arr;
  448.         }
  449.         ///
  450.         /// 主控方式  发送指令模板
  451.         ///
  452.         /// 节点
  453.         /// 数据
  454.         /// 地址
  455.         /// 变量各数
  456.         /// 功能码
  457.         ///
  458.         private static byte[] SendTrainCyclostyle(int node, byte[] data, int addr, int con, byte stat)
  459.         {
  460.             int crcVal = 0;
  461.             byte[] headData = SendTrainHead(node, addr, con, stat);                   //写首部分数据
  462.             byte[] headDataLen = SendTrainBytes(headData, ref con, stat);       //计算数据的长度,有字节则写入。
  463.             byte[] res = new byte[headDataLen.Length + con + 2];

  464.             headDataLen.CopyTo(res, 0);

  465.             if ((stat == MB_WRITE_MULTIPLE_REGS) || (stat == MB_WRITE_MULTIPLE_COILS))
  466.                 Array.Copy(data, 0, res, headDataLen.Length, con);                   //把数据复制到数据中

  467.             crcVal = Crc16(res, res.Length - 2);
  468.             res[res.Length - 2] = (byte)(crcVal & 0xFF);
  469.             res[res.Length - 1] = (byte)(crcVal >> 8);

  470.             return res;
  471.         }
  472.         ///
  473.         /// 封装发送数据帧
  474.         ///
  475.         /// 从机地址
  476.         /// 指令信息
  477.         ///
  478.         private static byte[] SendPduPack(int node, MBCmd cmd)
  479.         {
  480.             byte[] res = null;
  481.             switch (cmd.stat)
  482.             {
  483.                 case MB_READ_COILS:
  484.                 case MB_READ_DISCRETE:
  485.                 case MB_READ_HOLD_REG:
  486.                 case MB_READ_INPUT_REG:
  487.                 case MB_WRITE_SINGLE_COIL:
  488.                 case MB_WRITE_SINGLE_REG:
  489.                     res = SendTrainCyclostyle(node, null, cmd.addr, cmd.len, (byte)cmd.stat); break;

  490.                 case MB_WRITE_MULTIPLE_COILS:
  491.                 case MB_WRITE_MULTIPLE_REGS:
  492.                     byte[] data = GetAddressValues(cmd.addr, (byte)cmd.stat, cmd.len);
  493.                     res = SendTrainCyclostyle(node, data, cmd.addr, cmd.len, (byte)cmd.stat); break;
  494.             }
  495.             return res;
  496.         }
  497.         #endregion

  498.         #region 回传数据操作
  499.         ///
  500.         /// 存储回传的线圈
  501.         ///
  502.         /// 回传的数组
  503.         /// 首地址
  504.         /// 存储是否正确
  505.         private static bool ReadDiscrete(byte[] data, int addr)
  506.         {
  507.             bool res = true;
  508.             int len = data[2];

  509.             if (len != (data.Length - 5))  //数据长度不正确 直接退出
  510.                 return false;

  511.             for (int i = 0; i < len; i++)
  512.             {
  513.                 for (int j = 0; j < 8; j++)
  514.                 {
  515.                     if (SetAddressValue(addr + i * 8 + j, data[1], data[i + 3] & (0x01 << j)) == null)
  516.                     {
  517.                         return false;
  518.                     }
  519.                 }
  520.             }
  521.             return res;
  522.         }
  523.         ///
  524.         /// 读回传的寄存器
  525.         ///
  526.         /// 回传的数组
  527.         /// 首地址
  528.         /// 存储是否正确
  529.         private static bool ReadReg(byte[] data, int addr)
  530.         {
  531.             bool res = true;
  532.             int len = data[2];

  533.             if (len != (data.Length - 5))  //数据长度不正确 直接退出
  534.                 return false;

  535.             for (int i = 0; i < len; i += 2)
  536.             {
  537.                 if (SetAddressValue(addr + i / 2, data[1], (data[i + 3] << 8) | data[i + 4]) == null)
  538.                 {
  539.                     res = false;
  540.                     break;
  541.                 }
  542.             }
  543.             return res;
  544.         }
  545.         ///
  546.         /// 回传的数据处理
  547.         ///
  548.         /// 回传的整帧数据
  549.         /// 当前所操作的首地址
  550.         ///
  551.         private static bool ReceiveDataProcess(byte[] buff, int addr)
  552.         {
  553.             if (buff == null)
  554.                 return false;
  555.             if (buff.Length < 5)    //回传的数据 地址+功能码+长度+2效验 = 5字节
  556.                 return false;

  557.             bool res = true;
  558.             switch (buff[1])
  559.             {
  560.                 case MB_READ_COILS: ReadDiscrete(buff, addr); break;
  561.                 case MB_READ_DISCRETE: ReadDiscrete(buff, addr); break;
  562.                 case MB_READ_HOLD_REG: ReadReg(buff, addr); break;
  563.                 case MB_READ_INPUT_REG: ReadReg(buff, addr); break;
  564.                 case MB_WRITE_SINGLE_COIL:
  565.                 case MB_WRITE_SINGLE_REG:
  566.                 case MB_WRITE_MULTIPLE_COILS:
  567.                 case MB_WRITE_MULTIPLE_REGS: break;
  568.                 default: res = false; break;
  569.             }
  570.             return res;
  571.         }
  572.         #endregion

  573.         #region 收发调度
  574.         ///
  575.         /// 添加重复操作指令
  576.         ///
  577.         /// 待发送的指命管道
  578.         /// 所添加指令的首地址
  579.         /// 所添加指令的寄存器或线圈个数
  580.         /// 所添加指令的功能码
  581.         private static void SciAddRepeatCmd(ref MBSci sci, int addr, int len, int stat)
  582.         {
  583.             if (sci.rtCount >= MB_SCI_MAX_COUNT - 1)  //超出指令管道最大长度 直接退出
  584.                 return;
  585.             if (len == 0)                               //地址的数据长度为空 直接退出
  586.                 return;

  587.             sci.cmd[sci.rtCount].addr = addr;
  588.             sci.cmd[sci.rtCount].len = len;
  589.             sci.cmd[sci.rtCount].stat = stat;
  590.             sci.cmd[sci.rtCount].res = 0;
  591.             sci.rtCount++;
  592.         }
  593.         ///
  594.         /// 添加一次性操作指令
  595.         ///
  596.         /// 待发送的指命管道
  597.         /// 所添加指令的首地址
  598.         /// 所添加指令的寄存器或线圈个数
  599.         /// 所添加指令的功能码
  600.         private static void SciAddCmd(ref MBSci sci, int addr, int len, int stat)
  601.         {
  602.             if (len == 0)                               //地址的数据长度为空 直接退出
  603.                 return;

  604.             for (int i = sci.rtCount; i < MB_SCI_MAX_COUNT; i++)
  605.             {
  606.                 if (sci.cmd[i].addr == -1)      //把指令载入到空的管道指令上
  607.                 {
  608.                     sci.cmd[i].addr = addr;
  609.                     sci.cmd[i].len = len;
  610.                     sci.cmd[i].stat = stat;
  611.                     sci.cmd[i].res = 0;
  612.                     break;
  613.                 }
  614.             }
  615.         }
  616.         ///
  617.         /// 清空重复读取指令集
  618.         ///
  619.         /// 待发送的指命管道
  620.         private static void SciClearRepeatCmd(ref MBSci sci)
  621.         {
  622.             sci.rtCount = 0;
  623.         }
  624.         ///
  625.         /// 清空一次性读取指令集
  626.         ///
  627.         /// 待发送的指命管道
  628.         private static void SciClearCmd(ref MBSci sci)
  629.         {
  630.             for (int i = sci.rtCount; i < MB_SCI_MAX_COUNT; i++)
  631.             {
  632.                 sci.cmd[i].addr = -1;
  633.                 sci.cmd[i].len = 0;
  634.                 sci.cmd[i].res = 0;
  635.             }
  636.         }
  637.         ///
  638.         /// 跳到下一个操作指令
  639.         ///
  640.         /// 待发送的指命管道
  641.         private static void SciJumbNext(ref MBSci sci)
  642.         {
  643.             if (sci.index >= sci.rtCount)           //非实时读取地址会被清除
  644.             {
  645.                 sci.cmd[sci.index].addr = -1;
  646.                 sci.cmd[sci.index].len = 0;
  647.                 sci.cmd[sci.index].stat = 0;
  648.             }

  649.             do{
  650.                 sci.index++;
  651.                 if (sci.index >= MB_SCI_MAX_COUNT)    //超出指令最大范围
  652.                 {
  653.                     sci.index = 0;
  654.                     if (sci.rtCount == 0)               //如果固定实时读取 为空 直接跳出
  655.                         break;
  656.                 }

  657.             } while (sci.cmd[sci.index].addr == -1);
  658.             sci.cmd[sci.index].res = 0;             //本次返回状态清零
  659.         }
  660.         ///
  661.         /// 发送指令调度锁定
  662.         ///
  663.         public static void SciSchedulingLock()
  664.         {
  665.             sciLock = true;
  666.         }
  667.         ///
  668.         /// 发送指令调度解锁
  669.         ///
  670.         public static void SciSchedulingUnlock()
  671.         {
  672.             sciLock = false;
  673.         }
  674.         ///
  675.         /// 待发送的指令管道调度
  676.         ///
  677.         /// 待发送的指命管道
  678.         /// 收到正确的回传数据
  679.         /// 准备发送的指令数据
  680.         private static void SciScheduling(ref MBSci sci, ref byte[] rBuf, ref byte[] wBuf)
  681.         {
  682.             if (sciLock)   //如果被加锁 直接退出
  683.                 return;

  684.             if ((sci.cmd[sci.index].res != 0) || (sci.count >= sci.maxRepeatCount))
  685.             {
  686.                 sci.count = 0;       //发送次数清零
  687.                 if (sci.cmd[sci.index].res != 0)    //如果收到了正常返回
  688.                 {
  689.                     ReceiveDataProcess(rBuf, sci.cmd[sci.index].addr);     //保存数据
  690.                     rBuf = null;        //清空当前接收缓冲区的内容, 以防下次重复读取
  691.                 }
  692.                 else
  693.                 {
  694.                     //参数操作失败
  695.                 }

  696.                 SciJumbNext(ref sci);
  697.             }
  698.             wBuf = SendPduPack((int)gNode, sci.cmd[sci.index]);     //发送指令操作
  699.             sci.count++;                            //发送次数加1
  700.         }
  701.         ///
  702.         /// 快速刷新 处理接收到的数据   建议:10ms以下
  703.         ///
  704.         /// 所正确回传数据的功能码, null:回传不正确
  705.         private static int MBQuickRefresh()
  706.         {
  707.             int res = -1;
  708.             if (rBuff != null)
  709.             {
  710.                 SciSchedulingLock();
  711.                 if (ReceiveDataProcess(rBuff, gMBSci.cmd[gMBSci.index].addr) == true)
  712.                 {
  713.                     gMBSci.cmd[gMBSci.index].res = 1;   //标记 所接收到的数据正确
  714.                     res = gMBSci.cmd[gMBSci.index].stat;
  715.                 }
  716.                 rBuff = null;
  717.                 SciSchedulingUnlock();
  718.             }
  719.             return res;
  720.         }
  721.         ///
  722.         /// 调度间隔时间刷新        建议:50ms以上
  723.         ///
  724.         /// 封装好的协议帧
  725.         private static void MBSchedRefresh()
  726.         {
  727.             SciScheduling(ref gMBSci, ref rBuff, ref wBuff);
  728.             if (wBuff != null)
  729.                 comm.Write(wBuff, 0, wBuff.Length);
  730.         }

  731.         #endregion

  732.         #region 接口函数
  733.         ///
  734.         /// 清空存放一次性的指令空间
  735.         ///
  736.         public static void MBClearCmd()
  737.         {
  738.             SciClearCmd(ref gMBSci);
  739.         }
  740.         ///
  741.         /// 添加固定刷新(重复) 操作指令
  742.         ///
  743.         /// 地址
  744.         /// 功能码
  745.         public static void MBAddRepeatCmd(int addr, byte stat)
  746.         {
  747.             for (int i = 0; i < GetAddressValueLength(addr); i++ )
  748.                 if (GetAddressValue(addr, stat) == null)        //如果所添加的指令没有在MODBUS对应表中定义 直接退出
  749.                     return;
  750.             SciAddRepeatCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
  751.         }
  752.         ///
  753.         /// 添加一次性 操作指令
  754.         ///
  755.         ///
  756.         ///
  757.         public static void MBAddCmd(int addr, byte stat)
  758.         {
  759.             for (int i = 0; i < GetAddressValueLength(addr); i++)
  760.                 if (GetAddressValue(addr, stat) == null)        //如果所添加的指令没有在MODBUS对应表中定义 直接退出
  761.                     return;
  762.             SciAddCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
  763.         }
  764.         ///
  765.         /// 串口参数配置
  766.         ///
  767.         /// 所用到的串口
  768.         ///
  769.         ///
  770.         public static void MBConfig(SerialPort commx, UInt16 node, UInt16 baud)
  771.         {
  772.             gBaud = baud;
  773.             gNode = node;
  774.             comm = commx;
  775.             SciClearRepeatCmd(ref gMBSci);
  776.             SciClearCmd(ref gMBSci);
  777.         }
  778.         ///
  779.         /// 读取串口中接收到的数据
  780.         ///
  781.         /// 所用到的串口
  782.         public static void MBDataReceive()
  783.         {
  784.             if (comm == null)                       //如果串口没有被初始化直接退出
  785.                 return;
  786.             SciSchedulingLock();
  787.             System.Threading.Thread.Sleep(20);      //等待缓冲器满

  788.             buffLen = comm.BytesToRead;          //获取缓冲区字节长度
  789.             if (buffLen > MB_MAX_LENGTH)            //如果长度超出范围 直接退出
  790.             {
  791.                 SciSchedulingUnlock();
  792.                 return;
  793.             }
  794.             comm.Read(buff, 0, buffLen);            //读取数据
  795.             if (gMBSci.cmd[gMBSci.index].stat == buff[1])
  796.             {
  797.                 if (Crc16(buff, buffLen) == 0)
  798.                 {
  799.                     rBuff = new byte[buffLen];
  800.                     Array.Copy(buff, rBuff, buffLen);
  801.                 }
  802.             }
  803.             SciSchedulingUnlock();
  804.         }
  805.         ///
  806.         /// MODBUS的实时刷新任务,在定时器在实时调用此函数
  807.         /// 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次  指令发送间隔为50ms。
  808.         ///
  809.         /// 返回当前功能读取指令回传 的功能码
  810.         public static int MBRefresh()
  811.         {
  812.             if (sciLock)   //如果被加锁 直接退出
  813.                 return 0;

  814.             mbRefreshTime++;
  815.             if (mbRefreshTime > 10)
  816.             {
  817.                 mbRefreshTime = 0;
  818.                 MBSchedRefresh();
  819.             }
  820.             return MBQuickRefresh();
  821.         }
  822.         #endregion


  823.     }

  824. }
复制代码
下面是自己开发的一个小控制软件及原代码:

原文件上传到我的网盘中:
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;


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


--------------------------------------------------------------------------------------------------------------------
本站是工控技术交流站点,论坛内容均为网络收集或会员所发表,并不代表本站立场,会员拥有该内容的所有权力及责任!
本站内容如有侵犯您的版权,请按下面方式联系本站管理员,我们将及时删除处理
管理员:冰糖 QQ:5483695(请直击主题), Mail:admin#ziwai.net(#改成@) 其它非本人.
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论!

QQ|Archiver|手机版|小黑屋|紫外工控论坛. ( 苏ICP备11032118号-1 )

GMT+8, 2024-4-27 05:10 , Processed in 0.484387 second(s), 18 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表