前言
本文將使用一個NuGet公開的組件技術來實現一個ModBus RTU的客戶端,方便的對Modbus rtu的服務器進行讀寫,這個服務器可以是電腦端C#設計的,也可以是PLC實現的,也可以是其他任何支持這個通信協議的服務器。
github地址:https://github.com/dathlin/HslCommunication 如果喜歡可以star或是fork,還可以打賞支持。
聯系作者及加群方式(激活碼在群里發放):http://www.hslcommunication.cn/Cooperation
在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,本組件支持如下的功能操作:
- 0x01 讀取線圈的操作,
- 0x02 讀取離散的操作,
- 0x03 讀取寄存器的值,
- 0x05 寫一個線圈操作,
- 0x06 寫一個寄存器值,
- 0x0F 批量寫線圈操作,
- 0x10 批量寫寄存器值,
如果你的設備需要這些功能之外的數據,可以使用原生API方法,但是這個方法的前提就是你對MODBUS 協議非常清晰才可以,如果你不了解這個協議,可以參照下面的博客說明:
http://blog.csdn.net/thebestleo/article/details/52269999
如果你需要搭建自己的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[]數據,中間的分割符可以為空格,可以為'-',也可以為',','_'等等等等,調用了組件基礎的數據轉化功能。
