电竞比分网-中国电竞赛事及体育赛事平台

分享

C# ModBus Tcp讀寫數(shù)據(jù) 與服務(wù)器進(jìn)行通訊

 pphsy 2019-01-02

前言




 本文將使用一個(gè)NuGet公開的組件技術(shù)來(lái)實(shí)現(xiàn)一個(gè)ModBus TCP的客戶端,方便的對(duì)Modbus tcp的服務(wù)器進(jìn)行讀寫,這個(gè)服務(wù)器可以是電腦端C#設(shè)計(jì)的,也可以是PLC實(shí)現(xiàn)的,也可以是其他任何支持這個(gè)通信協(xié)議的服務(wù)器。


github地址:https://github.com/dathlin/HslCommunication 如果喜歡可以star或是fork,還可以打賞支持。


 


在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制臺(tái)輸入下面的指令安裝:



1
Install-Package HslCommunication


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


技術(shù)支持QQ群:群1:592132877(滿)  群2:948305931  (組件的版本更新細(xì)節(jié)也將第一時(shí)間在群里發(fā)布)組件API地址:http://www.cnblogs.com/dathlin/p/7703805.html


 


關(guān)于兩種模式




在PLC端,包括三菱和西門子,歐姆龍以及Modbus Tcp客戶端的訪問器上,都支持兩種模式,短連接模式和長(zhǎng)連接模式,現(xiàn)在就來(lái)解釋下什么原理。


短連接:每次讀寫都是一個(gè)單獨(dú)的請(qǐng)求,請(qǐng)求完畢也就關(guān)閉了,如果服務(wù)器的端口僅僅支持單連接,那么關(guān)閉后這個(gè)端口可以被其他連接復(fù)用,但是在頻繁的網(wǎng)絡(luò)請(qǐng)求下,容易發(fā)生異常,會(huì)有其他的請(qǐng)求不成功,尤其是多線程的情況下。


長(zhǎng)連接:創(chuàng)建一個(gè)公用的連接通道,所有的讀寫請(qǐng)求都利用這個(gè)通道來(lái)完成,這樣的話,讀寫性能更快速,即時(shí)多線程調(diào)用也不會(huì)影響,內(nèi)部有同步機(jī)制。如果服務(wù)器的端口僅僅支持單連接,那么這個(gè)端口就被占用了,比如三菱的端口機(jī)制,西門子的Modbus tcp端口機(jī)制也是這樣的。以下代碼默認(rèn)使用短連接,方便測(cè)試。


在短連接的模式下,每次請(qǐng)求都是單獨(dú)的訪問,所以沒有重連的困擾,在長(zhǎng)連接的模式下,如果本次請(qǐng)求失敗了,在下次請(qǐng)求的時(shí)候,會(huì)自動(dòng)重新連接服務(wù)器,直到請(qǐng)求成功為止。另外,盡量所有的讀寫都對(duì)結(jié)果的成功進(jìn)行判斷。


 


特別感謝





  • 網(wǎng)友:陳恩富                  對(duì)float,int數(shù)據(jù)的讀取測(cè)試,才修復(fù)了權(quán)重位顛倒的BUG。

  • 網(wǎng)友:U4幸福的蝸牛      發(fā)現(xiàn)了博客上錯(cuò)誤的一個(gè)方法名稱,已于2018年1月8日13:34:39更新。并反饋了一些特殊設(shè)備(modbus tcp服務(wù)器)的讀取數(shù)據(jù)的BUG。已修復(fù)。


 


隨便聊聊




只要是網(wǎng)絡(luò)訪問,就會(huì)存在主從的區(qū)別,此處的設(shè)計(jì)模式是客戶端主動(dòng)請(qǐng)求服務(wù)器數(shù)據(jù),然后接收服務(wù)器的反饋數(shù)據(jù),支持原生的指令收發(fā),支持其他一些方便的API收發(fā)。特殊功能碼需要使用原生收發(fā)的API,本組件支持如下的功能操作:



  • 0x01    讀取線圈的操作,

  • 0x02    讀取離散的操作,

  • 0x03    讀取寄存器的值,

  • 0x05    寫一個(gè)線圈操作,

  • 0x06    寫一個(gè)寄存器值,

  • 0x0F    批量寫線圈操作,

  • 0x10    批量寫寄存器值,


 如果你的設(shè)備需要這些功能之外的數(shù)據(jù),可以使用原生API方法,但是這個(gè)方法的前提就是你對(duì)MODBUS TCP協(xié)議非常清晰才可以,如果你不了解這個(gè)協(xié)議,可以參照下面的博客說明:


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


 如果你需要搭建自己的ModBus服務(wù)器,可以參照這邊文章:http://www.cnblogs.com/dathlin/p/7782315.html


 在你開發(fā)自己的客戶端程序之前,可以先用MODBUS測(cè)試工具進(jìn)行測(cè)試,以下地址的一個(gè)開源項(xiàng)目就是基于這個(gè)組件開發(fā)的Modbus tcp測(cè)試工具,可直接用于讀寫測(cè)試。


 ModbusTcpServer.zip


  


 


