Modbus 協議是工業自動化領域應用最廣泛的通信協議之一,廣泛應用於 PLC、傳感器、儀表等設備之間的數據交換。在 .NET 8 中實現 Modbus 通訊工具類可以大大簡化工業控制系統的開發工作。本文將詳細介紹如何封裝一個功能完整的 Modbus 工具類,支持 RTU 和 TCP 兩種傳輸模式。


1. Modbus 協議基礎

Modbus 協議主要有兩種傳輸模式:

  • Modbus RTU:基於串行通信(RS232/RS485),使用二進制數據格式
  • Modbus TCP:基於以太網通信,使用 TCP/IP 協議棧


1.1 Modbus 功能碼

常用功能碼:

  • 0x01:讀線圈狀態
  • 0x02:讀輸入狀態
  • 0x03:讀保持寄存器
  • 0x04:讀輸入寄存器
  • 0x05:寫單個線圈
  • 0x06:寫單個寄存器
  • 0x0F:寫多個線圈
  • 0x10:寫多個寄存器


2. Modbus 工具類設計


2.1 基礎接口和枚舉

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

28

29

30

31

32

33

34

35

36


public enum ModbusType

{

RTU,

TCP

}

 

public enum ModbusFunctionCode : byte

{

ReadCoils = 0x01,

ReadDiscreteInputs = 0x02,

ReadHoldingRegisters = 0x03,

ReadInputRegisters = 0x04,

WriteSingleCoil = 0x05,

WriteSingleRegister = 0x06,

WriteMultipleCoils = 0x0F,

WriteMultipleRegisters = 0x10

}

 

public interface IModbusClient : IDisposable

{

Task<bool> ConnectAsync();

void Disconnect();

bool IsConnected { get; }

 

// 讀取操作

Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils);

Task<bool[]> ReadDiscreteInputsAsync(byte slaveId, ushort startAddress, ushort numberOfInputs);

Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);

Task<ushort[]> ReadInputRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);

 

// 寫入操作

Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value);

Task<bool> WriteSingleRegisterAsync(byte slaveId, ushort registerAddress, ushort value);

Task<bool> WriteMultipleCoilsAsync(byte slaveId, ushort startAddress, bool[] values);

Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values);

}



2.2 Modbus 異常處理

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


public class ModbusException : Exception

{

public byte ExceptionCode { get; }

 

public ModbusException(byte exceptionCode, string message)

: base(message)

{

ExceptionCode = exceptionCode;

}

}

 

public static class ModbusErrorCodes

{

public static readonly Dictionary<byte, string> Errors = new Dictionary<byte, string>

{

{ 0x01, "非法功能碼" },

{ 0x02, "非法數據地址" },

{ 0x03, "非法數據值" },

{ 0x04, "從站設備故障" },

{ 0x05, "確認" },

{ 0x06, "從屬設備忙" },

{ 0x07, "存儲奇偶性錯誤" },

{ 0x08, "不可用網關路徑" },

{ 0x09, "網關目標設備響應失敗" },

{ 0x0A, "網關目標設備響應失敗" }

};

}



3. Modbus RTU 實現


3.1 ModbusRtuClient 類

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

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164


using System.IO.Ports;

using System.Threading.Tasks;

 

public class ModbusRtuClient : IModbusClient

{

private readonly SerialPort _serialPort;

private readonly int _responseTimeout;

private readonly byte _defaultSlaveId;

 

public bool IsConnected => _serialPort?.IsOpen ?? false;

 

public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None,

int dataBits = 8, StopBits stopBits = StopBits.One,

int responseTimeout = 1000, byte defaultSlaveId = 1)

{

_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)

{

ReadTimeout = responseTimeout,

WriteTimeout = responseTimeout

};

_responseTimeout = responseTimeout;

_defaultSlaveId = defaultSlaveId;

}

 

public async Task<bool> ConnectAsync()

{

if (IsConnected) return true;

 

try

{

_serialPort.Open();

return true;

}

catch

{

return false;

}

}

 

public void Disconnect()

{

if (IsConnected)

{

_serialPort.Close();

}

}

 

public void Dispose()

{

Disconnect();

_serialPort.Dispose();

}

 

// 核心通信方法

private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] request)

{

if (!IsConnected) throw new InvalidOperationException("串口未連接");

 

// 添加CRC校驗

byte[] frame = new byte[request.Length + 2];

Array.Copy(request, frame, request.Length);

ushort crc = CalculateCrc(request);

frame[request.Length] = (byte)(crc & 0xFF);

frame[request.Length + 1] = (byte)((crc >> 8) & 0xFF);

 

// 發送請求

_serialPort.DiscardInBuffer();

_serialPort.Write(frame, 0, frame.Length);

 

// 接收響應

int expectedLength = GetExpectedResponseLength(request[1]);

byte[] response = new byte[expectedLength + 2]; // +2 for CRC

 

int bytesRead = 0;

int totalBytesToRead = response.Length;

DateTime startTime = DateTime.Now;

 

while (bytesRead < totalBytesToRead)

{

if ((DateTime.Now - startTime).TotalMilliseconds > _responseTimeout)

{

throw new TimeoutException("讀取響應超時");

}

 

if (_serialPort.BytesToRead > 0)

{

bytesRead += _serialPort.Read(response, bytesRead, totalBytesToRead - bytesRead);

}

else

{

await Task.Delay(10);

}

}

 

// 驗證CRC

ushort receivedCrc = (ushort)(response[response.Length - 2] | (response[response.Length - 1] << 8));

ushort calculatedCrc = CalculateCrc(response, 0, response.Length - 2);

 

if (receivedCrc != calculatedCrc)

{

throw new ModbusException(0, "CRC校驗失敗");

}

 

// 檢查異常響應

if ((response[1] & 0x80) != 0)

{

byte exceptionCode = response[2];

string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)

? msg : $"未知異常代碼: 0x{exceptionCode:X2}";

throw new ModbusException(exceptionCode, errorMessage);

}

 

return response;

}

 

private ushort CalculateCrc(byte[] data, int offset = 0, int length = -1)

{

if (length == -1) length = data.Length - offset;

 

ushort crc = 0xFFFF;

 

for (int i = offset; i < offset + length; i++)

{

crc ^= data[i];

for (int j = 0; j < 8; j++)

{

if ((crc & 0x0001) != 0)

{

crc >>= 1;

crc ^= 0xA001;

}

else

{

crc >>= 1;

}

}

}

 

return crc;

}

 

private int GetExpectedResponseLength(byte functionCode)

{

switch (functionCode)

{

case (byte)ModbusFunctionCode.ReadCoils:

case (byte)ModbusFunctionCode.ReadDiscreteInputs:

return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC

case (byte)ModbusFunctionCode.ReadHoldingRegisters:

case (byte)ModbusFunctionCode.ReadInputRegisters:

return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC

case (byte)ModbusFunctionCode.WriteSingleCoil:

case (byte)ModbusFunctionCode.WriteSingleRegister:

return 6; // SlaveId + FC + Address + Value + CRC

case (byte)ModbusFunctionCode.WriteMultipleCoils:

case (byte)ModbusFunctionCode.WriteMultipleRegisters:

return 6; // SlaveId + FC + Address + Quantity + CRC

default:

throw new ArgumentException($"未知功能碼: 0x{functionCode:X2}");

}

}

 

// 具體功能實現將在下面展開...

}



3.2 Modbus RTU 功能實現

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

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108


// 在 ModbusRtuClient 類中添加以下方法

 

public async Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils)

{

if (numberOfCoils < 1 || numberOfCoils > 2000)

throw new ArgumentException("線圈數量必須在1-2000之間");

 

byte[] request = new byte[6];

request[0] = slaveId;

request[1] = (byte)ModbusFunctionCode.ReadCoils;

request[2] = (byte)(startAddress >> 8);

request[3] = (byte)(startAddress & 0xFF);

request[4] = (byte)(numberOfCoils >> 8);

request[5] = (byte)(numberOfCoils & 0xFF);

 

byte[] response = await SendReceiveAsync(slaveId, request);

 

int byteCount = response[2];

bool[] coils = new bool[numberOfCoils];

 

for (int i = 0; i < numberOfCoils; i++)

{

int byteIndex = 3 + i / 8;

int bitIndex = i % 8;

coils[i] = (response[byteIndex] & (1 << bitIndex)) != 0;

}

 

return coils;

}

 

public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)

{

if (numberOfRegisters < 1 || numberOfRegisters > 125)

throw new ArgumentException("寄存器數量必須在1-125之間");

 

byte[] request = new byte[6];

request[0] = slaveId;

request[1] = (byte)ModbusFunctionCode.ReadHoldingRegisters;

request[2] = (byte)(startAddress >> 8);

request[3] = (byte)(startAddress & 0xFF);

request[4] = (byte)(numberOfRegisters >> 8);

request[5] = (byte)(numberOfRegisters & 0xFF);

 

byte[] response = await SendReceiveAsync(slaveId, request);

 

int byteCount = response[2];

ushort[] registers = new ushort[numberOfRegisters];

 

for (int i = 0; i < numberOfRegisters; i++)

{

int offset = 3 + i * 2;

registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);

}

 

return registers;

}

 

public async Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value)

{

byte[] request = new byte[6];

request[0] = slaveId;

request[1] = (byte)ModbusFunctionCode.WriteSingleCoil;

request[2] = (byte)(coilAddress >> 8);

request[3] = (byte)(coilAddress & 0xFF);

request[4] = value ? (byte)0xFF : (byte)0x00;

request[5] = 0x00;

 

byte[] response = await SendReceiveAsync(slaveId, request);

 

// 驗證響應

if (response.Length != 6) return false;

if (response[2] != request[2] || response[3] != request[3]) return false;

if (response[4] != request[4] || response[5] != request[5]) return false;

 

return true;

}

 

public async Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values)

{

if (values == null || values.Length == 0 || values.Length > 123)

throw new ArgumentException("寄存器數量必須在1-123之間");

 

byte[] request = new byte[7 + values.Length * 2];

request[0] = slaveId;

request[1] = (byte)ModbusFunctionCode.WriteMultipleRegisters;

request[2] = (byte)(startAddress >> 8);

request[3] = (byte)(startAddress & 0xFF);

request[4] = (byte)(values.Length >> 8);

request[5] = (byte)(values.Length & 0xFF);

request[6] = (byte)(values.Length * 2);

 

for (int i = 0; i < values.Length; i++)

{

request[7 + i * 2] = (byte)(values[i] >> 8);

request[8 + i * 2] = (byte)(values[i] & 0xFF);

}

 

byte[] response = await SendReceiveAsync(slaveId, request);

 

// 驗證響應

if (response.Length != 6) return false;

if (response[2] != request[2] || response[3] != request[3]) return false;

if (response[4] != request[4] || response[5] != request[5]) return false;

 

return true;

}

 

// 其他功能實現類似...



4. Modbus TCP 實現


4.1 ModbusTcpClient 類

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

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152


using System.Net.Sockets;

using System.Threading.Tasks;

 

public class ModbusTcpClient : IModbusClient

