VB6实现三菱Q系列PLC MC协议通讯全解析
2026/9/13 14:59:57 网站建设 项目流程

简介:本资源是一套面向工业自动化开发者的VB上位机与三菱Q系列PLC通信实战源码,适用于初学者入门学习及工程师快速复用开发。聚焦串口通信场景,完整实现数据读写、寄存器映射、异常捕获与实时性保障等核心功能,解决VB与Q系列PLC联调中协议适配、格式转换与稳定性控制等典型工程问题。压缩包共51个文件,含6个VB源文件(Form1.vb、TcpClient.vbproj等构成可运行项目)、4个EXE可执行程序、6个DLL依赖库、3个XML配置及资源文件,辅以Sln解决方案、My Project配置项与PNG界面素材,结构完整、开箱即用,总大小仅277KB。内容预览显示项目已集成VS标准窗体架构与TCP/IP通信模块,具备良好可读性与调试基础。目前已有204人下载学习,读者可直接导入Visual Studio运行调试,掌握从串口初始化、指令构造、响应解析到PLC端I/O协同的全流程实现逻辑。

1. VB与三菱Q系列PLC通讯数据传输源码:不是调用控件就能跑通的“即插即用”

很多刚接触工业自动化上位机开发的工程师,看到“VB + Q系列PLC”这个组合,第一反应是:拖个ActiveX控件、填几个IP地址、点几下按钮,数据就该刷刷地传过来了。现实却常是——程序编译通过,但ReadDeviceRandom返回-1,WriteDeviceBlock超时无响应,甚至串口灯都不闪。根本原因在于:Q系列PLC不提供开箱即用的COM组件封装,其通讯协议(MC协议)需手动构造报文、校验、解析;而VB6对字节流、异步IO、结构体对齐的支持天然薄弱,直接套用FX系列或西门子的思路必然失败。本文面向已掌握VB6基础语法、了解PLC基本寄存器概念(D、M、W、R区)、但尚未在真实产线部署过Q系列通讯的开发者,从协议层拆解MC协议帧结构,给出可直接编译运行的VB6源码级实现(不含第三方OCX),覆盖TCP/IP和RS485两种物理链路,重点说明Q系列特有的网络号、站号、目标模块号三重寻址机制,以及如何规避VB6字符串内存模型导致的报文截断陷阱。


2. 理解MC协议本质:Q系列PLC通讯不是“读寄存器”,而是“发指令+收应答”

2.1 为什么不能用传统串口控件直接读写?——MC协议的三层封装结构

三菱Q系列PLC的通讯核心是MC协议(MELSEC Communication Protocol),它并非简单的Modbus RTU/ASCII映射,而是一个分层状态机协议。其报文结构严格分为三部分:

层级字段名长度说明
首部网络号(NET)1字节QnA/QnU等多网段系统中标识物理网络,单网段常设为0x00
站号(STN)1字节PLC站地址,出厂默认为0xFF(255),需在GX Works2中确认并匹配
目标模块号(UNIT)2字节指定CPU模块或扩展通讯模块编号,如CPU本体为0x0000,以太网模块为0x0001
命令部命令码(CMD)2字节如0x0000(批量读)、0x0001(批量写)、0x0004(监视读)
子命令(SUBCMD)2字节细化操作,如0x0000(按字读)、0x0001(按位读)
数据长度(LEN)2字节后续数据区字节数,非寄存器个数!需换算:D区读10个字=20字节
数据部起始地址(ADDR)4字节BCD编码的十六进制地址,如D100 →00 00 01 00(高位在前)
寄存器类型(TYPE)2字节D区=0x0000,M区=0x0001,W区=0x0002,R区=0x0003
实际数据可变写操作时填充,读操作时为空

提示:VB6的String类型默认为Unicode(UTF-16),每个字符占2字节,而MC协议要求纯字节流(Byte Array)。若用StrConv("0000", vbFromUnicode)转换,会因字节序错乱导致地址解析失败。必须全程使用Byte()数组拼接报文。

2.2 TCP与RS485链路的关键差异:不只是端口切换

