news 2026/11/2 8:34:34

三菱FX编程口通信实践:Qt串口助手与协议编解码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
三菱FX编程口通信实践:Qt串口助手与协议编解码

简介:FX_serial_asst.rar 是一份面向工业自动化上位机开发初学者与 PLC 通信实践者的 Qt 工程源码,围绕三菱 FX3U 编程口串行通信展开,可用于搭建上位机连接、数据采集与寄存器读写等练习场景。压缩包共 7 个文件,约 12KB,包含 2 个 cpp、2 个 h、1 个 ui、1 个 user 与 1 个 pro 文件,分别承载通信逻辑、类声明、界面布局、Qt Creator 用户配置及工程构建信息,结构紧凑。已有 1004 人学习下载。源码中可见 QSerialPort 串口参数配置、数据收发处理,以及面向 FX3U 指令集的解析与读写请求实现,并涉及通信失败后的重试与错误处理思路,适合对照理解编程口通信流程、FINS 指令组织方式和 Qt 串口工程搭建方法,也可作为自行开发 PLC 通信工具的基础样例。

1. 三菱FX编程口通信:一个串口助手小工具能顶多少事

车间里一台老 FX 系列 PLC 摆在电控柜里,编程口是圆头 8 针,笔记本上没有串口,手边只有一根 USB 转串口线和一堆对不上号的通信线。现场想确认 PLC 到底有没有在回数据,最土的办法是拿一个串口助手发一串十六进制报文,看返回的字节对不对。这个叫 FX_serial_asst 的小工具干的就是这件事:把三菱 FX 编程口通信里那些固定帧格式、校验位、读写命令封装成能点的界面,同时把 Qt 的串口收发、定时器、报文拼接这一套骨架完整摆出来。它适合三类人:现场调试 PLC 的电气工程师、要写上位机采集 FX 数据的开发、以及想学 Qt 串口编程但不知道从哪下手的初学者。后面拆的是它怎么组织代码、怎么发一帧、怎么把协议层和界面层切开,最后讲二次开发时怎么把固定协议换成可配置的。

2. 工程结构与技术选型:Qt串口助手怎么组织代码

先看骨架。这类基于 Qt 的串口上位机,常见的做法是 qmake 或 CMake 二选一,按模块拆成「界面」「串口封装」「协议编解码」三层。项目名里的 FX 指的是三菱 FX 系列 PLC,serial 指串口,asst 是 assistant 的缩写,合起来就是一个针对 FX 编程口协议的串口调试助手。它不是通用串口工具,报文格式、站号、校验都按 FX 的规则来,这是它和市面通用串口助手最大的区别。

2.1 分层结构与目录约定

我一般把这类项目拆成下面几个目录,源码包里即使叫法略有差异,职责也是这一套:

FX_serial_asst/ ├── FX_serial_asst.pro # qmake 工程文件,声明 QT += serialport widgets ├── main.cpp # 入口,QApplication + 主窗口 ├── mainwindow.h/.cpp/.ui # 界面层:控件、信号槽、日志显示 ├── serialworker.h/.cpp # 串口层:打开/关闭/读写/错误处理 └── fxprotocol.h/.cpp # 协议层:帧组装、校验、解析、软元件地址换算

界面层只管控件和显示,串口层只管字节流的进出,协议层只做纯函数式的组帧和拆帧,不碰 QSerialPort。这样分层的好处很实在:协议层可以脱离硬件做单元测试,界面层换皮肤不动逻辑,串口层换 USB 转串口芯片也不用改协议。很多人写串口工具把三者揉在一个 cpp 里,第一版能跑,等要加第二种 PLC 协议时就只能复制粘贴,血泪经验。

工程文件里最关键的一行是串口模块的引入:

QT += core gui serialport CONFIG += c++11 TARGET = FX_serial_asst TEMPLATE = app SOURCES += main.cpp mainwindow.cpp serialworker.cpp fxprotocol.cpp HEADERS += mainwindow.h serialworker.h fxprotocol.h FORMS += mainwindow.ui

这里serialport是 Qt5.1 之后才有的模块,Qt4 时代用的是第三方 QextSerialPort,如果拿到的代码里出现qextserialport.h,说明是老工程,得按新模块改写。CONFIG += c++11影响QByteArray的初始化列表写法,Qt5.12 以上不写也能过,但显式声明更稳。

