文章目录

  • 一、Modbus协议
  • 二、Modbus协议的分类
  • 三、Modbus通信报文解读

一、Modbus协议

  • 概念
    Modbus协议是MODICON(莫迪康)(现施耐德品牌)在1979年开发的,是全球第一个真正用于现场的总线协议。
    Modbus协议是应用于电子控制器的一种通用语言。通过此协议,可以实现控制器相互之间、控制器经由网络和其他设备之间的通信。
  • 特点
    • 标准开放、公开发表、无版税要求、无许可证费(没有费用)
    • 支持多种接口(RS232\RS422\RS485\RJ45);各种传输介质(双绞线,网线)
    • 格式简单、紧凑、通俗易懂,容易上手(好用)
  • Modbus总线通信环境
    • 基本通信
    • 从机编码

二、Modbus协议的分类

  • 分类

    • 串口 RS485(一注多从):ModbusAscii【Ascii字符方式进行发送】、ModbusRTU
    • 以太网(点对点链接)ModbusTCP、ModbusUDP
  • Modbus协议下的数据存储
    • 数据存储中的位、字节byte (8位)、字 word(2个字节,16位)、双字 word(4个字节 32位),C#中的数据显示:数据类型、显示格式
    • 内存分区与功能
      存储区 对象类型 访问类型 存储区标识 说明 可用功能码
      线圈状态 单个bit 读写 0XXXX 通过应用程序改变这种类型数据 01 05 15
      输入线圈 单个bit 只读 1XXXX I/O系统提供这种类型数据 02
      输入寄存器 16-位字 只读 3XXXX I/O系统提供这种类型数据 04
      保持寄存器 16-位字 读写 4XXXX 通过应用程序改变这种类型数据 03 06 16
    • 操作存储区的命令
      • 功能码:01、02、03、04、05、06、15、16

