C#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构:
- 采用定时器(Timer控件)为时间片。
- 串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。
- 把正确接收的数据取出,转换为有特定的结构体中。
- 数据通过时间片实时刷新。
- MODBUS协议(这里不介绍了,网上有很多的权威资料)。
串口接收问题
这里采用的是MODBUS_RTU协议,是没有回车等明显的结束符的哈。所以在C#也不可以用serialPort1.ReadLine来读取。我用的是serialPort1.BytesToRead先读缓冲区中的数据个数,再通过个数据读数据。这样在用串口软件测试的时候确实很有用,再随之问题又出现了。下位机传上来的数据长度高出8个,就会分断接收。即接收到的两次的长度,第一次是8个,然后再接收到后面的。 原因是因为软件没有接收完一整帧数据后就进行了中断。解决方法:在中断中加入线程阻塞方法,然后再读取串口中的数据。
发送读数据和发送写数据的结构
写了多个MODBUS协议的上位机后,总结了些经验,并将这部分程序封装在一个类中。
使用时只需对其接口函数调用即可,有很强的移植性。在写软件时不用再在协议这部分花太多的时间。
基本的使用方法在注释中。程序总体感觉 可能过于臃肿,希望各位大神批评指点。
以下是源代码:
1 /* 2 * MODBUS协议 3 * 4 * 5 * 介绍: 6 * 此modbus上位机 协议类 具有较强的通用性 7 * 本协议类最主要的思想是 把所有向下位机发送的指令 先存放在缓冲区中(命名为管道) 8 * 再将管道中的指令逐个发送出去。 9 * 管道遵守FIFO的模式。管道中所存放指令的个数 在全局变量中定义。 10 * 管道内主要分为两部分:1,定时循环发送指令。2,一次性发送指令。 11 * 定时循环发送指令:周期性间隔时间发送指令,一般针对“输入寄存器”或“输入线圈”等实时更新的变量。 12 * 这两部分的长度由用户所添加指令个数决定(所以自由性强)。 13 * 指令的最大发送次数,及管道中最大存放指令的个数在常量定义中 可进行设定。 14 * 15 * 使用说明: 16 * 1,首先对所定义的寄存器或线圈进行分组定义,并定义首地址。 17 * 2,在MBDataTable数组中添加寄存器或线圈所对应的地址。 注意 寄存器:ob = new UInt16()。线圈:ob = new byte()。 18 * 3,对所定义的地址 用属性进行定义 以方便在类外进行访问及了解所对应地址的含义。 19 * 4,GetAddressValueLength函数中 对使用说明的"第一步"分组 的元素个数进行指定。 20 * 5,在主程序中调用MBConfig进行协议初始化(初始化内容参考函数)。 21 * 6,在串口中断函数中调用MBDataReceive()。 22 * 7,定时器调用MBRefresh()。(10ms以下) 23 * 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次 指令发送间隔为50ms。 24 * 8,在主程序初始化中添加固定实时发送的指令操作 用MBAddRepeatCmd函数。 25 * 9,在主程序运行过程中 根据需要添加 单个的指令操作(非固定重复发送的指令)用MBAddCmd函数。 26 * 27 * 28 * 作者:王宏强 29 * 时间:2012.7.2 30 * 31 * 32 * 33 * 34 * 35 * 36 */ 37 38 using System; 39 using System.Collections.Generic; 40 using System.ComponentModel; 41 using System.Data; 42 using System.Drawing; 43 using System.Text; 44 using System.Windows.Forms; 45 using System.IO.Ports; 46 47 namespace WindowsApplication1 48 { 49 50 public class Modbus 51 { 52 #region 所用结构体 53 /// <summary> 54 /// 地址对应表元素单元 55 /// </summary> 56 public struct OPTable{ 57 public volatile int addr; 58 public volatile byte type; 59 public volatile object ob; 60 }; 61 /// <summary> 62 /// 当前的指令 63 /// </summary> 64 public struct MBCmd 65 { 66 public volatile int addr; //指令首地址 67 public volatile int stat; //功能码 68 public volatile int len; //所操作的寄存器或线圈的个数 69 public volatile int res; //返回码的状态, 0:无返回,1:正确返回 70 }; 71 /// <summary> 72 /// 当前操作的指令管道 73 /// </summary> 74 public struct MBSci 75 { 76 public volatile MBCmd[] cmd; //指令结构体 77 public volatile int index; //当前索引 78 public volatile int count; //当前功能码执行的次数 79 public volatile int maxRepeatCount; //最大发送次数 80 public volatile int rtCount; //实时读取的指令各数(无限间隔时间读取) 81 }; 82 #endregion 83 84 #region 常量定义 85 public const byte MB_READ_COILS = 0x01; //读线圈寄存器 86 public const byte MB_READ_DISCRETE = 0x02; //读离散输入寄存器 87 public const byte MB_READ_HOLD_REG = 0x03; //读保持寄存器 88 public const byte MB_READ_INPUT_REG = 0x04; //读输入寄存器 89 public const byte MB_WRITE_SINGLE_COIL = 0x05; //写单个线圈 90 public const byte MB_WRITE_SINGLE_REG = 0x06; //写单寄存器 91 public const byte MB_WRITE_MULTIPLE_COILS = 0x0f; //写多线圈 92 public const byte MB_WRITE_MULTIPLE_REGS = 0x10; //写多寄存器 93 94 private const int MB_MAX_LENGTH = 120; //最大数据长度 95 private const int MB_SCI_MAX_COUNT = 15; //指令管道最大存放的指令各数 96 private const int MB_MAX_REPEAT_COUNT = 3; //指令最多发送次数 97 #endregion 98 99 #region 全局变量 100 private static volatile bool sciLock = false; //调度器锁 true:加锁 false:解锁 101 private static volatile byte[] buff = new byte[MB_MAX_LENGTH]; //接收缓冲器 102 private static volatile int buffLen = 0; 103 private static volatile byte[] rBuff = null; //正确接收缓冲器 104 private static volatile byte[] wBuff = null; //正确发送缓冲器 105 public static MBSci gMBSci = new MBSci() { cmd = new MBCmd[MB_SCI_MAX_COUNT], index = 0, maxRepeatCount = MB_MAX_REPEAT_COUNT, rtCount = 0, count = 0 }; 106 private static SerialPort comm = null; 107 private static int mbRefreshTime = 0; 108 #endregion 109 110 #region MODBUS 地址对应表 111 //modbus寄存器和线圈分组 首地址定义 112 public const int D_DIO = 0x0000; 113 public const int D_BASE = 0x0014; 114 public const int D_RANGE = 0x0018; 115 public const int D_PWM = 0x001A; 116 public const int D_PID = 0x001E; 117 118 /// <summary> 119 /// 变量所对应的地址 在此位置 120 /// </summary> 121 public static volatile OPTable[] MBDataTable = 122 { 123 new OPTable(){addr = D_DIO, type = MB_READ_INPUT_REG, ob = new UInt16()}, //0 124 new OPTable(){addr = D_DIO + 1, type = MB_READ_INPUT_REG, ob = new UInt16()}, 125 new OPTable(){addr = D_DIO + 2, type = MB_READ_INPUT_REG, ob = new UInt16()}, 126 new OPTable(){addr = D_DIO + 3, type = MB_READ_INPUT_REG, ob = new UInt16()}, 127 new OPTable(){addr = D_DIO + 4, type = MB_READ_INPUT_REG, ob = new Int16()}, 128 new OPTable(){addr = D_DIO + 5, type = MB_READ_INPUT_REG, ob = new Int16()}, 129 130 new OPTable(){addr = D_BASE, type = MB_READ_HOLD_REG, ob = new Int16()}, //6 131 new OPTable(){addr = D_BASE + 1, type = MB_READ_HOLD_REG, ob = new Int16()}, 132 new OPTable(){addr = D_BASE + 2, type = MB_READ_HOLD_REG, ob = new Int16()}, 133 new OPTable(){addr = D_BASE + 3, type = MB_READ_HOLD_REG, ob = new Int16()}, 134 135 new OPTable(){addr = D_RANGE, type = MB_READ_HOLD_REG, ob = new Int16()}, //10 136 new OPTable(){addr = D_RANGE + 1, type = MB_READ_HOLD_REG, ob = new Int16()}, 137 138 new OPTable(){addr = D_PWM, type = MB_READ_HOLD_REG, ob = new Int16()}, //12 139 new OPTable(){addr = D_PWM + 1, type = MB_READ_HOLD_REG, ob = new Int16()}, 140 new OPTable(){addr = D_PWM + 2, type = MB_READ_HOLD_REG, ob = new Int16()}, 141 new OPTable(){addr = D_PWM + 3, type = MB_READ_HOLD_REG, ob = new Int16()}, 142 143 new OPTable(){addr = D_PID, type = MB_READ_HOLD_REG, ob = new UInt16()}, //16 144 new OPTable(){addr = D_PID + 1, type = MB_READ_HOLD_REG, ob = new UInt16()}, 145 new OPTable(){addr = D_PID + 2, type = MB_READ_HOLD_REG, ob = new UInt16()}, 146 new OPTable(){addr = D_PID + 3, type = MB_READ_HOLD_REG, ob = new UInt16()}, 147 new OPTable(){addr = D_PID + 4, type = MB_READ_HOLD_REG, ob = new UInt16()}, 148 new OPTable(){addr = D_PID + 5, type = MB_READ_HOLD_REG, ob = new UInt16()}, 149 150 }; 151 public static UInt16 gDioX { get { return Convert.ToUInt16(MBDataTable[0].ob); } set { MBDataTable[0].ob = value; } } 152 public static UInt16 gDioY { get { return Convert.ToUInt16(MBDataTable[1].ob); } set { MBDataTable[1].ob = value; } } 153 public static UInt16 gDioZ { get { return Convert.ToUInt16(MBDataTable[2].ob); } set { MBDataTable[2].ob = value; } } 154 public static UInt16 gDioD { get { return Convert.ToUInt16(MBDataTable[3].ob); } set { MBDataTable[3].ob = value; } } 155 public static Int16 gDioXx { get { return (Int16)Convert.ToInt32(MBDataTable[4].ob); } set { MBDataTable[4].ob = value; } } 156 public static Int16 gDioXy { get { return (Int16)Convert.ToInt32(MBDataTable[5].ob); } set { MBDataTable[5].ob = value; } } 157 158 public static Int16 gBaseF1 { get { return (Int16)Convert.ToInt32(MBDataTable[6].ob); } set { MBDataTable[6].ob = value; } } 159 public static Int16 gBaseF2 { get { return (Int16)Convert.ToInt32(MBDataTable[7].ob); } set { MBDataTable[7].ob = value; } } 160 public static Int16 gBaseF3 { get { return (Int16)Convert.ToInt32(MBDataTable[8].ob); } set { MBDataTable[8].ob = value; } } 161 public static Int16 gBaseF4 { get { return (Int16)Convert.ToInt32(MBDataTable[9].ob); } set { MBDataTable[9].ob = value; } } 162 163 public static Int16 gRangeMax { get { return (Int16)Convert.ToInt32(MBDataTable[10].ob); } set { MBDataTable[10].ob = value; } } 164