首页 > 编程语言 >通信协议_C#实现自定义ModbusRTU主站

通信协议_C#实现自定义ModbusRTU主站

时间:2024-07-07 23:00:35浏览次数:29  
标签:自定义 主站 通信协议 System serialPort crc using byte data

背景知识:modbus协议介绍

相关工具

  • mbslave:充当从站。
  • 虚拟串口工具:虚拟出一对串口。
  • VS2022。

实现过程以及Demo

  1. 打开虚拟串口工具:
  2. 打开mbslave:
    从站设置
    在这里插入图片描述
    此处从站连接COM1口。

Demo实现

  1. 创建DLL库,创建ModbusRTU类,进行实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace modBusDLL
{
    public class ModBusRTU
    {

        private SerialPort serialPort = null;

        public ModBusRTU()
        {
            serialPort = new SerialPort();
        }

        //连接方法

        //端口号,波特率,数据位,校验位,停止位
        public void Connect(string portName,int baudRate=9600,int dataBits=8,
                            Parity parity=Parity.None,StopBits stopBits=StopBits.One)
        { 
        if (serialPort.IsOpen) {
            
                serialPort.Close();
            }
        serialPort.BaudRate = baudRate;
        serialPort.PortName = portName;
        serialPort.Parity = parity;
        serialPort.StopBits = stopBits;
        serialPort.DataBits = dataBits;

            serialPort.Open();

            
        }

        //断开连接方法
        public void Disconnect()
        {
            if (serialPort.IsOpen)
            {

                serialPort.Close();
            }
        }

        //读消息方法
        public byte[] ReadKeepRegisters(byte devAdd,ushort start,ushort length)
        { 
        //拼接报文,发送报文,接受报文,校验报文,解析报文

            //创建一个字节集合
            List<byte> ret = new List<byte>();

            //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC

            //站地址
            ret.Add(devAdd);

            //功能码
            ret.Add(0x03);
 

            //起始寄存器地址
            //高位地址
            ret.Add((byte)(start / 256));
            //低位地址
            ret.Add((byte)(start % 256));


            //寄存器数量
            //高位地址
            ret.Add((byte)(length / 256));
            //低位地址
            ret.Add((byte)(length % 256));





            byte[] crc= CRCCalc(ret.ToArray());
            ret.AddRange(crc);





            //发送报文
            serialPort.Write(ret.ToArray(), 0, ret.Count);

            Thread.Sleep(50);
            
            //接受长度
            int byteCount = serialPort.BytesToRead;

            byte[] data = new byte[byteCount];

            //读入data
            serialPort.Read(data, 0, data.Length);

            byte[] result = new byte[length*2]; 
        
            Array.Copy(data,3,result, 0,length*2);

            return result;
        
        }

        #region 16位CRC校验
        /// <summary>
        /// CRC校验,参数data为byte数组
        /// </summary>
        /// <param name="data">校验数据,字节数组</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(byte[] data)
        {
            //crc计算赋初始值
            int crc = 0xffff;
            for (int i = 0; i < data.Length; i++)
            {
                crc = crc ^ data[i];
                for (int j = 0; j < 8; j++)
                {
                    int temp;
                    temp = crc & 1;
                    crc = crc >> 1;
                    crc = crc & 0x7fff;
                    if (temp == 1)
                    {
                        crc = crc ^ 0xa001;
                    }
                    crc = crc & 0xffff;
                }
            }
            //CRC寄存器的高低位进行互换
            byte[] crc16 = new byte[2];
            //CRC寄存器的高8位变成低8位,
            crc16[1] = (byte)((crc >> 8) & 0xff);
            //CRC寄存器的低8位变成高8位
            crc16[0] = (byte)(crc & 0xff);
            return crc16;
        }

        /// <summary>
        /// CRC校验,参数为空格或逗号间隔的字符串
        /// </summary>
        /// <param name="data">校验数据,逗号或空格间隔的16进制字符串(带有0x或0X也可以),逗号与空格不能混用</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(string data)
        {
            //分隔符是空格还是逗号进行分类,并去除输入字符串中的多余空格
            IEnumerable<string> datac = data.Contains(",") ? data.Replace(" ", "").Replace("0x", "").Replace("0X", "").Trim().Split(',') : data.Replace("0x", "").Replace("0X", "").Split(' ').ToList().Where(u => u != "");
            List<byte> bytedata = new List<byte>();
            foreach (string str in datac)
            {
                bytedata.Add(byte.Parse(str, System.Globalization.NumberStyles.AllowHexSpecifier));
            }
            byte[] crcbuf = bytedata.ToArray();
            //crc计算赋初始值
            return CRCCalc(crcbuf);
        }


        /// <summary>
        ///  CRC校验,截取data中的一段进行CRC16校验
        /// </summary>
        /// <param name="data">校验数据,字节数组</param>
        /// <param name="offset">从头开始偏移几个byte</param>
        /// <param name="length">偏移后取几个字节byte</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(byte[] data, int offset, int length)
        {
            byte[] Tdata = data.Skip(offset).Take(length).ToArray();
            return CRCCalc(Tdata);
        }

        #endregion
    }
}

  1. 在窗体代码中进行调用:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using modBusDLL;

