博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# MODBUS协议 上位机(转)
阅读量:4644 次
发布时间:2019-06-09

本文共 32287 字,大约阅读时间需要 107 分钟。

源:

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

  1. 采用定时器(Timer控件)为时间片。
  2. 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。
  3. 把正确接收的数据取出,转换为有特定的结构体中。
  4. 数据通过时间片实时刷新。
  5. MODBUS协议(这里不介绍了,网上有很多的权威资料)。

 

  串口接收问题

这里采用的是MODBUS_RTU协议,是没有回车等明显的结束符的哈。所以在C#也不可以用serialPort1.ReadLine来读取。我用的是serialPort1.BytesToRead先读缓冲区中的数据个数,再通过个数据读数据。这样在用串口软件测试的时候确实很有用,再随之问题又出现了。下位机传上来的数据长度高出8个,就会分断接收。即接收到的两次的长度,第一次是8个,然后再接收到后面的。 原因是因为软件没有接收完一整帧数据后就进行了中断。解决方法:在中断中加入线程阻塞方法,然后再读取串口中的数据。

  发送读数据和发送写数据的结构

 

写了多个MODBUS协议的上位机后,总结了些经验,并将这部分程序封装在一个类中。

使用时只需对其接口函数调用即可,有很强的移植性。在写软件时不用再在协议这部分花太多的时间。

基本的使用方法在注释中。程序总体感觉 可能过于臃肿,希望各位大神批评指点。

以下是源代码:

/* * 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 *  *  *  *  *  * */using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.IO.Ports;namespace WindowsApplication1{    public class Modbus    {        #region 所用结构体        ///         /// 地址对应表元素单元        ///         public struct OPTable{            public volatile int addr;            public volatile byte type;            public volatile object ob;        };        ///         /// 当前的指令        ///         public struct MBCmd        {            public volatile int addr;           //指令首地址            public volatile int stat;           //功能码            public volatile int len;            //所操作的寄存器或线圈的个数            public volatile int res;            //返回码的状态, 0:无返回,1:正确返回        };        ///         /// 当前操作的指令管道        ///         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;        ///         /// 变量所对应的地址 在此位置        ///         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;        ///         /// 获取寄存器或线圈 分组后的成员各数        ///         /// 首地址        /// 
成员各数
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; } /// /// 获取地址所对应的数据 /// /// 地址 /// 类型 ///
获取到的数据
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; } /// /// 设置地址所对应的数据 /// /// 地址 /// 类型 /// 数据 ///
是否成功
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; } /// /// 获取一连串数据 /// /// 首地址 /// 功能码 /// 长度 ///
转换后的字节数组
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 }; /// /// CRC效验 /// /// 效验数据 /// 数据长度 ///
效验结果
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); } #endregion #region 发送指命操作 /// /// 首部分数据 node:节点 /// /// 寄存器地址 /// 数据长度,或单个数据 /// ///
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; } /// /// 计算数据长度 并在0x0f,0x10功能下 加载字节数 /// /// /// /// ///
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; } /// /// 主控方式 发送指令模板 /// /// 节点 /// 数据 /// 地址 /// 变量各数 /// 功能码 ///
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; } /// /// 封装发送数据帧 /// /// 从机地址 /// 指令信息 ///
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 回传数据操作 /// /// 存储回传的线圈 /// /// 回传的数组 /// 首地址 ///
存储是否正确
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; } /// /// 读回传的寄存器 /// /// 回传的数组 /// 首地址 ///
存储是否正确
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; } /// /// 回传的数据处理 /// /// 回传的整帧数据 /// 当前所操作的首地址 ///
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 收发调度 /// /// 添加重复操作指令 /// /// 待发送的指命管道 /// 所添加指令的首地址 /// 所添加指令的寄存器或线圈个数 /// 所添加指令的功能码 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++; } /// /// 添加一次性操作指令 /// /// 待发送的指命管道 /// 所添加指令的首地址 /// 所添加指令的寄存器或线圈个数 /// 所添加指令的功能码 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; } } } /// /// 清空重复读取指令集 /// /// 待发送的指命管道 private static void SciClearRepeatCmd(ref MBSci sci) { sci.rtCount = 0; } /// /// 清空一次性读取指令集 /// /// 待发送的指命管道 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; } } /// /// 跳到下一个操作指令 /// /// 待发送的指命管道 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; //本次返回状态清零 } /// /// 发送指令调度锁定 /// public static void SciSchedulingLock() { sciLock = true; } /// /// 发送指令调度解锁 /// public static void SciSchedulingUnlock() { sciLock = false; } /// /// 待发送的指令管道调度 /// /// 待发送的指命管道 /// 收到正确的回传数据 /// 准备发送的指令数据 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 } /// /// 快速刷新 处理接收到的数据 建议:10ms以下 /// ///
所正确回传数据的功能码, null:回传不正确
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; } /// /// 调度间隔时间刷新 建议:50ms以上 /// ///
封装好的协议帧
private static void MBSchedRefresh() { SciScheduling(ref gMBSci, ref rBuff, ref wBuff); if (wBuff != null) comm.Write(wBuff, 0, wBuff.Length); } #endregion #region 接口函数 /// /// 清空存放一次性的指令空间 /// public static void MBClearCmd() { SciClearCmd(ref gMBSci); } /// /// 添加固定刷新(重复) 操作指令 /// /// 地址 /// 功能码 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); } /// /// 添加一次性 操作指令 /// /// /// 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); } /// /// 串口参数配置 /// /// 所用到的串口 /// /// public static void MBConfig(SerialPort commx, UInt16 node, UInt16 baud) { gBaud = baud; gNode = node; comm = commx; SciClearRepeatCmd(ref gMBSci); SciClearCmd(ref gMBSci); } /// /// 读取串口中接收到的数据 /// /// 所用到的串口 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(); } /// /// MODBUS的实时刷新任务,在定时器在实时调用此函数 /// 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次 指令发送间隔为50ms。 /// ///
返回当前功能读取指令回传 的功能码
public static int MBRefresh() { if (sciLock) //如果被加锁 直接退出 return 0; mbRefreshTime++; if (mbRefreshTime > 10) { mbRefreshTime = 0; MBSchedRefresh(); } return MBQuickRefresh(); } #endregion }}

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

原文件上传到我的网盘中:

提示:这个小软件用了第三方插件Developer Express v2011。确认安装此插件方能正常打开。

 

下面这个小工具是用modbus发送 大块数据的样例:

 

日志 BUG修改:

1,如下图增加 ,修正在无重复指令时,单次指令的次数的正确性。

if (sci.cmd[0].addr == -1)                 return;
 
 

转载于:https://www.cnblogs.com/LittleTiger/p/4429657.html

你可能感兴趣的文章
DP(动态规划)
查看>>
chkconfig
查看>>
TMS320F28335项目开发记录2_CCS与JTAG仿真器连接问题汇总
查看>>
最强的篮球队和马尔可夫模型
查看>>
pyQt 每日一练习 -- 登录框
查看>>
wp 删除独立存储空间文件(多级非空文件夹删除)
查看>>
Loadrunner安装使用入门
查看>>
smartupload 上传文件时 把页面编码改成gbk 解决乱码
查看>>
EPS是什么格式
查看>>
Python的数据库操作(Sqlalchemy)
查看>>
2.抽取代码(BaseActivity)
查看>>
My simplified pickit2 clone
查看>>
Redis 入门知识
查看>>
夏天过去了, 姥爷推荐几套来自smashingmagzine的超棒秋天主题壁纸
查看>>
转--Android如何在java代码中设置margin
查看>>
反射的所有api
查看>>
css 定位及遮罩层小技巧
查看>>
项目中非常有用并且常见的ES6语法
查看>>
[2017.02.23] Java8 函数式编程
查看>>
Knowledge Point 20180305 数据在计算机中的表示
查看>>