{

private TcpClient _tcpClient;

private NetworkStream _networkStream;

private readonly string _host;

private readonly int _port;

private readonly int _responseTimeout;

private readonly byte _defaultSlaveId;

private ushort _transactionId = 0;

 

public bool IsConnected => _tcpClient?.Connected ?? false;

 

public ModbusTcpClient(string host, int port = 502, int responseTimeout = 1000, byte defaultSlaveId = 1)

{

_host = host;

_port = port;

_responseTimeout = responseTimeout;

_defaultSlaveId = defaultSlaveId;

}

 

public async Task<bool> ConnectAsync()

{

if (IsConnected) return true;

 

try

{

_tcpClient = new TcpClient();

await _tcpClient.ConnectAsync(_host, _port);

_networkStream = _tcpClient.GetStream();

_networkStream.ReadTimeout = _responseTimeout;

return true;

}

catch

{

return false;

}

}

 

public void Disconnect()

{

if (IsConnected)

{

_networkStream?.Close();

_tcpClient?.Close();

_tcpClient = null;

_networkStream = null;

}

}

 

public void Dispose()

{

Disconnect();

_tcpClient?.Dispose();

_networkStream?.Dispose();

}

 

// 核心通信方法

private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] pdu)

{

if (!IsConnected) throw new InvalidOperationException("TCP連接未建立");

 

// 構建MBAP頭

byte[] mbapHeader = new byte[7];

ushort transactionId = _transactionId++;

mbapHeader[0] = (byte)(transactionId >> 8);

mbapHeader[1] = (byte)(transactionId & 0xFF);

mbapHeader[2] = 0x00; // 協議標識符高位

mbapHeader[3] = 0x00; // 協議標識符低位

mbapHeader[4] = (byte)((pdu.Length + 1) >> 8); // 長度高位

mbapHeader[5] = (byte)((pdu.Length + 1) & 0xFF); // 長度低位

mbapHeader[6] = slaveId; // 單元標識符

 

// 構建完整請求

byte[] request = new byte[mbapHeader.Length + pdu.Length];

Array.Copy(mbapHeader, request, mbapHeader.Length);

Array.Copy(pdu, 0, request, mbapHeader.Length, pdu.Length);

 

// 發送請求

await _networkStream.WriteAsync(request, 0, request.Length);

 

// 接收MBAP頭

byte[] mbapResponse = new byte[7];

int bytesRead = await _networkStream.ReadAsync(mbapResponse, 0, mbapResponse.Length);

 

if (bytesRead != mbapResponse.Length)

{

throw new IOException("讀取MBAP頭不完整");

}

 

// 驗證事務ID

ushort responseTransactionId = (ushort)((mbapResponse[0] << 8) | mbapResponse[1]);

if (responseTransactionId != transactionId)

{

throw new ModbusException(0, "事務ID不匹配");

}

 

// 獲取PDU長度

int pduLength = (mbapResponse[4] << 8) | mbapResponse[5];

 

// 接收PDU

byte[] pduResponse = new byte[pduLength - 1]; // 減去單元標識符

bytesRead = await _networkStream.ReadAsync(pduResponse, 0, pduResponse.Length);

 

if (bytesRead != pduResponse.Length)

{

throw new IOException("讀取PDU不完整");

}

 

// 檢查異常響應

if ((pduResponse[0] & 0x80) != 0)

{

byte exceptionCode = pduResponse[1];

string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)

? msg : $"未知異常代碼: 0x{exceptionCode:X2}";

throw new ModbusException(exceptionCode, errorMessage);

}

 

return pduResponse;

}

 

// 具體功能實現與RTU類似,只是使用PDU而不是完整幀

public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)

{

if (numberOfRegisters < 1 || numberOfRegisters > 125)

throw new ArgumentException("寄存器數量必須在1-125之間");

 

byte[] pdu = new byte[5];

pdu[0] = (byte)ModbusFunctionCode.ReadHoldingRegisters;

pdu[1] = (byte)(startAddress >> 8);

pdu[2] = (byte)(startAddress & 0xFF);

pdu[3] = (byte)(numberOfRegisters >> 8);

pdu[4] = (byte)(numberOfRegisters & 0xFF);

 

byte[] response = await SendReceiveAsync(slaveId, pdu);

 

int byteCount = response[1];

ushort[] registers = new ushort[numberOfRegisters];

 

for (int i = 0; i < numberOfRegisters; i++)

{

int offset = 2 + i * 2;

registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);

}

 

return registers;

}

 