Q系列PLC支持两种物理链路接入MC协议,但底层处理逻辑完全不同:

  • TCP/IP模式(推荐用于新项目)
    使用以太网模块(如QJ71E71-100),监听端口默认为5001(非Modbus的502)。需注意:

    • 连接建立后必须发送连接确认帧(05 00 00 00 00 00 00 00),否则PLC拒绝后续指令;
    • 每次读写操作后,PLC返回的应答帧包含错误码(ERR)字段(第10-11字节),常见值:0x0000(成功)、0x0001(目标模块不存在)、0x0004(地址超出范围)、0x000B(未启用远程RUN)。
  • RS485模式(兼容旧产线)
    通过QJ71C24N-R4串口模块,需配置:

    • 波特率:9600/19200/38400(Q系列默认9600,勿盲目设高);
    • 校验:Even(偶校验),非None
    • 停止位:1位;
    • 关键限制:单帧最大数据长度为256字节,超过需分片(如读D1000起500个字,需拆为2帧:D1000-255、D1255-245)。

2.3 VB6中构造MC协议报文的实操步骤

以下代码生成一个读取D100起10个字(20字节)的TCP请求帧:

' 定义报文各段字节数组 Dim Header() As Byte, CmdPart() As Byte, DataPart() As Byte Dim FullPacket() As Byte ' 首部:网络号0x00、站号0xFF、目标模块0x0000 Header = Array(&H00, &HFF, &H00, &H00) ' 命令部:CMD=0x0000(批量读)、SUBCMD=0x0000(按字)、LEN=0x0014(20字节) CmdPart = Array(&H00, &H00, &H00, &H00, &H00, &H14) ' 数据部:起始地址D100 → BCD编码00000100 → 字节序高位在前 → 00 00 01 00 ' 寄存器类型D区=0x0000 DataPart = Array(&H00, &H00, &H01, &H00, &H00, &H00) ' 合并为完整报文(注意:VB6数组合并需ReDim Preserve) ReDim FullPacket(0 To UBound(Header) + UBound(CmdPart) + UBound(DataPart) + 2) ' 复制首部 Call CopyMemory(FullPacket(0), Header(0), UBound(Header) + 1) ' 复制命令部(从索引6开始) Call CopyMemory(FullPacket(6), CmdPart(0), UBound(CmdPart) + 1) ' 复制数据部(从索引12开始) Call CopyMemory(FullPacket(12), DataPart(0), UBound(DataPart) + 1) ' 计算并填充报文长度(第4-5字节,大端序) Dim LenVal As Integer LenVal = UBound(FullPacket) + 1 ' 总长度 FullPacket(4) = (LenVal \ 256) And &HFF FullPacket(5) = LenVal And &HFF ' 此时FullPacket即为可发送的MC协议帧 ' 发送前需确保Socket已连接至PLC IP:5001 Winsock1.SendData FullPacket
2.3.1 关键参数说明与易错点
  • CopyMemoryAPI调用需声明:Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  • 地址BCD编码陷阱:D100的BCD值是00000100(8位),而非十六进制0064。VB6中用Format$(100, "00000000")生成字符串再转字节更可靠。
  • 字节序强制大端:Q系列所有多字节字段(如LEN、ADDR)均高位在前,VB6的Integer变量默认小端,必须手动拆解。
  • 报文长度字段位置:MC协议规定长度字段位于报文第4-5字节(索引3-4),且值为整个报文总长度(含首部),非数据部长度。

3. 在VB6中实现稳定通讯:Socket与MSComm控件的选型与配置

3.1 TCP通讯:Winsock控件的最小可靠配置

VB6自带Microsoft Winsock Control 6.0,但默认配置极易导致超时或粘包。必须调整以下属性:

属性推荐值说明
ProtocolsckTCPProtocol强制TCP,禁用UDP
RemotePort5001Q系列以太网模块默认端口,不可改
LocalPort0设为0由系统分配,避免端口冲突
SendTimeout3000单位毫秒,低于2000易误判超时
ReceiveTimeout5000PLCS响应较慢,需留足时间
' 连接PLC的完整流程(含错误处理) Private Sub ConnectToPLC() On Error GoTo ErrHandler Winsock1.RemoteHost = "192.168.1.10" ' PLC IP地址 Winsock1.Connect ' 等待连接建立(异步事件驱动) Do While Winsock1.State <> sckConnected DoEvents If Timer - StartTime > 10 Then ' 10秒超时 MsgBox "PLC连接超时,请检查IP和网络" Exit Sub End If Loop ' 发送连接确认帧(必需!) Dim ConfirmFrame() As Byte ConfirmFrame = Array(&H05, &H00, &H00, &H00, &H00, &H00, &H00, &H00) Winsock1.SendData ConfirmFrame Exit Sub ErrHandler: MsgBox "连接异常:" & Err.Description End Sub

