☰
三菱PLC上位机VC6.0源码解析:串口通信与FX系列调试实战
2026/10/20 2:44:00 网站建设 项目流程

简介:一套基于Visual C++ 6.0与MFC框架开发的三菱PLC上位机编程软件完整源码,面向工业自动化领域的软件工程师、PLC开发者及C++学习者。资源解决的是如何实现上位机与三菱PLC的通信、读取/写入/调试PLC程序,以及设计人机交互界面的问题。压缩包共60个文件,容量约731KB,包含14个头文件与13个C++源文件,覆盖主程序、通信模块、文档视图等核心逻辑;另有图标、光标、位图等界面资源,以及工程配置、示例梯形图与PLC样例文件,便于对照学习。这些文件共同构成完整MFC工程,可直接用VC6.0打开与编译。目前已有1282人学习下载,通过阅读和编译源代码,可掌握MFC下与三菱PLC通信的协议实现、指令解析方法及界面设计技巧,也可以此为模板,开发定制化的PLC监控或调试工具,对提升C++工程能力与工业自动化项目实践均有直接帮助。

1. 三菱PLC上位机VC6.0源码包:先看明白再解压

“三菱PLC上位机编程软件VC6.0源代码完整版v1.0”这个资源,下载的人多半不是图新鲜,而是生产现场急着把一个老式的PLC监控界面换成电脑显示。这份源码包是VC6.0/MFC对话框程序为主体、串口通信为桥梁、三菱FX系列PLC软元件读写为核心的一套完整上位机工程:能实现D寄存器实时读取、M线圈监控、简单按钮点动和报警提示。适合调试工位、小型产线以及第一次想把PLC通信协议跑通的工程师。但请记住一个前提:它是源代码工程,不是双击就能看的演示程序,必须在VC6.0环境里编译,并且要根据你的PLC型号改参数。后面几章我按“工程结构、编译联调、避坑记录、改造方法”的顺序给你捋清楚。

2. 源码包的骨架:从MFC对话框到协议收发

2.1 工程结构:拿到源码先认三类文件

解压后你会看到.dsw、.dsp工程文件,以及一堆.cpp/.h。不急着点编译,先按文件职责把源码分成三类,这样后续改起来才有方向。我拆过的FX系上位机源码,绝大多数都是下面这个结构:

文件类型常见命名职责
主对话框类PlcDlg.cpp / MonitorDlg.cpp界面控件管理、定时器刷新、按钮事件响应
串口封装类Comm.cpp / Serial.cpp打开/关闭串口、数据收发线程、波特率参数设置
协议处理类Protocol.cpp / PlcLink.cpp组帧、拆帧、校验和计算、软元件地址映射

拿到源码包后,第一件事是打开协议处理类文件,它决定了这份源码能不能跟你的PLC对上话。第二步看串口层用的是Win32 API还是MSComm ActiveX控件。MSComm控件在VC6.0里用起来省事,但系统里得注册mscomm.ocx,换一台电脑经常报“控件未注册”;Win32 API方式没有外部依赖,代码量稍大,但可移植性好。这份资源是完整版工程,通常两种写法都会出现,我建议优先读API实现的版本。

再看主对话框的定时器。FX系上位机最常用的方案是OnTimer里触发一次读取请求,然后界面List控件刷新。定时器间隔一般设在100~500ms之间,太短会让串口忙不过来,太长监控数据看起来是“跳”的。源码里如果定时器间隔小于50ms,要小心,这个我们放到避坑章节细说。

2.2 串口通信层:Win32 API用法与线程边界

串口层是上位机和PLC之间的物理通道。FX系列PLC的编程口本质上是RS-422/RS-485电平,但工控现场通常通过原装编程电缆或第三方转换线接到电脑的RS-232/RS-485口,或者通过USB转串口线虚拟成COM口。源码里串口打开代码常见写法如下:

// 打开串口并配置参数,句柄供后续ReadFile/WriteFile使用 HANDLE hComm = CreateFileA( "COM3", // 串口号,按实际修改 GENERIC_READ | GENERIC_WRITE, // 可读可写 0, // 串口不支持共享 NULL, // 默认安全属性 OPEN_EXISTING, // 必须已存在 0, // 不使用重叠IO,简化处理 NULL); if (hComm == INVALID_HANDLE_VALUE) { return FALSE; // 串口被占用或不存在 } DCB dcb = { 0 }; dcb.DCBlength = sizeof(DCB); GetCommState(hComm, &dcb); dcb.BaudRate = 9600; // 波特率,PLC侧也要是9600 dcb.ByteSize = 8; // 数据位8位 dcb.Parity = EVENPARITY; // 偶校验,常见配置 dcb.StopBits = ONESTOPBIT; // 1位停止位 SetCommState(hComm, &dcb); COMMTIMEOUTS tm = { 0 }; tm.ReadIntervalTimeout = 50; // 字节间超时50ms tm.ReadTotalTimeoutMultiplier = 10; tm.ReadTotalTimeoutConstant = 100; SetCommTimeouts(hComm, &tm); // 不设超时ReadFile会卡死主界面

逻辑说明:这段代码做了两件事——用CreateFileA拿到串口句柄,再用DCB结构体把波特率、校验位这些参数写进驱动。最关键的是后面的COMMTIMEOUTS设置:如果ReadFile收不到数据,又没有超时限制,它会一直阻塞在那里,整个界面跟着卡死。串口通信调试时,读超时永远要比写超时短,这是底层经验。

参数设定时注意三点。第一,波特率要和PLC编程口实际设定一致,FX系列在编程口默认常见的是9600,但也有通过D8120寄存器改成19200的情况,不匹配时表现为“能收到乱码”或“完全无响应”。第二,校验位最常用的是偶校验EVENPARITY,但有些第三方转换线内部做了RS-422电平转换,实际跑起来要试一下奇校验或无校验,这一点没有通用答案,以PLC侧模块说明为准。第三,串口号建议先在设备管理器里确认,USB转串口设备每次插拔后COM号会变,源码里写死COM3的话,现场换一台电脑就联不上。

2.3 协议层:帧格式、地址映射与校验和

上位机读PLC寄存器,本质是“发一帧请求、收一帧响应”。FX系列最常见的编程口协议是十六进制帧格式,结构如下:

帧段长度说明
帧头ENQ1字节0x05,表示请求开始
命令码1字节读/写操作的指令标识
起始地址2字节要访问的软元件编号
单元数2字节连续读取的寄存器个数
校验和2字节从命令码到单元数所有字节累加,取低8位
结束符2字节CR(0x0D)+LF(0x0A)

这里要注意,不同版本的源码对命令码的定义不完全一样,有的读命令是0x01,有的是0x30开头的ASCII码,实际以Protocol.cpp里的组帧函数为准。校验和部分几乎所有FX系协议都一致,实现如下:

// 计算校验和:从命令字节开始累加,到数据区末尾截止 BYTE CalcSum(const BYTE* pBuf, int nLen) { BYTE sum = 0; for (int i = 0; i < nLen; i++) { sum += pBuf[i]; // 每次都只保留低8位 } return sum; }

逻辑说明:校验和计算范围很讲究,帧头ENQ不参与计算,CR/LF不参与计算,中间的命令码、地址、字数、数据全部累加。漏了命令码或者把ENQ算进去,校验和永远对不上。源码里如果读出来的值偶尔正常偶尔乱码,先检查是不是校验范围画错了。

软元件地址映射是另一个重点。FX系列上位机的地址表和PLC内部的软元件编号不是一回事,源码里会有一个类似MapAddr的函数做转换,常见映射关系如下:

软元件PLC编号示例协议地址范围
D数据寄存器D0地址编码从0x0000开始,按字递增
M中间继电器M0独立区段,按位寻址
X输入端子X0对应输入映像区
Y输出端子Y0对应输出映像区

