当前位置: 首页 > news >正文

Modbus协议基础

文章目录

    • 1、Modbus协议基础知识
      • 1.1、Modbus存储范围
      • 1.2、Modbus协议功能码说明
      • 1.3、Modbus协议分类及测试
    • 2、ModbusRTU通信报文分析
      • 2.1、modbusRTU通信格式
    • 3、Modbus通信库开发
    • 4、通信库测试

1、Modbus协议基础知识

1.1、Modbus存储范围

modbus规定,每个存储区域的最大范围是65536。plc地址
西门子:MW100、DBI、DBD0,
三菱:D0、X0、Y0
绝对地址=区号+相对地址
Modbus地址=区号+(相对地址+1)
输出寄存器的第一个绝对地址是40001
地址模型:长地址模型、短地址模型
相对地址和绝对地址:说明文档用绝对地址、协议报文用相对地址。

1.2、Modbus协议功能码说明

协议的目的是传输数据,已经确定好了存储区,存储区不同的数据类型,那么必然会产生不同的行为方式,我们给每种行为制定一个代号,那么这个代号就是功能码,功能码其实就是行为的代号。

读取输入线圈、读取输出线圈、读取输入寄存器、读取输出寄存器、写入输出线圈、写入输入线圈

0x01 读取输入线圈
0x02 读取输出线圈
0x03 读取输出寄存器
0x04 读取输入寄存器
0x05 写入单个线圈
0x06 写入单个寄存器
0x0F 写入多个线圈
0x10 写入多个寄存器

1.3、Modbus协议分类及测试

报文帧 modbusRTU、modbusASCII、modbusTCP
通信介质 串口通信 232/485/422、以太网通信 TCP/IP UDP/IP
协议分类 modbusRTU协议、modbusASCII协议、modbusRTUOverTCP、modbusRTUOverTCP、modbusASCIIOverTCP、modbusASCIIOverUDP、modbusTCP协议、modbusUDP协议

2、ModbusRTU通信报文分析

2.1、modbusRTU通信格式

报文帧格式(字节):
从站地址(1):要和那个设备通信
功能码(1):要做什么
数据部分(N):读取发送:开始地址、读取数量。读取接收:字节计数、具体数据。写入单发送:字节计数、具体数据。写入多发送:开始地址、写入数量、写入数据。写入多接收:开始地址、写入数量。
校验部分(2):CRC16

校验部分: 这里的校验和串口的奇校验/偶校验是没有关系的,校验是为了保证数据的准确,校验的本质是算法。

01功能码:
发送格式:从站地址+功能码+开始线圈地址+线圈数量+CRC
接收报文格式:从站地址+功能码+字节计数+数据+CRC

读取线圈测试:读取1号站点从10开始的20个线圈的值
发送报文:01 01 00 0A 00 14 1C 07
接收报文:01 01 03 03 00 00 CC 4E

02功能码:
发送格式:从站地址+功能码+开始线圈地址+线圈数量+CRC
接收报文格式:从站地址+功能码+字节计数+数据+CRC
读取输入线圈:读取5号站点从20开始的10个线圈
发送报文 :05 02 00 14 00 0A B9 8D
接收报文:05 02 02 03 00 48 88

03功能码:
发送格式:从站地址+功能码+开始寄存器地址+寄存器数量+CRC
接收报文格式:从站地址+功能码+字节计数+数据+CRC
地区输出寄存器:读取2号站点从10开始的4个寄存器
CRC16MODBUS校验
发送报文 02 03 00 0A 00 04 64 38
接收报文 02 03 08 00 01 00 02 00 03 00 04 02 50

04功能码:
发送格式:从站地址+功能码+开始寄存器地址+寄存器数量+CRC
接收报文格式:从站地址+功能码+字节计数+数据+CRC
地区输出寄存器:读取2号站点从10开始的4个寄存器
发送报文:02 04 00 0A 00 04 64 38
接收报文:02 04 08 00 01 00 02 00 03 00 04 B3 8A

