前言
modbus通讯协议在工控行业的应用是很多的,并且也是上位机开发的基本技能之一。相关的类库也很多也很好用。以前只负责用,对其并没有深入学习和了解。前段时间有点空就在这块挖了挖。想做到知其然还要知其所以然。所以就有了自己封装的modbus工具类库的想法。一来是练练手,二来是自己封装的用的更顺手。
modbus通讯协议我在工作中目前只用到了两种一个是串口通讯modbusrtu,还有一个是网络通讯modbustcp。所以本文只有这两种通讯的实现。
设计思想
c#是高级语言有很多好用的东西,如面像对像,设计模式等。但我在工作中还是经常看到面像过程的编程。如有多个串口设备就有多个代码类似的工具类。代码重复非常严重。我认为这种事还是要发点时间总结和代码一下代码,把它封装工具类库。以便后继在其他上的使用。
本次的封装用了一点面像对像的方法,设计了一个多个modbus 基类将一些公共方法放在基类中,子类就可以继续使用。不同的子类有不同的功能。可以按需调用。使用简单方便。
调用示例
var _serialport = new modbusrtucoil(portname, baudrate, parity, databits, stopbits); var isok = false; var resultmodel = _serialport.readdatacoil(1, 1, modbusfunctioncode.readinputcoil, (ushort)type.gethashcode()); if (resultmodel.resultlist != null && resultmodel.resultlist.count > 0) { isok = resultmodel.resultlist.firstordefault(); }
类库项目结构
代码
modbus结果实体
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; namespace cjh.modbustool { /// <summary> /// modbus结果实体 /// </summary> /// <typeparam name="datetype"></typeparam> public class modbusresultmodel { public modbusresultmodel() { issucceed = false; msg = "失败(默认)"; } private bool _issucceed = false; /// <summary> /// 是否成功 /// </summary> public bool issucceed { get { return _issucceed; } set { _issucceed = value; if (issucceed) { msg = "成功"; } } } /// <summary> /// 返回消息 /// </summary> public string msg { get; set; } /// <summary> /// 发送报文 /// </summary> public string senddatastr { get; set; } /// <summary> /// 原始数据 /// </summary> public byte[] datas { get; set; } } /// <summary> /// modbus结果实体 /// </summary> /// <typeparam name="datetype"></typeparam> public class modbusresultmodel<datetype> : modbusresultmodel { public modbusresultmodel() : base() { resultlist = new list<datetype>(); } /// <summary> /// 解析后的数据 /// </summary> public list<datetype> resultlist { get; set; } } }
modbus 基类
using system; using system.collections; using system.collections.generic; using system.componentmodel; using system.linq; using system.text; using system.threading.tasks; namespace cjh.modbustool { /// <summary> /// modbus 基类 /// </summary> public abstract class modbusbase { /// <summary> /// 生成读取报文的 公共方法 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="length">寄存器数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns> protected byte[] generatereadcommandbytes(byte devaddr, ushort length, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { //1.拼接报文: var sendcommand = new list<byte>(); //协议格式:站地址+功能码+起始寄存器地址+寄存器数量 //站地址 sendcommand.add(devaddr); //功能码 sendcommand.add((byte)functioncode.gethashcode()); //起始寄存器地址 sendcommand.add((byte)(startaddr / 256)); sendcommand.add((byte)(startaddr % 256)); //寄存器数量 sendcommand.add((byte)(length / 256)); sendcommand.add((byte)(length % 256)); //crc //byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); //sendcommand.addrange(crc); return sendcommand.toarray(); } /// <summary> /// 生成读取报文的 公共方法 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="data">定入数据</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">写入地址</param> /// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns> protected byte[] generatewritecommandbytes(byte devaddr, ushort data, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { //1.拼接报文: var sendcommand = new list<byte>(); //协议格式:站地址+功能码+起始寄存器地址+寄存器数量 //站地址 sendcommand.add(devaddr); //功能码 sendcommand.add((byte)functioncode.gethashcode()); //写入地址 sendcommand.add((byte)(startaddr / 256)); sendcommand.add((byte)(startaddr % 256)); //写入数据 var temp_bytes = bitconverter.getbytes(data); if (bitconverter.islittleendian) { //temp_bytes.reverse(); array.reverse(temp_bytes); } sendcommand.addrange(temp_bytes); //crc //byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); //sendcommand.addrange(crc); return sendcommand.toarray(); } /// <summary> /// 生成发送命令报文 /// </summary> /// <param name="sendcommand"></param> /// <returns></returns> protected string generatesendcommandstr(byte[] sendcommand) { var sendcommandstr = string.empty; foreach (var item in sendcommand) { sendcommandstr += convert.tostring(item, 16) + " "; } return sendcommandstr; } /// <summary> /// 验证crc /// </summary> /// <param name="value">要验证的数据</param> /// <returns></returns> protected bool checkcrc(byte[] value) { var isok = false; if (value != null && value.length >= 2) { int length = value.length; byte[] buf = new byte[length - 2]; array.copy(value, 0, buf, 0, buf.length); //自己验证的结果 byte[] crcbuf = crc16(buf, buf.length); //把上面验证的结果和串口返回的校验码(最后两个)进行比较 if (crcbuf[0] == value[length - 2] && crcbuf[1] == value[length - 1]) { isok = true; } } return isok; } protected byte[] crc16(byte[] pucframe, int uslen) { int i = 0; byte[] res = new byte[2] { 0xff, 0xff }; ushort iindex; while (uslen-- > 0) { iindex = (ushort)(res[0] ^ pucframe[i++]); res[0] = (byte)(res[1] ^ auccrchi[iindex]); res[1] = auccrclo[iindex]; } return res; } protected 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 }; protected 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>2个字节</returns> protected byte[] calculatecrc(byte[] pucframe, int uslen) { int i = 0; byte[] res = new byte[2] { 0xff, 0xff }; ushort iindex; while (uslen-- > 0) { iindex = (ushort)(res[0] ^ pucframe[i++]); res[0] = (byte)(res[1] ^ auccrchi[iindex]); res[1] = auccrclo[iindex]; } return res; } } /// <summary> /// modbus 功能码 /// </summary> public enum modbusfunctioncode { /// <summary> /// 读取输出线圈 /// </summary> [description("读取输出线圈")] readoutcoil = 1, /// <summary> /// 读取输入线圈 /// </summary> [description("读取输入线圈")] readinputcoil = 2, /// <summary> /// 读取保持寄存器 /// </summary> [description("读取保持寄存器")] readregister = 3, /// <summary> /// 读取输入寄存器 /// </summary> [description("读取输入寄存器")] readinputregister = 4, /// <summary> /// (写入)预置单线圈 /// </summary> [description("(写入)预置单线圈")] writecoil = 5, /// <summary> /// (写入)预置单个寄存器 /// </summary> [description("(写入)预置单个寄存器")] writeregister = 6, /// <summary> /// (写入)预置多寄存器 /// </summary> [description("(写入)预置多寄存器")] writeregistermultiple = 16, } }
rtu
串口基类 serialportbase
using system; using system.collections.generic; using system.componentmodel; using system.io.ports; using system.linq; using system.text; using system.threading.tasks; namespace cjh.modbustool.rtu { //modbus 规定4个存储区 // 区号 名称 读写 范围 // 0区 输出线圈 可读可写 00001-09999 // 1区 输入线圈 只读 10001-19999 // 2区 输入寄存器 只读 30001-39999 // 4区 保存寄存器 可读可写 40001-19999 //功能码 //01h 读取输出线圈 //02h 读取输入线圈 //03h 读取保持寄存器 //04h 读取输入寄存器 //05h (写入)预置单线圈 //06h (写入)预置寄存器 //0fh (写入)预置多线圈 //10h (写入)预置多寄存器 /// <summary> /// 串口基类 /// </summary> public abstract class serialportbase : modbusbase { protected serialport serialportobj; /// <summary> /// 初始化 /// </summary> /// <param name="portname">com口名称</param> /// <param name="baudrate">波特率</param> /// <param name="parity">检验位</param> /// <param name="databits">数据位</param> /// <param name="stopbits">停止位</param> protected void init(string portname, int baudrate = 9600, parity parity = parity.none, int databits = 8, stopbits stopbits = stopbits.one) { serialportobj = new serialport(portname, baudrate, parity, databits, stopbits); if (serialportobj.isopen) { serialportobj.close(); } serialportobj.open(); } /// <summary> /// 关闭 /// </summary> public void close() { if (serialportobj.isopen) { serialportobj.close(); serialportobj.dispose(); serialportobj = null; } } } //功能码 //01h 读取输出线圈 //02h 读取输入线圈 //03h 读取保持寄存器 //04h 读取输入寄存器 //05h (写入)预置单线圈 //06h (写入)预置寄存器 //0fh (写入)预置多线圈 //10h (写入)预置多寄存器 }
modbus 串口通讯
(串口操作的所有功能这个类都能做)
using system; using system.collections.generic; using system.componentmodel.design; using system.io.ports; using system.linq; using system.text; using system.threading.tasks; namespace cjh.modbustool.rtu { /// <summary> /// modbus 串口通讯 /// </summary> public class modbusrtu : serialportbase { private string _classname = "modbusrtu"; /// <summary> /// modbus 串口通讯 /// </summary> /// <param name="portname">com口名称</param> /// <param name="baudrate">波特率</param> /// <param name="parity">检验位</param> /// <param name="databits">数据位</param> /// <param name="stopbits">停止位</param> public modbusrtu(string portname, int baudrate = 9600, parity parity = parity.none, int databits = 8, stopbits stopbits = stopbits.one) { init(portname, baudrate, parity, databits, stopbits); //serialportobj.datareceived += new serialdatareceivedeventhandler(comdatareceived); } /// <summary> /// 读取线圈数据 ok /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="length">寄存器数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>线圈数据</returns> public modbusresultmodel readdata(byte devaddr, ushort length, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { return readdata(devaddr, length, (byte)functioncode, startaddr); } /// <summary> /// 读取数据 ok /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="length">寄存器数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>线圈数据</returns> public modbusresultmodel readdata(byte devaddr, ushort length, byte functioncode = 2, ushort startaddr = 0) { var resultmodel = new modbusresultmodel(); //byte[] datas = null; if (functioncode >= 1 && functioncode <= 4) { try { //1.拼接报文: var sendcommand = new list<byte>(); //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+crc //站地址 sendcommand.add(devaddr); //功能码 sendcommand.add(functioncode); //起始寄存器地址 sendcommand.add((byte)(startaddr / 256)); sendcommand.add((byte)(startaddr % 256)); //寄存器数量 sendcommand.add((byte)(length / 256)); sendcommand.add((byte)(length % 256)); //crc byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); sendcommand.addrange(crc); resultmodel.senddatastr = generatesendcommandstr(sendcommand.toarray()); //2.发送报文 serialportobj.write(sendcommand.toarray(), 0, sendcommand.count); //3.接收报文 thread.sleep(50);//要延时一下,才能读到数据 //读取响应报文 byte[] respbytes = new byte[serialportobj.bytestoread]; serialportobj.read(respbytes, 0, respbytes.length); // respbytes -> 01 01 02 00 00 b9 fc resultmodel.datas = respbytes; // 检查一个校验位 //if (checkcrc(respbytes) && (respbytes.length == 5 + length * 2) // && respbytes[0] == devadd && respbytes[1] == functioncode && respbytes[1] == length * 2) if (checkcrc(respbytes) && respbytes[0] == devaddr && respbytes[1] == functioncode) { //datas = respbytes; resultmodel.issucceed = true; } else { resultmodel.msg = "响应报文校验失败"; } } catch (exception ex) { resultmodel.msg = "异常:" + ex.message; } } else { //throw new exception("功能码不正确[1-4]"); resultmodel.msg = "功能码不正确[1-4]"; } //serialportobj.close(); return resultmodel; } /// <summary> /// 写入单个寄存器 ok /// 数据示例: 200 /// 功能码 6 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="value">写入的数据</param> /// <param name="startaddr">写入地址</param> /// <returns>是否成功</returns> public modbusresultmodel writedatashort(int devaddr, short value, short startaddr = 0) { var resultmodel = new modbusresultmodel(); try { //bool isok = false; //1.拼接报文: //var sendcommand = getsingledatawritemessage(devadd, startaddr, value); //ok var sendcommand = getsingledatawritemessagelist(devaddr, startaddr, value); //ok //var sendcommandstr = string.join(' ', sendcommand.toarray()); resultmodel.senddatastr = generatesendcommandstr(sendcommand); //2.发送报文 serialportobj.write(sendcommand.toarray(), 0, sendcommand.length); //3.接收报文 thread.sleep(50);//要延时一下,才能读到数据 //读取响应报文 byte[] respbytes = new byte[serialportobj.bytestoread]; serialportobj.read(respbytes, 0, respbytes.length); // respbytes -> 01 01 02 00 00 b9 fc // 检查一个校验位 if (checkcrc(respbytes) && respbytes[0] == devaddr && respbytes[1] == 0x06) { //isok = true; resultmodel.issucceed = true; } else { resultmodel.msg = "响应报文校验失败"; } } catch (exception ex) { resultmodel.msg = "异常:" + ex.message; } //serialportobj.close(); return resultmodel; } /// <summary> /// 写入单个寄存器 ok /// 数据示例: 200 /// 功能码 6 /// </summary> /// <param name="devaddr">站地址</param> /// <param name="datalist">写入的数据集合</param> /// <param name="startaddr">写入地址</param> /// <returns>是否成功</returns> public modbusresultmodel writedatashort(int devaddr, list<short> datalist, short startaddr = 0) { var resultmodel = new modbusresultmodel(); if (datalist != null && datalist.count > 0) { foreach (var item in datalist) { resultmodel = writedatashort(devaddr, item, startaddr); startaddr++; } } return resultmodel; } /// <summary> /// 获取写入单个寄存器的报文 /// </summary> /// <param name="slavestation">从站地址</param> /// <param name="startaddr">寄存器地址</param> /// <param name="value">写入值</param> /// <returns>写入单个寄存器的报文</returns> private byte[] getsingledatawritemessage(int slavestation, short startaddr, short value) { //从站地址 byte station = (byte)slavestation; //功能码 byte type = 0x06;//06h (写入)预置寄存器 //寄存器地址 byte[] start = bitconverter.getbytes(startaddr); //值 byte[] valuebytes = bitconverter.getbytes(value); //根据计算机大小端存储方式进行高低字节转换 if (bitconverter.islittleendian) { array.reverse(start); array.reverse(valuebytes); } //拼接报文 byte[] result = new byte[] { station, type }; result = result.concat(start.concat(valuebytes).toarray()).toarray(); //计算校验码并拼接,返回最后的报文结果 return result.concat(crc16(result, result.length)).toarray(); } /// <summary> /// 获取写入单个寄存器的报文 /// </summary> /// <param name="slavestation">从站地址</param> /// <param name="startaddr">寄存器地址</param> /// <param name="data">写入值</param> /// <returns>写入单个寄存器的报文</returns> private byte[] getsingledatawritemessagelist(int slavestation, short startaddr, short data) { //1.拼接报文: var sendcommand = new list<byte>(); //从站地址 byte station = (byte)slavestation; //功能码 byte type = 0x06;//06h (写入)预置寄存器 //寄存器地址 byte[] start = bitconverter.getbytes(startaddr); //值 byte[] valuebytes = bitconverter.getbytes(data); //根据计算机大小端存储方式进行高低字节转换 if (bitconverter.islittleendian) { array.reverse(start); array.reverse(valuebytes); } sendcommand.add((byte)slavestation); sendcommand.add(type); sendcommand.addrange(start); sendcommand.addrange(valuebytes); byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); sendcommand.addrange(crc); return sendcommand.toarray(); } /// <summary> /// 写入多个寄存器 ok /// 数据示例: 123.45f, 14.3f /// 功能码 10 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="data">写入的数据</param> /// <param name="startaddr">写入地址</param> /// <returns>是否成功</returns> public modbusresultmodel writedatafloat(byte devaddr, float data, ushort startaddr = 0) { return writedatafloat(devaddr, new list<float>() { data }, startaddr); } /// <summary> /// 写入多个寄存器 ok /// 数据示例: 123.45f, 14.3f /// 功能码 10 /// </summary> /// <param name="devadd">从站地址</param> /// <param name="datalist">写入的数据</param> /// <param name="startaddr">写入地址</param> /// <returns>是否成功</returns> public modbusresultmodel writedatafloat(byte devaddr, list<float> datalist, ushort startaddr = 0) { var resultmodel = new modbusresultmodel(); if (datalist != null && datalist.count > 0) { try { byte functioncode = (byte)modbusfunctioncode.writeregistermultiple.gethashcode(); int length = datalist.count * 2; //1.拼接报文: var sendcommand = new list<byte>(); //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+crc //站地址 sendcommand.add(devaddr); //功能码 sendcommand.add(functioncode); //写入地址 sendcommand.add((byte)(startaddr / 256)); sendcommand.add((byte)(startaddr % 256)); //寄存器数量 sendcommand.add((byte)(length / 256)); sendcommand.add((byte)(length % 256)); // 获取数值的byte[] list<byte> valuebytes = new list<byte>(); foreach (var data in datalist) { list<byte> temp = new list<byte>(bitconverter.getbytes(data)); temp.reverse();// 调整字节序 valuebytes.addrange(temp); } // 字节数 sendcommand.add((byte)valuebytes.count); sendcommand.addrange(valuebytes); //crc byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); sendcommand.addrange(crc); //000004 - rx:01 10 00 02 00 04 08 42 f6 e6 66 41 64 cc cd 83 23 //000005 - tx:01 10 00 02 00 04 60 0a //000006 - rx:01 0a 00 02 00 04 08 42 f6 e6 66 41 64 cc cd 98 f9 //000007 - tx:01 8a 01 86 a0 //报错了 //2.发送报文 serialportobj.write(sendcommand.toarray(), 0, sendcommand.count); //3.接收报文 thread.sleep(50);//要延时一下,才能读到数据 //读取响应报文 byte[] respbytes = new byte[serialportobj.bytestoread]; serialportobj.read(respbytes, 0, respbytes.length); // respbytes -> 01 01 02 00 00 b9 fc // 检查一个校验位 if (checkcrc(respbytes) && respbytes[0] == devaddr && respbytes[1] == functioncode) { resultmodel.issucceed = true; } else { resultmodel.msg = "响应报文校验失败"; } } catch (exception ex) { resultmodel.msg = "异常:" + ex.message; } //serialportobj.close(); } else { resultmodel.msg = "datalis参数不能为null 且 count 要大于0"; } return resultmodel; } /// <summary> /// 写单个线圈输出 ok /// </summary> /// <param name="on">开关</param> /// <param name="devaddr">从站地址</param> /// <param name="startaddr">写入地址</param> /// <returns></returns> public modbusresultmodel writesingleoutonoff(bool on, byte devaddr = 1, ushort startaddr = 0) { var resultmodel = new modbusresultmodel(); try { //var isok = false; //1.拼接报文: var sendcommand = new list<byte>(); //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+crc //站地址 sendcommand.add(devaddr); //功能码 byte functioncode = 0x05; sendcommand.add(functioncode); //写入地址 sendcommand.add((byte)(startaddr / 256)); sendcommand.add((byte)(startaddr % 256)); //写入数据 sendcommand.add((byte)(on ? 0xff : 0x00));//true : 0xff 开,false : 0x00 关 sendcommand.add(0x00); //crc byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); sendcommand.addrange(crc); //2.发送报文 serialportobj.write(sendcommand.toarray(), 0, sendcommand.count); //isok = true; resultmodel.issucceed = true; } catch (exception ex) { resultmodel.msg = "异常:" + ex.message; } return resultmodel; } } }
modbus 串口通讯 读线圈状态
(这个类是针对线圈的 突出读取数据)
using system; using system.collections.generic; using system.io.ports; using system.linq; using system.text; using system.threading.tasks; namespace cjh.modbustool.rtu { /// <summary> /// modbus 串口通讯 读线圈状态 /// </summary> public class modbusrtucoil : modbusrtu { //modbusrtu rtu = new modbusrtu(portname); //var resultmodel = rtu.readdata(1, readlen, modbusfunctioncode.readoutcoil); /// <summary> /// modbus 串口通讯 /// </summary> /// <param name="portname">com口名称</param> /// <param name="baudrate">波特率</param> /// <param name="parity">检验位</param> /// <param name="databits">数据位</param> /// <param name="stopbits">停止位</param> public modbusrtucoil(string portname, int baudrate = 9600, parity parity = parity.none, int databits = 8, stopbits stopbits = stopbits.one) : base(portname, baudrate, parity, databits, stopbits) { //init(portname, baudrate, parity, databits, stopbits); //serialportobj.datareceived += new serialdatareceivedeventhandler(comdatareceived); } /// <summary> /// 读取线圈数据 ok /// </summary> /// <param name="devadd">从站地址</param> /// <param name="length">寄存器数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>线圈数据</returns> public modbusresultmodel<bool> readdatacoil(byte devadd, ushort length, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { var resultmodel = new modbusresultmodel<bool>(); var model = readdata(devadd, length, (byte)functioncode, startaddr); if (model != null && model.datas != null && model.datas.length > 5) { resultmodel.issucceed = model.issucceed; //报文解析 // 检查一个校验位 list<byte> resplist = new list<byte>(model.datas); resplist.removerange(0, 3); resplist.removerange(resplist.count - 2, 2); // 00 00 //集合反转 resplist.reverse(); //转换成2进制 var respstrlist = resplist.select(r => convert.tostring(r, 2)).tolist(); var values = string.join("", respstrlist).tolist(); values.reverse(); //values.foreach(c => console.writeline(convert.toboolean(int.parse(c.tostring())))); foreach (var v in values) { resultmodel.resultlist.add(v.tostring() == "1"); } } return resultmodel; } } }
tcp
modbustcp 基类
using system; using system.collections.generic; using system.linq; using system.net.sockets; using system.runtime.interopservices; using system.security.cryptography; using system.text; using system.threading.tasks; namespace cjh.modbustool.tcp { /// <summary> /// modbustcp 基类 /// </summary> public abstract class modbustcpbase : modbusbase { private socket _socket = null; ushort _tid = 0;//transactionid 最大 65535 /// <summary> /// 异常码 字典 /// </summary> protected dictionary<int, string> errors = new dictionary<int, string>() { { 0x01 , "非法功能码"}, { 0x02 , "非法数据地址"}, { 0x03 , "非法数据值"}, { 0x04 , "从站设备故障"}, { 0x05 , "确认,从站需要一个耗时操作"}, { 0x06 , "从站忙"}, { 0x08 , "存储奇偶性差错"}, { 0x0a , "不可用网关路径"}, { 0x0b , "网关目标设备响应失败"}, }; /// <summary> /// modbus tcp 通讯 初始化 /// </summary> /// <param name="host">主机地址</param> /// <param name="port">端口</param> protected void init(string host, int port) { _socket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp); _socket.connect(host, port); } /// <summary> /// 读取报文的 公共方法 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="length">寄存器数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>返回报文(协议格式:transactionid+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns> protected byte[] generatetcpcommandreadbytes(byte devaddr, ushort length, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { var basecommand = generatereadcommandbytes(devaddr, length, functioncode, startaddr); var sendcommand = new list<byte>(); //transactionid sendcommand.add((byte)(_tid / 256)); sendcommand.add((byte)(_tid % 256)); //modbus 协议标识 sendcommand.add(0x00); sendcommand.add(0x00); //后续字节数 sendcommand.add((byte)(basecommand.length / 256)); sendcommand.add((byte)(basecommand.length % 256)); _tid++; _tid %= 65535; sendcommand.addrange(basecommand); return sendcommand.toarray(); } /// <summary> /// 读取报文的 公共方法 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="data">输入数据</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始寄存器地址</param> /// <returns>返回报文(协议格式:transactionid+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns> protected byte[] generatetcpcommandwritebytes(byte devaddr, ushort data, modbusfunctioncode functioncode = modbusfunctioncode.writeregister, ushort startaddr = 0) { var basecommand = generatewritecommandbytes(devaddr, data, functioncode, startaddr); var sendcommand = new list<byte>(); //transactionid sendcommand.add((byte)(_tid / 256)); sendcommand.add((byte)(_tid % 256)); //modbus 协议标识 sendcommand.add(0x00); sendcommand.add(0x00); //后续字节数 sendcommand.add((byte)(basecommand.length / 256)); sendcommand.add((byte)(basecommand.length % 256)); _tid++; _tid %= 65535; sendcommand.addrange(basecommand); return sendcommand.toarray(); } protected modbusresultmodel sendcommand(byte[] sendcommand) { var resultmodel = new modbusresultmodel(); try { //报文 //transactionid modbus 协议标识 后续字节数 从站地址 功能码 起始寄存器地址 寄存器数量 crc //0x00 0x01 0x00 0x00 0x00 0x06 devaddr functioncode 0x00 0x0a resultmodel.senddatastr = generatesendcommandstr(sendcommand.toarray()); _socket.send(sendcommand.toarray()); //000002-rx:00 00 00 00 00 06 01 03 00 01 00 05 //000003-tx:00 00 00 00 00 0d 01 03 0a 00 00 00 00 00 00 00 00 00 00 // 00 00 00 00 00 0d 前6位 // 01 03 0a (0,1,2) // 0a 数据长度 (0a=10) //先取前6位,固定返回 var resp_bytes = new byte[6];// 00 00 00 00 00 0d 前6位 _socket.receive(resp_bytes, 0, resp_bytes.length, socketflags.none); //取出下标为 :4和5的数据[00 0d] var len_bytes = resp_bytes.tolist().getrange(4, 2); //起始寄存器地址 的反向操作 //将下标为4和5 两个字节转成10进制数 int len = len_bytes[0] * 256 + len_bytes[1]; //获取数据的长度 //01 03 0a 00 00 00 00 00 00 00 00 00 00 [正常] //01 83 02 [异常,83, 异常代码 :02] resp_bytes = new byte[len]; _socket.receive(resp_bytes, 0, len, socketflags.none); //检查响应报文是否正常 //0x83 1000 0011 //01 83 02 [异常,83, 异常代码 :02] if (resp_bytes[1] > 0x08)//判断是否异常 { //resp_bytes[2] = 异常代码 :02 //说明响应是异常报文 //返回异常信息,根据resp_bytes字节进行异常关联 if (errors.containskey(resp_bytes[2])) { resultmodel.msg = errors[resp_bytes[2]];//获取异常码对应的异常说明 } } else { //resp_bytes[2] = 0a 数据长度 (0a=10) //正常 resultmodel.datas = resp_bytes.tolist().getrange(3, resp_bytes[2]).toarray(); resultmodel.issucceed = true; } } catch (exception ex) { resultmodel.msg = ex.message; } return resultmodel; } /// <summary> /// 解析数据 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="datas"></param> /// <returns></returns> public list<t> analysisdatas<t>(byte[] datas) { //data_bytes 每两个字节转成一个数字, float 4个字节转成一个数字,double 8个字节转成一个数字 //2 ushort short int16 uint32 float //4 int uint int32 uint32 float //8 double //16 decimal var resultvalue = new list<t>(); try { var type_len = marshal.sizeof(typeof(t)); for (int i = 0; i < datas.length; i += type_len) { var temp_bytes = datas.tolist().getrange(i, type_len); if (bitconverter.islittleendian) { temp_bytes.reverse(); } //反射 方法 type bitconverter_type = typeof(bitconverter); var typemethodlist = bitconverter_type.getmethods().tolist(); //找到返回类型和传入的类型一至,且方法的参数是2个的方法 var method = typemethodlist.firstordefault(mi => mi.returntype == typeof(t) && mi.getparameters().length == 2); if (method == null) { throw new exception("数据转换类型出错!"); } else { //由 bitconverter_type 执行找到的 method方法,注意参数数量,上面找是的两个参数的方法 var value = method.invoke(bitconverter_type, new object[] { temp_bytes.toarray(), 0 }); resultvalue.add((t)value); } } } catch (exception ex) { } return resultvalue; } } }
modbus tcp 通讯
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using system.net.sockets; namespace cjh.modbustool.tcp { /// <summary> /// modbus tcp 通讯 /// </summary> public class modbustcp : modbustcpbase { /// <summary> /// modbus tcp 通讯 /// </summary> /// <param name="host">主机地址</param> /// <param name="port">端口</param> public modbustcp(string host, int port) { //_socket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp); //_socket.connect(host, port); init(host, port); } /// <summary> /// 读取保持型寄存器 03 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="count">数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始地址</param> //public modbusresultmodel readholdingregister(byte devaddr, ushort length, byte functioncode = 3, ushort startaddr = 0) //{ // var resultmodel = new modbusresultmodel(); // //报文 // //transactionid modbus 协议标识 后续字节数 从站地址 功能码 起始寄存器地址 寄存器数量 crc // //0x00 0x01 0x00 0x00 0x00 0x06 devaddr functioncode 0x00 0x0a // try // { // ushort tid = 0;//transactionid 最大 65535 // var sendcommand = new list<byte>(); // //transactionid // sendcommand.add((byte)(tid / 256)); // sendcommand.add((byte)(tid % 256)); // //modbus 协议标识 // sendcommand.add(0x00); // sendcommand.add(0x00); // //后续字节数 // sendcommand.add(0x00); // sendcommand.add(0x06); // //从站地址 // sendcommand.add(devaddr); // //功能码 // sendcommand.add(functioncode); // //起始寄存器地址 // sendcommand.add((byte)(startaddr / 256)); // sendcommand.add((byte)(startaddr % 256)); // //寄存器数量 // sendcommand.add((byte)(length / 256)); // sendcommand.add((byte)(length % 256)); // //crc // //byte[] crc = crc16(sendcommand.toarray(), sendcommand.count); // //sendcommand.addrange(crc); // tid++; // tid %= 65535; // resultmodel.senddatastr = generatesendcommandstr(sendcommand.toarray()); // _socket.send(sendcommand.toarray()); // //000002-rx:00 00 00 00 00 06 01 03 00 01 00 05 // //000003-tx:00 00 00 00 00 0d 01 03 0a 00 00 00 00 00 00 00 00 00 00 // // 00 00 00 00 00 0d 前6位 // // 01 03 0a (0,1,2) // // 0a 数据长度 (0a=10) // //先取前6位,固定返回 // var resp_bytes = new byte[6];// 00 00 00 00 00 0d 前6位 // _socket.receive(resp_bytes, 0, resp_bytes.length, socketflags.none); // //取出下标为 :4和5的数据[00 0d] // var len_bytes = resp_bytes.tolist().getrange(4, 2); // //起始寄存器地址 的反向操作 // //将下标为4和5 两个字节转成10进制数 // int len = resp_bytes[4] * 256 + resp_bytes[5]; // //获取数据的长度 // //01 03 0a 00 00 00 00 00 00 00 00 00 00 [正常] // //01 83 02 [异常,83, 异常代码 :02] // resp_bytes = new byte[len]; // _socket.receive(resp_bytes, 0, len, socketflags.none); // //检查响应报文是否正常 // //0x83 1000 0011 // //01 83 02 [异常,83, 异常代码 :02] // if (resp_bytes[1] > 0x08)//判断是否异常 // { // //resp_bytes[2] = 异常代码 :02 // //说明响应是异常报文 // //返回异常信息,根据resp_bytes字节进行异常关联 // if (errors.containskey(resp_bytes[2])) // { // resultmodel.msg = errors[resp_bytes[2]];//获取异常码对应的异常说明 // } // } // else // { // //resp_bytes[2] = 0a 数据长度 (0a=10) // //正常 // resultmodel.datas = resp_bytes.tolist().getrange(3, resp_bytes[2]).toarray(); // resultmodel.issucceed = true; // } // } // catch (exception ex) // { // resultmodel.msg = ex.message; // } // return resultmodel; //} /// <summary> /// 读取保持型寄存器 03 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="length">数量</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始地址</param> /// <returns>返回对象</returns> public modbusresultmodel<t> readholdingregister<t>(byte devaddr, ushort length, modbusfunctioncode functioncode = modbusfunctioncode.readregister, ushort startaddr = 0) { var resultmodel = new modbusresultmodel<t>(); try { var command = generatetcpcommandreadbytes(devaddr, length, functioncode, startaddr); resultmodel.senddatastr = generatesendcommandstr(command.toarray()); var receptionmodel = sendcommand(command.toarray()); if (receptionmodel.issucceed && receptionmodel.datas != null && receptionmodel.datas.length > 0) { resultmodel.datas = receptionmodel.datas; resultmodel.resultlist = analysisdatas<t>(receptionmodel.datas); resultmodel.issucceed = true; } } catch (exception ex) { resultmodel.msg = ex.message; } return resultmodel; } /// <summary> /// 写入保持型寄存器 03 /// </summary> /// <param name="devaddr">从站地址</param> /// <param name="datas">写入数据</param> /// <param name="functioncode">功能码</param> /// <param name="startaddr">起始地址</param> /// <returns>返回对象</returns> public modbusresultmodel writeholdingregister(byte devaddr, ushort data, modbusfunctioncode functioncode = modbusfunctioncode.writeregister, ushort startaddr = 0) { var resultmodel = new modbusresultmodel(); try { var command = generatetcpcommandwritebytes(devaddr, data, functioncode, startaddr); resultmodel.senddatastr = generatesendcommandstr(command.toarray()); var receptionmodel = sendcommand(command.toarray()); if (receptionmodel.issucceed) { resultmodel.issucceed = true; } } catch (exception ex) { resultmodel.msg = ex.message; } return resultmodel; } } }
这就是全部的代码。
以上就是使用c#实现自己封装的modbus工具类库的详细内容,更多关于c# modbus的资料请关注代码网其它相关文章!
发表评论