三、Modbus通信报文解读

  • 读寄存器消息帧格式

    • TX:发送 RX:接收
      示例如下: 16进制

           //01:读1号从站保持型寄存器 //03:功能码//00 00  :起始地址  (高低位)00 00 //00 0A  :读取数量  (高低位)00 0A//C5 CD:CRC校验Tx:000662-01 03 00 00 00 0A C5 CDRx:000663-01 03 14 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DA 85
      
    • 0x03 0x04
      • 请求

        从站地址 功能码 起始地址 读取长度(2byte - > 16bit) CRC
        01 03 00(Hi)00(Lo) 00(Hi)0A(Lo) CS CD
      • 响应
        从站地址 功能码 字节数 寄存器值(1) 寄存器值(2) 寄存器值(20) CRC
        01 03 14 00(Hi)00(Lo) 00(Hi)00(Lo) 00(Hi)00(Lo) XX XX
      • 代码如下
        • 实现方式一

              SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);serialPort.Open();Modbus.Device.ModbusMaster modbusMaster = Modbus.Device.ModbusSerialMaster.CreateRtu(serialPort);Task.Run(() =>{while (true){Task.Delay(5000).Wait();ushort[] arry = modbusMaster.ReadInputRegisters(1, 0, 2);Console.WriteLine($"温度:{(arry[1] * 0.1).ToString("#0.0")} ℃");Console.WriteLine($"湿度:{(arry[0] * 0.1).ToString("#0.0")}%");}});
          
        • 实现方式二
             SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> bytes = new List<byte>();//设备号bytes.Add(0x01);//功能码bytes.Add(0x03);//地址  两个字节ushort addr = 0;bytes.Add((byte)(addr / 256));  //高位bytes.Add((byte)(addr % 256)); //低位//数量ushort leng = 2;bytes.Add((byte)(leng / 256));  //高位bytes.Add((byte)(leng % 256)); //低位//CRC校验码bytes = CRC16(bytes);//发送报文serialPort.Write(bytes.ToArray(), 0, bytes.Count);//接收报文byte[] data = new byte[2 * 2 + 5];serialPort.Read(data, 0, data.Length);//解析报文  短整型    01 03 06 00 19 00 19 00 02 6C B1 for (int i = 3; i < data.Length - 2; i = i + 2){byte[] vb = new byte[2] { data[i + 1], data[i] };ushort u = BitConverter.ToUInt16(vb);//无符号短整型   BitConverter 为小端处理Console.WriteLine(u);}//解析浮点型    ABCDfor (int i = 3; i < data.Length - 2; i += 4){var v = data[i + 3];  //Dvar v1 = data[i + 2];//Cvar v3 = data[i + 1];//Bvar v4 = data[i];//Abyte[] vb = new byte[4] {data[i + 3],data[i + 2],data[i + 1],data[i]};float aa = BitConverter.ToSingle(vb);//   Console.WriteLine(u);}static List<byte> CRC16(List<byte> 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);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}
          
  • 写单寄存器消息帧格式

    • 请求与响应

      从站地址 功能码 写入地址 写入值(2) CRC
      01 06 00(Hi)00(Lo) 00(Hi)00(Lo) XX XX
    • 代码如下
              SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> bytes = new List<byte>();//设备号bytes.Add(0x01);//功能码bytes.Add(0x06);//地址  两个字节ushort addr = 3;bytes.Add((byte)(addr / 256));  //高位bytes.Add((byte)(addr % 256)); //低位// 写入寄存器的值ushort value = 100;bytes.Add((byte)(value / 256));  //高位bytes.Add((byte)(value % 256)); //低位//CRC校验码bytes = CRC16(bytes);serialPort.Write(bytes.ToArray(), 0, bytes.Count);
      
  • 写多寄存器消息帧格式

    • 请求

      从站地址 功能码 写入地址 写入数量 字节数 写入值 CRC
      01 10 00(Hi)00(Lo) 00(Hi)0A(Lo) 04 0A AB 00 01 XX XX
    • 响应
      从站地址 功能码 写入地址 写入数量 CRC
      01 0F 00(Hi)00(Lo) 00(Hi)0A(Lo) XX XX
    • 代码如下
      • 整形数据

               SerialPort serialPort = new SerialPort("COM1",9600,Parity.None,8,StopBits.One);serialPort.Open();List<byte> list = new List<byte>();//设备号list.Add(0x01);//功能码list.Add(0x10);//地址ushort addr = 0;list.Add((byte)(addr / 256));//高位list.Add((byte)(addr % 256));//低位 //写入多个相同类型的值List<ushort> values = new List<ushort>();values.Add(111);values.Add(item: 222);values.Add(333);//写入数量 list.Add((byte)(values.Count / 256));//高位list.Add((byte)(values.Count % 256));//低位 //写入字节数 6个字节list.Add((byte)(values.Count*2));for (int i = 0; i < values.Count; i++){//第一种//list.Add((byte)(values[i] / 256));//list.Add((byte)(values[i] %256));//第二种//list.Add(BitConverter.GetBytes(values[i])[1]);// list.Add(BitConverter.GetBytes(values[i])[0]);//第三种list.AddRange(BitConverter.GetBytes(values[i]).Reverse());}list = CRC16(list);serialPort.Write(list.ToArray(),0, list.Count);        //验证检验码static List<byte> CRC16(List<byte> 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);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}
        
      • 浮点型数据
               SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> list = new List<byte>();//设备号list.Add(0x01);//功能码list.Add(0x10);//地址ushort addr = 0;list.Add((byte)(addr / 256));//高位list.Add((byte)(addr % 256));//低位 List<float> values = new List<float>();values.Add(1.1f);values.Add(item: 2.1f);values.Add(item: 2.3f);//数量list.Add((byte)(values.Count * 2 / 256));list.Add((byte)(values.Count * 2 % 256));//字节长度list.Add((byte)(list.Count * 4));for (int i = 0; i < values.Count; i++){list.Add(BitConverter.GetBytes(values[i])[3]);  //Alist.Add(BitConverter.GetBytes(values[i])[2]); //Blist.Add(BitConverter.GetBytes(values[i])[1]);//Clist.Add(BitConverter.GetBytes(values[i])[0]);//D}list = CRC16(list);serialPort.Write(list.ToArray(), 0, list.Count);     //验证检验码static List<byte> CRC16(List<byte> 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);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}
        
  • 线圈状态

    • 读线圈消息帧格式:OXO1,0X02

      • 请求

        从站地址 功能码 起始地址 读取长度 CRC
        01 01 00(HI) 00(LO) 00(HI) 0A(LO) xx xx
      • 响应
        从站地址 功能码 字节数 输出状态15-8 输出状态15-8 CRC
        01 01 02 00 00 xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> list = new List<byte>();// 设备名称list.Add(0x01);//功能码list.Add(0x01);//起始地址ushort addr = 0;list.Add((byte)(addr / 256));//高位list.Add((byte)(addr % 256));//低位 //读取寄存器数量ushort leng = 10;list.Add((byte)(leng / 256));  //高位list.Add((byte)(leng % 256)); //低位list = CRC16(list);serialPort.Write(list.ToArray(), 0, list.Count);//响应byte[] data = new byte[(int)Math.Ceiling(leng * 1.0 / 8) + 5];serialPort.Read(data, 0, data.Length);List<byte> dataList = new List<byte>();//获取字节数据  2个字节   16 位for (int i = 3; i < data.Length && dataList.Count < (int)Math.Ceiling(leng * 1.0 / 8); i++){dataList.Add(data[i]);}int count = 0;//字节运算for (int i = 0; i < dataList.Count; i++){ //按位与运算的方式for (int k = 0; k < 8; k++){//移位byte temp = (byte)(1 << k % 8);//与运算byte b = (byte)(dataList[i] & temp);//输出结果 Console.WriteLine((dataList[i] & temp) != 0);count++;if (count == leng)break;}}//CRC校验码static List<byte> CRC16(List<byte> 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);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}
        
    • 写线圈状态帧 0x05
      • 请求
      • 响应
        从站地址 功能码 写入地址 写入值 CRC
        01 05 00(HI) 00(LO) FF(HI)/00(HI) 00(Lo) xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> data = new List<byte>();//设备名称data.Add(0x01);//功能码data.Add(0x05);//地址ushort addr = 11;data.Add((byte)(addr/256));//高位data.Add((byte)(addr % 256));//低位//写入值  on:0xFF  0x00 off:0x00 0x00data.Add(0x00);data.Add(0x00);//校验data = CRC16(data);serialPort.Write(data.ToArray(),0, data.Count);     //CRC校验码static List<byte> CRC16(List<byte> 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);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}
        
    • 写多线圈状态帧 0x0F
      • 请求

        从站地址 功能码 写入地址 写入数量 字节数 写入值 CRC
        01 0F 00(HI) 00(LO) 00(HI) 0A(LO) 02 0A(Hi) AB(Lo) xx xx
      • 响应
        从站地址 功能码 写入地址 写入数量 CRC
        01 0F 00(HI) 00(LO) 00(HI) 00(LO) xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);serialPort.Open();List<byte> data = new List<byte>();//设备名称data.Add(0x01);//功能码data.Add(0x0F);//地址ushort addr = 10;data.Add((byte)(addr / 256));//高位data.Add((byte)(addr % 256));//低位//输入值List<bool> state = new List<bool>() { true, false, true, false, true ,true, false, true, false, true };//写入数量  写入多少个寄存器 data.Add((byte)(state.Count / 256));//高位data.Add((byte)(state.Count % 256));//低位 byte data1 = 0;List<byte> list = new List< byte > ();int index = 0;//0000 0000 for (int i = 0; i < state.Count; i++){if (i % 8 == 0)list.Add(0x00);index = list.Count-1;if (state[i]){//移位byte temp = (byte)(1 << (i%8));//或运算list[index]  |=  temp;}}//字节数data.Add((byte)list.Count);// 添加data.AddRange(list);//校验data = CRC16(data);serialPort.Write(data.ToArray(),0,data.Count);
        

Modbus协议与SerialPort端口读写相关推荐

  1. Modbus 协议详解

    Modbus 协议详解 通信协议是指双方实体完成通信或服务所必须遵循的规则和约定,例如我们为实现人与人之间的交流需要约定统一的语言,统一的文字,规定语速等等. 而对于设备之间,协议定义了数据单元使用的 ...

  2. C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装)

    C#使用Modbus-TCP协议读取汇川PLC,Modbus读写是按照MW地址来处理的 [寄存器单位是字WORD,占用两个字节,类似于C#中的ushort(UInt16)],实际测试发现字符串是按照字 ...

  3. 基于MODBUS协议的单片机与(串口屏)触摸屏通信(图文)

    基于MODBUS协议的单片机与(串口屏)触摸屏通信(图文) 导读:触摸屏能够直观.生动地显示运行参数和运行状态,而且通过触摸屏画面可以直接修改系统运行参数,人机交互性好.触摸屏和单片机通信,需要根据触 ...

  4. Modbus协议(翻自wiki)

    文章目录 原文 译文 Modbus Modbus对象类型 协议版本 通讯和设备 帧格式 支持的功能代码 主要功能代码的请求和响应数据格式 功能代码 1(读取线圈)和功能代码 2(读取离散输入) 功能码 ...

  5. Modbus 协议从站开发通信西门子 PLC

    文章目录 1. Modbus 协议 1.1 传输模式:单工模式.全双工模式.半双工模式 1.2 广播模式.单播模式 1.3 Modbus 通信模式:ASCII.RTU 1.4 基本数据类型 2. 功能 ...

  6. 基于Modbus协议的KingSCADA-IOserver和仿真PLC通信

    1.获取本机和从机IP地址 设本机192.168.1.1 设从机192.168.1.2 cmd命令ping主从机保证网络连通 2.从机运行仿真PLC软件(ModSim32) 寄存器地址(Adderss ...

  7. C#上位机 串口上位机Modbus协议

    文章目录 前言 一.准备工作 二.界面设计 1.LED灯 2.图表 三.程序设计 1.串口配置 2.发送报文 3.CRC校验 4.读写寄存器函数 5.LED状态切换 6.串口接收事件 7.设置电压 8 ...

  8. C#编写Modbus协议加速度传感器上位机

    C#编写Modbus协议加速度传感器上位机 项目概述 功能描述 上位机原理 初始化连接 读取寄存器原始数据 换算为实际物理量 自动模式 数据保存 数据可视化分析 尾言 项目概述 笔者利用下班时间,编写 ...

  9. RS485(Modbus RTU)工业RFID读写器CK-FR03-A01与PLC三菱FX5U的通讯操作说明

    RS485(Modbus RTU)工业RFID读写器CK-FR03-A01支持各大PLC集成与应用,本文将重介绍CK-FR03-A01使用MELSOFT系列GX Works3程序(版本为GX Work ...

最新文章

  1. 设置行内元素宽高和背景色后,行内元素文本不水平垂直居中解决方案
  2. jdbc Template 介绍和 spring 链接数据源的四种方式
  3. selenium切换窗口 java_WebDriver(Selenium2) 根据新窗口title切换窗口
  4. CF#574E. OpenStreetMap 题解
  5. 数据库-MySQL-SQL语句
  6. java manager 如何使用_java - Android:如何使用AlarmManager
  7. Hammer.js分析(一)——基础结构
  8. 常用图像像素格式 NV12、NV2、I420、YV12、YUYV
  9. nginx之30分钟搞定nginx反向代理和负载均衡
  10. CAST 和 CONVERT
  11. mp4视频太大怎么压缩?
  12. android高斯模糊平均值,高斯模糊
  13. OpenCV绘制透明底的图片,简单易懂讲解alpha通道怎么用
  14. 用java实现复数的_Java实现复数运算
  15. undefsafe原型链[网鼎杯 2020 青龙组]notes
  16. oracle数据库uga中文全称,UGA的概念
  17. 阿里汪学长教导我们说。。。
  18. 迁移AndroidX,你该搬家了
  19. mysql中的转换类型数据类型_数据库中转换数据类型的几种方法
  20. pmp访谈法和焦点小组区别_访谈和书评:《简而言之的Java》,第六版

热门文章

  1. 无人机驾驶员培训学习记录(八)
  2. 两种常用的工作分解结构主要形式
  3. 应用程序图标设置 系统图标缓存问题
  4. linux系统 、Docker(简单了解)
  5. C语言利用循环方法实现排列组合
  6. 通过品牌和型号查询车轴接口文档
  7. ElasticSearch-整合SpringBoot
  8. 基于BP神经网络预测电力负荷(Matlab代码实现)
  9. 文献检索、筛选和阅读
  10. creo怎么画线_cero草绘基础图文教程,教你CREO工程图绘制中心线的方法