05功能码:
发送报文格式:从站地址+功能码+线圈地址+线圈的值+CRC
接收报文格式:从站地址+功能码+线圈地址+线圈的值+CRC
预置单线圈:将2号线圈点的05地址置位
发送报文格式:02 05 00 05 FF 00 9C 08
接收报文格式:02 05 00 05 FF 00 9C 08

06功能码:
发送报文格式:从站地址+功能码+寄存器地址+寄存器值+CRC
接收报文格式:从站地址+功能码+寄存器地址+寄存器值+CRC

3、Modbus通信库开发

创建windows窗体应用
在这里插入图片描述
通信库内容:
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ModbusRTULib
{
    public class ModbusRTU
    {
        #region 字段和属性
        //串口通信对象
        private SerialPort serialPort;
        /// <summary>
        /// 读取超时时间
        /// </summary>
        private int ReadTimeOut { set; get; } = 2000;
        /// <summary>
        /// 串口延迟时间
        /// </summary>
        private int DelayTime { set; get; } = 10;
        /// <summary>
        /// 最大写入时间
        /// </summary>
        private int MaxWriteTime { set; get; } = 5000;
        /// <summary>
        /// 写入超时时间
        /// </summary>
        private int WriteTimeOut { set; get; } = 2000;
        private bool dtrEnable = false;
        /// <summary>
        /// Dtr使能标志
        /// </summary>
        private bool DtrEnable
        {
            get { return dtrEnable; }
            set { dtrEnable = value;
                this.serialPort.DtrEnable = dtrEnable;
            }
        }
        private bool rtsEnable = false;
        /// <summary>
        /// Rts使能标志
        /// </summary>
        private bool RtsEnable
        {
            get { return rtsEnable; }
            set
            {
                rtsEnable = value;
                this.serialPort.RtsEnable = rtsEnable;
            }
        }
        #endregion
        #region 构造方法
        //构造方法
        public ModbusRTU()
        {
            serialPort = new SerialPort();
        }
        #endregion
        #region 连接方法
        /// <summary>
        /// 建立连接
        /// </summary> n    
        /// <param name="portName">串口号</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">校验位</param>
        /// <param name="dataBis">数据位</param>
        /// <param name="stopBits">停止位</param>
        /// <returns></returns>

        public bool Connect(String portName,int baudRate=9600,Parity parity=Parity.None,int dateBits=8,StopBits stopBits = StopBits.One)
        {
            //如果窗口打开就关闭
            if (serialPort != null && serialPort.IsOpen)
            {
                serialPort.Close();
            }
            //参数设置
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate;
            serialPort.Parity = parity;
            serialPort.DataBits = dateBits;
            serialPort.StopBits = stopBits;

            serialPort.ReadTimeout = this.ReadTimeOut;
            serialPort.WriteTimeout = this.WriteTimeOut;
            try
            {
                serialPort.Open();
            }catch(Exception)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            //如果窗口打开就关闭
            if (serialPort != null && serialPort.IsOpen)
            {
                serialPort.Close();
            }
        }
        #endregion
        #region 01H读取输出线圈
        /// <summary>
        /// 读取输出线圈
        /// </summary>
        /// <param name="slaveId">站地址</param>
        /// <param name="start">起始地址</param>
        /// <param name="length">读取长度</param>
        /// <returns></returns>
        public byte[] ReadOutputCoils(byte slaveId,ushort start,ushort length)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x01);

            SendCommand.Add(((byte)(start/256)));
            SendCommand.Add(((byte)(start%256)));

            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));

            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            int byteLength = length % 8 == 0 ? length / 8 : length / 8 + 1;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 5 + byteLength && receive[2] == byteLength)
                {
                    if (receive[0] == slaveId && receive[1] == 0x01)
                    {
                        //5 解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 3, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion
        #region 02H读取输入线圈
        /// <summary>
        /// 读取输入线圈
        /// </summary>
        /// <param name="slaveId">站地址</param>
        /// <param name="start">起始地址</param>
        /// <param name="length">读取长度</param>
        /// <returns></returns>
        public byte[] ReadInputCoils(byte slaveId, ushort start, ushort length)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x02);

            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));

            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            int byteLength = length % 8 == 0 ? length / 8 : length / 8 + 1;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 5 + byteLength&& receive[2] == byteLength)
                {
                    if (receive[0] == slaveId && receive[1] == 0x02)
                    {
                        //5 解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 3, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion
        #region 03H读取输出寄存
        public byte[] ReadOutputRegisters(byte slaveId,ushort start,ushort length)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x03);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));

            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            int byteLength = length * 2;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 5 + byteLength)
                {
                    if (receive[0] == slaveId && receive[1] == 0x03 && receive[2] == byteLength)
                    {
                        //5 解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 3, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion
        #region 04H读取输如寄存
        public byte[] ReadInputRegisters(byte slaveId, ushort start, ushort length)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x04);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add((byte)(length / 256));
            SendCommand.Add((byte)(length % 256));

            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            int byteLength = length * 2;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 5 + byteLength)
                {
                    if (receive[0] == slaveId && receive[1] == 0x04 && receive[2] == byteLength)
                    {
                        //5 解析报文
                        byte[] result = new byte[byteLength];
                        Array.Copy(receive, 3, result, 0, byteLength);
                        return result;
                    }
                }
            }
            return null;
        }
        #endregion
        #region 05H预置单线圈
        public bool PreSetSingleCoil(byte slaveId, ushort start, bool value)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x05);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add(value?(byte)0xFF:(byte)0x00);
            SendCommand.Add(0x00);

            SendCommand.AddRange(CRC16(SendCommand.ToArray(),SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 8)
                {
                    return ByteArrayEquals(SendCommand.ToArray(), receive);
                }
            }
            return false;
        }
        #endregion
        #region 06H预置寄存器
        /// <summary>
        /// 06H预置寄存器
        /// </summary>
        /// <param name="slaveId">站地址</param>
        /// <param name="start">起始地址</param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool PreSetSingleRegister(byte slaveId, ushort start, byte[] value)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();
            SendCommand.Add(slaveId);
            SendCommand.Add(0x06);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.AddRange(value);

            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));
            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 8)
                {
                    return ByteArrayEquals(SendCommand.ToArray(), receive);
                }
            }
            return false;
        }
        public bool PreSetSingleRegister(byte slaveId,ushort start,short value)
        {
            return PreSetSingleRegister(slaveId, start, BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public bool PreSetSingleRegister(byte slaveId, ushort start, ushort value)
        {
            return PreSetSingleRegister(slaveId, start, BitConverter.GetBytes(value).Reverse().ToArray());
        }
        #endregion
        #region 0FH预置多线圈
        public bool PreSetManyCoil(byte slaveId, ushort start, bool[] value)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            List<byte> SendCommand = new List<byte>();

            byte[] setArray = GetByteArrayFromBoolArray(value);

            SendCommand.Add(slaveId);
            SendCommand.Add(0x0F);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add(((byte)(value.Length / 256)));
            SendCommand.Add(((byte)(value.Length % 256)));

            SendCommand.Add((byte)setArray.Length);
            SendCommand.AddRange(setArray);
            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));

            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 8)
                {
                    for(int i = 0; i < 6; i++)
                    {
                        if (SendCommand[i] != receive[i])
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }
        #endregion
        #region 10H预置多寄存器
        public bool PreSetMutiRegister(byte slaveId, ushort start, byte[] value)
        {
            //1 拼接报文 从站地址+功能码+开始线圈地址+线圈数量+CRC
            if (value == null || value.Length == 0 || value.Length % 2 == 1)
            {
                return false;
            }
            List<byte> SendCommand = new List<byte>();

            int registerLength = value.Length / 2;

            SendCommand.Add(slaveId);
            SendCommand.Add(0x10);
            SendCommand.Add(((byte)(start / 256)));
            SendCommand.Add(((byte)(start % 256)));

            SendCommand.Add(((byte)(registerLength / 256)));
            SendCommand.Add(((byte)(registerLength % 256)));

            SendCommand.Add(((byte)value.Length));
            SendCommand.AddRange(value);
            SendCommand.AddRange(CRC16(SendCommand.ToArray(), SendCommand.Count));

            //2 发送报文

            //3 接收报文
            byte[] receive = null;
            if (SendAndReceive(SendCommand.ToArray(), ref receive))
            {
                //4 验证报文
                if (CheckCRC(receive) && receive.Length == 8)
                {
                    for (int i = 0; i < 6; i++)
                    {
                        if (SendCommand[i] != receive[i])
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }
        #endregion
        #region 发送和接受报文
        private bool SendAndReceive(byte[] send,ref byte[] receive)
        {
            SimpleHybirdLock hybirdLock = new SimpleHybirdLock();
            //加锁
            hybirdLock.Enter();
            try
            {
                //发送报文
                serialPort.Write(send, 0, send.Length);
                //定义一个buffer缓冲区
                byte[] buffer = new byte[1024];
                //定义一个内存
                MemoryStream stream = new MemoryStream();
                //定义一个开始时间
                DateTime start = DateTime.Now;
                //为了一次性读取不完整,需要循环读取

                while (true)
                {
                    Thread.Sleep(DelayTime);//延迟一段时间再读取
                    if (this.serialPort.BytesToRead > 0)
                    {
                        int count = serialPort.Read(buffer, 0, buffer.Length);//读取到缓冲区
                        stream.Write(buffer, 0, count);//缓冲区写入到内存
                    }
                    else
                    {
                        if (stream.Length > 0)
                        {
                            break;
                        }
                        else if ((DateTime.Now - start).TotalMilliseconds > MaxWriteTime)
                        {
                            return false;
                        }
                    }
                }
                receive = stream.ToArray();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                //解锁
                hybirdLock.Leave();
            }
          
        }
        #endregion
        #region 简单的混合锁
        public sealed class SimpleHybirdLock : IDisposable
        {
            private bool disposedValue = false;
            void Dispose(bool disposting)
            {
                if (!disposedValue)
                {
                    if (disposedValue)
                    {

                    }
                    m_waiterLock.Close();
                    disposedValue = true;
                }
            }
            public void Dispose()
            {
                Dispose(true);
            }
            private Int32 m_waiters = 0;
            private AutoResetEvent m_waiterLock = new AutoResetEvent(false);
            public void Enter()
            {
                if (Interlocked.Increment(ref m_waiters) == 1) return;
                m_waiterLock.WaitOne();
            }
            public void Leave()
            {
                if (Interlocked.Decrement(ref m_waiters) == 0) return;
                m_waiterLock.Set();
            }
            public bool IsWaitting => m_waiters == 0;
        }
        #endregion
        #region CRC校验
        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
        };

        private byte[] CRC16(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort index;

            while (usLen-- > 0)
            {
                index = (ushort)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[index]);
                res[1] = aucCRCLo[index];
            }

            return res;
        }

        private bool CheckCRC(byte[] value)
        {
            if (value == null) return false;
            if (value.Length <= 2) return false;

            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])
                return true;

            return false;
        }

        #endregion
        #region 数组比较方法
        private bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return false;
            if (b1.Length != b2.Length) return false;
            for(int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i]) return false;
            }
            return true;
        }
        #endregion
        #region 将布尔数组转换为字节数组
        private byte[] GetByteArrayFromBoolArray(bool[] value)
        {
            int byteLength = value.Length % 8 == 0 ? value.Length / 8 : value.Length / 8 + 1;
            byte[] result = new byte[byteLength];
            for(int i = 0; i < result.Length; i++)
            {
                //获取每个字节值
                int total = value.Length < 8 * (i + 1) ? value.Length - 8 * i : value.Length / 8 + 1;
                for(int j = 0; j < total; j++)
                {
                    result[i] = SetBitValue(result[i], j, value[8 * i + j]);
                }
            }
            return result;
        }
        private byte SetBitValue(byte src,int bit ,bool value)
        {
            return value ? (byte)(src | (byte)Math.Pow(2, bit)) : (byte)(src & ~(byte)Math.Pow(2, bit));
        }
        #endregion
    }
}

