• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

c#MODBUS协议上位机

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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 所用结构体
        /// <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
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#中时间比较的方法发布时间:2022-07-10
下一篇:
C#获取动态验证码?发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap