简介这份源码包面向工业自动化开发者聚焦Visual Basic与三菱Q系列PLC之间的数据通信实现适合需要搭建上位机监控或调试串口/TCP通讯的入门及进阶人员。资源共51个文件压缩包仅277KB包含VB项目源文件、可执行程序、动态链接库及调试符号等其中vb与exe文件便于直接查看和运行dll与pdb可辅助理解底层调用与排错配置文件和资源文件则展示了通讯参数的设置方式。目前已有204人学习下载。通过剖析源码可掌握串口或网络通讯的初始化、读写指令封装、数据解析及异常处理等关键环节也能了解PLC端寄存器映射与数据格式转换的配合思路为实际工程项目提供可直接借鉴的代码框架和排错经验。1. 通讯数据传输这块硬骨头为什么值得啃做自动化项目集成的人十有八九都遇到过这个需求上位机要用 VB 写的老系统去跟三菱 Q 系列 PLC 交换数据。别小看这件事很多工程师一听到“VB”就觉得是上个世纪的东西一听“Q系列”又觉得是三菱的旧平台两者凑一块儿第一反应往往是“有现成控件吗直接拖拽行不行” 真去做了才发现通讯这事儿比想象中复杂得多协议帧格式、站号设置、寄存器地址映射、握手时序哪一个环节不对数据就是乱码或者干脆超时。这篇笔记要解决的就是这个“VB 与三菱 Q 系列 PLC 通讯数据传输”从能通到通得稳的问题。不管是设备改造、产线数据采集还是老系统升级VB 配合 Q 系列 PLC 依然是很常见的一条落地路径。适合谁正在维护老产线系统的工程师、刚接手工厂自动化项目的新手、想把上位机数据打通到 MES 的开发者。文章会先从通讯协议和选型讲清楚再给出可运行的代码框架、参数设置和排错手段最后聊一些让通讯更稳的进阶做法。2. 选对通讯通道MC协议、串口与以太网哪种适合你的场景2.1 三菱Q系列通讯的三种常见通道与适用边界三菱 Q 系列 PLC 对外通讯主要走三种物理通道串口RS232/RS422/RS485、以太网内置或加装以太网模块、以及总线通道如 CC-Link。对于 VB 上位机开发来说总线方案通常不是首选因为它牵扯到专用板卡和驱动绝大多数场景直接用串口或者以太网就能解决。串口的优势是稳定、成本低、抗干扰设计成熟缺点是速率受限而且如果通讯距离远、节点多需要用好 RS485 的收发控制和终端电阻。以太网的优势是速度快、布线简单、可以和上位机网络共用缺点是如果现场网络环境乱丢包和延迟会让通讯表现飘忽不定。从协议层看无论走串口还是以太网三菱 Q 系列最常用的都是 MC 协议Melsec Communication Protocol。这是三菱官方定义的通讯格式通过帧结构来承载读写请求。用 VB 实现时本质上就是按照 MC 协议去组帧、发送、解析响应。理解了这一点就不容易被各种封装库和控件牵着走——底层逻辑都是同一套。2.2 为什么我倾向于用以太网 MC协议如果你问我做项目时怎么选我一般会优先考虑以太网。原因很实际Q 系列 PLC 很多型号本身自带以太网口或者可以加装以太网模块例如 QJ71E71 这类常见模块通讯不需要额外搭建串口线路直接用网线连接交换机就能和上位机通讯。调试的时候也可以借用现有的网络工具去排查问题比串口要直观。另一个原因是 VB 里用 WinSock 控件或者 Socket API 做 TCP 客户端非常成熟代码量不大网上也有大量可以参考的实现思路。MC 协议走以太网时Q 系列支持两种模式一种是二进制码帧数据紧凑、适合程序内部处理另一种是 ASCII 码帧可读性强、方便人工排查报文内容。早期 VB 项目里我见过不少工程师为了调试方便选 ASCII 模式因为可以在调试工具里直接看到组帧内容。但如果对通讯效率有要求或者数据块比较大二进制模式会更好。下面章节的代码示例会以 ASCII 模式为主因为它的帧结构更直观方便理解协议项目里真要换二进制模式改动也主要是帧格式部分。2.3 串口通道在什么场景下仍然值得用串口通道也不该被一棒子打死。老设备、距离短、数据量小、抗干扰要求高的场景串口依然很稳健。尤其是 RS422 全双工长距离传输比 RS232 更可靠在电噪声大的车间里表现反而比脆弱的有线网络好。如果 PLC 侧不带以太网模块用串口是最经济的选择。只是串口通讯要多花一点心思在参数匹配上波特率、数据位、停止位、校验位PLC 侧通讯参数与 VB 串口控件属性必须一一对应。常见坑是PLC 侧默认波特率是 9600而 VB 串口控件里配了 19200结果就是通讯完全没反应或者偶尔收到一帧错乱数据。串口协议上同样是 MC 协议只是帧格式里多了串口控制的细节比如帧头、站号、监视定时器、末尾的校验码通常用累加和校验。以太网帧通常没有站号IP 和端口承担了寻址任务串口帧则必须指定 PLC 站号。所以即使同样的读写功能串口与以太网的帧结构也有区别代码不能直接复用这也是很多初学者把例程从以太网搬到串口后跑不通的原因。3. 数据读写最小实现从建立TCP连接到解析帧响应3.1 先搭一个能跑的Socket客户端基座在 VB 中实现以太网通讯最直接的方式是使用 WinSock 控件或者调用 Winsock API。WinSock 控件对新手更友好拖到窗体上、设几个属性就能用。下面这段代码演示了如何初始化控件并连接到 PLC 的以太网模块其中 PLC 侧 IP 与端口号需要根据实际模块配置去填。 使用WinSock控件连接三菱Q系列PLC以太网模块 With Winsock1 .Protocol sckTCPProtocol 使用TCP协议 .RemoteHost 192.168.1.10 PLC以太网模块IP地址 .RemotePort 5000 PLC以太网模块端口号常见默认配置 .Connect 发起连接 End With这段代码的逻辑就是配置远端地址和端口然后建立 TCP 连接。RemoteHost 要和 PLC 侧网络参数对应RemotePort 默认我们项目里用的是 5000Q 系列内置以太网口的常用端口加装模块等不同情况下端口号可能不同。连接成功后WinSock 控件的 Connect 事件会触发在此之后才能进行数据读写。参数说明上如果你的模块是 QJ71E71端口号通常可以自由设置但必须和 PLC 参数中打开的端口一致否则连接直接被拒。连接建立后下一步就是发送 MC 协议帧。这是整个通讯里最关键的部分帧格式错了PLC 会返回错误码甚至直接丢弃报文。3.2 组装一条读寄存器的MC协议帧以读 D 寄存器数据寄存器为例ASCII 模式的读取帧由多个部分组成帧头、命令、起始地址、软元件代码、读取长度、结束符。Q 系列 MC 协议的读命令是 0x01ASCII 中是“0101”地址部分需要做转换不同软元件的地址偏移不一样。D 寄存器的起始地址通常是从 0 开始计算但协议里需要加上一个基准地址偏移量。 拼接读取D100开始的10个字的请求帧ASCII模式 Dim strFrame As String strFrame strFrame strFrame 0101 帧头与读命令 strFrame strFrame 0064 起始地址D100的十六进制表示 strFrame strFrame 00A8 D寄存器在MC协议中的地址偏移 strFrame strFrame 000A 读取长度10个字 strFrame strFrame 0000 结束符 Winsock1.SendData strFrame这段代码的核心是理解了 MC 协议帧各段的含义。帧长方面ASCII 模式每两个字符表示一个字节因此实际发送的字节数等于字符串长度的一半。起始地址那两段其实对应一个完整的软元件地址偏移量D 寄存器的基准偏移为 0x00A8所以 D100 的地址偏移计算方式是0x00A8 100 0x010C而不是直接在帧里写“0064”。上面的示例里为了演示简化成了两段拼接真正项目里应该写成Dim lAddr As Long lAddr 100 HA8 加上D寄存器基准偏移 Dim strAddr As String strAddr Right(000 Hex(lAddr), 4) 转成4位十六进制 strFrame 0101 strAddr 000A 00003.3 解析响应帧从乱码里捞出真实数值PLC 收到请求后会回一帧响应。响应的结构是帧头D0 命令和请求对应 响应状态码 数据。先判断状态码是否为 0如果为 0 说明请求成功后续才是真正的数据。以读 D 寄存器的响应为例返回数据的每个字固定为 4 个字符ASCII 模式的十六进制按顺序排列。 解析PLC响应将十六进制字符串转为整型数组 Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Dim strData As String Winsock1.GetData strData 假设响应为 D0 0101 00 后跟 10 个字的数据 If Mid(strData, 3, 4) 0101 And Mid(strData, 7, 2) 00 Then Dim i As Integer For i 0 To 9 Dim strWord As String strWord Mid(strData, 9 i * 4, 4) 将十六进制转为十进制数值并存入数组 DValues(i) Val(H strWord) Next i Else 错误处理解析响应状态码定位通讯问题 Debug.Print PLC返回错误状态码 Mid(strData, 7, 2) End If End Sub这段代码把响应后段的数据切分成每 4 个字符一组然后按十六进制转成十进制数值。值得注意的地方是PLC 返回的字数据在高字节和低字节的排列顺序上可能与直觉相反但 MC 协议的 ASCII 模式直接按地址顺序排列不用做字节交换这一点比二进制帧要省事。上面代码里的Val(H strWord)在 VB 里是把十六进制字符串转换为数值的常见写法但它的行为对超出整数范围的值会有溢出风险如果读取的是 32 位数据比如 D 和 D1 组合需要分两次解析再拼接或者使用CLng(H strWord)来避免溢出。4. 写数据也绕不开的帧结构从组帧到错误码对照4.1 单个字的写入怎么构造请求读数据解决了写数据的帧结构也差不多只是命令码从读变成了写且多了要写入的数据段。MC 协议里写单个字的命令是 0x01ASCII 中是“0101”但和读命令是同一个命令码区别在于地址后面多了数据字段。实际上三菱 MC 协议的 ASCII 写命令是用 0x01 后面接地址和写入值。注意不要和读流程混淆。 拼接写入D200 1234十六进制为 04D2的请求帧 Dim strData As String strData 0101 00C8 000A 04D2 0000 Winsock1.SendData strData这里00C8是 D200 与 D 寄存器基准偏移 0x00A8 之和 0x00C8 的十六进制表示。如果是自己组地址务必先加上偏移再转十六进制否则地址就偏了。写入长度的位置在这个帧里实际上对应的是数据内容而不是长度这个很容易弄混。真正协议里写单字的帧是命令码 起始地址 数据内容 结束符长度字段只出现在批量写或读请求里。所以更严谨的写法应当区分单字写和多字写单字写帧里没有独立的长度段而是数据段直接在地址后。整帧结构在官方 MC 协议文档里有明确说明这里给出的代码是按照常见实现思路整理的适用大多数 Q 系列模块。4.2 批量写入与按位写入的差异批量写入的帧结构类似但命令码不同写批量是 0x01按位写是 0x02。按位写入通常用于 M 继电器、Y 输出这类位软元件数据以 bit 为单位。批量写的地址可以指向 D 区域起址后面跟随多个连续寄存器的数据PLC 会自动按地址递增写入。 批量写入D300-D302值分别为 1, 2, 3 Dim strValues As String strValues 0001 0002 0003 批量写命令 起始地址 数据长度 数据 结束符 strFrame 0101 0177 0003 strValues 0000 Winsock1.SendData strFrame这段代码的地址0177是 D300 0xA8 0x0177 的十六进制D300 十进制对应 0x012C加上偏移 0x01D4实际上要重新验证。注意批量写的长度是指字数不是字节数也不是地址偏移量比如上面写 3 个字对应 3 个数据值PLC 会写入 D300、D301、D302。批量写的好处是减少通讯次数尤其在需要周期性更新一组参数时一次请求比循环单次写高效很多。如果你要更新的是模拟量输出、设备参数表这类连续区优先用批量写。4.3 错误码排查对照表写操作返回错误状态码时不要急着怀疑是 VB 代码问题先对照三菱 MC 协议的错误码表。常见返回码的含义如下状态码十六进制含义常见原因0正常完成无10帧校验和错误帧内容拼错或通讯干扰11命令错误命令码和软元件不匹配12长度错误读取长度超过范围或帧长度不对14地址错误起始地址超过软元件范围15数据类型错误指定的软元件代码不存在如果响应里看到 10 开头大概率是帧内容不对或者校验和算错。如果看到 14先把地址代入偏移公式重算一遍。这个表格是排错的第一步再往下就要用通讯监视工具去抓报文。我在项目里习惯在 VB 代码里加一个 Debug 输出窗口把发送帧和响应帧都打印出来和协议文档对照这样定位错误码快很多。5. 避坑指南VB与三菱Q系列通讯最常见的6个踩坑点5.1 帧头、结束符与校验码字符串拼接的隐藏陷阱现象通讯时灵时不灵偶尔能读到数据偶尔数据完全不对。原因ASCII 模式帧缺少结束符或者字符串拼接时混入了空格、换行符导致 PLC 无法识别帧边界另一个常见问题是计算累加和串口模式时把字符串直接累加而不是按照十六进制字节来计算。解决帧的结尾必须带结束符以太网模式通常是“0000”或“0D0A”视模块参数而定累加和要在发送前对字节数组计算不要对字符串计算。我用过的做法是先把字符串转成字节数组再逐字节做累加最后把校验字节再转回十六进制拼到帧尾。5.2 站号与网络参数不匹配导致连接异常现象TCP 能连上socket 建立成功但发送请求后完全没有响应串口模式下则是连接正常但请求一直超时。原因以太网模式下PLC 的端口号没有打开或者连接的是 PLC 内置口而模块参数里设置的通讯协议不是 MC 协议串口模式下站号和波特率不匹配。解决先在 PLC 侧用 GX Works 或者 GX Developer 打开模块参数确认端口号启用 MC 协议、波特率与 VB 侧一致、串口站号无误。项目上我见过太多例程改个 IP 就直接用的结果端口号对不上白白排查了半天。5.3 寄存器地址偏移搞错数据错位或读不到现象读取 D100返回的数据对应的是 D99 或 D101 的内容。原因MC 协议的软元件地址编码不是按自然连续地址来算不同软元件类型有自己的基准偏移。D 寄存器的偏移是 0x00A8W 寄存器是 0x0000R 寄存器是 0x00C8不同手册版本略有差异。解决不要手工做“自然地址直接转十六进制”的换算用一个函数把软元件类型、编号统一封装起来内部处理偏移这样所有调用点都不会因为忘记加偏移而出错。你如果只写一个例程踩一次坑记住了但要在一个大项目里读写十几个不同区域没有统一封装风险极大。5.4 通讯超时与重试机制缺失导致卡界面现象PLC 断电、网线断开或 PLC 程序运行到 STOP 状态时VB 程序发送请求后没有任何响应界面假死需要强制关闭进程。原因WinSock 控件默认没有超时处理SendData不会因为对方无响应而自动超时如果代码里没有在DataArrival事件里做超时计时程序就会卡在等待状态。解决自己实现一个超时定时器每次发送请求后启动 Timer计时 1~3 秒如果 Timer 到点没有收到响应主动断开连接重连并触发错误提示。示例伪代码如下 每次发送请求后启动超时判断 Private Sub SendRequest(frame As String) Winsock1.SendData frame Timer1.Interval 2000 2秒超时 Timer1.Enabled True End Sub Private Sub Timer1_Timer() Timer1.Enabled False Winsock1.Close MsgBox 通讯超时请检查PLC状态 End Sub5.5 VB长整型与PLC双字数据的边界值溢出现象读取一个 32 位计数器值时结果变成负数或者值明显不对。原因PLC 里 D 和 D1 组成一个 32 位数据用 16 位方式逐段解析时高字造成溢出VB 的Val(H strWord)返回的是整型超出了 32767 范围后变成负值。解决用CLng(H strWord)解析每个字然后再把低位和高位合并lValue lLow (lHigh * 65536)。这个坑在变频器速度、冲压计数这类场景特别常见因为参数值经常超过 32767。5.6 响应数据字节序与PLC程序内排列不一致现象写进去的值和读出来的值对不上但是单看帧结构又是对的。原因Q 系列 PLC 内部 D 寄存器存储多字节数据时低位在前、高位在后也就是小端序。如果上位机软件端用的是大端序解析就会把高低字节搞反。解决解析时统一做一次字节交换或移位处理。用下面这段代码可以统一处理 小端序转大端序将低字节和高字节交换 Function SwapWord(strWord As String) As Integer SwapWord CLng(H Mid(strWord, 3, 2) Mid(strWord, 1, 2)) End Function6. 把通讯做稳轮询策略、日志记录与断线自动重连通讯能做通只是第一步能让它持续稳定运行才是项目验收的关键。最后一个阶段我分享一下把通讯从“能跑”提升到“稳跑”的三个具体做法这些经验直接决定你在产线上能不能睡个好觉。第一个做法是设计合理的轮询策略。如果你要监控很多个寄存器区域不要在一个循环里把几百个地址一次性读完因为单次通讯数据量太大容易超过 PLC 响应上限还会阻塞其他操作。我一般会把变量分组按重要性分优先级关键参数每 200ms 轮询一次普通状态数据 1 秒一次批量不敏感的报表数据 5 秒一次。轮询的核心是控制单次帧里地址连续性和长度上限不要跨区读取连续地址一次读完分散地址分帧读。第二个做法是完整的日志记录。日志不只是错误日志还包括发送帧、接收帧、耗时、重试次数。在调试阶段把日志打开跑一个晚上第二天看日志定位问题非常高效。上线后可以把详细帧日志关掉但保留错误日志和连接状态日志。日志建议采用文本文件按天分割记录时间戳这样排障的时候可以用文本对比工具把一段时间内正常区间和异常区间的日志做 diff差异点通常就是问题所在。第三个做法是断线自动重连机制。生产中 PLC 偶尔重启、网线被误碰、交换机电源波动都会导致连接断开。如果没有自动重连又要专人去现场重启软件。一个可靠的重连机制应该包括心跳检测和指数退避重试。所谓心跳检测就是周期性地发送一条读取 PLC 固定寄存器的请求如果能正常收到响应就认为链路健康如果超时就认为链路断开。指数退避指的是重连间隔不要固定第一次断开 500ms 重试第二次 1 秒第三次 2 秒最多到 10 秒避免 PLC 还在恢复过程中程序就发疯式地重连把 CPU 占满。 心跳监测与自动重连的简化示意 Private Sub HeartbeatTimer_Timer() If Not bConnected Then AttemptReconnect() 重连逻辑指数退避 Exit Sub End If SendRequest ReadFrame(D0) 读取固定心跳寄存器 End Sub这套机制做完以后我经历过最典型的一次验证是产线工人误碰交换机电源PLC 断网约 30 秒程序自动检测到心跳失败开始指数退避重连网络恢复后大约 2 秒内自动恢复数据同步整个过程无人干预生产数据没有丢失。放在没有这套机制的早期版本那次事故至少会让产线停机十几分钟。这块儿的价值不是做一个工整的 Demo而是让你在项目验收时能挺起腰杆说即使现场网络环境差这套通讯也能自己缓过来。最后说一个最真实的教训。早期我做这种通讯项目时总觉得只要两条命令能通就完事了结果连续遇到 PLC 程序改动导致地址偏移、现场电磁干扰导致偶发帧错误、操作人员误关上位机软件这类状况才意识到通讯程序的本质是可靠性工程。后来我把“指令能通”和“系统能稳”当成两件事来做先实现最小命令再叠加超时、重试、日志、重连。这套习惯到现在都在沿用也希望帮到你。本文还有配套的精品资源点击获取