C# 開發Modbus Rtu客戶端 modbus測試Demo,Modbus 串口通訊 , 虛擬MODBUS-RTU測試

前言


 本文將使用一個NuGet公開的組件技術來實現一個ModBus RTU的客戶端,方便的對Modbus rtu的服務器進行讀寫,這個服務器能夠是電腦端C#設計的,也能夠是PLC實現的,也能夠是其餘任何支持這個通訊協議的服務器。html

github地址:https://github.com/dathlin/HslCommunication 若是喜歡能夠star或是fork,還能夠打賞支持。git

聯繫做者及加羣方式(激活碼在羣裏發放):http://www.hslcommunication.cn/Cooperationgithub

在Visual Studio 中的NuGet管理器中能夠下載安裝,也能夠直接在NuGet控制檯輸入下面的指令安裝:設計模式

Install-Package HslCommunication

NuGet安裝教程  http://www.cnblogs.com/dathlin/p/7705014.html數組

組件API地址:http://www.cnblogs.com/dathlin/p/7703805.html服務器

 

 

 

特別感謝


  • 網友:陳恩富                  對float,int數據的讀取測試,才修復了權重位顛倒的BUG。
  • 網友:U4幸福的蝸牛      發現了博客上錯誤的一個方法名稱,已於2018年1月8日13:34:39更新。並反饋了一些特殊設備(modbus tcp服務器)的讀取數據的BUG。已修復。

 

隨便聊聊


此處的設計模式是客戶端主動請求服務器數據,而後接收服務器的反饋數據,支持原生的指令收發,支持其餘一些方便的API收發。特殊功能碼須要使用原生收發的API,本組件支持以下的功能操做:tcp

  • 0x01    讀取線圈的操做,
  • 0x02    讀取離散的操做,
  • 0x03    讀取寄存器的值,
  • 0x05    寫一個線圈操做,
  • 0x06    寫一個寄存器值,
  • 0x0F    批量寫線圈操做,
  • 0x10    批量寫寄存器值,

 若是你的設備須要這些功能以外的數據,可使用原生API方法,可是這個方法的前提就是你對MODBUS 協議很是清晰才能夠,若是你不瞭解這個協議,能夠參照下面的博客說明:工具

 http://blog.csdn.net/thebestleo/article/details/52269999oop

 若是你須要搭建本身的ModBus服務器,能夠參照這邊文章:http://www.cnblogs.com/dathlin/p/7782315.html測試

 

訪問測試項目

須要下載一個串口虛擬的軟件

Virtual Serial Port Driver

下載地址:https://virtual-serial-port-driver.en.softonic.com/

而後虛擬化兩個串口出來,COM4 ,COM5 默認是鏈接在一塊兒的。這樣咱們就能夠進行本地的測試了

 

 在你開發本身的客戶端程序以前,能夠先用MODBUS測試工具進行測試,如下地址的一個開源項目就是基於這個組件開發的Modbus rtu測試工具,可直接用於讀寫測試。

 ModbusTcpServer.zip   先啓動服務,而後啓動串口

  

 


下面的一個項目是這個組件的訪問測試項目,您能夠進行初步的訪問的測試,免去了您寫測試程序的麻煩,這個項目是和三菱,西門子PLC的訪問寫在一塊兒的。能夠同時參考。

下載地址爲:HslCommunicationDemo.zip

 

 

Reference


 

ModBus組件全部的功能類都在 HslCommunication.ModBus命名空間,因此再使用以前先添加

using HslCommunication.ModBus;
using HslCommunication;

  

How to Use


 

實例化:

在使用讀寫功能以前必須先進行實例化:

private ModbusRtu busRtuClient = new ModbusRtu( station );

 

注意:在Modbus服務器的設備裏,大部分的設備都是從地址0開始的,有些特殊的設備是從地址1開始的,因此本組件裏面,默認從地址0開始,若是想要從地址1開始,那麼就須要以下的配置:

busRtuClient.AddressStartWithZero = False;

 

 

而後接下來須要初始化參數,對串口來講,一般的參數有串口名稱,波特率,數據位,中止位,校驗位,提供了一個委託設置的方式

try
            {
                busRtuClient.SerialPortInni( sp =>
                 {
                     sp.PortName = "COM5";
                     sp.BaudRate = 9600;
                     sp.DataBits = 8;
                     sp.StopBits = System.IO.Ports.StopBits.One;
                     sp.Parity = System.IO.Ports.Parity.None;
                 } );
                busRtuClient.Open( ); // 打開
            }
            catch (Exception ex)
            {
                MessageBox.Show( ex.Message );
            }

 

 關閉的話,調用以下的方法