读D寄存器时,起始地址填的是D0对应的协议地址;读X输入时,填的是另一套地址。源码里经常见到有人把D100的地址直接填成100,结果读回来全是0,就是因为没走映射函数。拿这份源码做二次开发时,先把这个映射表吃透,比改界面按钮优先级高得多。

3. 让源码跑起来:编译、自测、连真PLC

3.1 编译环境:VC6.0在Win10/11上的固定动作

VC6.0是1998年的开发环境,在Win10/11上不能直接双击打开就编译,这是老工程的老规矩。第一次在Win10 64位系统上编译这份源码时,常见现象是资源编辑器打不开、编译时报“Cannot open include file”或链接时缺msvcp60.dll。我的固定动作如下。

第一步,把整个源码目录放到纯英文路径下,路径里不能有中文和空格,否则资源编译阶段大概率报错。第二步,VC6.0的vc6.exe设置兼容模式运行,右键属性,兼容性选项卡里选择Windows XP SP3,勾选“以管理员身份运行此程序”。第三步,如果双击工程文件闪退,用VC6.0的File菜单里的Open Workspace打开.dsw,不要双击工程文件。第四步,编译前确认工程配置是“Win32 Debug”,而不是“Win32 Release”,因为源码包里往往只维护了Debug版本的调试信息。

编译常见的错误还有两类:一是找不到mscomm.h,说明工程用了MSComm控件但你本机没装ActiveX;二是链接时提示unresolved external symbol,多半是源码里用了Win32 API但工程没有链接对应的库文件。MSComm方案我建议直接放弃,改用2.2节那种API写法更省心;库文件缺失就在Project Settings里的Link选项卡把kernel32.lib、user32.lib这些基础库补上。

3.2 不接PLC也能自测:虚拟串口对模拟应答

现场最尴尬的时刻是“源码编译过了,但PLC还没接线,没法验证”。实际上FX系上位机通信逻辑完全可以通过虚拟串口对自测。先装一个虚拟串口工具,创建COM3和COM4一对互联的虚拟串口;上位机打开COM3,再用Python脚本或串口调试助手监听COM4,模拟PLC回帧。

这里给一个最小可用的模拟器脚本,用来验证读D寄存器的请求和响应逻辑:

import serial, time # 打开虚拟串口的另一端,波特率要和上位机一致 ser = serial.Serial('COM4', 9600, timeout=0.2) while True: data = ser.read(16) # 等待上位机发来请求帧 if not data: continue if data[0] == 0x05: # 收到ENQ开头的读请求 # 模拟PLC响应:ACK + 两个字节的D值(0x04D2 = 1234) resp = bytes([0x06, 0x04, 0xD2]) ser.write(resp) print("已模拟响应,D0=1234")

逻辑说明:脚本用pyserial打开COM4,循环读取上位机发来的帧,只要帧头是0x05就认为是读请求,然后回一帧ACK加数据。这样跑通后,上位机界面应该能稳定显示1234这个值。如果显示乱码,说明上位机的波特率或校验位不对;如果毫无反应,说明帧结构里命令码的判定条件没对上,打开Protocol.cpp查一下实际发的第一个字节是不是0x05。

这个自测手段还有一个好处:你可以手动修改resp里的数据字节,模拟正数、负数、超大值,观察上位机界面上的解析结果是否正确。负数的验证尤其重要,因为很多FX系源码用int类型接收数据,读回的WORD值大于32767时会显示成负数,这个坑在下一章还会遇到。

3.3 接真PLC的联调顺序:从点读到点写

虚拟串口自测通过后,接真PLC就顺理成章了。先说接线:FX系列PLC通常用编程口通信,原装编程电缆一头接PLC的编程口,一头接电脑串口或USB口。需要注意的是,所谓“编程口”是三菱系PLC自带的圆形或D-Sub接口,电平标准接近RS-422,第三方USB转串口线直接插普通串口十有八九通讯超时,必须用带电平转换的编程线。

接好线后按以下顺序操作。第一,打开设备管理器确认编程线的COM口号,通常USB转接线会凭空多一个COM号。第二,把源码里串口号改成实际COM号。第三,PLC通电,确认PLC不在RUN运行状态——有些上位机在STOP状态下才能读写程序,如果要监控运行中的PLC,需要把PLC切换到RUN。第四,按下上位机的“连接”按钮,观察状态栏是否显示已连接。第五,先读D0,确认数值和PLC监控软件读到的一致。

写入操作放到最后验证。第一次写入建议写M线圈,不要直接写D寄存器数据,因为M线圈只有ON/OFF两种状态,判断逻辑简单,发现问题容易定位。写D寄存器前先确认地址映射正确,不然可能把数据写进别的寄存器,轻则数值跳变,重则引起设备误动作。有一条铁律:任何上位机源码第一次上真机时,先把写入按钮禁用掉,只保留读取功能,跑通一小时后再做写入验证。

4. 避坑实录:FX上位机调试的五个高频坑

4.1 界面假死:点击按钮后整个窗口失去响应

现象:程序运行后,点“读取”按钮,界面卡住不动,要过好几秒才恢复,甚至一直卡死只能结束任务。

原因:串口通信直接在界面线程里同步调用ReadFile,而串口数据没到或应答超时时,ReadFile会一直阻塞。更隐蔽的是MFC定时器里轮询读取,WM_TIMER消息间隔本身有约55ms的最小粒度,这几个因素叠加,界面就成了“假死”。

解决:把串口读写放到独立工作线程,界面线程通过PostMessage把数据抛回主对话框。如果源码里已经把通信写在OnTimer里,至少要给串口配置COMMTIMEOUTS短超时,把ReadTotalTimeoutConstant控制在100ms内,而不是默认的无限等待。从那以后我拿到源码第一件事就是搜OnTimer里有没有ReadFile,有则必改。

4.2 帧校验和永远差一个数

现象:用串口监视器抓上位机发出的帧,手动计算校验和,发现和帧尾的校验字节对不上,而且总是固定差一个值。

原因:这是协议理解的边界问题。很多初学都把校验范围理解成“从帧头到数据末尾”,实际FX系协议里帧头ENQ不进校验,部分源码还要求结束符CR/LF不进校验,多算或少算一个字节都会错。还有一种情况是源码里CalcSum函数传入了包括命令码在内的长度,而组帧时校验字节却从数据区后一位开始回填,错位一位。

解决:打开串口监视器抓一帧完整数据,手动从命令码算到数据末尾,再用CalcSum函数带入调试器单步执行,两个值比对。我一般会在组帧函数里加一行临时打印,把计算长度nLen和实际帧长度打印出来,一眼就能看出是谁欠了谁。

4.3 D寄存器读出负值或超大值

现象:PLC里D0存的是-50(有符号数),上位机显示成65486;或者PLC里存的是65535,界面却显示-1。

原因:D寄存器的底层存储是无符号16位WORD,上位机源码如果直接把这个WORD赋值给int型变量,CPU按有符号解析,高位为1时就变成负数。反过来,如果源码强制定义了WORD类型并直接显示,-50就被解析成无符号的65486。

解决:在协议解析层统一处理好符号转换,别把转换逻辑散落在界面代码里。具体做法是读回WORD后,判断最高位,如果bit15为1就减掉65536,得到一个负的short值。最好在协议类里写一个统一的ToSigned函数,界面层只管调用,不要再各自转换。

4.4 写入后PLC立刻停机或运行异常

现象:点击“写入D100”按钮,数值写进去了,但PLC随即进入STOP状态,或者设备输出动作突然跳变。

原因:地址映射错误是最常见的——你以为是写D100,协议地址对应的实际位置可能是特殊寄存器或运行控制字。另外,FX系列PLC在RUN状态下直接写某些地址会被系统拒绝或触发看门狗异常,有些程序里把Y输出区也当普通寄存器写,这就直接驱动了外部设备。