// 其他功能實現類似...

}



5. 工廠模式創建 Modbus 客户端

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

28

29

30

31

32

33

34


public static class ModbusClientFactory

{

public static IModbusClient CreateClient(ModbusType type, string connectionString, byte defaultSlaveId = 1)

{

switch (type)

{

case ModbusType.RTU:

// 連接字符串格式: COM1,9600,None,8,One

string[] rtuParams = connectionString.Split(',');

if (rtuParams.Length < 1) throw new ArgumentException("無效的RTU連接字符串");

 

string portName = rtuParams[0];

int baudRate = rtuParams.Length > 1 ? int.Parse(rtuParams[1]) : 9600;

Parity parity = rtuParams.Length > 2 ? (Parity)Enum.Parse(typeof(Parity), rtuParams[2]) : Parity.None;

int dataBits = rtuParams.Length > 3 ? int.Parse(rtuParams[3]) : 8;

StopBits stopBits = rtuParams.Length > 4 ? (StopBits)Enum.Parse(typeof(StopBits), rtuParams[4]) : StopBits.One;

 

return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, defaultSlaveId: defaultSlaveId);

 

case ModbusType.TCP:

// 連接字符串格式: 192.168.1.100:502

string[] tcpParams = connectionString.Split(':');

if (tcpParams.Length < 1) throw new ArgumentException("無效的TCP連接字符串");

 

string host = tcpParams[0];

int port = tcpParams.Length > 1 ? int.Parse(tcpParams[1]) : 502;

 

return new ModbusTcpClient(host, port, defaultSlaveId: defaultSlaveId);

 

default:

throw new ArgumentException("不支持的Modbus類型");

}

}

}



6. 高級功能擴展


6.1 批量讀取優化

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


public static class ModbusExtensions

{

public static async Task<Dictionary<ushort, ushort>> ReadHoldingRegistersRangeAsync(

this IModbusClient client, byte slaveId,

ushort startAddress, ushort endAddress, int maxPerRequest = 100)

{

var results = new Dictionary<ushort, ushort>();

ushort current = startAddress;

 

while (current <= endAddress)

{

ushort count = (ushort)Math.Min(maxPerRequest, endAddress - current + 1);

ushort[] values = await client.ReadHoldingRegistersAsync(slaveId, current, count);

 

for (int i = 0; i < values.Length; i++)

{

results[(ushort)(current + i)] = values[i];

}

 

current += count;

}

 

return results;

}

}



6.2 自動重連機制

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

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69


public class AutoReconnectModbusClient : IModbusClient

{

private readonly IModbusClient _innerClient;

private readonly int _maxRetries;

private readonly int _retryDelay;

 

public bool IsConnected => _innerClient.IsConnected;

 

public AutoReconnectModbusClient(IModbusClient innerClient, int maxRetries = 3, int retryDelay = 1000)

{

_innerClient = innerClient;

_maxRetries = maxRetries;

_retryDelay = retryDelay;

}

 

public async Task<bool> ConnectAsync()

{

for (int i = 0; i < _maxRetries; i++)

{

if (await _innerClient.ConnectAsync())

return true;

 

await Task.Delay(_retryDelay);

}

return false;

}

 

public void Disconnect() => _innerClient.Disconnect();

 

public void Dispose() => _innerClient.Dispose();

 

private async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation)

{

for (int i = 0; i < _maxRetries; i++)

{

try

{

if (!_innerClient.IsConnected)

{

await ConnectAsync();

}

 

return await operation();

}

catch (IOException) when (i < _maxRetries - 1)

{

// 連接錯誤,嘗試重連

Disconnect();

await Task.Delay(_retryDelay);

}

catch (TimeoutException) when (i < _maxRetries - 1)

{

// 超時錯誤,嘗試重試

await Task.Delay(_retryDelay);

}

}

 

// 最後一次嘗試

return await operation();

}

 

public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)