訪問測(cè)試項(xiàng)目




下面的一個(gè)項(xiàng)目是這個(gè)組件的訪問測(cè)試項(xiàng)目,您可以進(jìn)行初步的訪問的測(cè)試,免去了您寫測(cè)試程序的麻煩,這個(gè)項(xiàng)目是和三菱,西門子PLC的訪問寫在一起的??梢酝瑫r(shí)參考。


下載地址為:HslCommunicationDemo.zip



 


 


Reference




 


ModBus組件所有的功能類都在 HslCommunication.ModBus命名空間,所以再使用之前先添加



1
2
using HslCommunication.ModBus;
using HslCommunication;


  


How to Use




 


實(shí)例化:


在使用讀寫功能之前必須先進(jìn)行實(shí)例化:



1
private ModbusTcpNet busTcpClient = new ModbusTcpNet("192.168.1.195", 502, 0x01);   // 站號(hào)1


上面的實(shí)例化指定了服務(wù)器的IP地址,端口號(hào)(一般都是502),以及自己的站號(hào),允許設(shè)置為0-255,后面的兩個(gè)參數(shù)有默認(rèn)值,在實(shí)例化的時(shí)候可以省略。



1
private ModbusTcpNet busTcpClient = new ModbusTcpNet("192.168.1.195");   // 端口號(hào)502,站號(hào)1


 


注意:在Modbus服務(wù)器的設(shè)備里,大部分的設(shè)備都是從地址0開始的,有些特殊的設(shè)備是從地址1開始的,所以本組件里面,默認(rèn)從地址0開始,如果想要從地址1開始,那么就需要如下的配置:



1
busTcpClient.AddressStartWithZero = False;


 


 


上面兩個(gè)聲明選擇其中一個(gè)就行了。然后實(shí)例化之后(也可以放在窗體的Load方法中)就可以調(diào)用下面的方法切換為長(zhǎng)連接了,



1
busTcpClient.ConnectServer();


 關(guān)閉的話,調(diào)用如下的方法



1
busTcpClient.ConnectClose( );


 


 


 


以下代碼演示常用的讀寫操作,為了方便起見,不再對(duì)IsSuccess判斷,一般都是成功的:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void userButton30_Click(object sender, EventArgs e)
{
    // 讀取操作
    bool coil100 = busTcpClient.ReadCoil("100").Content;   // 讀取線圈100的通斷
    short short100 = busTcpClient.ReadInt16("100").Content; // 讀取寄存器100的short值
    ushort ushort100 = busTcpClient.ReadUInt16("100").Content; // 讀取寄存器100的ushort值
    int int100 = busTcpClient.ReadInt32("100").Content;      // 讀取寄存器100-101的int值
    uint uint100 = busTcpClient.ReadUInt32("100").Content;   // 讀取寄存器100-101的uint值
    float float100 = busTcpClient.ReadFloat("100").Content; // 讀取寄存器100-101的float值
    long long100 = busTcpClient.ReadInt64("100").Content;    // 讀取寄存器100-103的long值
    ulong ulong100 = busTcpClient.ReadUInt64("100").Content; // 讀取寄存器100-103的ulong值
    double double100 = busTcpClient.ReadDouble("100").Content; // 讀取寄存器100-103的double值
    string str100 = busTcpClient.ReadString("100", 5).Content;// 讀取100到104共10個(gè)字符的字符串
    // 寫入操作
    busTcpClient.WriteCoil("100", true);// 寫入線圈100為通
    busTcpClient.Write("100", (short)12345);// 寫入寄存器100為12345
    busTcpClient.Write("100", (ushort)45678);// 寫入寄存器100為45678
    busTcpClient.Write("100", 123456789);// 寫入寄存器100-101為123456789
    busTcpClient.Write("100", (uint)123456778);// 寫入寄存器100-101為123456778
    busTcpClient.Write("100", 123.456);// 寫入寄存器100-101為123.456
    busTcpClient.Write("100", 12312312312414L);//寫入寄存器100-103為一個(gè)大數(shù)據(jù)
    busTcpClient.Write("100", 12634534534543656UL);// 寫入寄存器100-103為一個(gè)大數(shù)據(jù)
    busTcpClient.Write("100", 123.456d);// 寫入寄存器100-103為一個(gè)雙精度的數(shù)據(jù)
    busTcpClient.Write("100", "K123456789");
     
}


 


