一. 背景
在做大型工業設備數據采集 監控的時候常遇到 PLC控制器,常見的三菱 台達 歐姆龍等,本文以三菱q系列為例,通過go語言編寫MC協議客戶端程序 實現數據寄存器的讀寫
二 . MC協議介紹
三菱官網有詳細文檔(參考q16 ,配備網絡模塊),此處不再贅述,文檔下載鏈接
上位機可以通過網絡調試助手測試通訊端口的配置狀況,二進制與ASCII碼兩種通訊方式協議內容有所不同,具體可以查詢三菱的通訊協議資料。我這邊主要使用的是如下這份,主要是其中的第3章 通過 QnA 兼容 3E/3C/4C 幀、4E 幀進行通信時
下載地址:https://pan.baidu.com/s/1jQs8l2M7IZHvMKFqrgP0qw
三. PLC 配置
3.1 自帶網口的CPU
GX Works 軟件打開【參數】-->>【PLC參數】-->>【內置以太網端口設置】配置IP,協議格式等參數。

然后進【打開設置】,如下是按TCP協議開放了兩個供上位機MC協議的端口。

這樣PLC端的配置就結束了。
3.2 使用網絡模塊QJ71E71-100通訊(界面與上述的內置網口PLC有所不同)
GX Works 軟件打開【參數】-->>【網絡參數】-->>【以太網***】配置IP,協議格式等參數。下圖中的初始時間設置,允許RUN中寫入一定要勾對!!!

然后進【打開設置】,如下是按TCP協議開放了一個供上位機MC協議的端口3210。

三 . 讀寫實例
讀寫方式有兩種,一種是用ASSIC 方式,另外一直是十六進制
1. 讀寫D7000 寄存器為例子
7000 的十六進制表示方式為 001B58,分配了三個字節,需要倒敘轉換581B00
如下指令為讀取D7000指令
發送:50 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 58 1B 00 A8 01 00
接收:D0 00 00 FF FF 03 00 04 00 00 00 0C 00
各個指令說明
副頭部 :5000 指令為5000,響應為D000
網絡編號:00
PLC編號:FF
IO編號:FF03
模塊站號:00
請求數據長度:0C00 請求數據長度計算為之后的所有數據
時鍾 :0100 表示等待PLC響應的timeout時間
高低位互換,實際為0001 即最大等待時間250ms*1=0.25秒
指令:0104 實際為0401,即為批量讀取 (后面單獨列出指令)
子指令:0000 值是0表示按字讀取(1個字=16位),如果值是1就按位讀取
首地址:58 1B 00 實際為001B58 十進制為7000
軟元件:表示讀取PLC寄存器的類型 A8 對應D點(后面有詳細對應)
長度:01
結束代碼:00
示例回復:
成功:D0 00 00 FF FF 03 00 04 00 00 00 0C 00(D7000寄存器數據為13)
副頭部:D000 網絡編號:00 PLC編號:FF
IO編號:FF03 模塊站號:00
應答數據長度:0400 實際為0004 即為4
異常代碼:0000 如果正常的話,就是0000
應答數據:0C00 實際為000C 即為13
2.寫入
如下指令為向D7000寫入H000C
發送:50 00 00 FF FF 03 00 OE 00 10 00 01 14 00 00 58 1B 00 A8 01 00 0C 00
接收:D0 00 00 FF FF 03 00 02 00 00 00

四. go 程序實現
可以用如下兩種方式實現
1. 采用modelbus tcp協議,引用go的net包
import (
"encoding/csv"
"fmt"
"net"
"os"
"log"
"time"
)
// 當前給D7000 寄存器寫值為 14
func writeTest(c net.Conn){
buf := make([]byte, 64)
// 寫入指令 50 00 00 FF FF 03 00 0E 00 10 00 01 14 00 00 58 1B 00 A8 01 00 0E 00
input := []byte{0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x0E, 0x00, 0x10, 0x00, 0x01, 0x14, 0x00, 0x00, 0x58, 0x1B, 0x00, 0xA8, 0x01, 0x00, 0x0E, 0x00}
//客戶端請求數據寫入 conn,並傳輸
c.Write([]byte(input))
//服務器端返回的數據寫入空buf
cnt, err := c.Read(buf)
if err != nil {
fmt.Println(err)
}
fmt.Println("寫入成功",cnt)
}
func ClientSocket() net.Conn {
conn, err := net.Dial("tcp", "192.168.18.44:5002")
if err != nil {
fmt.Println("客戶端建立連接失敗")
log.Printf("客戶端建立連接失敗:%s", err)
return nil
}
fmt.Println("客戶端建立成功")
log.Printf("客戶端建立連接成功:%s", "11")
return conn
}
2. 引用github.com/future-architect/go-mcprotocol/mcp包
代碼如下:
package main
import (
// "encoding/hex"
// "os"
// "strconv"
"strings"
"github.com/future-architect/go-mcprotocol/mcp"
"fmt"
"time"
"log"
"encoding/hex"
)
func main() {
//opts.Host, opts.Port
//192.168.18.34 2000
fmt.Println("start connect")
log.Printf("tufei :%s", "start connect")
// 43 139,44 5002
client, err := mcp.New3EClient("192.168.18.44", 5002, mcp.NewLocalStation())
fmt.Println("end connect")
log.Printf("tufei :%s", "end connect")
if err != nil {
fmt.Println(err)
//log.Printf("New3EClient err :%s", err)
}
fmt.Println("connect success!")
fmt.Println("start read !")
// 1 device
resp1, err := client.Read("D", 7000, 1)
if err != nil {
log.Printf("unexpected mcp read err: %v", err)
}
if len(resp1) != 13 {
log.Printf("expected %v but actual is %v", 13, len(resp1))
}
if hex.EncodeToString(resp1) != strings.ReplaceAll("d000 00 ff ff03 0004 0000 0000 00", " ", "") {
log.Printf("expected %v but actual is %X", "d00000ffff0300040000000000", hex.EncodeToString(resp1))
}
fmt.Println(resp1)
time.Sleep(100 * time.Second)
}
兩種方式都已驗證 確認ok
五.參考資料
https://www.cnblogs.com/haozhanggy/p/12213159.html
https://blog.csdn.net/cmwanysys/article/details/106681255