{

return await ExecuteWithRetry(() =>

_innerClient.ReadHoldingRegistersAsync(slaveId, startAddress, numberOfRegisters));

}

 

// 其他方法實現類似...

}



7. 使用示例

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

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70


class Program

{

static async Task Main(string[] args)

{

// 創建Modbus TCP客户端

var tcpClient = ModbusClientFactory.CreateClient(

ModbusType.TCP, "192.168.1.100:502", defaultSlaveId: 1);

 

// 創建帶自動重連的客户端

var autoReconnectClient = new AutoReconnectModbusClient(tcpClient);

 

try

{

// 連接設備

if (await autoReconnectClient.ConnectAsync())

{

Console.WriteLine("Modbus連接成功");

 

// 讀取保持寄存器

ushort[] registers = await autoReconnectClient.ReadHoldingRegistersAsync(1, 0, 10);

Console.WriteLine("寄存器值: " + string.Join(", ", registers));

 

// 寫入單個寄存器

bool writeResult = await autoReconnectClient.WriteSingleRegisterAsync(1, 5, 1234);

Console.WriteLine($"寫入寄存器結果: {writeResult}");

 

// 批量讀取寄存器範圍

var registerMap = await autoReconnectClient.ReadHoldingRegistersRangeAsync(1, 0, 100);

foreach (var kvp in registerMap)

{

Console.WriteLine($"地址 {kvp.Key}: {kvp.Value}");

}

}

else

{

Console.WriteLine("Modbus連接失敗");

}

}

catch (ModbusException ex)

{

Console.WriteLine($"Modbus錯誤: {ex.Message} (代碼: 0x{ex.ExceptionCode:X2})");

}

catch (Exception ex)

{

Console.WriteLine($"發生錯誤: {ex.Message}");

}

finally

{

autoReconnectClient.Disconnect();

}

 

// 使用Modbus RTU

var rtuClient = ModbusClientFactory.CreateClient(

ModbusType.RTU, "COM3,9600,None,8,One", defaultSlaveId: 2);

 

try

{

if (await rtuClient.ConnectAsync())

{

// 讀取線圈狀態

bool[] coils = await rtuClient.ReadCoilsAsync(2, 0, 8);

Console.WriteLine("線圈狀態: " + string.Join(", ", coils.Select(c => c ? "1" : "0")));

}

}

finally

{

rtuClient.Disconnect();

}

}

}



8. 最佳實踐

1.連接管理

  • 保持連接時間儘可能短
  • 使用自動重連機制處理網絡波動
  • 合理設置超時時間

2.錯誤處理

  • 捕獲並處理所有Modbus異常
  • 實現重試機制處理臨時錯誤
  • 記錄詳細錯誤日誌

3.性能優化

  • 批量讀取數據減少請求次數
  • 使用異步方法避免阻塞
  • 合理設置請求大小(避免過大PDU)

4.線程安全

  • Modbus客户端不是線程安全的
  • 在多線程環境中使用鎖或創建多個實例
  • 避免併發訪問同一連接

5.配置管理

  • 將Modbus配置存儲在配置文件或數據庫中
  • 支持動態更新配置
  • 提供配置驗證功能


9. 總結

本文介紹瞭如何使用 .NET 8 實現一個功能完整的 Modbus 通訊工具類,主要特點包括:

1.雙協議支持

  • 完整實現 Modbus RTU 和 Modbus TCP 協議
  • 支持所有常用功能碼

2.優雅封裝

  • 統一的接口設計
  • 工廠模式創建客户端
  • 擴展方法提供高級功能

3.健壯性設計

  • 完善的錯誤處理機制
  • 自動重連功能
  • CRC 和事務ID驗證

4.易用性

  • 簡潔的API設計
  • 詳細的異常信息
  • 豐富的使用示例

通過這種封裝,開發者可以快速集成 Modbus 通訊功能到各種工業控制系統中,大大提高了開發效率和系統可靠性。該工具類適用於 SCADA 系統、MES 系統、設備監控平台等各種工業自動化場景。