2.2 串口层的封装:QSerialPort 怎么用才不出错

串口层要解决的是一件事:把「打开端口、设参数、收数据、报错误」这四步做扎实。FX 编程口默认参数是 9600 波特率、7 位数据位、偶校验、1 位停止位,这个 7E1 组合是最容易翻车的地方,因为大多数 USB 转串口默认是 8N1,参数不对时收到的全是乱码或者干脆没反应。

// serialworker.cpp 核心片段 bool SerialWorker::openPort(const QString &name) { m_port.setPortName(name); m_port.setBaudRate(QSerialPort::Baud9600); // FX 编程口默认 9600 m_port.setDataBits(QSerialPort::Data7); // 关键:7 位数据位 m_port.setParity(QSerialPort::EvenParity); // 偶校验 m_port.setStopBits(QSerialPort::OneStop); // 1 位停止位 m_port.setFlowControl(QSerialPort::NoFlowControl); // 编程口不用流控 if (!m_port.open(QIODevice::ReadWrite)) { emit errorOccurred(m_port.errorString()); // 把错误抛给界面显示 return false; } connect(&m_port, &QSerialPort::readyRead, this, &SerialWorker::onReadyRead); return true; }

setDataBits(QSerialPort::Data7)和EvenParity这两行是 FX 编程口的命门,缺一个都不通。FlowControl必须关掉,编程口是两线半双工风格的时序,开流控会把帧卡住。错误不吞掉而是用信号抛给界面,是因为现场最常见的问题就是端口被别的软件占用(返回 PermissionError),把errorString()原样显示出来,一眼就知道是被占用了还是线没插好。

读数据的槽函数要处理粘包和半包,串口不是一次 readyRead 就给你一整帧:

void SerialWorker::onReadyRead() { m_buffer.append(m_port.readAll()); // 先全部收进缓冲 // FX 应答帧以 0x02(STX) 开头、0x03(ETX) 结尾,按这个边界切帧 int start = m_buffer.indexOf(0x02); int end = m_buffer.indexOf(0x03, start); while (start >= 0 && end > start) { QByteArray frame = m_buffer.mid(start, end - start + 1); emit frameReceived(frame); // 完整帧交给上层解析 m_buffer.remove(0, end + 1); start = m_buffer.indexOf(0x02); end = m_buffer.indexOf(0x03, start); } if (m_buffer.size() > 1024) m_buffer.clear(); // 防御:异常数据不无限涨 }

这里用0x02/0x03做帧边界是 FX 协议本身的约定,比按长度切稳得多,因为应答长度随命令变化。缓冲上限 1024 是个保险,防止波特率设错时收到一堆乱码把内存撑爆,这个防呆在实际现场救过不少次。

2.3 界面层与信号槽的接线方式

界面层典型布局是:顶部串口选择下拉框 + 打开/关闭按钮,中部是十六进制发送框,底部是收发日志。Qt Designer 画好 .ui 后,用connect把按钮信号接到槽上,串口层的frameReceived信号接到日志显示槽。数据统一用十六进制字符串渲染,因为 FX 报文里大量不可打印字节,直接当文本显示会变成方框。

界面上还有个容易被忽略的细节:发送按钮在串口没打开时必须禁用,否则用户点了没反应会以为程序死了。这个状态管理放到openPort成功/失败的分支里同步更新ui->btnSend->setEnabled(bool),比在槽函数里再判断一次端口状态更直观。日志框用QPlainTextEdit而不是QTextEdit,几千行报文刷下来,前者性能明显更好,这也是我踩过卡顿之后才改的习惯。

3. FX协议报文编解码:一帧读命令怎么拼出来

协议层是整个项目含金量最高的部分。三菱 FX 编程口的通信格式是 ASCII 报文,帧结构固定为 STX + 站号 + 命令 + 数据 + 校验 + ETX,校验是把从站号到数据末尾的 ASCII 码逐字节相加取低两位十六进制。搞懂这一帧,读写软元件就是换命令字的事情。

3.1 帧结构与校验算法

以读 D 寄存器为例,命令字是0(读),软元件代码 D 是D0(十六进制 0xA0 加地址),应答里带同样的校验。组帧函数不该依赖任何 Qt 控件,纯输入输出:

// fxprotocol.cpp QByteArray FxProtocol::buildReadFrame(int station, const QString &device, int count) { QString body = QString("%1%2%3%4") .arg(station, 2, 16, QChar('0')) // 站号,默认 00 .arg("0") // 命令:读 .arg(device) // 软元件地址,如 "0A00" 表示 D0 .arg(count, 2, 16, QChar('0')); // 读取点数,十六进制 int sum = 0; for (QChar c : body) sum += c.toLatin1(); // ASCII 累加和 QString frame = QString("\x02%1%2\x03") // STX + body + 校验 + ETX .arg(body) .arg(sum & 0xFF, 2, 16, QChar('0')); // 取低字节,两位十六进制 return frame.toLatin1(); }

几个参数要盯住:station是站号,单机默认00,多机时每台不同;device的编码规则是软元件类型码加 4 位地址,D 区类型码0A、M 区08、X 区80、Y 区8C,地址用十进制转十六进制补四位;count是一次读的点数,十六进制表示,读 10 个点要写0A而不是10。校验算法sum & 0xFF后转两位十六进制,位数不够要补零,少补一个零对方直接判帧错误。

提示:不同 FX 子型号(FX1N/FX2N/FX3U)的软元件地址范围和类型码有细微差别,改协议前先对一遍对应型号的通信手册,别拿一个型号的地址表套所有机器。

3.2 应答解析与异常码处理

应答帧里最需要处理的是异常。FX 的否定应答是NAK(0x15)开头或者返回带错误码的报文,错误码常见有02(软元件地址越界)、03(数据长度超限)、06(帧格式错误)。解析时先看首字节,再分流:

void FxProtocol::parseResponse(const QByteArray &frame, QVector<quint16> &out) { out.clear(); if (frame.isEmpty()) return; if (frame.at(0) == 0x15) { // NAK:PLC 拒绝了这帧 emit protocolError(QString("PLC 返回 NAK,检查地址与长度")); return; } QByteArray body = frame.mid(1, frame.size() - 4); // 去掉 STX、校验、ETX // 校验比对:重算 body 的 ASCII 和 int sum = 0; for (char c : body) sum += static_cast<quint8>(c); QString calc = QString("%1").arg(sum & 0xFF, 2, 16, QChar('0')).toUpper(); QString recv = QString::fromLatin1(frame.right(3).left(2)).toUpper(); if (calc != recv) { emit protocolError(QString("校验不匹配:算出 %1,收到 %2").arg(calc, recv)); return; } // 数据区每 4 个 ASCII 字符是一个 16 位寄存器值 for (int i = 0; i + 4 <= body.size(); i += 4) { bool ok = false; out.append(body.mid(i, 4).toUShort(&ok, 16)); if (!ok) emit protocolError("数据区出现非十六进制字符"); } }

frame.mid(1, frame.size() - 4)这个偏移量要算清楚:首字节 STX 占 1,尾部校验 2 位加 ETX 1 位共 3,所以正文长度是总长减 4。数据区按 4 个 ASCII 字符一组解析,因为一个 16 位寄存器用 4 位十六进制表示。这里把校验不匹配单独报出来,而不是静默丢弃,是因为现场接触不良时校验失败频率会明显上升,能当成线路质量的指示灯用。

3.3 软元件地址换算的一个坑

地址换算是最容易写错的地方。D 寄存器地址是十进制的,报文里要转成十六进制 4 位,D100 对应0064;但 X/Y 区是八进制编号,X10 不是十六进制 0x10,要按八进制换算再补位。常见做法是做一个换算函数,输入软元件名和数字,输出 4 位十六进制地址串:

QString FxProtocol::deviceAddress(const QString &dev, int num) { if (dev.startsWith('X') || dev.startsWith('Y')) return QString("%1").arg(num, 4, 8, QChar('0')); // 八进制编号 return QString("%1").arg(num, 4, 16, QChar('0')).toUpper(); // 其余十六进制 }

八进制和十六进制混用这件事,我第一次写的时候直接把 X10 当十六进制发出去,PLC 返回 NAK,查了半天才反应过来。这个函数看着只有几行,但是整个协议层最值得单独测的部分,输入输出明确,适合写一组对照用例。

4. 现场调试避坑:串口打不开、收到乱码、帧不回怎么办

下面几条都是这类工具跑现场时高频出现的问题,按「现象 → 原因 → 解决」记下来,比查手册快。

4.1 串口打开失败,提示权限或占用

现象:点打开按钮没反应,日志里出现PermissionError或Device or resource busy。原因:端口被别的调试软件占着,或者驱动没装好,设备管理器里显示黄色感叹号。解决:先关掉其他串口工具,确认设备管理器里端口号正常;程序里把errorString()原样显示出来,别自己编一句「打开失败」,否则你永远不知道是被占用还是线不对。USB 转串口线插拔后端口号会变,下拉框刷新逻辑要跟着做。

4.2 通信参数设成 8N1,收回来全是乱码

现象:能打开端口,发出去有回应,但日志里一堆乱码,STX/ETX 都找不到。原因:FX 编程口是 7 位数据位偶校验,用了默认 8N1 导致高位截断。解决:把Data7、EvenParity、OneStop三个参数一起改,只改一个照样不通。改完还乱就换一根质量好点的 USB 转串口线,劣质芯片在 9600 下也会丢位。

4.3 发了帧但一直没有应答

现象:发送日志有记录,接收区空白。原因:常见有三种——接线把 TX/RX 接反了、站号写错、软元件地址越界但 PLC 返回的 NAK 被程序当垃圾数据丢了。解决:先用一根线短接 TX/RX 做回环测试,确认收发通路正常;再把站号固定成00试;最后检查parseResponse里对0x15的处理有没有被提前 return 掉。

4.4 偶发校验失败,重发就好

现象:十次里有两次校验报错,重发一次又正常。原因:多为接地不良或线太长引起的位错误,也可能是程序没做半包拼接,把两帧的碎片当成一帧处理。解决:先确认onReadyRead里的缓冲区切帧逻辑是按 STX/ETX 边界切的,不是按固定长度切;再从硬件上缩短通信线、给 PLC 和电脑共地。软件层面加一次重发机制比什么都实在。

4.5 界面卡死,日志框越来越慢

现象:通信跑几分钟后界面明显变卡,日志窗口滚动迟滞。原因:每收到一个字节就 append 一次,信号槽高频触发;加上用了QTextEdit而非QPlainTextEdit。解决:收发日志做批量刷新,用QTimer每 100ms 把缓冲的报文一次性刷进界面;日志超过一定行数自动截断。这个改动前后差别很大,现场长时间挂着调试的时候体会最深。

5. 二次开发:把固定协议改成可配置的采集上位机

这份代码最值得学的不是界面,是它把协议层独立出来的结构。想把它变成自己的采集工具,改造点集中在三个地方,我按动手顺序说。

第一处是协议参数外置。现在站号、软元件、点数是写在代码里的,改成从配置文件或界面表格读。做一个简单的设备配置结构体,用QSettings或 JSON 存,界面加一个表格控件列出「站号 / 软元件 / 地址 / 点数 / 周期」,采集线程按表格逐行轮询。这样加一台 PLC 只是加一行配置,不用重新编译。

第二处是采集调度从主线程挪出去。现在大多是在界面槽里发帧,采集周期一短就卡。用QThread把SerialWorker挪到子线程,主线程只负责 UI 更新和配置下发,跨线程用信号槽传数据,注意QSerialPort对象必须在它被创建的线程里使用,不要在主线程 new 了再 moveToThread 后又直接调它的方法。轮询用QTimer加一个待发队列,发一帧等应答超时再做下一帧,避免把 PLC 的通信缓冲打满。

// 采集线程里的轮询骨架 void Poller::tick() { if (m_waiting) return; // 上一帧还没回,跳过本轮 if (m_queue.isEmpty()) buildQueue(); // 一轮读完重新生成任务 auto task = m_queue.dequeue(); m_waiting = true; m_serial->write(FxProtocol::buildReadFrame( task.station, task.device, task.count)); m_timeout.start(500); // 500ms 无应答判超时 }

m_waiting这个标志位是防止命令叠发的关键,串口是半双工的,上一帧没回就发下一帧,回来的是哪一帧的应答就分不清了。超时时间按现场线长和 PLC 扫描周期定,500ms 对 9600 波特率读十几个点足够。

第三处是解析结果落库。读数解析成QVector<quint16>之后,按软元件类型和地址做快照,带时间戳写进本地数据库或者推给 MQTT,做成历史曲线就完成了从调试助手到采集上位机的转变。这里建议把「原始报文」和「解析值」都留一条日志,参数出问题时能回放对账,比事后猜强得多。

验证改造是否正确的办法很直接:拿一个已知值的 D 寄存器,从界面上写一个数进去,再用 PLC 编程软件在线监视读回来,两边对得上,说明组帧、校验、地址换算整条链是通的。我习惯先用最小的帧——单站号、读 1 个点——跑通再扩到批量轮询,一上来就配几十个点,出错了根本定位不到是地址错了还是切帧错了。从那以后我每次改协议层都强制先跑这一条最小用例,成了固定动作。希望这套拆法帮到你,源码拿到手先照第 2 章的结构对着看一遍,再动第 5 章的三个改造点。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 8:33:52

WeKnora:Go 语言开源 RAG 与 Agent 企业知识管理框架解析

一个内部 Wiki 翻了三分钟才找到半年前写的一句话&#xff0c;把问题抛给大模型又得到一堆幻觉——这种场景我相信很多团队都经历过。腾讯开源的 WeKnora 就是冲着这个痛点来的&#xff1a;用 Go 写成的企业知识管理框架&#xff0c;把 RAG&#xff08;检索增强生成&#xff09…

作者头像 李华
网站建设 2026/11/2 8:33:46

零基础入门机器视觉:Sherlock图形化配置与位置标定实战

简介&#xff1a;这份PPT面向机器视觉初学者与自动化检测工程人员&#xff0c;系统介绍Sherlock机器视觉应用软件的功能与操作。内容围绕Landmark位置标定、Calibration刻度校准、Search区域搜索与Color Processing颜色处理四大核心功能展开&#xff0c;并逐一讲解主菜单栏、工…

作者头像 李华
网站建设 2026/11/2 8:33:35

国产PLM供应商实施考察关键点与实战指南

1. 国产PLM供应商实施现场考察要点解析十年前我第一次带队考察PLM供应商时&#xff0c;在苏州某工厂亲眼目睹了价值千万的系统因为数据迁移方案缺陷导致产线停摆三天。从那时起我就明白&#xff0c;选择PLM供应商绝不能只看产品手册和演示视频。国产PLM经过近十年发展&#xff…

作者头像 李华
网站建设 2026/11/2 8:33:12

两级CMOS运放原理与Spectre仿真实战指南

简介&#xff1a;本资源是一份面向模拟集成电路初学者与高校电子类专业学生的CMOS运放设计实践指南&#xff0c;聚焦两级运算放大器的原理分析、参数计算与Spectre仿真全流程。资源系统讲解了长沟道器件下两级CMOS运放的设计动机&#xff08;解决单级增益与输出摆幅矛盾&#x…

作者头像 李华
网站建设 2026/11/2 8:33:03

VCF 9.0集群管理:跨区域平衡与节点撤离技术解析

1. VCF 9.0 集群管理新特性解析VCF 9.0作为VMware Cloud Foundation的最新版本&#xff0c;在集群管理方面带来了多项重要改进。其中最核心的两项功能就是跨区域平衡&#xff08;Cross-Zone Balancing&#xff09;和节点撤离&#xff08;Node Evacuation&#xff09;。这两个功…

作者头像 李华
网站建设 2026/11/2 8:33:02

学术写作AI:预测审稿意见的智能策略引擎

1. 项目概述&#xff1a;当学术写作遇上"狼人杀"思维去年帮学弟改论文时&#xff0c;我看着他被审稿意见"刀"得遍体鳞伤的第三稿&#xff0c;突然意识到学术评审就像一场高端"狼人杀"——审稿专家是带着专业视角的"狼人"&#xff0c;而…

作者头像 李华