C# ModBus協(xié)議RTU 通訊詳解
前言
ModBus協(xié)議:官方的解釋是Modbus協(xié)議是一種通信協(xié)議,用于在自動化設(shè)備之間進行數(shù)據(jù)傳輸。它最初是由Modicon公司于1979年開發(fā)的,現(xiàn)在已成為工業(yè)界的一種通用協(xié)議。Modbus協(xié)議有多種變體,包括Modbus-RTU、Modbus-TCP和Modbus-ASCII等,其中Modbus-RTU是最常用的變體之一。Modbus協(xié)議基于主從結(jié)構(gòu)進行通信。主設(shè)備通過發(fā)送讀寫請求來與從設(shè)備進行通信,從設(shè)備則響應(yīng)這些請求。Modbus協(xié)議支持多種數(shù)據(jù)類型,包括線圈、離散輸入、保持寄存器和輸入寄存器等。在Modbus協(xié)議中,每個數(shù)據(jù)幀都包含了設(shè)備地址、功能碼、數(shù)據(jù)和錯誤檢查等信息。設(shè)備地址用于標識從設(shè)備,功能碼用于指定讀寫請求的類型,數(shù)據(jù)則包含了讀取或?qū)懭氲募拇嫫鞯刂泛蛿?shù)據(jù)值等信息。錯誤檢查通常使用CRC校驗碼來確保數(shù)據(jù)的完整性。Modbus協(xié)議廣泛應(yīng)用于工業(yè)自動化領(lǐng)域,可以用于控制器之間的通信、傳感器的數(shù)據(jù)采集和PLC與HMI之間的通信等。由于其簡單、可靠和易于實現(xiàn)等特點,Modbus協(xié)議仍然是工業(yè)界中最常用的通信協(xié)議之一。
講人話就是:規(guī)定了一個設(shè)備和軟件之間發(fā)送和接收數(shù)據(jù)的規(guī)則,根據(jù)這個規(guī)則數(shù)據(jù)格式去發(fā)送數(shù)據(jù)和接收數(shù)據(jù)
那么為什么要用這個協(xié)議呢:
- 可以實現(xiàn)跨平臺(Modbus協(xié)議可以在不同的硬件和操作系統(tǒng)平臺上運行,因此可以實現(xiàn)不同設(shè)備之間的互操作性)
- 可靠性高(Modbus協(xié)議支持多種錯誤檢測和糾正機制,包括CRC校驗和奇偶校驗等,從而保證了數(shù)據(jù)傳輸?shù)目煽啃裕?/li>
- 靈活性好(Modbus協(xié)議支持多種數(shù)據(jù)類型和數(shù)據(jù)格式,可以滿足不同應(yīng)用場景的需求。)
- 易于維護,成本低(由于Modbus協(xié)議是一種標準協(xié)議,因此可以使用各種現(xiàn)有的工具和庫來進行開發(fā)和維護,從而降低了開發(fā)和維護成本)
- 最關(guān)鍵一點也是最重要的一點就是:部署簡單,易于實現(xiàn)(Modbus協(xié)議是一種簡單的協(xié)議,易于實現(xiàn)和部署。這使得它成為許多工業(yè)設(shè)備和控制系統(tǒng)的標準通信協(xié)議)
就是因為它部署簡單容易實現(xiàn)才使得ModBus被廣泛運用
ModBus-RTU
自我理解:
Modbus-RTU是一個串口通訊的方式,主要分為五個部分一個設(shè)備ID號(占一個字節(jié))、功能號(占一個字節(jié))、寄存器地址(占兩個字節(jié))、讀取數(shù)據(jù)長度(占兩個字節(jié))、CRC16校驗碼(占兩個字節(jié)),通過對前四個設(shè)置值計算出CRC16 的檢驗碼記住(11222這個規(guī)則,代表字節(jié))發(fā)送的規(guī)則就是這樣,下面我們舉例說明更好的理解
官方解釋:
Modbus-RTU:RTU是Modbus-RTU協(xié)議的一部分,它代表“Remote Terminal Unit”,即遠程終端單元。在Modbus-RTU協(xié)議中,RTU是指一種串行通信方式,通常在RS-485物理層上運行。在這種通信方式中,數(shù)據(jù)以二進制編碼的形式傳輸,并使用16位CRC錯誤檢查。RTU協(xié)議是Modbus協(xié)議的一種變體,通常在RS-485物理層上運行。它是Modbus協(xié)議的一種子集,使用二進制編碼,支持16位CRC錯誤檢查。Modbus-RTU使用主從結(jié)構(gòu)進行通信。主設(shè)備通過發(fā)送讀寫請求來與從設(shè)備進行通信,從設(shè)備則響應(yīng)這些請求。Modbus-RTU支持讀寫線圈、離散輸入、保持寄存器和輸入寄存器等四種數(shù)據(jù)類型。在Modbus-RTU中,每個數(shù)據(jù)幀都包含了設(shè)備地址、功能碼、數(shù)據(jù)和CRC校驗。設(shè)備地址用于標識從設(shè)備,功能碼用于指定讀寫請求的類型,數(shù)據(jù)則包含了讀取或?qū)懭氲募拇嫫鞯刂泛椭?,CRC校驗用于檢查數(shù)據(jù)傳輸?shù)恼_性。由于Modbus-RTU是一種比較簡單的協(xié)議,因此它在工業(yè)自動化領(lǐng)域得到了廣泛的應(yīng)用。
我們來解析一下這個命令的值
設(shè)備ID | 功能號選擇 | 寄存器地址 | 讀數(shù)據(jù)長度 | CRC16 |
---|---|---|---|---|
01 | 03 | 00 00 | 00 0A | C5 CD |
1 | 1 | 2 | 2 | 2 |
01:表示從設(shè)備的地址,這里為1。
03:表示Modbus讀取保持寄存器的功能碼。
00 00:表示要讀取的保持寄存器的起始地址,這里為0。
00 0A:表示要讀取的保持寄存器的數(shù)量,這里為10。 (我們需要讀取多少個值)
C5 CD:表示CRC校驗碼,用于檢查命令是否正確。
因此,這個命令的含義是從地址為1的Modbus設(shè)備中讀取0~9號保持寄存器的值。
注:以11222的格式來看這個命令,只有功能號是需要選擇,CRC校驗碼是計算出來的,其他的都可以根據(jù)我們的需求去定義
常用的功能號
Modbus-RTU的功能碼是用于指示Modbus協(xié)議進行何種數(shù)據(jù)操作的標識符。以下是常用的Modbus-RTU功能碼及其含義:
01:讀取線圈狀態(tài),用于讀取開關(guān)量輸入(DO)。
02:讀取離散輸入狀態(tài),用于讀取開關(guān)量輸入(DI)。
03:讀取保持寄存器,用于讀取模擬量輸入(AI)。
04:讀取輸入寄存器,用于讀取模擬量輸入(AI)。
05:寫單個線圈,用于控制開關(guān)量輸出(DO)。
06:寫單個保持寄存器,用于控制模擬量輸出(AO)。
15:寫多個線圈,用于控制多個開關(guān)量輸出(DO)。
16:寫多個保持寄存器,用于控制多個模擬量輸出(AO)。
需要注意的是,不同設(shè)備支持的功能碼可能不同,因此在使用Modbus-RTU通信時需要根據(jù)實際情況選擇合適的功能碼。
注: ModBus通訊是基于一個主站和從站的基礎(chǔ),我需要一個服務(wù)端,一般我們使用PLC為主站,也就是服務(wù)端,而我們的軟件是需要連接主站的服務(wù)器進行通訊的,我采用一個模擬的ModBus的主站方便通訊
發(fā)送命令我們得到了一個答復(fù),我們可以看到我們的服務(wù)端的數(shù)據(jù)
01 03 14 00 01 00 02 00 03 00 04 00 00 00 00 00 07 00 08 00 09 00 04 34 BE
01 設(shè)備ID號
03 功能號
14 數(shù)據(jù)長度 這里是16進制 14===》20 ,有20個數(shù)據(jù)因為我們收到數(shù)據(jù)是兩個字節(jié),所有是2*10 =20
00 01 讀取到0x0000寄存器地址的數(shù)據(jù)(兩個字節(jié))(高位在前,低位在后)
00 02
00 03
00 04
00 00
00 00
00 07
00 08
00 09
00 04 數(shù)據(jù)(兩個字節(jié))
34 BE CRC16校驗碼(兩個字節(jié))
注意:接收數(shù)據(jù)的長度最大是127個
**注意:注意:注意:接收的數(shù)據(jù)是高位在前低位在后 **
比如我在0x1000 地址取一個,得到的數(shù)據(jù)是2個字節(jié) 比如接收的是01 03 02 0x01 0x02 CRC CRC 那么 高字節(jié)就是01 低字節(jié)就是02
就是0x0102 我們在計算的時候要么就用 int num = 0x01; num = (num<<8)+0x2;或者你把這兩個字節(jié)賦值給一個2個為的byte數(shù)組,再用數(shù)組反轉(zhuǎn),使用BitConverter.ToUInt16(數(shù)組,起始位置);
byte[] num = new byte[2];
Array.Copy(data, 0, num, 0, 2);//將需要的數(shù)據(jù)復(fù)制
num.Reverse();//將需要的數(shù)據(jù)反轉(zhuǎn),因為BitConverter 是低位在前高位在后
UInt16 number = BitConverter.ToUInt16(num,0); //從0位置默認取兩個,不會的BitConverter的可以看我前面的文章
ModBus-RTU報錯代碼及解決辦法
錯誤代碼 01:讀取離散輸入量時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 02:讀取線圈時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 03:讀取保持寄存器時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 04:讀取輸入寄存器時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 05:寫單個線圈時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 06:寫單個保持寄存器時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 07:讀取異常狀態(tài)時,請求的數(shù)據(jù)值無效。解決方法:檢查請求的數(shù)據(jù)值是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 08:寫多個線圈時,請求的數(shù)據(jù)值無效。解決方法:檢查請求的數(shù)據(jù)值是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 09:寫多個保持寄存器時,請求的數(shù)據(jù)值無效。解決方法:檢查請求的數(shù)據(jù)值是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 10:讀取文件記錄時,請求的文件號無效。解決方法:檢查請求的文件號是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 11:寫文件記錄時,請求的文件號無效。解決方法:檢查請求的文件號是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 12:屏蔽寫寄存器時,請求地址錯誤或無法訪問該地址。解決方法:檢查請求地址和設(shè)備是否正確連接。如果地址正確,可能是設(shè)備故障或通信線路問題。
錯誤代碼 13:讀/寫多個寄存器時,請求的數(shù)據(jù)值無效。解決方法:檢查請求的數(shù)據(jù)值是否有效,并且設(shè)備是否正確響應(yīng)請求。
錯誤代碼 14:ModBus 從站設(shè)備忙。解決方法:等待從站設(shè)備空閑,并重新發(fā)送請求。
錯誤代碼 15:ModBus 從站設(shè)備返回錯誤異常碼。解決方法:參考 ModBus 協(xié)議文檔中的異常碼表,并進行相應(yīng)的處理。
錯誤代碼 16:設(shè)備返回的數(shù)據(jù)長度錯誤。解決方法:檢查設(shè)備是否正確響應(yīng)請求,并且返回的數(shù)據(jù)長度是否與請求匹配。如果不匹配,可能是設(shè)備故障或通信線路問題。
錯誤代碼 17:設(shè)備返回的數(shù)據(jù)值錯誤。解決方法:檢查設(shè)備是否正確響應(yīng)請求,并且返回的數(shù)據(jù)值是否正確。如果不正確,可能是設(shè)備故障或通信線路問題。
C# 連接ModBus-RTU詳解
我是使用第三方庫,使用NModBus4
using System;
using System.IO.Ports;
using NModbus;
namespace ModbusRtuExample
{
class Program
{
static void Main(string[] args)
{
// 創(chuàng)建SerialPort對象來配置串口參數(shù)
SerialPort serialPort = new SerialPort("COM1");
serialPort.BaudRate = 9600;
serialPort.DataBits = 8;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
// 創(chuàng)建ModbusFactory對象來進行Modbus RTU通信
IModbusFactory modbusFactory = new ModbusFactory();
IModbusMaster modbusMaster = modbusFactory.CreateRtuMaster(serialPort);
try
{
// 打開串口連接
serialPort.Open();
// 使用Modbus函數(shù)來讀取和寫入數(shù)據(jù)
ushort startAddress = 0;
ushort numRegisters = 10;
ushort[] registers = modbusMaster.ReadHoldingRegisters(1, startAddress, numRegisters);
Console.WriteLine($"讀取位保持寄存器地址 {startAddress} 開始的 {numRegisters} 個寄存器:");
for (int i = 0; i < registers.Length; i++)
{
Console.WriteLine($"寄存器 {startAddress + i}: {registers[i]}");
}
// 寫入保持寄存器的值
ushort[] writeValues = new ushort[] { 10, 20, 30, 40, 50 };
modbusMaster.WriteMultipleRegisters(1, startAddress, writeValues);
Console.WriteLine($"將 {writeValues.Length} 個值寫入位保持寄存器地址 {startAddress} 開始的寄存器。");
// 讀取離散輸入的值
bool[] inputs = modbusMaster.ReadInputs(1, startAddress, numRegisters);
Console.WriteLine($"讀取離散輸入地址 {startAddress} 開始的 {numRegisters} 個輸入:");
for (int i = 0; i < inputs.Length; i++)
{
Console.WriteLine($"輸入 {startAddress + i}: {inputs[i]}");
}
// 關(guān)閉串口連接
serialPort.Close();
}
catch (Exception ex)
{
Console.WriteLine($"發(fā)生錯誤:{ex.Message}");
}
}
}
}
以下是NModbus4庫中常用的一些函數(shù):
01:讀取線圈狀態(tài)
02:讀取離散輸入狀態(tài)
03:讀取保持寄存器的值
04:讀取輸入寄存器的值
05:寫單個線圈狀態(tài)
06:寫單個保持寄存器的值
0F:寫多個線圈狀態(tài)
10:寫多個保持寄存器的值
讀取線圈狀態(tài):
bool[] coils = modbusMaster.ReadCoils(slaveAddress, startAddress, numCoils);
寫入單個線圈狀態(tài):
modbusMaster.WriteSingleCoil(slaveAddress, coilAddress, value);
寫入多個線圈狀態(tài):
bool[] coilValues = new bool[] { true, false, true };
modbusMaster.WriteMultipleCoils(slaveAddress, startAddress, coilValues);
讀取離散輸入狀態(tài):
bool[] inputs = modbusMaster.ReadInputs(slaveAddress, startAddress, numInputs);
讀取保持寄存器的值:
ushort[] registers = modbusMaster.ReadHoldingRegisters(slaveAddress, startAddress, numRegisters);
寫入單個保持寄存器的值:
modbusMaster.WriteSingleRegister(slaveAddress, registerAddress, value);
寫入多個保持寄存器的值:
ushort[] registerValues = new ushort[] { 100, 200, 300 };
modbusMaster.WriteMultipleRegisters(slaveAddress, startAddress, registerValues);
讀取輸入寄存器的值:
ushort[] inputs = modbusMaster.ReadInputRegisters(slaveAddress, startAddress, numInputs);
使用自定義方式發(fā)送
我們在使用自定義的方式的時候,需要知道設(shè)備號,功能碼,地址,數(shù)據(jù)個數(shù),CRC16校驗,比如我們只想使用03功能碼,我們就可以自己封裝一個03功能碼發(fā)送的方法,比如下圖,我的實參是地址和長度,里面的設(shè)備號,功能碼我都是固定的,然后計算CRC16,最后使用Serport的write發(fā)送,read接收數(shù)據(jù)校驗CRC16碼,不會serport的看我之前的文章。
注意:CRC16 校驗是從設(shè)備號開始到CRC校驗碼之前,也就是最后兩個字節(jié)除外,因為用前面的才能算出后兩個字節(jié)的CRC16校驗碼
public byte[] SendGen(UInt16 address,UInt16 number)
{
try
{
byte[] data1 = new byte[6];
data1[0] = 0x01;
data1[1] = 0x03;
data1[2] = Convert.ToByte((address >> 8) & 0xff);
data1[3] = Convert.ToByte(address & 0xff);
data1[4] = Convert.ToByte((number >> 8) & 0xff);
data1[5] = Convert.ToByte(number & 0xff);
byte[] data2 = new byte[2];
data2 = CRC16(data1);
byte[] data3 = new byte[8];
Array.Copy(data1, 0, data3, 0, 6);//CRC16校驗,從前面設(shè)備號一直到CRC碼之前的數(shù)據(jù)
Array.Copy(data2, 0, data3, 6, 2);
return data3;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return null;
}
}
CRC16 校驗
public static byte[] CRC16(byte[] data)
{
ushort crc = 0xFFFF;
for (int i = 0; i < data.Length; i++)
{
crc ^= (ushort)data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}
使用自定義封裝的功能方法,比較好查BUG,靈活度就高一點,我們在使用第三方庫的時候,有時數(shù)據(jù)不對,第三方庫不好打斷點,所以我們用自定義封裝的就不會出現(xiàn)這種情況,可以清楚知道哪里的問題。文章來源:http://www.zghlxwxcb.cn/news/detail-763436.html
總結(jié)
ModBus RTU 是我們廣泛使用的,在我們的PLC或者上位機里面用的比較多。Modbus協(xié)議廣泛應(yīng)用于工業(yè)自動化領(lǐng)域,可以用于控制器之間的通信、傳感器的數(shù)據(jù)采集和PLC與HMI之間的通信等。由于其簡單、可靠和易于實現(xiàn)等特點,Modbus協(xié)議仍然是工業(yè)界中最常用的通信協(xié)議之一。講人話:就是規(guī)定了一個設(shè)備和軟件之間發(fā)送和接收數(shù)據(jù)的規(guī)則,根據(jù)這個規(guī)則數(shù)據(jù)格式去發(fā)送數(shù)據(jù)和接收數(shù)據(jù);牢記使用的功能碼文章來源地址http://www.zghlxwxcb.cn/news/detail-763436.html
到了這里,關(guān)于C# ModBus協(xié)議(RTU )詳細指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!