ModBus串口【02】读取输出线圈状态

本文详细介绍了Modbus协议中读取输出线圈状态的功能码01H,包括主站询问和从站响应报文格式,并提供了WinformUI设计和CRC校验算法的实现示例,以及串口通信的锁机制,帮助读者理解Modbus通信过程。
摘要由CSDN通过智能技术生成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2jPhlKN-1673782346472)(E:%5Cthinger%20%E8%B5%84%E6%96%99%5C%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E3%80%90%E8%BE%93%E5%87%BA%E3%80%91%5C%E5%B7%A5%E6%8E%A7%E9%80%9A%E8%AE%AF%E5%8D%8F%E8%AE%AE%5C%E8%AF%AD%E9%9B%80%E5%AF%BC%E5%87%BA%5CTypora_Picture%5C23cb971a7a01ca4089c4c189fc6d0f38.png)]

在破土而出的那刻,小草就做好了拼尽全力成长的准备,加油,永远热情向前!
本文主要介绍了Modbus读取输出线圈的状01H功能码的询问报文和响应报文的格式以及含义,也通过编码实现读取线圈状态示例,希望对你理解Modbus如何读取线圈状态有点帮助。欢迎点赞 + 关注,第一时间了解更多Modbus信息。

【2】知识点概括:

【1】掌握Modbus串口通信,读取输出线圈状态 01H功能码 主站询问报文和从站应答报文内容含义;

【2】使用winform 设计简易的UI,使用modbus slave工具模拟Modbus通讯;

【3】ModbusRTU类:串口对象MyCom,打开关闭串口方法,拼接报文,发送和接收报文,解析报文及异常处理;

【4】ByteArray类:字节数组类初始化,方法,目的是为了拼接报文方便;

【5】UI窗体:combox,button以及listview控件,窗体内容初始化,读取方法;

【6】其他:CRC校验算法,混合锁;

【3】原理介绍(BYI):

读取输出线圈 功能码:01H

主站询问报文格式:

从站地址功能码起始地址(高位)起始地址(低位)线圈数量(高位)线圈数量(低位)CRC
0x110x010x000x130x000x1BXXXX

主站询问报文格式:从站地址+功能码+起始地址(高位)+起始地址(低位)+线圈数量(高位)+线圈数量(低位)+CRC

从站应答报文格式:

从站地址功能码字节计数线圈状态20-27线圈状态28-35线圈状态36-43线圈状态44-46CRC
0x110x010x040xCD0x6B0xB20x05XXXX

从站应答报文格式:从站地址+功能码+字节计数+线圈状态(根据实际确定数量)+CRC校验

【4】应用场景介绍:

我们是通过模拟的方式进行的modbus串行链路上的通讯测试

实现的是线圈输出状态的读取和获得:

【5】具体示例(代码,图片,注释及应用实践注意事项)

//创建winform的窗体,涉及下拉框combox和button按钮,listview和imagelist的使用;

在这里插入图片描述

//【1】类库DAL中的类===>>>封装:

【1-1】ByteArray类:

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

namespace DAL
{
    //这是一组具有相同特征的类或类结构的集合;
    public class ByteArray
    {
        //这个字节数组类,我们主要是实现对获取字节数组的各种构造方法
        List<byte> listByte = null;

        //初始化字节数组集合
        public ByteArray() 
        {
            listByte = new List<byte>();
        }

        //添加单个字节
        public void Add(byte item)
        {
            listByte.Add(item);
        }

        //添加数组
        public void Add(byte[] item)
        {
            listByte.AddRange(item);
        }

        //清空
        public void Clear()
        {
            listByte = new List<byte>();
        }

        //获取数组
        public byte[] array
        {
            get { return listByte.ToArray(); }
        }

    }
}

【1-2】Common类:在公共类中我们定义了一个DataFormat数据顺序格式的枚举和一个混合锁;锁的作用:防止我们同步进行读写操作时会产生冲突;

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

namespace DAL
{
    //枚举类型
    public enum DataFormat
    {
        ABCD,
        BADC,
        CDAB,
        DCBA
    }

    #region 简单的混合锁
    