busRtuClient.Close( );

 

 

 

 

如下代碼演示經常使用的讀寫操做,爲了方便起見,再也不對IsSuccess判斷,通常都是成功的:

private void userButton30_Click(object sender, EventArgs e)
        {
            // 讀取操做
            bool coil100 = busRtuClient.ReadCoil("100").Content;   // 讀取線圈100的通斷
            short short100 = busRtuClient.ReadInt16("100").Content; // 讀取寄存器100的short值
            ushort ushort100 = busRtuClient.ReadUInt16("100").Content; // 讀取寄存器100的ushort值
            int int100 = busRtuClient.ReadInt32("100").Content;      // 讀取寄存器100-101的int值
            uint uint100 = busRtuClient.ReadUInt32("100").Content;   // 讀取寄存器100-101的uint值
            float float100 = busRtuClient.ReadFloat("100").Content; // 讀取寄存器100-101的float值
            long long100 = busRtuClient.ReadInt64("100").Content;    // 讀取寄存器100-103的long值
            ulong ulong100 = busRtuClient.ReadUInt64("100").Content; // 讀取寄存器100-103的ulong值
            double double100 = busRtuClient.ReadDouble("100").Content; // 讀取寄存器100-103的double值
            string str100 = busRtuClient.ReadString("100", 5).Content;// 讀取100到104共10個字符的字符串

            // 寫入操做
            busRtuClient.WriteCoil("100", true);// 寫入線圈100爲通
            busRtuClient.Write("100", (short)12345);// 寫入寄存器100爲12345
            busRtuClient.Write("100", (ushort)45678);// 寫入寄存器100爲45678
            busRtuClient.Write("100", 123456789);// 寫入寄存器100-101爲123456789
            busRtuClient.Write("100", (uint)123456778);// 寫入寄存器100-101爲123456778
            busRtuClient.Write("100", 123.456);// 寫入寄存器100-101爲123.456
            busRtuClient.Write("100", 12312312312414L);//寫入寄存器100-103爲一個大數據
            busRtuClient.Write("100", 12634534534543656UL);// 寫入寄存器100-103爲一個大數據
            busRtuClient.Write("100", 123.456d);// 寫入寄存器100-103爲一個雙精度的數據
            busRtuClient.Write("100", "K123456789");
            
        }

 

下面再分別講解嚴格的操做,以及批量化的複雜的讀寫操做,假設你要讀取1000個M,循環讀取1千次可能要3秒鐘,若是用了下面的批量化讀取,只須要50ms,可是須要你對字節的原理比較熟悉才能駕輕就熟的處理

 

 

讀取線圈API:

 在此處舉例讀取地址爲0,長度爲10的線圈數量,讀取出來的數據已經自動轉化成了bool數組,方便的進行二次處理:

private void userButton8_Click(object sender,EventArgs e)
        {
            HslCommunication.OperateResult<bool[]> read = busRtuClient.ReadCoil("0", 10);
            if(read.IsSuccess)
            {
                bool coil_0 = read.Content[0];
                bool coil_1 = read.Content[1];
                bool coil_2 = read.Content[2];
                bool coil_3 = read.Content[3];
                bool coil_4 = read.Content[4];
                bool coil_5 = read.Content[5];
                bool coil_6 = read.Content[6];
                bool coil_7 = read.Content[7];
                bool coil_8 = read.Content[8];
                bool coil_9 = read.Content[9];
            }
            else
            {
                MessageBox.Show(read.ToMessageShowString());
            }
        }

  固然也能夠用組件提供的數據轉換API實現數據提取:

讀取離散數據:

讀取離散數據和讀取線圈的代碼幾乎是一致的,處理方式也是一致的,只是方法名稱改爲了:

private void userButton8_Click(object sender,EventArgs e)
        {
            HslCommunication.OperateResult<bool[]> read = busRtuClient.ReadDiscrete("0", 10);
            if(read.IsSuccess)
            {
                bool coil_0 = read.Content[0];
                bool coil_1 = read.Content[1];
                bool coil_2 = read.Content[2];
                bool coil_3 = read.Content[3];
                bool coil_4 = read.Content[4];
                bool coil_5 = read.Content[5];
                bool coil_6 = read.Content[6];
                bool coil_7 = read.Content[7];
                bool coil_8 = read.Content[8];
                bool coil_9 = read.Content[9];
            }
            else
            {
                MessageBox.Show(read.ToMessageShowString());
            }
        }

讀取寄存器數據:

 假設咱們須要讀取地址爲0,長度爲10的數據,也便是10個數據,每一個數據2個字節,總計20個字節的數據。下面解析數據前,先進行了假設,你在解析本身的數據前能夠參照下面的解析