注意:Winsock的DataArrival事件中,bytesTotal参数返回的是本次到达的字节数,但MC协议应答帧可能被TCP分片。必须缓存Winsock1.Input直到收到完整帧(根据首部LEN字段判断),否则解析必错。

3.2 RS485通讯:MSComm控件的致命陷阱与绕过方案

VB6的Microsoft Comm Control 6.0虽支持串口,但存在两个硬伤:

  • 无法设置偶校验(Even Parity):控件属性Settings"9600,E,8,1"E被忽略,实际仍为无校验;
  • InputLen=0时读取不完整:当PLC返回长应答帧(如读100个字),Input属性可能只返回前64字节。

解决方案:改用API直接操作串口

' 声明关键API Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" _ (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, _ ByVal dwShareMode As Long, lpSecurityAttributes As Any, _ ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, _ ByVal hTemplateFile As Long) As Long Private Declare Function SetCommState Lib "kernel32" (ByVal hCom As Long, lpDCB As DCB) As Long ' 配置串口为9600,E,8,1 Dim hCom As Long hCom = CreateFile("\\.\COM3", GENERIC_READ Or GENERIC_WRITE, 0, ByVal 0&, OPEN_EXISTING, 0, 0) If hCom = -1 Then Exit Sub Dim dcb As DCB dcb.DCBlength = Len(dcb) Call GetCommState(hCom, dcb) dcb.BaudRate = 9600 dcb.ByteSize = 8 dcb.Parity = 2 ' EVEN PARITY dcb.StopBits = 0 ' ONE STOP BIT Call SetCommState(hCom, dcb)
3.2.1 串口读取的防丢帧策略

PLC的RS485应答帧末尾固定为03(ETX),利用此特征做帧边界识别:

' 从串口读取完整帧(直到遇到03) Dim Buffer() As Byte, ReadBuf(0 To 1023) As Byte Dim BytesRead As Long, i As Long Do Call ReadFile(hCom, ReadBuf(0), 1024, BytesRead, ByVal 0&) If BytesRead > 0 Then ReDim Preserve Buffer(0 To UBound(Buffer) + BytesRead) For i = 0 To BytesRead - 1 Buffer(UBound(Buffer) - BytesRead + i + 1) = ReadBuf(i) Next i ' 检查是否收到ETX If Buffer(UBound(Buffer)) = &H3 Then Exit Do End If DoEvents Loop ' 此时Buffer即为完整MC应答帧

4. 解析PLC返回数据:从原始字节流到VB变量的精准映射

4.1 应答帧结构与错误码提取

MC协议应答帧格式与请求帧类似,但首部后紧跟错误码(ERR)数据长度(DATA LEN)

字段位置说明
首部0-5字节同请求帧
错误码6-7字节0x0000表示成功,非零值需查《MC协议手册》
数据长度8-9字节实际返回的数据字节数(如读10个字=20字节)
数据区10字节起原始字节流,需按寄存器类型解析
' 解析D区读取结果(假设读10个字,应答帧已存入RecvBuf()) Dim ErrCode As Integer ErrCode = (RecvBuf(6) * 256) + RecvBuf(7) ' 大端序转整数 If ErrCode <> 0 Then MsgBox "PLC错误码:" & Hex(ErrCode) Exit Sub End If Dim DataLen As Integer DataLen = (RecvBuf(8) * 256) + RecvBuf(9) ' 数据长度 ' 提取D区数据(每个字2字节,高位在前) Dim DValues(0 To 9) As Integer ' 存放10个D寄存器值 Dim i As Integer For i = 0 To 9 ' 从数据区第10字节开始,每2字节取一个字 DValues(i) = (RecvBuf(10 + i * 2) * 256) + RecvBuf(10 + i * 2 + 1) Next i ' 此时DValues(0)即为D100的值,DValues(1)为D101...

4.2 M区(位元件)的特殊解析:字节内位顺序反转