    /// <summary>
    /// 一个简单的混合线程同步锁,采用了基元用户加基元内核同步构造实现
    /// 我们使用锁:就是解决读写同步的矛盾问题
    /// </summary>
    public sealed class SimpleHybirdLock : IDisposable
    {

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。
                m_waiterLock.Close();

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~SimpleHybirdLock() {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion

        /// <summary>
        /// 基元用户模式构造同步锁
        /// </summary>
        private Int32 m_waiters = 0;
        /// <summary>
        /// 基元内核模式构造同步锁
        /// </summary>
        private AutoResetEvent m_waiterLock = new AutoResetEvent(false);

        /// <summary>
        /// 获取锁
        /// </summary>
        public void Enter()
        {
            if (Interlocked.Increment(ref m_waiters) == 1) return;//用户锁可以使用的时候,直接返回,第一次调用时发生
            //当发生锁竞争时,使用内核同步构造锁
            m_waiterLock.WaitOne();
        }

        /// <summary>
        /// 离开锁
        /// </summary>
        public void Leave()
        {
            if (Interlocked.Decrement(ref m_waiters) == 0) return;//没有可用的锁的时候
            m_waiterLock.Set();
        }

        /// <summary>
        /// 获取当前锁是否在等待当中
        /// </summary>
        public bool IsWaitting => m_waiters != 0;
    }
    #endregion

}

//【1-3】ModbusRTU类:这是ModbusRTU的类,我们通过new的方式可以创建ModbusRTU的对象;符合职责明确元素,对象的属性,方法应该单独放置在对象的类中;UI中如需要使用,创建对象进行调用即可。

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 DAL
{
    public class ModbusRTU
    {
        # region 01:对象或者属性
        //建立连接和断开连接
        //读取输出线圈,读取输入线圈.....

        //定义串口通信的对象
        private SerialPort MyCom = new SerialPort();
        //创建通信超时属性
        private int ReadTimeOut { get; set; } = 2000;
        public int WriteTimeOut { get; set; } = 2000;

        //读取返回报文超时时间
        public int RecTimeOut { get; set; } = 2000;
        //睡眠时间
        public int SleepTime { get; set; } = 20; //这个睡眠时间,20ms很重要,原来写的是2000ms,这样就会造成超时报错!

        //字节顺序
        public DataFormat DataFormat { get; set; } = DataFormat.ABCD;

        //创建一个互斥锁对象===>>>>后面我们发送报文的时候会使用这个互斥锁===>>>
        private SimpleHybirdLock InteractiveLock = new SimpleHybirdLock();

        #endregion

        #region 02:打开关闭串口
        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="iBaudRate"></param>
        /// <param name="iPortName"></param>
        /// <param name="iDataBits"></param>
        /// <param name="iParity"></param>
        /// <param name="iStopBits"></param>
        public void OpenMyCom(int iBaudRate,string iPortName,int iDataBits,Parity iParity,StopBits iStopBits)
        {
            if (MyCom.IsOpen)
            {
                MyCom.Close();
            }
            //串口通信对象实例化
            MyCom = new SerialPort(iPortName,iBaudRate,iParity,iDataBits,iStopBits);
            //设置超时时间
            MyCom.ReadTimeout = this.ReadTimeOut;
            MyCom.WriteTimeout = this.WriteTimeOut;
            //打开串口
            MyCom.Open();
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        public void CloseMyCom()
        {
            if (MyCom.IsOpen)
            {
                MyCom.Close();
            }
        }

        #endregion

        #region 03:读取输出线圈 功能码01H
        public byte[] ReadOutputStatus(int iDevAddress, int iniAddress, int iReadLength)
        {
            //第一步:拼接报文
            ByteArray SendCommand = new ByteArray();
            SendCommand.Add(new byte[] { (byte)iDevAddress, 0x01, (byte)(iniAddress / 256), (byte)(iniAddress % 256) });
            SendCommand.Add(new byte[] { (byte)(iReadLength / 256), (byte)(iReadLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));//【注意】:CRC校验,是将前面的6个字节的东西拿过来校验

            //第二步:发送和接收报文
            int sendByteLength = 0;
            if (iReadLength % 8 == 0)
            {
                sendByteLength = iReadLength / 8;
            }
            else
            {
                sendByteLength = iReadLength / 8 + 1;
            }

            byte[] responseByteArray = new byte[5 + sendByteLength];
            if (SendData(SendCommand.array, ref responseByteArray))
            {
                //第三步:解析报文: //验证:功能码+字节计数
                if (responseByteArray[1]==0x01 && responseByteArray[2]==sendByteLength)
                {
                    return GetByteArray(responseByteArray, 3, sendByteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion


        #region 通用发送报文并接受        
        /// <summary>
        /// 通用发送报文并接收
        /// </summary>
        /// <param name="sendByte"></param>
        /// <param name="responseByteArray"></param>
        /// <returns></returns>
        public bool SendData(byte[] sendByte ,ref byte[] responseByteArray)
        {
            InteractiveLock.Enter();
            try
            {
                //发送报文
                MyCom.Write(sendByte, 0, sendByte.Length);
                //定义一个Buffer
                byte[] buffer = new byte[1024];
                //定义一个内存
                MemoryStream ms = new MemoryStream();
                //定义读取开始时间:
                DateTime start = DateTime.Now;
                //【1】获取当前缓冲区的值,判断是否有值,如果有值,读取过来,放到内存中,接着再判断,如果当前缓冲区的值为0,说明读完了。
                //【2】如果每次读取都读不到,我们用超时时间来做判断;
                while (true)
                {
                    Thread.Sleep(SleepTime);
                    if (MyCom.BytesToRead >= 1)
                    {
                        int spcount = MyCom.Read(buffer, 0, buffer.Length);
                        ms.Write(buffer, 0, spcount);
                    }
                    else
                    {
                        //判断是否超时
                        if ((DateTime.Now - start).TotalMilliseconds > this.RecTimeOut)
                        {
                            ms.Dispose();
                            return false;
                        }
                        else if (ms.Length > 0)
                        {
                            break;
                        }
                    }
                }
                responseByteArray = ms.ToArray();
                ms.Dispose();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                //解锁
                InteractiveLock.Leave();
            }
        }

        #endregion

        #region 自定义截取字节数组
        /// <summary>
        /// 自定义截取字节数组
        /// </summary>
        /// <param name="dest">目标字节数组</param>
        /// <param name="offset">偏移量</param>
        /// <param name="count">数量</param>
        /// <returns></returns>
        private byte[] GetByteArray(byte[] dest,int offset,int count)
        {
            byte[] res = new byte[count];
            if (dest != null && dest.Length >= offset + count)
            {
                for (int i = 0; i < count; i++)
                {
                    res[i] = dest[offset + i];
                }
                return res;
            }
            else
            {
                return null;
            }    
        }


        #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 };

            UInt16 iIndex = 0x0000;

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

        #endregion


    }

}

//【2】、窗体代码:

using DAL;
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.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Modbus01Practice
{
    public enum StoreArea
    {
        输出线圈0x,
        输入状态1x,
        输出寄存器4x,
        输入寄存器3x
    }

    public enum VarType
    {
        Bit,
        Short,
        UShort,
        Int,
        UInt,
        Float
    }
    
    public partial class FrmModbusRTU : Form
    {
        public FrmModbusRTU()
        {
            InitializeComponent();
            this.Load += FrmodbusRTU_Load;
        }
		//添加引用后才能创建:
        private ModbusRTU objModbus = new ModbusRTU();

        //系统当前时间
        private string CurrentTime
        {
            get { return DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); }
        }

        //创建连接标志位
        private bool IsConnected = false;


     #region Load初始化
         
        private void FrmodbusRTU_Load(object sender, EventArgs e)
         {
                //初始化控件 
                lst_Info.Columns[1].Width = lst_Info.ClientSize.Width - lst_Info.Columns[0].Width;

                //端口号
                string[] PortList = SerialPort.GetPortNames();
                if (PortList.Length > 0)
                {
                    this.cmb_Port.Items.AddRange(PortList);
                    this.cmb_Port.SelectedIndex = 0;
                }
         
                //波特率
                this.cmb_Paud.DataSource = new string[] { "2400", "4800", "9600", "19200", "38400" };
                this.cmb_Paud.SelectedIndex = 2;

                //校验位
                this.cmb_Parity.DataSource = Enum.GetNames(typeof(Parity));

                //停止位
                this.cmb_StopBits.DataSource = Enum.GetNames(typeof(StopBits));
                this.cmb_StopBits.SelectedIndex = 1;

                //数据格式
                this.cmb_DataFormat.DataSource = Enum.GetNames(typeof(DataFormat));

                //存储区
                this.cmb_StoreArea.DataSource = Enum.GetNames(typeof(StoreArea));

                //数据类型==初始化,进行绑定
                this.cmb_VarType.DataSource = Enum.GetNames(typeof(VarType));
            }

     #endregion

         
     #region 建立连接和断开连接
         private void btn_Connect_Click(object sender, EventArgs e)
            {
                if (IsConnected)
                {
                    AddLog(1, "ModbusRTU已经连接,请勿重复连接");
                    return;
                }
                //如果没有连接,则建立连接
                try
                {
                    objModbus.OpenMyCom(int.Parse(this.cmb_Paud.Text.Trim()), this.cmb_Port.Text.Trim(), int.Parse(this.txt_DataBits.Text.Trim()), (Parity)(Enum.Parse(typeof(Parity), this.cmb_Parity.SelectedItem.ToString(), false)), (StopBits)(Enum.Parse(typeof(StopBits), this.cmb_StopBits.SelectedItem.ToString(), false)));
                }
                catch (Exception ex)
                {
                    IsConnected = false;
                    AddLog(1,"ModbusRTU连接失败"+ex.Message);
                    return;
                }
                IsConnected = true;
                AddLog(0,"ModbusRTU连接成功");
          }

        private void btn_DisConn_Click(object sender, EventArgs e)
        {
            objModbus.CloseMyCom();
            IsConnected = false;
            AddLog(0,"断开连接ModbusRTU");
        }

        #endregion

        #region 通用读取方法
        private void btn_Read_Click(object sender, EventArgs e)
        {
            if (!IsConnected)
            {
                AddLog(1,"请先检查与Modbus从站之间的连接");
                return;
            }
            //UI读写测试中,我们对应UI中的属性
            ushort DevAdd = 0;
            ushort Address = 0;
            ushort Length = 0;

            if (!ushort.TryParse(this.txt_SlaveAdd.Text.Trim(),out DevAdd))
            {
                AddLog(1,"写入失败,从站地址必须为正整数");
                return;
            }

            if (!ushort.TryParse(this.txt_Variable.Text.Trim(), out Address))
            {
                AddLog(1, "写入失败,起始地址必须为正整数");
                return;
            }

            if (!ushort.TryParse(this.txt_Length.Text.Trim(), out Length))
            {
                AddLog(1, "写入失败,读取长度必须为正整数");
                return;
            }

            //获取选择的变量类型
            ValueType varType = (VarType)(Enum.Parse(typeof(VarType),this.cmb_VarType.SelectedItem.ToString(),false));
            //获取选择的存储区
            StoreArea storeArea = (StoreArea)(Enum.Parse(typeof(StoreArea),this.cmb_StoreArea.SelectedItem.ToString(),false));
            //创建字节数组
            byte[] result = null;
            switch (varType)
            {
                case VarType.Bit:
                    switch (storeArea)
                    {
                        case StoreArea.输出线圈0x:
                            result = objModbus.ReadOutputStatus(DevAdd, Address, Length);
                            break;
                        case StoreArea.输入状态1x:
                        case StoreArea.输出寄存器4x:
                        case StoreArea.输入寄存器3x:
                            AddLog(1,"读取失败,存储区类型不正确");
                            return;
                    }
                    string binarystring = string.Empty;
                    if (result != null)
                    {
                        foreach (var item in result)
                        {
                            char[] c = Convert.ToString(item, 2).PadLeft(8, '0').ToCharArray();
                            Array.Reverse(c);
                            binarystring += new string(c);
                        }
                        AddLog(0, "读取成功,结果为:"+binarystring.Substring(0,Length));
                    }
                    else
                    {
                        AddLog(1,"读取失败,请检查地址、类型或者连接状态");
                    }
                    break;
                default:
                    break;
            }
        }

        #endregion

            
        #region 写入日志的通用方法
        /// <summary>
        /// 写入日志的通用方法
        /// </summary>
        /// <param name="type">我们日志的类型</param>
        /// <param name="info">日志的具体信息</param>
        private void AddLog(int type, string info)
            {
                ListViewItem lst = new ListViewItem("  " + CurrentTime, type);
                lst.SubItems.Add(info);
                lst_Info.Items.Insert(0, lst);
            }

     #endregion


     #region 修改字节顺序
        //数据格式下拉框的选择时改变事件方法:
            private void cmb_DataFormat_SelectedIndexChanged(object sender, EventArgs e)
            {
                if (objModbus != null)
                {
                    objModbus.DataFormat = (DataFormat)(Enum.Parse(typeof(DataFormat), this.cmb_DataFormat.SelectedItem.ToString(), false));
                }
            }

     #endregion

    }

}

//【3】连接读取输出线圈的结果:
在这里插入图片描述

【6】总结

(1)原理:清楚modbus主站询问报文格式和从站应答报文格式内容含义很重要;

(2)职责明确:公共类,ModbusRTU类,ByteArray类,通用方法,UI各司其职,需要时就调用,保证了逻辑清晰,可扩展;

(3)对象的应用:SerialPort串口对象及属性、方法,ModbusRTU对象的成员等是如何运用;

(4)零碎知识点:读写冲突加锁,CRC校验,load初始化,超时验证,数据格式大小端等知识点;

作者:小羊,软件开发/技术写作者,欢迎关注,我们一起探讨有趣的编程历程。
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昆腾码头羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值