网站 谁建设谁负责,网站建设概算,做思维导图好看的网站,wordpress 代码目录 知识点常用链接一、Modbus1.ModbusRTU消息帧解析2.主站poll、从站slave通讯仿真-modbusRTU1.功能码01读线圈状态2.功能码03读保持寄存器报文解析#xff08;寄存器存整型#xff09;报文解析#xff08;寄存器存float#xff09; 3.C#模拟主站Poll#xff08;ModbusR… 目录 知识点常用链接一、Modbus1.ModbusRTU消息帧解析2.主站poll、从站slave通讯仿真-modbusRTU1.功能码01读线圈状态2.功能码03读保持寄存器报文解析寄存器存整型报文解析寄存器存float 3.C#模拟主站PollModbusRTU协议-组报文4.NModbus4模拟主站pollModbusRTU协议5.C#模拟主站PollModbusTCP协议-组报文6.NModbus4模拟从站slaveModbusTCP协议7.NModbus4模拟从站slaveModbusRTU协议8.modbusRTU、modbusTCP报文不同之处 二、明文TCP 知识点
PLC寄存器中存储整型和无符号整型2字节。长整型4字节。单精度浮点数4字节。双精度浮点数8字节我们只要知道数据类型是2个字节一截取还是4个字节 对接收到的报文进行字节截取然后编码成str就行向PLC中写入Floatfloat占4个字节2个寄存器所以要使用功能码“写多寄存器0x10”, 功能码0x06只能写一个寄存器”serialPort.write(bytes,0,bytes.Length); thread.sleep(300); serialPort.Read() 发完指令后要等待从站响应300ms,然后再去读数据主站请求从站有两种方式主动手动点击查询线圈状态的按钮被动通过委托方式一件事情的发生触发另外事件。场景称菜菜一放上去触发去查询的功能代码块一个F要用4个二进制表示两个F用8个二进制表示所以 0xFA 表示1个字节modbusTCP响应 Tx:00 00 00 00 00 03 01 83 02 【831000 0011 功能码03 的高位为1就是异常02是错误码代号要查表】 send()/recv()和write()/read()发送数据和接收数据 参考链接socket原理不同协议图 比如omoronsocekt, modbustcp,他们都是用socket进行数据交互只是他们在应用层采用不同的协议约定对报文进行不同方式的解析明文协议就是直接编码不组包其他协议都是组包发出去如明文协议将字符串编码后直接send modbustcp协议要组装发送报文为从站地址功能码等等字符串数据
常用链接
虚拟串口调试工具 V6.9 汉化版免费版 串口、Modbus通信协议
一、Modbus
课程 文章介绍 一篇博客
1.ModbusRTU消息帧解析 2.主站poll、从站slave通讯仿真-modbusRTU
从站slave用于模拟PLC中的功能区一个tab页表示一个功能模块下图建了两个功能块 主站poll发送请求获取PLC中数据。 poll、slave都要设置connection、setup两个区域只有参数配对了才能正常收发数据
1.功能码01读线圈状态 报文都是16进制表示 16进制 0X010000 0001一位16进制需要4位二进制表达F 1111两个16进制数字表示1个字节 线圈中数据要么是0要么是1 读取长度00 0A表示读取10个寄存器 响应字节数单位是字节02 表示两个字节从02往后数两个字节都是数据未位 输出状态A2 02 这是两字节解析先颠倒高低位 02 A2 0000 0010 1010 0010 再反向读取数据0100 0101 0100 0000
2.功能码03读保持寄存器
寄存器中数据可以是整数浮点型 整型和无符号整型2字节。长整型4字节。单精度浮点数4字节。双精度浮点数8字节
报文解析寄存器存整型
读取长度00 0A表示读取10个寄存器1个寄存器是16位2个字节所以返回20个字节一个整型2字节所以返回的是10个数据 响应字节数单位是字节14 表示20个字节 输出状态007B 00 00 00 00 00 00 00 00 00 00 这是20个字节解析: 第一个数为123
报文解析寄存器存float
读取长度00 0A表示读取10个寄存器1个寄存器是16位2个字节所以返回20个字节一个float 占4字节所以返回的是5个数据 响应字节数单位是字节14 表示20个字节 输出状态解析: 42 0A 00 00 通过IEEE转换标准-第一个数为34.5
3.C#模拟主站PollModbusRTU协议-组报文
说明 1.下面代码模拟的是主站需要开启小程序mbslave作为从站PLC 2.主站发起的功能码请求有读线圈读保持寄存器写多个寄存器 3.主站发送报文然后对响应报文按消息帧进行解析
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading;namespace 通讯收发功能解析
{class Program{static void Main(string[] args){Console.WriteLine(Hello World!);//Test_0x01();Test_0x03();//Test_0x10();Console.ReadKey();}static void Test_0x01()/// 01功能码-读线圈状态测试PLC中线圈全为0{ushort startAddr 0;ushort readLen 10;// 请求// byte[] 需要指定长度不支持LinqListbyte command new Listbyte();command.Add(0x01);// 1号从站command.Add(0x01);// 功能码读线圈状态// 起始地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 读取数量command.Add(BitConverter.GetBytes(readLen)[1]);command.Add(BitConverter.GetBytes(readLen)[0]);// CRCcommand CRC16(command);//command 为长度8的字节{0x01 0x01 0x00 0x00 0x00 0x0A 0xBC 0x0D}// 以上报文组装完成// 发送-》SerialPortSerialPort serialPort new SerialPort(COM1, 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();//将command的第0位-command.count所有数据都发出去serialPort.Write(command.ToArray(), 0, command.Count);//发送报文为01 01 00 00 00 0A BC 0D 请求10个线圈的状态响应时1个字节8位接收不够所以两字节Thread.Sleep(5);//加上延时等待PLC反应时间// 进行响应报文的接收和解析 byte[] respBytes new byte[serialPort.BytesToRead];//缓冲区信息serialPort.Read(respBytes, 0, respBytes.Length);// respBytes - 01 01 02 00 00 B9 FC// 检查一个校验位//对报文进行解析数据Listbyte respList new Listbyte(respBytes);respList.RemoveRange(0, 3);//00 00 B9 FCrespList.RemoveRange(respList.Count - 2, 2);//00 00 数据报文// 1。高低位切换// 2。从后往前读respList.Reverse();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()))));//Convert.ToBoolean(1);}static void Test_0x03()//读保持寄存器 PLC中第一个寄存器为123其他0{ushort startAddr 0;ushort readLen 10;// 请求// byte[] 需要指定长度不支持LinqListbyte command new Listbyte();command.Add(0x01);// 1号从站command.Add(0x03);// 功能码读保持型寄存器// 起始地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 读取数量command.Add(BitConverter.GetBytes(readLen)[1]);command.Add(BitConverter.GetBytes(readLen)[0]);// CRCcommand CRC16(command);// 报文组装完成// 发送-》SerialPortSerialPort serialPort new SerialPort(COM1, 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();serialPort.Write(command.ToArray(), 0, command.Count);// 进行响应报文的接收和解析byte[] respBytes new byte[serialPort.BytesToRead];serialPort.Read(respBytes, 0, respBytes.Length);// respBytes - 01 01 02 00 00 B9 FC// 检查一个校验位Listbyte respList new Listbyte(respBytes);respList.RemoveRange(0, 3);respList.RemoveRange(respList.Count - 2, 2);// 拿到实际的数据部分进行数据解析// 明确一点读的是无符号单精度占两个字节//byte[] data new byte[2]; //for (int i 0; i readLen; i)//{// // 字节序问题 小端 大端// data[0] respList[i * 2 1];// data[1] respList[i * 2];// // 根据此两个字节转换成想要的实际数字// var value BitConverter.ToUInt16(data, 0);// Console.WriteLine(value);//}// 明确一点读的是Float 占4个字节byte[] data new byte[4];for (int i 0; i readLen / 2; i){// 字节序问题 小端 大端data[0] respList[i * 4 3];data[1] respList[i * 4 2];data[2] respList[i * 4 1];data[3] respList[i * 4];// 根据此两个字节转换成想要的实际数字var value BitConverter.ToSingle(data, 0);Console.WriteLine(value);}}//向PLC中写入Floatfloat占4个字节2个寄存器所以要使用功能码“写多寄存器0x10”, 功能码0x06只能写一个寄存器”, static void Test_0x10()//写多个寄存器功能码0x10{ushort startAddr 2;ushort writeLen 4;float[] values new float[] { 123.45f, 14.3f };// 请求// byte[] 需要指定长度不支持LinqListbyte command new Listbyte();command.Add(0x01);// 1号从站command.Add(0x10);// 功能码写多个保持型寄存器// 写入地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 写入数量command.Add(BitConverter.GetBytes(writeLen)[1]);command.Add(BitConverter.GetBytes(writeLen)[0]);// 获取数值的byte[]Listbyte valueBytes new Listbyte();for (int i 0; i values.Length; i){Listbyte temp new Listbyte(BitConverter.GetBytes(values[i]));temp.Reverse();// 调整字节序valueBytes.AddRange(temp);}// 字节数command.Add((byte)valueBytes.Count);command.AddRange(valueBytes);// CRCcommand CRC16(command);// 报文组装完成// 发送-》SerialPortSerialPort serialPort new SerialPort(COM1, 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();serialPort.Write(command.ToArray(), 0, command.Count);}static Listbyte CRC16(Listbyte value, ushort poly 0xA001, ushort crcInit 0xFFFF){if (value null || !value.Any())throw new ArgumentException();//运算ushort crc crcInit;for (int i 0; i value.Count; i){crc (ushort)(crc ^ (value[i]));for (int j 0; j 8; j){crc (crc 1) ! 0 ? (ushort)((crc 1) ^ poly) : (ushort)(crc 1);}}byte hi (byte)((crc 0xFF00) 8); //高位置byte lo (byte)(crc 0x00FF); //低位置Listbyte buffer new Listbyte();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}}
}4.NModbus4模拟主站pollModbusRTU协议 ReadHoldingRegisters(1, 0, 1)# 参数从站地址起始地址读取数量
5.C#模拟主站PollModbusTCP协议-组报文
课程视频P17-P14
03读保持寄存器报文
说明 1.下面代码模拟的是modbusTCP主站需要开启小程序mbslave作为从站PLC要设置slave的connection、setup两个区域的TCP相关参数 2.主站发起的功能码请求有ReadHoldingRegisterReadInputRegister 3.主站发送报文然后对响应报文按消息帧进行解析
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace 通讯收发功能解析
{public class ModbusTcp: ModbusBase{Socket socket new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);public Result connect(string host, int port){Result result new Result();try{socket.Connect(host, port);result.State true;}catch (Exception ex){result.State false;result.Exception ex.Message;}return result;}// 读保持型寄存器03// ModbusRTU:0x01(从站地址) 03 (功能码) 0x00 0x0A(起始地址) 0x00 0x05(读取长度) 0xXX 0xXX(CRC16校验码)// ModbusTCP:请求报文0x00 0x00(TransationID 最大65535) 0x00 0x00 (Modbus协议标识) 0x00 0x06(后续字节数) 0x01 (单元标识) 0x03 0x0 0x0A 0x00 0x05// 响应报文 0x00 0x00(TransationID 最大65535) 0x00 0x00 (Modbus协议标识) 0x00 0x0D(后续字节数) 0x01 (单元标识) 0x03功能码// 0x0A返回10个字节数据 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00/// summary////// /summary/// param nameunit_id从站地址/param/// param namestart_addr寄存器起始地址/param/// param namecount读寄存器数量/parampublic Result ReadHoldingRegister(byte unit_id , ushort start_addr,ushort count){Result result new Result();try {ushort tid 0;//报文组装byte[] req_bytes new byte[]{(byte)(tid/256),(byte)(tid%256),0x00, 0x00,0x00, 0x06,unit_id,0x03,(byte)(start_addr/256),(byte)(start_addr%256),//10进制转成16(byte)(count/256),(byte)(count%256),};tid;tid % 65535;//var req_bytes this.ReadCommandBytes( unit_id, 0x03,start_addr, count);//发送请求socket.Send(req_bytes);//接收响应byte[] resp_bytes new byte[6];//由于plc返回的响应字数长度是不一样的先取前6个字节socket.Receive(resp_bytes, 0 ,6 ,SocketFlags.None);var len_bytes resp_bytes.ToList().GetRange(4, 2);ushort len (ushort)(len_bytes[4] * 256 len_bytes[5]);//解析报文中返回的有多少个字节数resp_bytes new byte[len];//这个resp_bytes len 表明数据中有多少个字节数据是有用的数据报文socket.Receive(resp_bytes, 0, len, SocketFlags.None);//上面从缓存区拿走了6个字节现在把剩余的都拿走//检查响应报文是否正常功能码的高位为1就是异常//0x83 1000 0011if (resp_bytes[1] 0x80){//说明响应的是异常报文// 返回异常信息 根据resp_bytes[2]字节进行异常的关联throw new Exception(错误了);}//提取PLC中寄存器中的数据部分报文var data_bytes resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();result.State true;result.Datas data_bytes;}catch(Exception ex){result.State false;result.Exception ex.Message;}return result;}public Result ReadInputRegister(byte unit_id, ushort start_addr, ushort count){Result result new Result();try{ushort tid 0;//报文组装byte[] req_bytes new byte[]{(byte)(tid/256),(byte)(tid%256),0x00, 0x00,0x00, 0x06,unit_id,0x04,(byte)(start_addr/256),(byte)(start_addr%256),//10进制转成16(byte)(count/256),(byte)(count%256),};tid;tid % 65535;//var req_bytes this.ReadCommandBytes(unit_id, 0x04, start_addr, count);//发送请求socket.Send(req_bytes);//接收响应byte[] resp_bytes new byte[6];//由于plc返回的响应字数长度是不一样的先取前6个字节socket.Receive(resp_bytes, 0, 6, SocketFlags.None);var len_bytes resp_bytes.ToList().GetRange(4, 2);ushort len (ushort)(len_bytes[4] * 256 len_bytes[5]);//解析报文中返回的有多少个字节数resp_bytes new byte[len];//这个resp_bytes len 表明数据中有多少个字节数据是有用的数据报文socket.Receive(resp_bytes, 0, len, SocketFlags.None);//上面从缓存区拿走了6个字节现在把剩余的都拿走//检查响应报文是否正常功能码的高位为1就是异常//0x83 1000 0011if (resp_bytes[1] 0x80){//说明响应的是异常报文// 返回异常信息 根据resp_bytes[2]字节进行异常的关联throw new Exception(错误了);}//解析PLC中寄存器中的数据var data_bytes resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();result.State true;result.Datas data_bytes;}catch (Exception ex){result.State false;result.Exception ex.Message;}return result;}public void write(){}public T[] GetvaluesT(byte[] data_bytes)//解析报文{var type_len Marshal.SizeOf(typeof(T));//检查类型的长度 int32是4字节float是4字节for (var i 0; idata_bytes.Length;itype_len){//根据数据类型将报文切割获取一个类型的字节内容,并将字节转换成对应 的strvar temp_bytes data_bytes.ToList().GetRange(i, type_len);temp_bytes.Reverse();//字序short v BitConverter.ToInt16(temp_bytes.ToArray(), 0);ushort vv BitConverter.ToUInt16(temp_bytes.ToArray(), 0);float vvv BitConverter.ToSingle(temp_bytes.ToArray(),0);}return null;}}
}
6.NModbus4模拟从站slaveModbusTCP协议
用小工具poll连接自己建立的从站可读取从站的值 文章
using System.Threading;
using System.Net.Sockets;
using System.Net;
using Modbus.Data;
using Modbus.Device;public class slave{/// summary/// 服务器提供的数据区/// /summarypublic DataStore DataDataStoreFactory.CreateDefaultDataStore(); //初始化服务数据区;/// summary/// Modbus服务器/// /summarypublic ModbusSlave modbus_tcp_server;public void modbustcpslave(){modbus_tcp_server ModbusTcpSlave.CreateTcp(1, new TcpListener(IPAddress.Parse(127.0.0.1), 502)); //创建ModbusTcp服务器modbus_tcp_server.DataStore Data;//数据区赋值Thread th_0 new Thread(() {modbus_tcp_server.Listen();//异步 非阻塞 启动服务}){IsBackground true,};th_0.SetApartmentState(ApartmentState.STA);th_0.Start();Thread th_1 new Thread(() {SetData(); //数据区数据赋值}){IsBackground true,};th_1.SetApartmentState(ApartmentState.STA);th_1.Start();}/// summary/// 设置数据/// /summarypublic void SetData() //static修饰的函数或变量都是在类初始化的时候加载的而非静态的变量都是在对象初始化的时候加载。{while (true){Data.InputRegisters[1] (ushort)DateTime.Now.Year; //年Data.InputRegisters[2] (ushort)DateTime.Now.Month; //月Data.InputRegisters[3] (ushort)DateTime.Now.Day; //日Data.InputRegisters[4] (ushort)DateTime.Now.Hour; //时Data.InputRegisters[5] (ushort)DateTime.Now.Minute; //分Data.InputRegisters[6] (ushort)DateTime.Now.Second; //秒Data.InputRegisters[7] (ushort)DateTime.Now.Millisecond; //毫秒Random ran new Random();Data.InputRegisters[8] (ushort)ran.Next(0, 32767); //产生的随机数}}
}7.NModbus4模拟从站slaveModbusRTU协议
文章
public class slave_RTU{public ModbusSlave modbus_rtu_server;public void create(){SerialPort slavePort new SerialPort();slavePort.PortName COM1;slavePort.BaudRate 9600;slavePort.DataBits 8;slavePort.Parity Parity.Even;slavePort.StopBits StopBits.One;slavePort.Open();byte slaveID 1;modbus_rtu_server ModbusSerialSlave.CreateRtu(slaveID, slavePort);modbus_rtu_server.ModbusSlaveRequestReceived new EventHandlerModbusSlaveRequestEventArgs(Modbus_Request_Event);modbus_rtu_server.DataStore Modbus.Data.DataStoreFactory.CreateDefaultDataStore();modbus_rtu_server.DataStore.DataStoreWrittenTo new EventHandlerDataStoreEventArgs(Modbus_DataStoreWriteTo);modbus_rtu_server.DataStore.InputRegisters[1] (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.InputRegisters[2] (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.InputRegisters[3] (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.CoilDiscretes[1] true;modbus_rtu_server.DataStore.CoilDiscretes[2] false;modbus_rtu_server.DataStore.CoilDiscretes[3] false;modbus_rtu_server.Listen();}private void Modbus_Request_Event(object sender, Modbus.Device.ModbusSlaveRequestEventArgs e){try{//request from masterbyte fc e.Message.FunctionCode;byte[] data e.Message.MessageFrame;byte[] byteStartAddress new byte[] { data[3], data[2] };byte[] byteNum new byte[] { data[5], data[4] };Int16 StartAddress BitConverter.ToInt16(byteStartAddress, 0);Int16 NumOfPoint BitConverter.ToInt16(byteNum, 0);bool BOOL true;string FCNUM fc.ToString();if (fc.ToString() 6){//AOmodbus_rtu_server.DataStore.HoldingRegisters[StartAddress] 16;modbus_rtu_server.DataStore.HoldingRegisters[StartAddress 1] 17;}Console.WriteLine(fc.ToString() , StartAddress.ToString() , NumOfPoint.ToString());}catch (Exception exc){}}private void Modbus_DataStoreWriteTo(object sender, Modbus.Data.DataStoreEventArgs e){//this.Text DataType e.ModbusDataType.ToString() StartAdress e.StartAddress;int iAddress e.StartAddress;//e.StartAddress;switch (e.ModbusDataType){case ModbusDataType.HoldingRegister:for (int i 0; i e.Data.B.Count; i){//Set AO modbus_rtu_server.DataStore.HoldingRegisters[e.StartAddress i 1] e.Data.B[i];//e.Data.B[i] already write to slave.DataStore.HoldingRegisters[e.StartAddress i 1]//e.StartAddress starts from 0//You can set AO value to hardware here//DoAOUpdate(iAddress, e.Data.B[i].ToString());iAddress;}break;case ModbusDataType.Coil:for (int i 0; i e.Data.A.Count; i){//Set DOmodbus_rtu_server.DataStore.CoilDiscretes[e.StartAddress i 1] e.Data.A[i];//e.Data.A[i] already write to slave.DataStore.CoilDiscretes[e.StartAddress i 1]//e.StartAddress starts from 0//You can set DO value to hardware here//DoDOUpdate(iAddress, e.Data.A[i]);iAddress;if (e.Data.A.Count 1){break;}}break;}}}8.modbusRTU、modbusTCP报文不同之处 二、明文TCP
博客 视频
using System.Net;//创建Socket套接字Socket server new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint point new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));try { server.Bind(point); }catch (Exception ex){MessageBox.Show(无法启动服务器);}server.Listen(3);//Socket Client server.Accept();//Accept 抓取的连接请求是客户端发来的string client Client.RemoteEndPoint.ToString();MessageBox.Show(client连接了服务器);byte[] b new byte[1024 * 1024 * 2];//缓冲器int length 0;try{length Client.Receive(b);}catch{MessageBox.Show(client 失去连接);}if (length 0){string msg Encoding.Default.GetString(b, 0, length);Client.Send(Encoding.Default.GetBytes(textBox3.Text));}else{MessageBox.Show(client 失去连接);}