目錄
- Modbus協議概述
- 協議架構與通信模式
- Modbus數據模型
- Modbus協議幀格式
- 功能碼詳解
- Go Modbus庫完整實現
- 高級應用示例
- 調試與故障排除
Modbus協議概述
Modbus是一種串行通信協議,由Modicon公司(現施耐德電氣)于1979年開發,用于PLC(可編程邏輯控制器)之間的通信。如今已成為工業領域最流行的通信協議之一。
主要特點:
- 主從架構:單一主設備,多個從設備
- 簡單高效:協議簡單,易于實現
- 開放標準:公開發布,無需授權費用
- 多接口支持:RS-232、RS-485、以太網等
協議架構與通信模式
通信模式對比
模式 | 傳輸介質 | 最大設備數 | 通信距離 | 速率 |
---|---|---|---|---|
RTU | RS-485 | 247 | 1200m | 115.2kbps |
ASCII | RS-232 | 247 | 15m | 19.2kbps |
TCP | 以太網 | 理論無限 | 網絡范圍 | 100/1000Mbps |
OSI模型中的位置
應用層 (7) ← Modbus協議
數據鏈路層 (2) ← Modbus RTU/ASCII
物理層 (1) ← RS-485/RS-232
Modbus數據模型
四種數據類型
數據類型 | 功能碼 | 讀寫權限 | 說明 |
---|---|---|---|
線圈 | 01,05,15 | 讀寫 | 布爾值,1位 |
離散輸入 | 02 | 只讀 | 布爾值,1位 |
保持寄存器 | 03,06,16 | 讀寫 | 16位整數 |
輸入寄存器 | 04 | 只讀 | 16位整數 |
地址映射示例
線圈: 00001-09999 (實際地址0-9998)
離散輸入: 10001-19999 (實際地址0-9998)
輸入寄存器: 30001-39999 (實際地址0-9998)
保持寄存器: 40001-49999 (實際地址0-9998)
Modbus協議幀格式
Modbus RTU幀格式
[地址][功能碼][數據][CRC校驗]
- 地址: 1字節,從設備地址(1-247)
- 功能碼: 1字節,操作類型
- 數據: N字節,具體操作數據
- CRC: 2字節,循環冗余校驗
Modbus TCP幀格式
[事務標識][協議標識][長度][單元標識][功能碼][數據]
- 事務標識: 2字節,請求響應匹配
- 協議標識: 2字節,通常為0
- 長度: 2字節,后續字節數
- 單元標識: 1字節,從站地址
功能碼詳解
常用功能碼表
功能碼 | 名稱 | 操作 | 最大數量 |
---|---|---|---|
0x01 | Read Coils | 讀線圈 | 2000線圈 |
0x02 | Read Discrete Inputs | 讀離散輸入 | 2000輸入 |
0x03 | Read Holding Registers | 讀保持寄存器 | 125寄存器 |
0x04 | Read Input Registers | 讀輸入寄存器 | 125寄存器 |
0x05 | Write Single Coil | 寫單個線圈 | 1線圈 |
0x06 | Write Single Register | 寫單個寄存器 | 1寄存器 |
0x0F | Write Multiple Coils | 寫多個線圈 | 1968線圈 |
0x10 | Write Multiple Registers | 寫多個寄存器 | 123寄存器 |
Go Modbus庫完整實現
完整的TCP客戶端實現
package mainimport ("encoding/binary""fmt""log""time""github.com/goburrow/modbus"
)// ModbusClient 封裝Modbus客戶端
type ModbusClient struct {handler modbus.ClientHandlerclient modbus.Clientaddress stringslaveID byte
}// NewModbusTCPClient 創建TCP客戶端
func NewModbusTCPClient(address string, slaveID byte, timeout time.Duration) (*ModbusClient, error) {handler := modbus.NewTCPClientHandler(address)handler.Timeout = timeouthandler.SlaveId = slaveIDhandler.Logger = &modbusLogger{}if err := handler.Connect(); err != nil {return nil, fmt.Errorf("連接失敗: %v", err)}return &ModbusClient{handler: handler,client: modbus.NewClient(handler),address: address,slaveID: slaveID,}, nil
}// Close 關閉連接
func (m *ModbusClient) Close() {if m.handler != nil {m.handler.Close()}
}// 自定義日志記錄器
type modbusLogger struct{}func (l *modbusLogger) Printf(format string, v ...interface{}) {log.Printf("[MODBUS] "+format, v...)
}// ReadHoldingRegistersWithRetry 帶重試的讀取保持寄存器
func (m *ModbusClient) ReadHoldingRegistersWithRetry(address, quantity uint16, retries int) ([]byte, error) {for i := 0; i < retries; i++ {results, err := m.client.ReadHoldingRegisters(address, quantity)if err == nil {return results, nil}log.Printf("第%d次讀取嘗試失敗: %v", i+1, err)time.Sleep(time.Duration(i+1) * time.Second) // 指數退避}return nil, fmt.Errorf("經過%d次重試后讀取失敗", retries)
}// ReadFloat32 讀取32位浮點數(兩個寄存器)
func (m *ModbusClient) ReadFloat32(address uint16) (float32, error) {data, err := m.ReadHoldingRegistersWithRetry(address, 2, 3)if err != nil {return 0, err}// 將兩個16位寄存器轉換為32位浮點數uintValue := binary.BigEndian.Uint32(data)return float32frombits(uintValue), nil
}// float32frombits 將32位無符號整數轉換為浮點數
func float32frombits(b uint32) float32 {return *(*float32)(unsafe.Pointer(&b))
}// WriteFloat32 寫入32位浮點數
func (m *ModbusClient) WriteFloat32(address uint16, value float32) error {// 將浮點數轉換為字節uintValue := *(*uint32)(unsafe.Pointer(&value))data := make([]byte, 4)binary.BigEndian.PutUint32(data, uintValue)_, err := m.client.WriteMultipleRegisters(address, 2, data)return err
}// ReadCoilsBatch 批量讀取線圈狀態
func (m *ModbusClient) ReadCoilsBatch(startAddress, quantity uint16) (map[uint16]bool, error) {results, err := m.client.ReadCoils(startAddress, quantity)if err != nil {return nil, err}coils := make(map[uint16]bool)for i := uint16(0); i < quantity; i++ {byteIndex := i / 8bitIndex := i % 8coils[startAddress+i] = (results[byteIndex] & (1 << bitIndex)) != 0}return coils, nil
}func main() {// 創建Modbus客戶端client, err := NewModbusTCPClient("192.168.1.100:502", 1, 10*time.Second)if err != nil {log.Fatal(err)}defer client.Close()// 示例:讀取溫度值(浮點數)temperature, err := client.ReadFloat32(100)if err != nil {log.Fatal("讀取溫度失敗:", err)}fmt.Printf("溫度: %.2f°C\n", temperature)// 示例:讀取線圈狀態coils, err := client.ReadCoilsBatch(0, 16)if err != nil {log.Fatal("讀取線圈失敗:", err)}for addr, state := range coils {fmt.Printf("線圈 %d: %t\n", addr, state)}
}
RTU客戶端增強實現
package mainimport ("log""time""github.com/goburrow/modbus"
)// ModbusRTUClient RTU客戶端
type ModbusRTUClient struct {handler *modbus.RTUClientHandlerclient modbus.Client
}// NewModbusRTUClient 創建RTU客戶端
func NewModbusRTUClient(port string, baudRate int, slaveID byte) (*ModbusRTUClient, error) {handler := modbus.NewRTUClientHandler(port)handler.BaudRate = baudRatehandler.DataBits = 8handler.Parity = "N"handler.StopBits = 1handler.SlaveId = slaveIDhandler.Timeout = 5 * time.Second// 設置串口參數handler.SerialPort = &serialPortConfig{ReadTimeout: 1 * time.Second,WriteTimeout: 1 * time.Second,}if err := handler.Connect(); err != nil {return nil, err}return &ModbusRTUClient{handler: handler,client: modbus.NewClient(handler),}, nil
}// 串口配置結構
type serialPortConfig struct {ReadTimeout time.DurationWriteTimeout time.Duration
}// ReadAllRegisters 讀取所有類型的寄存器
func (m *ModbusRTUClient) ReadAllRegisters() map[string]interface{} {result := make(map[string]interface{})// 讀取線圈if coils, err := m.client.ReadCoils(0, 16); err == nil {result["coils"] = coils}// 讀取離散輸入if inputs, err := m.client.ReadDiscreteInputs(0, 16); err == nil {result["discrete_inputs"] = inputs}// 讀取輸入寄存器if inputRegs, err := m.client.ReadInputRegisters(0, 10); err == nil {result["input_registers"] = inputRegs}// 讀取保持寄存器if holdingRegs, err := m.client.ReadHoldingRegisters(0, 10); err == nil {result["holding_registers"] = holdingRegs}return result
}
高級應用示例
設備監控系統
// DeviceMonitor 設備監控器
type DeviceMonitor struct {clients map[string]*ModbusClientinterval time.DurationstopChan chan bool
}// NewDeviceMonitor 創建設備監控器
func NewDeviceMonitor(interval time.Duration) *DeviceMonitor {return &DeviceMonitor{clients: make(map[string]*ModbusClient),interval: interval,stopChan: make(chan bool),}
}// AddDevice 添加設備
func (dm *DeviceMonitor) AddDevice(name, address string, slaveID byte) error {client, err := NewModbusTCPClient(address, slaveID, 10*time.Second)if err != nil {return err}dm.clients[name] = clientreturn nil
}// StartMonitoring 開始監控
func (dm *DeviceMonitor) StartMonitoring() {ticker := time.NewTicker(dm.interval)defer ticker.Stop()for {select {case <-ticker.C:dm.readAllDevices()case <-dm.stopChan:return}}
}// readAllDevices 讀取所有設備數據
func (dm *DeviceMonitor) readAllDevices() {for name, client := range dm.clients {go func(name string, client *ModbusClient) {// 讀取設備數據if data, err := client.ReadHoldingRegistersWithRetry(0, 10, 3); err == nil {log.Printf("設備 %s 數據: %v", name, data)} else {log.Printf("設備 %s 讀取失敗: %v", name, err)}}(name, client)}
}// Stop 停止監控
func (dm *DeviceMonitor) Stop() {close(dm.stopChan)for _, client := range dm.clients {client.Close()}
}
調試與故障排除
常見問題及解決方案
-
連接超時
- 檢查網絡連接
- 確認設備地址和端口
- 檢查防火墻設置
-
CRC校驗錯誤
- 檢查串口參數(波特率、數據位、停止位)
- 檢查物理連接質量
-
從設備無響應
- 確認從設備地址正確
- 檢查從設備是否在線
- 確認功能碼支持
調試工具推薦
- Modbus Poll:Windows平臺Modbus調試工具
- mbpoll:Linux命令行Modbus工具
- Wireshark:網絡協議分析工具(支持Modbus TCP)
# 使用mbpoll測試Modbus設備
mbpoll -a 1 -b 9600 -t 3 -r 100 -c 5 /dev/ttyUSB0
通過本教程,您不僅學會了Go Modbus庫的使用,還深入了解了Modbus協議的原理和實現細節。這些知識將幫助您更好地開發和調試工業自動化應用。