下面再分別講解嚴(yán)格的操作,以及批量化的復(fù)雜的讀寫操作,假設(shè)你要讀取1000個(gè)M,循環(huán)讀取1千次可能要3秒鐘,如果用了下面的批量化讀取,只需要50ms,但是需要你對(duì)字節(jié)的原理比較熟悉才能得心應(yīng)手的處理


 


 


讀取線圈API:


 在此處舉例讀取地址為0,長(zhǎng)度為10的線圈數(shù)量,讀取出來(lái)的數(shù)據(jù)已經(jīng)自動(dòng)轉(zhuǎn)化成了bool數(shù)組,方便的進(jìn)行二次處理:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void userButton8_Click(object sender,EventArgs e)
{
    HslCommunication.OperateResult<bool[]> read = busTcpClient.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());
    }
}


  當(dāng)然也可以用組件提供的數(shù)據(jù)轉(zhuǎn)換API實(shí)現(xiàn)數(shù)據(jù)提取:


讀取離散數(shù)據(jù):


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



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void userButton8_Click(object sender,EventArgs e)
{
    HslCommunication.OperateResult<bool[]> read = busTcpClient.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());
    }
}


讀取寄存器數(shù)據(jù):


 假設(shè)我們需要讀取地址為0,長(zhǎng)度為10的數(shù)據(jù),也即是10個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)2個(gè)字節(jié),總計(jì)20個(gè)字節(jié)的數(shù)據(jù)。下面解析數(shù)據(jù)前,先進(jìn)行了假設(shè),你在解析自己的數(shù)據(jù)前可以參照下面的解析



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void userButton10_Click(object sender, EventArgs e)
{
    HslCommunication.OperateResult<byte[]> read = busTcpClient.Read("0", 10);
    if (read.IsSuccess)
    {
        // 共返回20個(gè)字節(jié),每個(gè)數(shù)據(jù)2個(gè)字節(jié),高位在前,低位在后
        // 在數(shù)據(jù)解析前需要知道里面到底存了什么類型的數(shù)據(jù),所以需要進(jìn)行一些假設(shè):
        // 前兩個(gè)字節(jié)是short數(shù)據(jù)類型
        short value1 = busTcpClient.ByteTransform.TransInt16(read.Content, 0);<br>
        // 接下來(lái)的2個(gè)字節(jié)是ushort類型
        ushort value2 = busTcpClient.ByteTransform.TransUInt16(read.Content, 2);<br>
        // 接下來(lái)的4個(gè)字節(jié)是int類型
        int value3 = busTcpClient.ByteTransform.TransInt32(read.Content, 4);<br>
        // 接下來(lái)的4個(gè)字節(jié)是float類型
        float value4 = busTcpClient.ByteTransform.TransFloat(read.Content, 8);<br>
        // 接下來(lái)的全部字節(jié),共8個(gè)字節(jié)是規(guī)格信息
        string speci = Encoding.ASCII.GetString(read.Content, 12, 8);
        // 已經(jīng)提取完所有的數(shù)據(jù)
    }
    else
    {
        MessageBox.Show(read.ToMessageShowString());
    }
}


寫一個(gè)線圈:


寫一個(gè)線圈,這個(gè)相對(duì)比較簡(jiǎn)單,假設(shè)我們需要寫入線圈0,為通