4、通信库测试

测试页面
在这里插入图片描述

主要方法
在这里插入图片描述

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 ModbusRTULib;
using thinger.DataConvertLib;

namespace Modbus_project
{
    public partial class Modbus_ui : Form
    {
        public enum StoreArea
        {
            输出线圈0x,
            输入线圈1x,
            输出寄存器4x,
            输入寄存器3x
        }
        public Modbus_ui()
        {
            InitializeComponent();
            init();
        }
        //Modbus通信对象
        private ModbusRTU modBus = new ModbusRTU();
        //当前连接状态
        private bool isConnected = false;
        private DataFormat dataFormat = DataFormat.ABCD;
        #region 初始化
        /// <summary>
        /// 初始化参数
        /// </summary>
        private void init()
        {
            //获取本机的端口列表
            String[] portList = SerialPort.GetPortNames();
            if (portList.Length > 0)
            {
                this.port_comboBox.Items.AddRange(portList);
                this.port_comboBox.SelectedIndex = 0;
            }
            //波特率
            this.rate_comboBox.Items.AddRange(new String[] { "2400", "4800", "9600", "19200", "38400" });
            this.rate_comboBox.SelectedIndex = 2;

            //校验位
            this.check_comboBox.DataSource = Enum.GetNames(typeof(Parity));//enum提供枚举的基类,typeof运算符用于获取某个类型的 System.Type 实例。 typeof 运算符的实参必须是类型或类型形参的名称
            this.check_comboBox.SelectedIndex = 0;
            //停止位
            this.stop_comboBox.DataSource = Enum.GetNames(typeof(StopBits));
            this.stop_comboBox.SelectedIndex = 1;

            //数据位
            this.data_comboBox.Items.AddRange(new string[] { "7", "8" });
            this.data_comboBox.SelectedIndex = 1;

            //大小端枚举
            this.big_comboBox.DataSource = Enum.GetNames(typeof(DataFormat));
            this.big_comboBox.SelectedIndex = 0;

            //存储区
            this.store_comboBox.DataSource = Enum.GetNames(typeof(StoreArea));
            this.big_comboBox.SelectedIndex = 0;

            //数据类型
            this.datatype_comboBox.DataSource = Enum.GetNames(typeof(DataType));
            this.datatype_comboBox.SelectedIndex = 0;

            //动态修改listview第二列宽度
            this.read_listView.Columns[1].Width = this.read_listView.Width - this.read_listView.Columns[0].Width - 20;

        }
        #endregion
        #region 建立连接 & 断开连接
        private void connect_button_Click(object sender, EventArgs e)
        {
            if (isConnected)
            {
                AddLog(1,"modbus已经连接");
                return;
            }
            Parity parity = (Parity)Enum.Parse(typeof(Parity), this.check_comboBox.Text, true);
            StopBits stopBits = (StopBits)Enum.Parse(typeof(StopBits), this.stop_comboBox.Text, true);

            isConnected = modBus.Connect(this.port_comboBox.Text, Convert.ToInt32(this.rate_comboBox.Text), parity, Convert.ToInt32(this.data_comboBox.Text), stopBits);
            if (isConnected)
            {
                AddLog(0, "modbus连接成功");
            }
            else
            {
                AddLog(2, "modbus连接失败");
            }
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void close_button_Click(object sender, EventArgs e)
        {
            modBus.DisConnect();
            isConnected = false;
            AddLog(0, "modbus断开连接");
        }
        #endregion
        #region 通用日志
        /// <summary>
        /// 通用日志方法
        /// </summary>
        /// <param name="level"></param>
        /// <param name="info"></param>
        private void AddLog(int level,string info)
        {
            ListViewItem lst = new ListViewItem("  "+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), level);
            lst.SubItems.Add(info);
            //让最新的数据在上面
            this.read_listView.Items.Insert(0, lst);
        }
        #endregion
        #region 读取数据
        private void read_button1(object sender, EventArgs e)
        {
            if (commentVerify())
            {
                byte bvId = byte.Parse(this.slave_textBox.Text.Trim());
                ushort start = ushort.Parse(this.start_textBox.Text.Trim());
                ushort length = ushort.Parse(this.length_textBox.Text.Trim());
                //enum提供枚举的基类
                DataType dataType = (DataType)Enum.Parse(typeof(DataType), this.datatype_comboBox.Text, true);
                StoreArea storeArea = (StoreArea)Enum.Parse(typeof(StoreArea), this.store_comboBox.Text, true);
                dataFormat = (DataFormat)Enum.Parse(typeof(DataFormat), this.big_comboBox.Text, true);

                switch (dataType)
                {
                    case DataType.Bool:
                        ReadBool(storeArea, bvId, start, length);
                        break;
                    case DataType.Byte:
                        ReadByte(storeArea, bvId, start, length);
                        break;
                    case DataType.Short:
                        ReadShort(storeArea, bvId, start, length);
                        break;
                    case DataType.UShort:
                        ReadUShort(storeArea, bvId, start, length);
                        break;
                    case DataType.Int:
                        ReadInt(storeArea, bvId, start, length);
                        break;
                    case DataType.UInt:
                        ReadUInt(storeArea, bvId, start, length);
                        break;
                    case DataType.Float:
                        ReadFloat(storeArea, bvId, start, length);
                        break;
                    case DataType.Double:
                    case DataType.Long:
                    case DataType.ULong:
                    case DataType.String:
                    case DataType.ByteArray:
                    case DataType.HexString:
                        AddLog(1, "读取失败,暂时不支持");
                        return;
                    default:
                        break;
                }
            }
        }
        #endregion
        #region 读取bool
        private void ReadBool(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                    result = modBus.ReadOutputCoils(devId, start, length);
                    break;
                case StoreArea.输入线圈1x:
                    result = modBus.ReadInputCoils(devId, start, length);
                    break;
                case StoreArea.输出寄存器4x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "读取失败不支持该寄存器");
                    return;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(BitLib.GetBitArrayFromByteArray(result, 0, length)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取byte
        private void ReadByte(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                    result = modBus.ReadOutputCoils(devId, start, length);
                    break;
                case StoreArea.输入线圈1x:
                    result = modBus.ReadInputCoils(devId, start, length);
                    break;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, length);
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, length);
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(result));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取short
        private void ReadShort(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                    AddLog(1, "读取失败,不支持该存储区");
                    return;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, length);
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, length);
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(ShortLib.GetShortArrayFromByteArray(result, this.dataFormat)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取ushort
        private void ReadUShort(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                    AddLog(1, "读取失败,不支持该存储区");
                    return;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, length);
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, length);
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(UShortLib.GetUShortArrayFromByteArray(result, this.dataFormat)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取int
        private void ReadInt(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                    AddLog(1, "读取失败,不支持该存储区");
                    return;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, (ushort)(length * 2));
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, (ushort)(length * 2));
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(IntLib.GetIntArrayFromByteArray(result, this.dataFormat)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取uint
        private void ReadUInt(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                    AddLog(1, "读取失败,不支持该存储区");
                    return;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, (ushort)(length * 2));
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, (ushort)(length * 2));
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(UIntLib.GetUIntArrayFromByteArray(result, this.dataFormat)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion
        #region 读取float
        private void ReadFloat(StoreArea storeArea, byte devId, ushort start, ushort length)
        {
            byte[] result = null;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                    AddLog(1, "读取失败,不支持该存储区");
                    return;
                case StoreArea.输出寄存器4x:
                    result = modBus.ReadOutputRegisters(devId, start, (ushort)(length * 2));
                    break;
                case StoreArea.输入寄存器3x:
                    result = modBus.ReadInputRegisters(devId, start, (ushort)(length * 2));
                    break;
            }
            if (result != null)
            {
                AddLog(0, "读取成功:" + StringLib.GetStringFromValueArray(FloatLib.GetFloatArrayFromByteArray(result, this.dataFormat)));
            }
            else
            {
                AddLog(1, "读取失败,请检查参数");
            }
        }
        #endregion

        #region 写入数据
        private void write_button1(object sender, EventArgs e)
        {
            if (commentVerify())
            {
                byte bvId = byte.Parse(this.slave_textBox.Text.Trim());
                ushort start = ushort.Parse(this.start_textBox.Text.Trim());
                //enum提供枚举的基类
                DataType dataType = (DataType)Enum.Parse(typeof(DataType), this.datatype_comboBox.Text, true);
                StoreArea storeArea = (StoreArea)Enum.Parse(typeof(StoreArea), this.store_comboBox.Text, true);

                string setValue = this.write_textBox.Text.Trim();

                dataFormat = (DataFormat)Enum.Parse(typeof(DataFormat), this.big_comboBox.Text, true);

                switch (dataType)
                {
                    case DataType.Bool:
                        WriteBool(storeArea, bvId, start, setValue);
                        break;
                    case DataType.Byte:
                        WriteByte(storeArea, bvId, start, setValue);
                        break;
                    case DataType.Short:
                        WriteShort(storeArea, bvId, start, setValue);
                        break;
                    case DataType.UShort:
                        WriteUShort(storeArea, bvId, start, setValue);
                        break;
                    case DataType.Int:
                        WriteInt(storeArea, bvId, start, setValue);
                        break;
                    case DataType.UInt:
                        WriteUInt(storeArea, bvId, start, setValue);
                        break;
                    case DataType.Float:
                        WriteFloat(storeArea, bvId, start, setValue);
                        break;
                    case DataType.Double:
                    case DataType.Long:
                    case DataType.ULong:
                    case DataType.String:
                    case DataType.ByteArray:
                    case DataType.HexString:
                        AddLog(1, "读取失败,暂时不支持");
                        return;
                    default:
                        break;
                }
            }
        }
        #endregion
        #region 写入bool
        private void WriteBool(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出线圈0x:
                    bool[] values = BitLib.GetBitArrayFromBitArrayString(setValue);
                    if (values.Length == 1)
                    {
                        result = modBus.PreSetSingleCoil(devId, start, values[0]);
                    }
                    else
                    {
                        result = modBus.PreSetManyCoil(devId, start, values);
                    }
                    break;
                case StoreArea.输入线圈1x:
                case StoreArea.输出寄存器4x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "读写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功" );
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入Byte
        private void WriteByte(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromHexString(setValue));
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入Short
        private void WriteShort(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    short[] values = ShortLib.GetShortArrayFromString(setValue);
                    if (values.Length == 1)
                    {
                        result = modBus.PreSetSingleRegister(devId, start, values[0]);
                    }
                    else
                    {
                        result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromShortArray(values));
                    }
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入UShort
        private void WriteUShort(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    ushort[] values = UShortLib.GetUShortArrayFromString(setValue);
                    if (values.Length == 1)
                    {
                        result = modBus.PreSetSingleRegister(devId, start, values[0]);
                    }
                    else
                    {
                        result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromUShortArray(values));
                    }
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入Int
        private void WriteInt(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    int[] values = IntLib.GetIntArrayFromString(setValue);
                   result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromIntArray(values));
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入UInt
        private void WriteUInt(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    uint[] values = UIntLib.GetUIntArrayFromString(setValue);
                    result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromUIntArray(values));
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 写入Float
        private void WriteFloat(StoreArea storeArea, byte devId, ushort start, string setValue)
        {
            bool result = false;
            switch (storeArea)
            {
                case StoreArea.输出寄存器4x:
                    float[] values = FloatLib.GetFloatArrayFromString(setValue);
                    result = modBus.PreSetMutiRegister(devId, start, ByteArrayLib.GetByteArrayFromFloatArray(values));
                    break;
                case StoreArea.输出线圈0x:
                case StoreArea.输入线圈1x:
                case StoreArea.输入寄存器3x:
                    AddLog(1, "写入失败不支持该存储区");
                    return;
            }
            if (result)
            {
                AddLog(0, "写入成功");
            }
            else
            {
                AddLog(1, "写入失败");
            }
        }
        #endregion
        #region 通用验证方法
        private bool commentVerify()
        {
            if (isConnected==false)
            {
                AddLog(1, "请检查连接是否正常");
                return false;
            }
            if(byte.TryParse(this.slave_textBox.Text,out _) == false)
            {
                AddLog(1, "请检查站地址是否正常");
                return false;
            }
            if (ushort.TryParse(this.start_textBox.Text, out _) == false)
            {
                AddLog(1, "请检查起始地址是否正常");
                return false;
            }
            //将数字的字符串表示形式转换为其等效的 16 位无符号整数
            if (ushort.TryParse(this.length_textBox.Text, out _) == false)
            {
                AddLog(1, "请检查长度格式是否正常");
                return false;
            }
            return true;
        }
        #endregion
    }
}

