前言 前面我們介紹了 "ModBusTcp協議" 。今天我們接著來介紹ModBusRtu協議。和ModBusTcp不同的是ModBusRtu基於串口通信,ModBusTcp是基於Tcp乙太網通信。 所以我們在講解ModBusRtu協議之前會先介紹下串口通信。 串口通信 串口出現在1980年前後,當初 ...
前言
前面我們介紹了ModBusTcp協議。今天我們接著來介紹ModBusRtu協議。和ModBusTcp不同的是ModBusRtu基於串口通信,ModBusTcp是基於Tcp乙太網通信。
所以我們在講解ModBusRtu協議之前會先介紹下串口通信。
串口通信
串口出現在1980年前後,當初主要目的是用來做電腦外設設備的連接,如滑鼠、鍵盤等。現在最新的電腦慢慢的取消了原始的串口介面,不過依然廣泛用於工控和測量等設備。
串口通信參數
串口通信指的是串口按位(bit)發送和接收位元組,串口通信參數主要有波特率、數據位、停止位、校驗位。
波特率
波特率表達的是串口通信的速率,一秒鐘內傳送的信號單元(碼元)個數。信號單元一般包含10位(7個數據位、1個校驗位、1到2個停止位)。註意:波特率和距離成反比
數據位
通信中實際的數據,有效值為6、7和8。
停止位
用來表示單個包的最後一位,有效值為1、1.5和2。停止位可用來表示傳輸的結束和校正時鐘同步。註意:停止位的位數越多,時鐘同步的容忍程度越大,但是數據傳輸率會越慢。
校驗位
奇偶校驗作為通信中的檢錯方式,如果發現錯誤則重新發送。
示例數據 | 偶校驗位 | 奇校驗位 |
---|---|---|
0000000 | 00000000 | 00000001 |
1010001 | 10100011 | 10100010 |
1101001 | 11010010 | 11010011 |
1111111 | 11111111 | 11111110 |
從上可以看出奇偶校驗就是在數據最後加一位,使數據中的1的數量保持偶數或奇數。
波特率和比特率 (擴展知識)
比特率是我們常用來表達寬頻速率的一種方法。看上去和波特率很像,如果波特率的信號碼元只傳1比特(bit),那麼它們之間是相等的。如果波特率的信號碼元傳10比特,那麼波特率是比特率的10倍。所以,波特率和比特率表達的意義是不一樣的,不要搞混了。
寬頻比特率的實際下載速度 (擴展知識)
Mbps和Mbit/s等效、kbit/s和kbps等效、bps和bit/s等效
1Mbps(Mbit/s) = 11024kbit(kbit/s) = 11024*1024bps(bit/s),註意他們的單位都是bit(比特),而不是byte(位元組),所以實際下載速度要除以八。1024 / 8 = 128 kb/s。
CRC16校驗
CRC,Cyclic Redundancy Check迴圈冗餘檢驗,是基於數據計算一組效驗碼,用於核對數據傳輸過程中是否被更改或傳輸錯誤。而ModBusRtu用到的是其中的CRC16校驗。
其計算原理,可參考 1、2、3
以下是CRC16反向演算法,經測試可用於ModBusRtu的CRC計算。
public class CRC16
{
/// <summary>
/// 驗證CRC16校驗碼
/// </summary>
/// <param name="value">校驗數據</param>
/// <param name="poly">多項式碼</param>
/// <param name="crcInit">校驗碼初始值</param>
/// <returns></returns>
public static bool CheckCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
{
if (value == null || !value.Any())
throw new ArgumentException("生成CRC16的入參有誤");
var crc16 = GetCRC16(value, poly, crcInit);
if (crc16[crc16.Length - 2] == crc16[crc16.Length - 1] && crc16[crc16.Length - 1] == 0)
return true;
return false;
}
/// <summary>
/// 計算CRC16校驗碼
/// </summary>
/// <param name="value">校驗數據</param>
/// <param name="poly">多項式碼</param>
/// <param name="crcInit">校驗碼初始值</param>
/// <returns></returns>
public static byte[] GetCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
{
if (value == null || !value.Any())
throw new ArgumentException("生成CRC16的入參有誤");
//運算
ushort crc = crcInit;
for (int i = 0; i < value.Length; i++)
{
crc = (ushort)(crc ^ (value[i]));
for (int j = 0; j < 8; j++)
{
crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
}
}
byte hi = (byte)((crc & 0xFF00) >> 8); //高位置
byte lo = (byte)(crc & 0x00FF); //低位置
byte[] buffer = new byte[value.Length + 2];
value.CopyTo(buffer, 0);
buffer[buffer.Length - 1] = hi;
buffer[buffer.Length - 2] = lo;
return buffer;
}
}
協議報文分析
數據【讀取-請求報文】:01 03 00 04 00 01 C5 CB
- 01 站號
- 03 功能碼
- 00 04 讀取的寄存器的起始地址
- 00 01 讀取寄存器的個數
- C5 CB 為CRC16的校驗碼【使用上面的CRC16類進行的計算結果,CRC16.GetCRC16([01,03,00,04,00,01])】
數據【讀取-響應報文】:01 03 02 00 21 78 5C
- 01 站號
- 03 功能碼
- 02 數據的位元組長度
- 00 21 數據
- 78 5C 為CRC16的校驗碼
數據【寫入-請求報文】:01 10 00 04 00 01 02 00 21 67 CC
- 01 站號
- 10 功能碼
- 00 04 寫入的寄存器的起始地址
- 00 01 寫入寄存器的個數
- 02 寫位元組的個數
- 00 21 要寫的數據
- 67 CC 為CRC16的校驗碼
數據【寫入-響應報文】:01 10 00 04 00 01 40 08
- 01 站號
- 10 功能碼
- 00 04 寫入的寄存器的起始地址
- 00 01 寫入寄存器的個數
- 40 08 為CRC16的校驗碼
有了報文的分析,具體的協議實現也就不難了。完整實現可參考https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient/Clients/ModBus/ModBusRtuClient.cs
IoTClient中ModBusRtu協議的使用
安裝
Nuget安裝 Install-Package IoTClient
或圖形化安裝
使用
//1、實例化客戶端 - [COM埠名稱,波特率,數據位,停止位,奇偶校驗]
ModBusRtuClient client = new ModBusRtuClient("COM3", 9600, 8, StopBits.One, Parity.None);
//2、寫操作 - 參數依次是:地址 、值 、站號 、功能碼
client.Write("4", (short)33, 2, 16);
client.Write("4", (short)3344, 2, 16);
//3、讀操作 - 參數依次是:地址 、站號 、功能碼
var value = client.ReadInt16("4", 2, 3).Value;
var value2 = client.ReadInt32("4", 2, 3).Value;
//4、如果沒有主動Open,則會每次讀寫操作的時候自動打開自動和關閉連接,這樣會使讀寫效率大大減低。所以建議手動Open和Close。
client.Open();
//5、讀寫操作都會返回操作結果對象Result
var result = client.ReadInt16("4", 2, 3);
//5.1 讀取是否成功(true或false)
var isSucceed = result.IsSucceed;
//5.2 讀取失敗的異常信息
var errMsg = result.Err;
//5.3 讀取操作實際發送的請求報文
var requst = result.Requst;
//5.4 讀取操作服務端響應的報文
var response = result.Response;
//5.5 讀取到的值
var value3 = result.Value;
參考
- 同步至索引目錄:《物聯網基礎組件IoTClient開發系列》
- https://baike.baidu.com/item/%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1
- https://baike.baidu.com/item/%E6%AF%94%E7%89%B9%E7%8E%87/1022775
- https://zh.wikipedia.org/wiki/%E5%A5%87%E5%81%B6%E6%A0%A1%E9%AA%8C%E4%BD%8D
- https://www.cnblogs.com/esestt/archive/2007/08/09/848856.html
- https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E5%86%97%E9%A4%98%E6%A0%A1%E9%A9%97