前言
進過前面兩章的介紹,今天開始正式的實戰,
進制轉換
很多朋友對于進制轉換可能是在剛學計算機的時候有接觸,后來做高級語言開發可能就慢慢忘記了,我們做工控開發的時候需要經常進行進制轉換,這里和大家一起復習下,
一個位元組等8位(1byte = 8bit),可以存盤2^8(0-255)共計256個數字,所以我們要對8、256等數字要敏感,
int16(short), int32(int), int64(long) 分別是占用2個位元組、4個位元組、8個位元組,Single(float)也是占用4個位元組,
bool System.Boolean (布爾型,其值為 true 或者 false)
byte System.Byte (位元組型,占 1 位元組,表示 8 位正整數,范圍 0 ~ 255)
sbyte System.SByte (帶符號位元組型,占 1 位元組,表示 8 位整數,范圍 -128 ~ 127)
char System.Char (字符型,占有 2 個位元組,表示 1 個 Unicode 字符)
short System.Int16 (短整型,占 2 位元組,表示 16 位整數,范圍 -32,768 ~ 32,767)
ushort System.UInt16 (無符號短整型,占 2 位元組,表示 16 位正整數,范圍 0 ~ 65,535)
uint System.UInt32 (無符號整型,占 4 位元組,表示 32 位正整數,范圍 0 ~ 4,294,967,295)
int System.Int32 (整型,占 4 位元組,表示 32 位整數,范圍 -2,147,483,648 到 2,147,483,647)
float System.Single (單精度浮點型,占 4 個位元組)
ulong System.UInt64 (無符號長整型,占 8 位元組,表示 64 位正整數)
long System.Int64 (長整型,占 8 位元組,表示 64 位整數)
double System.Double (雙精度浮點型,占8 個位元組)
接著我們來看其他進制轉十進制的計算
十進制轉十進制
1263 = 1*10^3 + 2*10^2 + 6*10^1 + 3*10^0 = 1000 + 200 + 60 + 3 = 1263
二進制轉十進制
1001 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 8 + 0 + 0 + 1 = 9
十六進制轉十進制
3245 = 3*16^3 + 2*16^2 + 4*16^1 + 5*16^0 = 3*4096 + 2*256 + 4*16 + 5 = 12869
十進制轉二進制
第八位 第七位 第六位 第五位 第四位 第三位 第二位 第一位
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
128 64 32 16 8 4 2 1
以上位二進制位能存盤最大十進制數,所以我們反過來也可以對照把十進制轉二進制,比如86,
86小于128多以第八位是0,86大于64所以第七位是1,86-64=22,22小于32所以第六位是0,22大于16所以第五位是1,,,

所以最好轉成二進制是:0101 0110
二進制轉十六進制
我們用二進制 0101 0110來演示,也就是上面十進制的86,

當然,你最好用計算器驗證下