private void userButton10_Click(object sender, EventArgs e)
        {
            HslCommunication.OperateResult<byte[]> read = busRtuClient.Read("0", 10);
            if (read.IsSuccess)
            {
                // 共返回20個字節,每一個數據2個字節,高位在前,低位在後
                // 在數據解析前須要知道里面到底存了什麼類型的數據,因此須要進行一些假設:
                // 前兩個字節是short數據類型
                short value1 = busTcpClient.ByteTransform.TransInt16(read.Content, 0); 
                // 接下來的2個字節是ushort類型
                ushort value2 = busTcpClient.ByteTransform.TransUInt16(read.Content, 2); 
                // 接下來的4個字節是int類型
                int value3 = busTcpClient.ByteTransform.TransInt32(read.Content, 4); 
                // 接下來的4個字節是float類型
                float value4 = busTcpClient.ByteTransform.TransFloat(read.Content, 8); 
                // 接下來的所有字節,共8個字節是規格信息
                string speci = Encoding.ASCII.GetString(read.Content, 12, 8);

                // 已經提取完全部的數據
            }
            else
            {
                MessageBox.Show(read.ToMessageShowString());
            }
        }

寫一個線圈:

寫一個線圈,這個相對比較簡單,假設咱們須要寫入線圈0,爲通

private void userButton11_Click(object sender, EventArgs e)
        {
            HslCommunication.OperateResult write = busRtuClient.WriteCoil("0", true);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

寫一個寄存器:

寫一個寄存器的操做也是很是的方便,在這裏提供了三個重載的方法,容許使用三種方式寫入:分別寫入,short,ushort,byte三種:

private void userButton12_Click(object sender, EventArgs e)
        {
            short value = -1234;
            HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

  

private void userButton12_Click(object sender, EventArgs e)
        {
            ushort value = 56713;
            HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

  

private void userButton12_Click(object sender, EventArgs e)
        {
            // 0x00爲高位,0x10爲低位
            HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", 0x00, 0x10);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

 

批量寫入線圈:

private void userButton13_Click(object sender, EventArgs e)
        {
            // 線圈0爲True,線圈1爲false,線圈2爲true.....等等,以此類推,數組長度多少,就寫入多少線圈
            bool[] value = new bool[] { true, false, true, true, false, false };
            HslCommunication.OperateResult write = busRtuClient.WriteCoil("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

  

批量寫入寄存器:

第一種狀況寫入一串short數組,這種狀況比較簡單:

private void userButton14_Click(object sender, EventArgs e)
        {
            short[] value = new short[] { -1234, 467, 12345 };
            HslCommunication.OperateResult write = busRtuClient.Write("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

第二狀況寫入一串ushort數組,也是比較簡單:

private void userButton14_Click(object sender, EventArgs e)
        {
            ushort[] value = new ushort[] { 46789, 467, 12345 };
            HslCommunication.OperateResult write = busRtuClient.Write("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

比較複雜的是寫入自定義的數據,按照上述讀取寄存器,好比我須要寫入寄存器0,寄存器1共同組成的一個int數據,那麼咱們這麼寫:

private void userButton15_Click(object sender, EventArgs e)
        {
            int value = 12345678;// 等待寫入的一個數據

            HslCommunication.OperateResult write = busRtuClient.Write("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }

其餘數據參考這個就行,若是有不明白的,能夠聯繫上面的QQ羣。

 

究極數據操做,使用原生的報文來操做數據:

傳入一個字節數組,數據內容和原生的數據一致,好比我要經過原生API讀取寄存器地址爲0,長度爲3的數據,那麼字節的HEX標識形式爲 01 03 00 00 00 03 不包括CRC校驗碼

private void button26_Click( object sender, EventArgs e )
        {
            try
            {
                OperateResult<byte[]> read = busRtuClient.ReadBase( HslCommunication.Serial.SoftCRC16.CRC16(HslCommunication.BasicFramework.SoftBasic.HexStringToBytes( "01 03 00 00 00 03" )) );
                if (read.IsSuccess)
                {
                    textBox11.Text = "結果:" + HslCommunication.BasicFramework.SoftBasic.ByteToHexString( read.Content,' ' );
                }
                else
                {
                    MessageBox.Show( "讀取失敗:" + read.ToMessageShowString( ) );
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show( "讀取失敗:" + ex.Message );
            }
        }

 

 

  上述代碼在操做時用了一個轉化機制,輸入爲十六進制的文本,轉化爲byte[]數據,中間的分割符能夠爲空格,能夠爲'-',也能夠爲',','_'等等等等,調用了組件基礎的數據轉化功能。

相關文章
相關標籤/搜索