源: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. 使用btoa和atob来进行Base64转码和解码

      btoa: 将普通字符串转为Base64字符串 atob: 将Base64字符串转为普通字符串   说明:window.btoa不支持汉字:   ===>使用window.encodeURI ...

  2. 创建zend framework 项目要注意的

    1.必须要设置变量环境 我的电脑右击-属性-高级-环境变量 则在环境变量中添加 变量名:PATH 环境值:D:\phpserver\php5.4;D:\ZendFramework\bin 把php.e ...

  3. Converting between IEEE 754 and Float (Format related

    The float can be converted to well known single-precision IEEE 754 number, why 754? It's the standar ...

  4. jQuery Mobile 学习

    jQuery Mobile 学习系列 http://blog.csdn.net/bao990423420/article/details/13995021

  5. AutoTile 自动拼接(一) 学习与实践

    恩,大家好,这两天江苏冷空气袭击,下了今年 第一场第二场雪. 不过今天我要说的 ,和 上面的 屁关系都没有. 今天要说的是 2d无缝自动拼接.大家有没有玩过  RPG Maker VX Ace. 类似 ...

  6. startActivityForResult与onActivityResult

    androidActivity之间的跳转不只是有startActivity(Intent i)的,startActivityForResult(Intent intent, int requestCo ...

  7. Python CGI编程和CGIHTTPServer

    Python2.7 的CGIHTTPServer 可以作为一个简单的HTTP服务器,能够调用cgi脚本 1 在任意目录下创建一个特殊的目录 cgi-bin ,用于存放自己写的脚本(.py或.cgi) ...

  8. MVC 使用Jquery的$.post传递参数

    MVC中,如果要使用 $.post 给 COntroller 传递参数,需要类实现 属性 get set,这样才行

  9. linux 用户管理维护 清缓存

    #echo 1 > /proc/sys/ vm/drop_caches 2013.10.10 其实一直user group一直都没去弄清楚 只是没去归类,@@一种是对用户/组直接修改(同时也更改 ...

  10. IMosaicWorkspaceExtensionHelper

    Provides a helper for working with a mosaic dataset workspace extension. Product Availability Availa ...