namespace easyProjectPractice
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            this.comboBox1.DataSource=SerialPort.GetPortNames();
        }

        private ModBusRTU modBusRTU = new ModBusRTU();



        private void label1_Click(object sender, EventArgs e)
        {

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            modBusRTU.Connect(this.comboBox1.Text);

            MessageBox.Show("连接成功");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            modBusRTU.Disconnect();
            MessageBox.Show("断开连接");
        }

        private void button3_Click(object sender, EventArgs e)
        {

            byte[] data = modBusRTU.ReadKeepRegisters(1, 2, 1);

       

            textBox1.Text = (data[0] * 256 + data[1]).ToString();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }
    }
}

  1. 最终可实现通过mdbusRTU协议进行主从站通信:
    在这里插入图片描述
    在这里插入图片描述

总结

简单的modbusRTU主从通信自定义。

标签:自定义,主站,通信协议,System,serialPort,crc,using,byte,data
From: https://blog.csdn.net/weixin_63243357/article/details/140253948

相关文章

  • 前端JS特效第22集:html5音乐旋律自定义交互特效
    html5音乐旋律自定义交互特效,先来看看效果:部分核心的代码如下(全部代码在文章末尾):<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>ChimeTime™</title><linkrel="stylesheet"href="css/style.css......
  • Simple WPF: WPF 自定义按钮外形
    最新内容优先发布于个人博客:小虎技术分享站,随后逐步搬运到博客园。WPF的按钮提供了Template模板,可以通过修改Template模板中的内容对按钮的样式进行自定义,完整代码Github自取。使用Style定义扁平化的按钮样式定义一个ButtonStyleDictonary.xaml资源字典文件,在ControlTemplate......
  • SpringSecurity简单自定义配置
    初学者对于学习SpringSecurity相关的一些简单自定义配置总结。由于自身能力并不能和大佬相比较,以下的一些内容有误或有可改进地方,希望指出,我抱有一颗谦虚好学的心保持热情,并感谢指正。实现案例:1.基于内存的用户认证2.基于数据库的用户认证3.添加用户(数据库)4.自定义密......
  • srpingboot 自定义 start
    自动配置工程绑定配置文件,上逼格的start都支持自定义配置,我们也装像点~~@ConfigurationProperties("cyrus.hello")publicclassCyrusHelloProperties{//绑定配置文件cyrus.hello.username属性privateStringusername;publicStringgetUsernam......
  • Android面试题自定义View之Window、ViewRootImpl和View的三大流程
    本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点View的三大流程指的是measure(测量)、layout(布局)、draw(绘制)。下面我们来分别看看这三大流程View的measure(测量)MeasureSpecMeasureSpec是View的一个内部静......
  • 微信小程序开发基础知识5----自定义组件
    一、组件的创建与引用1、创建组件①在项目的根目录中,鼠标右键,创建components->test文件夹②)在新建的components->test文件夹上,鼠标右键,点击“新建Component'③键入组件的名称之后回车,会自动生成组件对应的4个文件,后名分别为.js,json,.wxml和.wxss2、引用组件组......
  • C++初学者指南-3.自定义类型(第一部分)-异常
    C++初学者指南-3.自定义类型(第一部分)-异常文章目录C++初学者指南-3.自定义类型(第一部分)-异常简介什么是异常?第一个示例用途:报告违反规则的行为异常的替代方案标准库异常处理问题和保证资源泄露使用RAII避免内存泄漏!析构函数:不要让异常逃脱!异常保证无抛出异常保......
  • Windows防火墙 日志 自定义 以记录被丢弃的数据包和成功的连接日志。以下是一个示例.r
     配置注册表,以记录被丢弃的数据包和成功的连接日志 WindowsRegistryEditorVersion5.00;WindowsDefender防火墙日志记录设置[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy];以下是针对不同配置文件的设置,例如......
  • Spring Boot 自定义 starter 启动器
    前言:SpringBoot为我们提供了自动配置的功能,我们可以像使用插件一样,对各个组件自由组合装配,只需引入定义好的starter即可,有点类似于Java的SPI机制,SPI机制是为了解决项目与项目之间的解耦,而SpringBootstarter方式实现了模块化的解耦,前文我们从SpringBooot源......
  • IIC通信协议
    一、IIC(Inter-IntegratedCircuit)通信的基本概念:硬件结构:只需要两根线:SCL(串行时钟线)和SDA(串行数据线)可以连接多个设备,每个设备都有唯一的地址(7bit)通信原理:主从通信:一个主设备,多个从设备同步通信:由SCL线提供时钟信号串行、半双工:数据可以双向传输,但同一时刻只能单向基......