一些注意:

  1. 过程中会用到thinger.DataConvertLib数据转换库
    在这里插入图片描述

  2. 在listview列表中,需要用imagelist来添加标签

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

相关文章:

  • AWS云从业者认证题库 AWS Cloud Practitioner(2.21)
  • 【练习】【回溯:组合:一个集合 元素可重复】力扣 39. 组合总和
  • 如何实现使用DeepSeek的CV模型对管道内模糊、低光照或水渍干扰的图像进行去噪、超分辨率重建。...
  • 推理模型时代:大语言模型如何从对话走向深度思考?
  • java后端开发day18--学生管理系统
  • 多门店协同管理困难重重,管理系统如何破局?
  • MySQL 中的回表是什么?MySQL 中使用索引一定有效吗?如何排查索引效果?在 MySQL 中建索引时需要注意哪些事项?
  • matlab 轮边驱动系统汽车垂向动力学分析
  • NVM是什么,以及NVM的作用?
  • 代码讲解系列-CV(六)——视觉生成模型
  • Unity学习笔记-Unity了解,安装,简单配置(一)
  • Trae AI驱动开发实战:30分钟从0到1实现Django REST天气服务
  • 论文解读 | AAAI'25 Cobra:多模态扩展的大型语言模型,以实现高效推理
  • 信号与系统研究
  • 纷析云开源版- Springboot-增加操作日志接口
  • python脚本实现接入企微机器人
  • Tomcat理论(Ⅰ)
  • 业务流程中的流程管理
  • 图表控件Aspose.Diagram入门教程:使用 Python 将 VSDX 转换为 PDF
  • 对免认证服务提供apikey验证
  • 山东大良网站建设/种子搜索神器在线引擎
  • 推广app网站/正规手游代理平台有哪些
  • 什么网站有做册子版/网络营销管理名词解释
  • 做ppt一般在什么网站/经典广告语
  • 学什么专业可以做网站/百度应用商店app下载
  • 网站关键词标签/seo优化服务公司