解决:写操作前先读回相同地址确认映射无误;写入数据前,在代码里加一个“写入确认对话框”,把目标寄存器编号和值都显示出来再执行。真机验证时,先把PLC切换到STOP状态写入一次,确认无异常再切回RUN写入一次,两次都正常才说明地址和权限没问题。

4.5 关闭软件后再打开连不上串口

现象:程序第一次运行正常,关闭后再启动,提示“串口打开失败”或COM口被占用。

原因:程序退出时没有正确关闭串口句柄,进程虽然结束了,但驱动层面句柄未被释放,系统要过十几秒才会回收。还有一种情况是调试时程序崩溃,串口句柄没走关闭流程就被系统强杀,残留占用。

解决:在对话框的OnClose和OnDestroy里都加上CloseComm操作,确保所有退出路径都能Release串口。另外,程序里捕获到WM_QUERYENDSESSION时也要关串口。如果已经出现占用情况,最有效的办法是拔插USB转串口线强制复位,比重启电脑快得多。这个坑我踩过一次后,每次改上位机源码都会检查“关闭按钮”和“窗口右上角X”两个退出路径是否都执行了CloseHandle。

5. 把资源改成自己的监控界面:三步改法

5.1 批量读代替点读:把D0~D99一次收回来

源码包里读寄存器通常是一个地址一个请求,界面需要显示几十个数据时,就会几十次串口往返,每帧间隔几十毫秒,刷新慢得像放幻灯片。协议本身支持一次读多单元:起始地址后面的“单元数”字段就是干这个的。改成批量读后,界面只发一次请求,PLC回一帧长数据,界面再按偏移量拆解。

// 批量读取D寄存器:startAddr为起始地址,count为连续个数 BOOL ReadDBlock(int startAddr, int count, WORD* outBuf) { BYTE cmd[16]; int len = 0; cmd[len++] = 0x05; // 帧头ENQ cmd[len++] = 0x01; // 读命令码(以实际源码为准) cmd[len++] = (BYTE)(startAddr >> 8); // 起始地址高字节 cmd[len++] = (BYTE)(startAddr & 0xFF); // 起始地址低字节 cmd[len++] = (BYTE)(count >> 8); // 单元数高字节 cmd[len++] = (BYTE)(count & 0xFF); // 单元数低字节 cmd[len++] = CalcSum(cmd + 1, 5); // 累加校验,不含ENQ cmd[len++] = 0x0D; cmd[len++] = 0x0A; // 结束符CR LF DWORD wrote = 0; WriteFile(m_hComm, cmd, len, &wrote, NULL); // 接收响应后按下表拆解,注意响应里ACK后面才是数据区 BYTE resp[256]; DWORD readed = 0; ReadFile(m_hComm, resp, count * 2 + 4, &readed, NULL); if (resp[0] != 0x06) return FALSE; // 不是ACK就失败 for (int i = 0; i < count; i++) { outBuf[i] = (WORD)((resp[i * 2 + 1] << 8) | resp[i * 2 + 2]); } return TRUE; }

逻辑说明:这个函数把原先“一帧一读”改成了“一帧一批”,通信轮询次数直接除以count。起始地址和单元数都拆成高、低两个字节,这是协议层的字节序规定,不能颠倒。判断响应时,第一字节必须是0x06即ACK,代表PLC已经正确收到请求,接下来才是数据区。数据区每个寄存器占两个字节,按大端方式还原成WORD。

参数设置时,count不要一次拉满,我看过有人一次读255个寄存器,PLC响应帧数据量大,串口缓冲区不够就把后面的数据截断了。一般一次读20~60个比较稳妥,如果需要监控100个点,拆成两次批量读。批量读的间隔可以和界面刷新频率解耦,比如100ms读一批,界面500ms刷新一次,网络稳定性和界面流畅度都更好。

5.2 工程单位换算:把原始值变成温度或液位

PLC模拟量模块读回来的D值本质是数字量,比如4~20mA电流环对应0~4000或0~32768的数字量范围,直接显示“32675”没人看得懂。上位机改造的第一步就是把原始数字量映射成工程单位。以常见的温度变送器为例,4~20mA对应0~100摄氏度,换算代码可以这样写:

// rawValue为PLC读回的数字量,满量程32768 // 电流范围4~20mA,温度范围0~100度 double ConvertToTemp(int rawValue) { if (rawValue <= 0) rawValue = 0; // 数据下限保护 if (rawValue >= 32768) rawValue = 32768; double current = 4.0 + (double)rawValue / 32768.0 * 16.0; double temp = (current - 4.0) / 16.0 * 100.0; return temp; }

逻辑说明:这个函数先把数字量还原成电流值,再按电流与温度的线性关系换算。注意rawValue是模拟量模块的原始采样值,不是物理电流值,比例关系由模块参数决定。不同模块的满量程不一样,有的是4096,有的是32768,换算前先查PLC模块手册确认一下。

界面显示时保留一位小数就够了,别把小数点后四位都显示出来,工控现场看数据没有人需要温度精确到0.0001度。另外,把量程上下限抽成配置项,存到INI文件里,这样换一个量程不同的变送器时,不需要重新编译源码,只改配置文件。这些都是一个合格上位机该有的细节。

5.3 扩展边界:FX系协议与Q系列MC协议不是一回事

很多人在FX系上位机跑通后,会问“能不能直接拿它连接Q系列或L系列PLC”。这里要泼一盆冷水:不能。FX系上位机用的是编程口协议,帧格式以ENQ/ACK/NAK控制码为主,地址映射表针对FX系列软元件;Q系列走的是MC协议,帧头是STX/ETX或固定控制代码,地址编码是完全不同的体系,而且Q系列普遍通过以太网通信,串口帧根本进不了网络模块。

有人尝试在协议层打补丁,把帧头改一改就去连MC协议设备,最后都倒在地址映射上。Q系列的D寄存器地址和FX系列不是一个编号规则,点位多且软元件类型复杂,硬改源码的工程量几乎等于重写协议层。正确的做法是把这份源码定位成“FX系专用上位机”,如果项目要跨到Q系列,另找一份MC协议库做二次开发。边界画清楚,比什么都重要。

6. 验证技巧:不被黑匣子卡住的三个动作

上位机开发里最容易出问题的地方是“你永远不知道PLC那头到底回没回数据”。我的习惯是用三个动作把黑匣子打开。

第一个动作是抓帧。用串口监视工具挂到实际COM口上,把上位机发出的请求帧和PLC回应的响应帧全部记录下来。帧是十六进制的,完完整整印在屏幕上,和协议文档逐字节对照。这个动作能把80%的解析问题杀死在萌芽阶段,比反复修改代码、编译、烧录、再试错高效太多。注意串口监视工具要选能过滤方向的,别把上行下行混在一起看,容易看花眼。

第二个动作是写模拟器脚本做回归测试。准备一组测试用例:D0=0、D0=65535、D0=-1、连续读10个寄存器、写入M0置ON、写入D10后又读回。把脚本里模拟PLC的响应数据做成参数化,每改一次上位机代码都跑一遍全套用例。时间长了你会发现,很多“程序跑通了”其实是运气好,只测了一个点值;批量读、写后读回、断开重连这几个场景才是真正容易翻车的地方。

第三个动作是断线重连验证。上位机在使用过程中,PLC断电、串口线松动、USB转串口线被拔掉,这些情况一定会发生。给源码加上通信看门狗逻辑:定时器每500ms发一帧查询请求,连续3次无响应就显示“通信中断”,恢复响应后自动重连。验证时就盯着通信状态指示灯,故意拔掉串口线等待,观察程序会不会卡死或者崩溃;再插回去,观察能不能自动恢复。这套机制做完,上位机才敢说能在现场稳定运行。

从那以后,我每次拿到一份上位机源码,都强制自己先抓帧、再写模拟器、最后做断线重连,三道关卡走完才允许自己碰PLC真机。这三步看起来麻烦,却能省掉你在现场满头大汗的一个下午。希望帮到你。

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

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

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

立即咨询