1
2
3
4
5
6
7
8
9
10
11
12
13
private void userButton11_Click(object sender, EventArgs e)
{
    HslCommunication.OperateResult write = busTcpClient.WriteCoil("0", true);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


寫一個(gè)寄存器:


寫一個(gè)寄存器的操作也是非常的方便,在這里提供了三個(gè)重載的方法,允許使用三種方式寫入:分別寫入,short,ushort,byte三種:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void userButton12_Click(object sender, EventArgs e)
{
    short value = -1234;
    HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", value);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


  



1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void userButton12_Click(object sender, EventArgs e)
{
    ushort value = 56713;
    HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", value);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


  



1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void userButton12_Click(object sender, EventArgs e)
{
    // 0x00為高位,0x10為低位
    HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", 0x00, 0x10);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


 


批量寫入線圈:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void userButton13_Click(object sender, EventArgs e)
        {
            // 線圈0為True,線圈1為false,線圈2為true.....等等,以此類推,數(shù)組長(zhǎng)度多少,就寫入多少線圈
            bool[] value = new bool[] { true, false, true, true, false, false };
            HslCommunication.OperateResult write = busTcpClient.WriteCoil("0", value);
            if (write.IsSuccess)
            {
                // 寫入成功
                textBox1.Text = "寫入成功";
            }
            else
            {
                MessageBox.Show(write.ToMessageShowString());
            }
        }


  


批量寫入寄存器:


第一種情況寫入一串short數(shù)組,這種情況比較簡(jiǎn)單:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void userButton14_Click(object sender, EventArgs e)
{
    short[] value = new short[] { -1234, 467, 12345 };
    HslCommunication.OperateResult write = busTcpClient.Write("0", value);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


第二情況寫入一串ushort數(shù)組,也是比較簡(jiǎn)單:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void userButton14_Click(object sender, EventArgs e)
{
    ushort[] value = new ushort[] { 46789, 467, 12345 };
    HslCommunication.OperateResult write = busTcpClient.Write("0", value);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


比較復(fù)雜的是寫入自定義的數(shù)據(jù),按照上述讀取寄存器,比如我需要寫入寄存器0,寄存器1共同組成的一個(gè)int數(shù)據(jù),那么我們這么寫:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void userButton15_Click(object sender, EventArgs e)
{
    int value = 12345678;// 等待寫入的一個(gè)數(shù)據(jù)
    HslCommunication.OperateResult write = busTcpClient.Write("0", value);
    if (write.IsSuccess)
    {
        // 寫入成功
        textBox1.Text = "寫入成功";
    }
    else
    {
        MessageBox.Show(write.ToMessageShowString());
    }
}


其他數(shù)據(jù)參考這個(gè)就行,如果有不明白的,可以聯(lián)系上面的QQ群。


 


模式切換(支持熱切換,想什么時(shí)候切換都可以):


上面默認(rèn)都是使用短連接的機(jī)制,如果需要使用長(zhǎng)連接的話,這種通訊模式更加穩(wěn)定。多線程已經(jīng)同步。


 



1
2
3
4
private void userButton11_Click(object sender, EventArgs e)
{
    modBusTcpClient.ConnectServer();
}


 執(zhí)行完這一行代碼后,一般在實(shí)例化后面就可以切換長(zhǎng)連接了,會(huì)返回一個(gè)OperateResult對(duì)象,連接成功IsSuccess為True,后面所有的讀寫操作都調(diào)用同一個(gè)通信通道。如果想要切換回短連接。



1
modBusTcpClient.ConnectClose();


 


究極數(shù)據(jù)操作,使用原生的報(bào)文來(lái)操作數(shù)據(jù):


傳入一個(gè)字節(jié)數(shù)組,數(shù)據(jù)內(nèi)容和原生的數(shù)據(jù)一致,比如我要通過原生API讀取寄存器地址為0,長(zhǎng)度為3的數(shù)據(jù),那么字節(jié)的HEX標(biāo)識(shí)形式為 00 00 00 00 00 06 00 03 00 00 00 03



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void userButton2_Click(object sender, EventArgs e)
{
    byte[] data = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes("00 00 00 00 00 06 00 03 00 00 00 03");
    HslCommunication.OperateResult<byte[]> read = busTcpClient.ReadFromCoreServer(data);
    if(read.IsSuccess)
    {
        // 獲取結(jié)果,并轉(zhuǎn)化為Hex字符串,方便顯示
        string result = HslCommunication.BasicFramework.SoftBasic.ByteToHexString(read.Content, ' ');
    }
    else
    {
        MessageBox.Show(read.ToMessageShowString());
    }
}


  上述代碼在操作時(shí)用了一個(gè)轉(zhuǎn)化機(jī)制,輸入為十六進(jìn)制的文本,轉(zhuǎn)化為byte[]數(shù)據(jù),中間的分割符可以為空格,可以為'-',也可以為',','_'等等等等,調(diào)用了組件基礎(chǔ)的數(shù)據(jù)轉(zhuǎn)化功能。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多