M区寄存器以位(bit)为单位,但MC协议将8个连续M位打包为1字节,且位序为M0-M7对应字节的bit7-bit0(即M0在最高位)。例如M0-M7 =10100000(二进制)→ 字节值&HA0,但VB6中&HA0 And &H01得到的是M7而非M0。

' 解析M0-M7的状态(假设RecvBuf(10) = &HA0) Dim MByte As Byte MByte = RecvBuf(10) Dim MStatus(0 To 7) As Boolean For i = 0 To 7 ' 将M0映射到位7,M1到位6...M7到位0 MStatus(i) = ((MByte And (2 ^ (7 - i))) <> 0) Next i ' MStatus(0)即为M0状态,MStatus(1)为M1...

4.3 R区(链接寄存器)的双字解析:处理32位浮点数

Q系列R区常用于传递浮点数,需按IEEE754标准解析。VB6无原生float支持,需手动组合:

' 读取R100起2个字(4字节),解析为Single Dim RBytes(0 To 3) As Byte RBytes(0) = RecvBuf(10) ' R100低字节 RBytes(1) = RecvBuf(11) ' R100高字节 RBytes(2) = RecvBuf(12) ' R101低字节 RBytes(3) = RecvBuf(13) ' R101高字节 ' 按IEEE754规则:[S][E E E E E E E E][M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M] ' VB6中用LSet和Type转换(此处简化为API调用) Dim fValue As Single Call CopyMemory(fValue, RBytes(0), 4) ' fValue即为R100的浮点数值

5. 生产环境必备技巧:心跳保活、批量读写优化与异常恢复

5.1 防止PLC主动断连:TCP心跳帧的发送时机

Q系列以太网模块默认30秒无通讯即断开连接。单纯用Timer每25秒发空帧风险高(可能与业务帧冲突)。正确做法是:在每次业务帧发送后,启动心跳计时器;若30秒内无新业务,则发心跳

' 全局变量 Dim LastActivity As Single ' 记录最后业务活动时间 Dim HeartbeatTimer As Single ' 发送任何MC帧后更新 LastActivity = Timer ' Timer控件Interval=1000,检查心跳 Private Sub Timer1_Timer() If Timer - LastActivity > 28 Then ' 提前2秒触发 Dim Heartbeat() As Byte Heartbeat = Array(&H05, &H00, &H00, &H00, &H00, &H00, &H00, &H00) Winsock1.SendData Heartbeat LastActivity = Timer ' 重置活动时间 End If End Sub

5.2 批量读写的性能瓶颈与突破:单帧最大长度利用

MC协议单帧TCP最大256字节,其中首部10字节、命令部6字节,剩余240字节为数据区。计算可读寄存器数:

  • D区(字):240 / 2 =120个字(D100-D219)
  • M区(位):240 * 8 =1920个位(M0-M1919)
' 优化后的批量读D区函数(自动分片) Public Sub ReadDRange(StartAddr As Long, Count As Long) Dim TotalRead As Long TotalRead = 0 Do While TotalRead < Count Dim BatchSize As Long BatchSize = IIf(Count - TotalRead > 120, 120, Count - TotalRead) ' 构造读BatchSize个字的帧(代码同2.3节) ' ...省略构造逻辑... ' 发送并接收 Winsock1.SendData FullPacket WaitForResponse ' 自定义等待函数 ' 解析结果并存入全局数组 ' ...省略解析逻辑... TotalRead = TotalRead + BatchSize DoEvents ' 防界面卡死 Loop End Sub

5.3 断线自动重连的鲁棒性设计:状态机而非简单Retry

简单On Error Resume Next+Retry会导致状态混乱。应构建三态机:

状态触发条件行为
stDisconnected初始化或断线清空缓冲区,重置LastActivity,尝试连接
stConnectingWinsock1_Connect触发发送连接确认帧,启动连接超时Timer
stConnected收到确认应答关闭连接Timer,启用业务Timer,允许发送
' 状态枚举 Private Enum ConnState stDisconnected = 0 stConnecting = 1 stConnected = 2 End Enum Private CurrentState As ConnState ' Winsock1_Close事件中 Private Sub Winsock1_Close() CurrentState = stDisconnected MsgBox "PLC连接已断开,正在重连..." ConnectToPLC ' 触发重连 End Sub

提示:重连时需延时(如Sleep 2000),避免高频重试触发PLC保护机制。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询