ModBusTcp協議介紹
我們在對進制轉換進行復習過后,接下來講ModBusTcp協議,
ModBus協議是現在工控里面用的比較多比較通用的一種協議,什么可靠啊、簡單啊等等一些優點就不說了,直接入正題,
ModBus分為RTU、ASCII、TCP三種方式進行通信,今天我們只講TCP,
在ModBus里面有站號、功能碼、暫存器地址等概念,
- 站號:多設備的標識號
- 功能碼:一些功能的標識號
功能碼詳解:
01:讀線圈
02:讀離散量
03:讀保持暫存器(每個暫存器含有兩個位元組)
04:讀輸入暫存器
05:寫單個線圈
06:寫單個暫存器
15:用于寫多個線圈
16:寫多個暫存器
ModBusTcp報文分析
協議的理解和實作主要就是要對協議報文理解,(注意:以下報文資料都是十六進制)
資料【讀取-請求報文】:19 B2 00 00 00 06 02 03 00 04 00 01
- 19 B2 是客戶端發的檢驗資訊,隨意定義,
- 00 00 代表是基于tcp/ip協議的modbus
- 00 06 標識后面還有多長的位元組
- 02 表示站號地址
- 03 為功能碼(讀保持暫存器)
- 00 04 為暫存器地址
- 00 01 為暫存器的長度(暫存器個數)
資料【讀取-回應報文】(分兩次獲取)
第一次獲取前八個位元組(Map報文頭):19 B2 00 00 00 05 02 03 02 00 20
- 19 B2 檢驗驗證資訊(復制的客戶端發的,配件檢驗)
- 00 00 代表是基于tcp/ip協議的modbus(復制的客戶端發的)
- 00 05 為當前位置到最后的長度
- 02 表示站號地址(復制的客戶端發的)
- 03 為功能碼(復制的客戶端發的)
第二次獲取的報文:02 00 20
- 02 位元組個數
- 00 20 回應的資料
資料【寫入-請求報文】:19 B2 00 00 00 09 02 10 00 04 00 01 02 00 20
- 19 B2 是客戶端發的檢驗資訊,隨意定義,
- 00 00 代表是基于tcp/ip協議的modbus
- 00 09 從本位元組下一個到最后
- 02 站號
- 10 功能碼(轉十進制就是16)
- 00 04 暫存器地址
- 00 01 暫存器的長度(暫存器個數)
- 02 寫位元組的個數
- 00 20 要寫入的值(轉十進制為32)
資料【寫入-回應報文】:19 B2 00 00 00 06 02 10 00 04 00 01
和請求報文的區別
- 沒有了請求報文的資料值
- 00 09 變成了00 06 因為報文長度變了
- 其他的報文意義和請求報文一致
ModBusTcp對暫存器的讀取
有了上面的三個報文做參考,我們就可以用Socket來實作ModBusTcp協議了,其實協議就是按照報文的規定來,也沒有想的那么復雜,和我們前面實作的聊天通訊軟體區別不大,
第一步,我們先實作資料讀取報文的組裝:
/// <summary>
/// 獲取讀取命令(此方法傳入引數后就可以得到類似19 B2 00 00 00 06 02 03 00 04 00 01這樣的請求報文)
/// </summary>
/// <param name="address">暫存器起始地址</param>
/// <param name="stationNumber">站號</param>
/// <param name="functionCode">功能碼</param>
/// <param name="length">讀取長度</param>
/// <returns></returns>
public static byte[] GetReadCommand(ushort address, byte stationNumber, byte functionCode, ushort length)
{
byte[] buffer = new byte[12];
buffer[0] = 0x19;
buffer[1] = 0xB2;//Client發出的檢驗資訊
buffer[2] = 0x00;
buffer[3] = 0x00;//表示tcp/ip 的協議的modbus的協議
buffer[4] = 0x00;
buffer[5] = 0x06;//表示的是該位元組以后的位元組長度
buffer[6] = stationNumber; //站號
buffer[7] = functionCode; //功能碼
buffer[8] = BitConverter.GetBytes(address)[1];
buffer[9] = BitConverter.GetBytes(address)[0];//暫存器地址
buffer[10] = BitConverter.GetBytes(length)[1];
buffer[11] = BitConverter.GetBytes(length)[0];//表示request 暫存器的長度(暫存器個數)
return buffer;
}
第二步,就是建立我們的Socket連接,并發送請求報文
//1 創建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 建立連接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 埠));
//3 獲取命令[組裝請求報文](暫存器起始地址:4、站號:2、功能碼:3、讀取暫存器長度:1)
byte[] command = GetReadCommand(4, 2, 3, 1);
//4 發送命令
socket.Send(command);
第三步,決議回應報文,得到資料值
//5 讀取回應
byte[] buffer1 = new byte[8];//先讀取前面八個位元組(Map報文頭)
socket.Receive(buffer1, 0, buffer1.Length, SocketFlags.None);
//5.1 獲取將要讀取的資料長度
int length = buffer1[4] * 256 + buffer1[5] - 2;//減2是因為這個長度資料包括了單元識別符號和功能碼,占兩個位元組
//5.2 讀取資料
byte[] buffer2 = new byte[length];
var readLength2 = socket.Receive(buffer2, 0, buffer2.Length, SocketFlags.None);
byte[] buffer3 = new byte[readLength2 - 1];
//5.3 過濾第一個位元組(第一個位元組代表資料的位元組個數)
Array.Copy(buffer2, 1, buffer3, 0, buffer3.Length);
var buffer3Reverse = buffer3.Reverse().ToArray();
var value = https://www.cnblogs.com/zhaopei/p/BitConverter.ToInt16(buffer3Reverse, 0);
//6 關閉連接
socket.Shutdown(SocketShutdown.Both);
socket.Close();
ModBusTcp對暫存器的寫入
對于資料寫入就更簡單了,
第一步,組裝請求報文
/// <summary>
/// 獲取寫入命令
/// </summary>
/// <param name="address">暫存器地址</param>
/// <param name="values"></param>
/// <param name="stationNumber">站號</param>
/// <param name="functionCode">功能碼</param>
/// <returns></returns>
public static byte[] GetWriteCommand(ushort address, byte[] values, byte stationNumber, byte functionCode)
{
byte[] buffer = new byte[13 + values.Length];
buffer[0] = 0x19;
buffer[1] = 0xB2;//檢驗資訊,用來驗證response是否串資料了
buffer[4] = BitConverter.GetBytes(7 + values.Length)[1];
buffer[5] = BitConverter.GetBytes(7 + values.Length)[0];//表示的是header handle后面還有多長的位元組
buffer[6] = stationNumber; //站號
buffer[7] = functionCode; //功能碼
buffer[8] = BitConverter.GetBytes(address)[1];
buffer[9] = BitConverter.GetBytes(address)[0];//暫存器地址
buffer[10] = (byte)(values.Length / 2 / 256);
buffer[11] = (byte)(values.Length / 2 % 256);//寫暫存器數量(除2是一個暫存器兩個位元組,暫存器16位,除以256是byte最大存盤255,)
buffer[12] = (byte)(values.Length); //寫位元組的個數
values.CopyTo(buffer, 13); //把目標值附加到陣列后面
return buffer;
}
第二步,建立Socket連接,并發送報文
//1 創建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 建立連接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 埠));
//值的轉換
short value = https://www.cnblogs.com/zhaopei/p/32;
var values = BitConverter.GetBytes(value).Reverse().ToArray();
//3 獲取并發送命令(暫存器起始地址、站號、功能碼)
var command = GetWriteCommand(4, values, 2, 16);
socket.Send(command);
//4 關閉連接
socket.Shutdown(SocketShutdown.Both);
socket.Close();
結束
- 同步至索引目錄:《物聯網基礎組件IoTClient開發系列》
- 參考1:https://www.cnblogs.com/any91/p/3530540.html
- 參考2:https://www.cnblogs.com/DreamRecorder/p/9081134.html
- demo:https://github.com/zhaopeiym/BlogDemoCode/tree/master/IoTClient/ModbusTCP
- 完整實作:https://github.com/zhaopeiym/IoTClient
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/98047.html
標籤:.NET Core
上一篇:.Net Core實作健康檢查
下一篇:C#/.NET/.NET Core定時任務調度的方法或者組件有哪些--Timer,FluentScheduler還是...
