源:C# MODBUS协议 上位机

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 = ; //最大数据长度
private const int MB_SCI_MAX_COUNT = ; //指令管道最大存放的指令各数
private const int MB_MAX_REPEAT_COUNT = ; //指令最多发送次数
#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 = ;
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 = , maxRepeatCount = MB_MAX_REPEAT_COUNT, rtCount = , count = };
private static SerialPort comm = null;
private static int mbRefreshTime = ;
#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()}, //
new OPTable(){addr = D_DIO + , type = MB_READ_INPUT_REG, ob = new UInt16()},
new OPTable(){addr = D_DIO + , type = MB_READ_INPUT_REG, ob = new UInt16()},
new OPTable(){addr = D_DIO + , type = MB_READ_INPUT_REG, ob = new UInt16()},
new OPTable(){addr = D_DIO + , type = MB_READ_INPUT_REG, ob = new Int16()},
new OPTable(){addr = D_DIO + , type = MB_READ_INPUT_REG, ob = new Int16()}, new OPTable(){addr = D_BASE, type = MB_READ_HOLD_REG, ob = new Int16()}, //
new OPTable(){addr = D_BASE + , type = MB_READ_HOLD_REG, ob = new Int16()},
new OPTable(){addr = D_BASE + , type = MB_READ_HOLD_REG, ob = new Int16()},
new OPTable(){addr = D_BASE + , type = MB_READ_HOLD_REG, ob = new Int16()}, new OPTable(){addr = D_RANGE, type = MB_READ_HOLD_REG, ob = new Int16()}, //
new OPTable(){addr = D_RANGE + , type = MB_READ_HOLD_REG, ob = new Int16()}, new OPTable(){addr = D_PWM, type = MB_READ_HOLD_REG, ob = new Int16()}, //
new OPTable(){addr = D_PWM + , type = MB_READ_HOLD_REG, ob = new Int16()},
new OPTable(){addr = D_PWM + , type = MB_READ_HOLD_REG, ob = new Int16()},
new OPTable(){addr = D_PWM + , type = MB_READ_HOLD_REG, ob = new Int16()}, new OPTable(){addr = D_PID, type = MB_READ_HOLD_REG, ob = new UInt16()}, //
new OPTable(){addr = D_PID + , type = MB_READ_HOLD_REG, ob = new UInt16()},
new OPTable(){addr = D_PID + , type = MB_READ_HOLD_REG, ob = new UInt16()},
new OPTable(){addr = D_PID + , type = MB_READ_HOLD_REG, ob = new UInt16()},
new OPTable(){addr = D_PID + , type = MB_READ_HOLD_REG, ob = new UInt16()},
new OPTable(){addr = D_PID + , type = MB_READ_HOLD_REG, ob = new UInt16()}, };
public static UInt16 gDioX { get { return Convert.ToUInt16(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static UInt16 gDioY { get { return Convert.ToUInt16(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static UInt16 gDioZ { get { return Convert.ToUInt16(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static UInt16 gDioD { get { return Convert.ToUInt16(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gDioXx { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gDioXy { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } } public static Int16 gBaseF1 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gBaseF2 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gBaseF3 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gBaseF4 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } } public static Int16 gRangeMax { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gRangeMin { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } } public static Int16 gPwmF1 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gPwmF2 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gPwmF3 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } }
public static Int16 gPwmF4 { get { return (Int16)Convert.ToInt32(MBDataTable[].ob); } set { MBDataTable[].ob = value; } } public static float gP
{
get
{
int tmp = (Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) << );
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, );
}
set
{
byte[] val = BitConverter.GetBytes(value);
MBDataTable[].ob = BitConverter.ToUInt16(val, );
MBDataTable[].ob = BitConverter.ToUInt16(val, );
}
}
public static float gI
{
get
{
int tmp = (Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) << );
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, );
}
set
{
byte[] val = BitConverter.GetBytes(value);
MBDataTable[].ob = BitConverter.ToUInt16(val, );
MBDataTable[].ob = BitConverter.ToUInt16(val, );
}
}
public static float gD
{
get
{
int tmp = (Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[].ob) & 0xFFFF) << );
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, );
}
set
{
byte[] val = BitConverter.GetBytes(value);
MBDataTable[].ob = BitConverter.ToUInt16(val, );
MBDataTable[].ob = BitConverter.ToUInt16(val, );
}
} public static UInt16 gNode = ;
public static UInt16 gBaud = ;
/// <summary>
/// 获取寄存器或线圈 分组后的成员各数
/// </summary>
/// <param name="addr">首地址</param>
/// <returns>成员各数</returns>
private static int GetAddressValueLength(int addr)
{
int res = ;
switch (addr)
{
case D_DIO: res = ; break;
case D_BASE: res = ; break;
case D_RANGE: res = ; break;
case D_PWM: res = ; break;
case D_PID: res = ; 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 = ; 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 = ; 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 % == ) ? (len / ) : (len / + )];
for (int i = ; i < arr.Length; i++)
{
for (int j = ; j < ; j++)
{ //获取地址所对应的数据 并判断所读数据 是否被指定,有没被指定的数据 直接返回null
obj = GetAddressValue(addr + i * + j, MB_READ_COILS);
if (obj == null)
return null;
else
temp = Convert.ToByte(obj);
arr[i] |= (byte)((temp == ? : ) << j);
}
}
break;
case MB_WRITE_MULTIPLE_REGS:
arr = new byte[len * ];
for (int i = ; i < len; i++)
{
obj = GetAddressValue(addr + i, MB_READ_HOLD_REG);
if (obj == null)
return null;
else
temp2 = Convert.ToInt32(obj);
arr[i * ] = (byte)(temp2 >> );
arr[i * + ] = (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 = ;
byte ucCRCHi = 0xFF;
byte ucCRCLo = 0xFF;
UInt16 iIndex = 0x0000; while (usLen-- > )
{
iIndex = (UInt16)(ucCRCLo ^ pucFrame[i++]);
ucCRCLo = (byte)(ucCRCHi ^ aucCRCHi[iIndex]);
ucCRCHi = aucCRCLo[iIndex];
}
return (ucCRCHi << | ucCRCLo);
} #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[]; head[] = Convert.ToByte(node);
head[] = stat;
head[] = (byte)(addr >> );
head[] = (byte)(addr & 0xFF);
head[] = (byte)(len >> );
head[] = (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 = ; 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 = ;
break; case MB_WRITE_MULTIPLE_COILS:
len = (len % == ) ? (len / ) : (len / + );
res = new byte[arr.Length + ];
arr.CopyTo(res, );
res[arr.Length] = (byte)(len);
arr = res;
break; case MB_WRITE_MULTIPLE_REGS:
len *= ;
res = new byte[arr.Length + ];
arr.CopyTo(res, );
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 = ;
byte[] headData = SendTrainHead(node, addr, con, stat); //写首部分数据
byte[] headDataLen = SendTrainBytes(headData, ref con, stat); //计算数据的长度,有字节则写入。
byte[] res = new byte[headDataLen.Length + con + ]; headDataLen.CopyTo(res, ); if ((stat == MB_WRITE_MULTIPLE_REGS) || (stat == MB_WRITE_MULTIPLE_COILS))
Array.Copy(data, , res, headDataLen.Length, con); //把数据复制到数据中 crcVal = Crc16(res, res.Length - );
res[res.Length - ] = (byte)(crcVal & 0xFF);
res[res.Length - ] = (byte)(crcVal >> ); 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[]; if (len != (data.Length - )) //数据长度不正确 直接退出
return false; for (int i = ; i < len; i++)
{
for (int j = ; j < ; j++)
{
if (SetAddressValue(addr + i * + j, data[], data[i + ] & (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[]; if (len != (data.Length - )) //数据长度不正确 直接退出
return false; for (int i = ; i < len; i += )
{
if (SetAddressValue(addr + i / , data[], (data[i + ] << ) | data[i + ]) == 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 < ) //回传的数据 地址+功能码+长度+2效验 = 5字节
return false; bool res = true;
switch (buff[])
{
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 - ) //超出指令管道最大长度 直接退出
return;
if (len == ) //地址的数据长度为空 直接退出
return; sci.cmd[sci.rtCount].addr = addr;
sci.cmd[sci.rtCount].len = len;
sci.cmd[sci.rtCount].stat = stat;
sci.cmd[sci.rtCount].res = ;
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 == ) //地址的数据长度为空 直接退出
return; for (int i = sci.rtCount; i < MB_SCI_MAX_COUNT; i++)
{
if (sci.cmd[i].addr == -) //把指令载入到空的管道指令上
{
sci.cmd[i].addr = addr;
sci.cmd[i].len = len;
sci.cmd[i].stat = stat;
sci.cmd[i].res = ;
break;
}
}
}
/// <summary>
/// 清空重复读取指令集
/// </summary>
/// <param name="sci">待发送的指命管道</param>
private static void SciClearRepeatCmd(ref MBSci sci)
{
sci.rtCount = ;
}
/// <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 = -;
sci.cmd[i].len = ;
sci.cmd[i].res = ;
}
}
/// <summary>
/// 跳到下一个操作指令
/// </summary>
/// <param name="sci">待发送的指命管道</param>
private static void SciJumbNext(ref MBSci sci)
{
if (sci.index >= sci.rtCount) //非实时读取地址会被清除
{
sci.cmd[sci.index].addr = -;
sci.cmd[sci.index].len = ;
sci.cmd[sci.index].stat = ;
} do{
sci.index++;
if (sci.index >= MB_SCI_MAX_COUNT) //超出指令最大范围
{
sci.index = ;
if (sci.rtCount == ) //如果固定实时读取 为空 直接跳出
break;
} } while (sci.cmd[sci.index].addr == -);
sci.cmd[sci.index].res = ; //本次返回状态清零
}
/// <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 != ) || (sci.count >= sci.maxRepeatCount))
{
sci.count = ; //发送次数清零
if (sci.cmd[sci.index].res != ) //如果收到了正常返回
{
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 = -;
if (rBuff != null)
{
SciSchedulingLock();
if (ReceiveDataProcess(rBuff, gMBSci.cmd[gMBSci.index].addr) == true)
{
gMBSci.cmd[gMBSci.index].res = ; //标记 所接收到的数据正确
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, , 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 = ; 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 = ; 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(); //等待缓冲器满 buffLen = comm.BytesToRead; //获取缓冲区字节长度
if (buffLen > MB_MAX_LENGTH) //如果长度超出范围 直接退出
{
SciSchedulingUnlock();
return;
}
comm.Read(buff, , buffLen); //读取数据
if (gMBSci.cmd[gMBSci.index].stat == buff[])
{
if (Crc16(buff, buffLen) == )
{
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 ; mbRefreshTime++;
if (mbRefreshTime > )
{
mbRefreshTime = ;
MBSchedRefresh();
}
return MBQuickRefresh();
}
#endregion } }

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

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

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;
 
 

C# MODBUS协议 上位机(转)的更多相关文章

  1. NMEA协议 上位机 C# (转)

    源:NMEA协议 上位机 c# 前些时间写做了两款用NMEA协议的上位机,在这里做一个总结和记录.和大家分享,也为了以后不会忘记. NMEA协议总体来说,相对简单,是气象上比较成熟的协议. 主要有以下 ...

  2. RS485通信和Modbus协议(转)

    转自:http://www.51hei.com/bbs/dpj-23230-1.html 在工业控制.电力通讯.智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换.最初采用的方式是RS232接 ...

  3. modbus-poll和modbus-slave工具的学习使用——modbus协议功能码1的解析

    一.数据解析 上一文介绍了modbus工具的基本使用情况,但是还没用说明modbus中的协议的具体意义, 1.左边是slave,id=1,说明地址是1,f=01说明是功能码01,功能码是一个字节,说明 ...

  4. 【STM32 .Net MF开发板学习-05】PC通过Modbus协议远程操控开发板

    从2002年就开始接触Modbus协议,以后陆续在PLC.DOS.Windows..Net Micro Framework等系统中使用了该协议,在我以前写的一篇博文中详细记载了这一段经历,有兴趣的朋友 ...

  5. PC+PLC通过Modbus协议构建工控系统

    一. 概述 工业设备采用HMI+PLC控制是比较常见的方案,随着工业自动化的要求越来越高,现在很多设备都要求接入企业MES系统,MES系统一般为WEB系统,接口形式大部分为HTTP协议,这种传统方案和 ...

  6. modbus协议讲义

        Modbus 一个工业上常用的通讯协议.一种通讯约定.Modbus协议包括RTU.ASCII.TCP.其中MODBUS-RTU最常用,比较简单,在单片机上很容易实现.虽然RTU比较简单,但是看 ...

  7. 基于AVR128单纯Modbus协议实施

    Modbus通信协议Modicon公司1979在发展中,适用于工业现场总线协议控制.Modbus通信系统包含芯片的节点,并与组合物可编程控制的公共传输线,它的目的是收集和监视多个节点的数据.Modbu ...

  8. MODBUS协议详解

    MODBUS是一个工业上通信常用的通讯协议,一般在PLC上面用的比较多,主要是定义了一种数据传输的规范,比如数据发给谁,数据是干嘛的,数据错没错,接收到数据的从机告诉我数据有没有接受到等. 传输的方式 ...

  9. LPC1768IAP(详解,有上位机)

    之前说了stm32的iap编程,今天天气真好,顺手就来说说lpc1788的iap编程(没看前面的请查看stm笔记下的内容) 首先是flash的算法,lpc1768并没有寄存器来让我们操作flash,他 ...

随机推荐

  1. HDU 2255 奔小康赚大钱 KM算法的简单解释

    KM算法一般用来寻找二分图的最优匹配. 步骤: 1.初始化可行标杆 2.对新加入的点用匈牙利算法进行判断 3.若无法加入新编,修改可行标杆 4.重复2.3操作直到找到相等子图的完全匹配. 各步骤简述: ...

  2. 动态链接库dll,导入库lib,静态链接库lib

    目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库” ...

  3. [转]SSL协议详解

    背景介绍    最近在看<密码学与网络安全>相关的书籍,这篇文章主要详细介绍一下著名的网络安全协议SSL. 在开始SSl介绍之前,先给大家介绍几个密码学的概念和相关的知识.     1.密 ...

  4. 前端知识复习二(js)

    JS的作用 页面特效 移动端 异步交互(AJAX) 服务器端开发(node.js) 由ECMAScript和dom(操作网页上的api).bom组成(操作浏览器的部分api) 输出到页面内容 cons ...

  5. [原]左右的移动&lt;&lt;&gt;&gt;&lt;&gt;jQuery的实现

    $(function () {             $('#toAllLeft').click(function () {                 $('#se1 option').app ...

  6. 转:selenium 并行启动多个浏览器

    https://github.com/fool2fish/selenium-doc/blob/master/official-site/selenium-grid.md Selenium Grid 快 ...

  7. hashmap源码

    Java里各种基础容器的实现都是链表外加引用的形式.So,hashmap也不例外. 那小考虑下:hashmap是怎么产生的呢? 常用的两种数据结构数组和链表各有优劣,数组寻址容易,插入和删除困难:而链 ...

  8. Cisco 学会使用Telnet、SSH

    实验目的:通过控制R1 后 , TELNET 到R2(12.1.1.2),R3(13.1.1.3)对R2,R3 进行远程管理. 在R1 上配置: R1#telnet 12.1.1.2 //从R1 TE ...

  9. Jquery页面跳转

    <mce:script type="text/javascript"><!-- $(function(){ var pn = $("#gotopagen ...

  10. cakephp , the subquery (2)

    Cakephp 框架帮我们做了很多的工作,的确省了我们很多工作,提高了效率. 但是,碰到一些比较复杂的查询时,还是有些问题,官方的cookbook api 有说明一些详细的用法,但感觉还是不太够,有些 ...