FEATURED · 精选文章

NB-IoT采集终端与Qt上位机开发实战:从串口通信到FFT频谱分析

发布时间 / 2026/9/9 22:10:35
来源 / 创域科博编辑部
栏目 / 资讯中心
NB-IoT采集终端与Qt上位机开发实战:从串口通信到FFT频谱分析 做嵌入式的人都知道这两年NB-IoT几乎是远程采集类项目的默认答案。低功耗、广覆盖、室内深覆盖能力强一块电池跑几年专门为物联网碎片化场景设计。我去年接手了一个中国移动NB-IoT QT采集终端的项目说白了就是做一个既能本地采集传感器数据、又能通过NB-IoT网络把数据上传到平台的终端设备同时还要配一个基于Qt的上位机用来做参数配置、实时波形显示和数据分析。项目做下来最大的感受是硬件选型也好上位机架构也好真正决定项目能不能顺利落地的往往是那些文档里不写、论坛里翻半天才找得到的坑。这篇文章就把整个项目的技术路线、关键实现和踩坑记录整理出来给正在做或者准备做类似终端的朋友一个参考。这个项目的核心场景在工业现场和数据采集比如水表、气表、环境监测、管道压力监测这类分散布置、供电困难的地方。终端通过传感器采集模拟量或数字量信号本地用MCU做初步处理再通过NB-IoT模块接入中国移动物联网平台把数据推到云端。Qt上位机在这套系统里承担的是“人机交互”的角色通过串口连接终端下发采集参数接收实时数据在界面上用图表展示波形甚至把时域信号做FFT变成频域图方便现场工程师直接分析频谱特征。整个系统分成终端硬件、NB-IoT通信链路、Qt上位机三大块每一块都有各自的技术难点。1. 项目整体设计与方案选型1.1 为什么选NB-IoT而不是Wi-Fi或4G很多第一次接触这类项目的人会问为什么不用Wi-Fi或者4G模块成本也差不多。这里面的逻辑其实很实在。NB-IoT走的是运营商授权频段工作在License频段抗干扰能力比Wi-Fi这类非授权频段强很多。更重要的是NB-IoT的覆盖增益高比传统GSM多出20dB的链路预算可以穿透楼层、井盖、地下管廊这类复杂环境这是Wi-Fi完全做不到的。4G虽然速率高但功耗也高对没有外部供电、只能靠电池的设备来说4G模组的休眠功耗和峰值功耗都是不可接受的。NB-IoT的速率其实很低上行峰值也就60kbps左右不过采集终端上传的数据量本来就不大一次上报几十字节到几百字节完全够用。再加上中国移动NB-IoT网络覆盖在全国范围都很成熟资费也低一年几十块钱就能搞定特别适合海量部署的采集场景。1.2 终端硬件架构终端硬件的核心是一颗低功耗MCU我选的是STM32L4系列。这款MCU在低功耗模式下可以跑到微安级别的电流同时有充足的外设接口UART、SPI、I2C、ADC一应俱全很适合做采集终端的灵魂部件。NB-IoT通信模块用的是移远BC26这是一个支持中国移动OneNET平台协议的NB-IoT模组尺寸小、功耗低最重要的是SDK支持MQTT和CoAP协议对接云平台非常方便。传感器数据通过MCU的ADC或者数字接口采集进来MCU做校验和组帧后通过串口发给BC26BC26再通过网络发到平台。这里要提一下终端和Qt上位机的通信接口。调试阶段最容易用的是USB转TTL串口上位机通过虚拟串口和终端通信。量产出厂时也可以保留一个调试串口方便现场维护人员用电脑连接终端查看状态。1.3 Qt在采集终端系统中的角色定位Qt在这个项目里不是跑在终端上而是跑在上位机电脑上。一开始也考虑过用Python写上位机开发速度快但考虑到后续要做得比较重要集成串口调试、波形显示、数据存储、协议解析、远程参数配置这些功能最终选定了Qt和C。Qt在这个场景里最大的优势是跨平台。项目验收的时候有时候用户用的是Windows电脑有时候是麒麟系统或者Ubuntu的国产化电脑Qt写一套代码维护成本低得多。再加上Qt的图形视图框架、QCustomPlot这类绘图库生态非常成熟做波形显示和频域分析非常顺手。2. NB-IoT通信链路与终端入网实现2.1 中国移动NB-IoT平台接入流程终端的入网流程可以拆成三个环节注册、入网、数据上报。注册阶段终端先给NB-IoT模组通电模组自动搜索网络并驻网。这时候MCU通过串口向BC26发送AT指令比如查询网络状态用ATCGATT?如果可以附着网络会返回CGATT:1。刚上电的时候模组搜网需要时间建议等待5到10秒再查询。入网之后就是连接到平台。中国移动OneNET物联网开放平台提供了多种接入方式最常用的是MQTT协议。用BC26接入OneNET时的典型指令流程是ATCMQTTSTART ATCMQTTACCQ0,client_id ATCMQTTSSLCFG0,1 ATCMQTTCONNECT0,tcp://183.230.40.96:1883,60,1,product_id,auth_info其中product_id和auth_info是平台创建产品时生成的一对鉴权信息。连接成功后会返回OK。之后数据上报使用ATCMQTTCONNECT的topic发布指令数据格式可以自定义我习惯用JSON方便平台端做解析。2.2 数据帧格式与协议设计采集终端和上位机之间的串口通信协议一定要设计得严谨。我用的帧格式是帧头0xAA 0x55然后是命令字、数据长度、数据区和CRC16校验。CRC我之前用求和校验后来现场遇到一次数据错乱排查了很久换成CRC16之后问题彻底解决了。凡是涉及远程传输的通信协议校验位不要省宁可多花几个字节的传输开销。终端上报数据的标准JSON格式大致是这样的{ device_id: NB001, timestamp: 1717056000, sensors: [ {type: temperature, value: 26.5}, {type: humidity, value: 58.2} ] }这里推荐一个经验时间戳用Unix时间戳而不是可读字符串既省流量又方便平台端做时间比对。因为NB-IoT单次传输数据包的限制比较严格虽然TCP可以发大包但NB-IoT网络针对小包做了优化一次上报的包体控制在512字节以内最稳。2.3 中国移动NB-IoT模块的功耗管理低功耗是NB-IoT终端的生命线。BC26模组支持PSMPower Saving Mode和eDRX两种省电模式。PSM模式下模组在空闲期会进入深睡眠此时电流只有微安级别网络侧会暂存下行数据等终端下次唤醒再下发。我的做法是传感器每30分钟采集一次MCU带着BC26一起唤醒采集完立即上报上报完成马上让模组进入PSM。实测下来一节18650锂电池配一个低功耗传感器理论续航可以做到一年半以上。如果采集频率更低的场景比如一天上报两次的水表数据续航甚至能做到三年以上。3. Qt上位机整体架构与串口通信3.1 上位机功能模块划分Qt上位机我按功能拆成了四个模块串口通信模块、协议解析模块、数据展示模块、参数配置模块。串口通信模块用Qt自带的QSerialPort类实现底层事件循环处理收发不卡界面。协议解析模块负责把终端上报的原始字节流按协议解析成结构体。数据展示模块用QCustomPlot绘制实时曲线和频谱图。参数配置模块提供表单界面让用户设置采集间隔、传感器量程、上报地址等信息然后下发到终端保存。这四个模块之间用信号槽机制通信比如串口收到一帧完整数据就发射一个dataFrameReady(const DataFrame)信号协议解析模块接到信号后做解析再发射parsedDataReady()信号给界面刷新。这种分层的好处是某个模块出问题不影响其他模块调试起来定位很快。3.2 串口通信实现的关键细节Qt串口编程的坑主要在两个方面一是配置参数二是粘包处理。配置参数上波特率、数据位、停止位、校验位必须和终端固件一致。这个项目里我用的是1152008N1和BC26默认串口参数保持一致。QSerialPort::setPortName()在Windows下要用COM口编号Linux下要用设备名比如/dev/ttyUSB0。粘包和半包是串口通信最常见的问题。我的做法是维护一个接收缓冲区每当串口有数据到达就追加到缓冲区然后循环查找帧头找到帧头后再判断数据长度是否足够。数据不够就继续等超出了就截断处理。这里有一个细节如果缓冲区长时间积累脏数据找不到有效帧头要清空缓冲区否则系统会因为脏数据越来越多而卡死。void SerialWorker::onReadyRead() { QByteArray chunk port-readAll(); buffer.append(chunk); int pos 0; while (buffer.indexOf(QByteArray::fromHex(AA55), pos) ! -1) { int headIndex buffer.indexOf(QByteArray::fromHex(AA55), pos); if (buffer.size() - headIndex 4) break; int len (quint8)buffer.at(headIndex 2); if (buffer.size() - headIndex len 4) break; QByteArray frame buffer.mid(headIndex, len 4); processFrame(frame); buffer.remove(0, headIndex len 4); pos 0; } }这个是典型的串口粘包处理逻辑的骨架实际项目中我还会在processFrame里做CRC校验校验不过的直接丢弃顺便给日志模块写一条警告。3.3 界面布局与用户交互设计上位机界面我分成了左右两个主区域。左侧是参数配置面板和数据列表右侧是波形显示区。参数配置面板用QGroupBox和QFormLayout组合做出来像一张表单操作者可以直观地填写采集间隔、传感器类型、阈值上下限点击“写入终端”按钮下发配置。右边波形显示区默认展示实时时域波形用一个QComboBox切换显示通道切换时QCustomPlot重新绘制数据。考虑到现场操作人员可能不熟悉这个系统控件上的说明文字要明确比如“采集间隔秒”括号里标注了合法范围界面上不要出现只能专业人员才看得懂的缩写。4. Qt下时域转频域与波形绘制4.1 FFT算法的选型与集成热词里经常看到“qt时域图转换为频域图”“qcustomplot kissfft时域到频域波形”这个功能在这个项目里确实很实用尤其是分析传感器信号中的特征频率时。Qt本身没有内置FFT算法库需要在工程里引入第三方库。我对比过几种方案FFTW功能最强但库体积大KissFFT非常适合嵌入式场景和桌面端代码小巧、无依赖性能也够用。最后选了KissFFT因为它简单可靠不会像FFTW那样折腾半天配置。下载KissFFT源码后把kiss_fft.c、kiss_fft.h和tools/kiss_fftr.c、tools/kiss_fftr.h加进Qt工程即可。注意它需要C语言编译支持在.pro文件里加一行CONFIG console或者确认MSVC/GCC都能编译就行。4.2 从时域数据到频谱的完整实现FFT的前提是输入数据必须是等时间间隔采样的否则频谱会出现严重的频率混叠结果不可信。所以在上位机里我先把串口采集到的波形数据按时间戳重采样成等间隔序列然后对这段序列做FFT。KissFFT的使用流程分三步第一步初始化变换对象int nfft 1024; kiss_fftr_cfg cfg kiss_fftr_alloc(nfft, 0, NULL, NULL);第二步把时域数据拷贝进来执行正变换QVectorfloat timeData(nfft); // timeData 填充采样点略 QVectorkiss_fft_cpx freqData(nfft / 2 1); kiss_fftr(cfg, timeData.data(), freqData.data());第三步计算幅值谱QVectordouble freqAxis(nfft / 2); QVectordouble ampSpec(nfft / 2); double fs 1000.0; // 采样率 for (int i 0; i nfft / 2; i) { freqAxis[i] (double)i * fs / nfft; double re freqData[i].r; double im freqData[i].i; ampSpec[i] 2.0 * std::sqrt(re * re im * im) / nfft; }这里有个关键细节频谱幅值要乘以2除以N否则幅值会变为真实幅值的一半。频率分辨率由采样率 / FFT点数决定比如采样率1000HzFFT点数1024分辨率约0.98Hz。被测信号的频率如果小于分辨率谱线会展得很宽看起来像漏出的信号这是FFT的固有特性。4.3 QCustomPlot绘制实时波形与频谱QCustomPlot是Qt里我用得最顺手的绘图库MIT协议没有授权风险而且性能足够应付实时曲线。绘制实时波形我用的是QCPGraph通过setData()传入一个不断更新缓冲区的数据队列。这里要处理缓冲区的长度我固定保留最近2000个点超过之后自动丢弃旧数据不然内存会越涨越多。void WaveWidget::updateWave(const QVectordouble newData) { for (double v : newData) { buffer.append(v); if (buffer.size() maxPoints) buffer.pop_front(); } QVectordouble x(buffer.size()), y(buffer.size()); for (int i 0; i buffer.size(); i) { x[i] i / sampleRate; y[i] buffer[i]; } graph-setData(x, y); xAxis-rescale(); yAxis-rescale(); replot(); }QCustomPlot默认提供鼠标滚轮缩放和右键拖拽平移功能实测对于频谱分析非常顺手。为了让操作更友好我又加了坐标轴网格线还把频域图的横轴切换成了对数坐标这样低频段和高频段都能看清。5. Qt开发环境安装、打包与麒麟系统部署5.1 Qt 5.15.2安装与国内镜像加速项目里用的Qt版本是5.15.2。这个版本非常经典模块齐全网上资料多稳定性也不错。安装时建议不带Qt Creator的独立安装包直接下载qt-opensource-windows-x86-64-5.15.2.exe即可。不过Qt官方下载服务器在国外裸连下安装包能教你重新做人。解决办法是换国内镜像我常用清华TUNA和中科大USTC镜像。以中科大的Qt在线安装器为例安装时打开安装器在设置里添加https://mirrors.ustc.edu.cn/qtproject/作为镜像源下载速度直接拉满。安装时勾选组件有讲究我通常勾选MSVC 2019 64-bit、MinGW 8.1.0 64-bit、Qt Charts、Qt SerialPort、Qt Multimedia以及底部的Developer and Designer Tools里的Qt Creator和调试器。不要贪多勾选太多组件会拖慢安装速度也用不上。5.2 发布打包时必须用windeployqtQt程序发布到没装Qt的电脑上运行必须处理动态依赖。用windeployqt这个官方工具cd C:\Qt\5.15.2\msvc2019_64\bin windeployqt.exe D:\project\build\release\Collector.exe这条命令会自动把Qt相关的DLL、plugins、qml等依赖拷到exe同目录下。但要注意如果你用了第三方库比如QCustomPlot它只是一个头文件加源文件不需要额外DLL如果你用了KissFFT也只是源码编译不需要额外拷贝。但如果你在工程里用了别的第三方动态库windeployqt不会管它必须手动拷贝。很多新手运行release版本exe时报“应用程序无法正常启动”或者“no Qt platform plugin could be initialized”就是这个环节出了错。最大的坑是platforms目录缺失。Qt的Windows平台插件位于plugins\platforms\qwindows.dllwindeployqt会把它拷贝到发布目录的platforms\下。如果手动拷DLL时漏了这个目录程序一启动就崩。5.3 麒麟系统离线安装Qt现场部署环境往往是信创整机麒麟操作系统是常客。在麒麟v10 x86_64上装Qt我踩过的坑集中在依赖库上。Qt运行需要一堆xcb相关的图形库部署时经常报QXcbConnection: Could not connect to display或找不到libxcb-xinerama。解决方法是预先安装一堆系统依赖sudo apt-get install -y libxcb-*-dev libxkbcommon-x11-0 libxkbcommon-dev libgl1-mesa-dev libfontconfig1-dev libdbus-1-dev如果现场不能联网就要提前准备离线deb包把32位和64位的都下载好再过去装。Qt的安装目录建议放在/opt/Qt5.15.2然后修改用户的.bashrc把Qt库路径加进环境变量否则启动时报找不到Qt库。5.4 串口模块在Linux下部署的权限问题Qt串口程序在麒麟或Ubuntu上还有一个绕不开的坑当前用户没有访问/dev/ttyUSB0的权限。表现为打开串口成功但读不到数据。解决办法是把用户加入dialout组sudo usermod -a -G dialout $USER然后注销重新登录生效。如果还是不行检查一下串口设备是否被ModemManager这个服务占用。这个服务会去抢3G/4G上网卡和串口设备用sudo systemctl stop ModemManager禁掉就清爽了。6. 常见问题与排查技巧实录6.1 串口与日志排查三板斧调试采集终端时我把一套行之有效的排查流程做了标准化这里分享给读者。第一串口调试助手打底。不管Qt上位机写得有多顺手排查底层问题时先用第三方串口助手直接连终端看原始数据排除上位机解析逻辑的影响。我常用的工具是友善串口调试助手简单、稳定实测不乱码。第二日志级别要分明。Qt上位机的日志我用qDebug输出到控制台同时用qInstallMessageHandler重定向到日志文件。线上跑的时候关掉调试日志保留警告和错误等级可以显著减少日志文件体积。第三善用QCustomPlot的实时刷新看数据连续性。如果曲线断断续续大概率是串口数据丢帧或者协议解析帧长不对检查波特率和校验位。6.2 Qt崩溃问题排查热词里有“qt崩溃”这一项说明大家对这个高度共鸣。本项目里我遇到过几次Qt崩溃归纳下来无非这几类第一类跨线程操作UI控件。串口读数据是在子线程里如果在子线程里直接调ui-widget-update()轻则界面卡住重则程序崩溃。正确的做法是通过信号槽跨线程通知UI线程刷新。Qt的信号槽默认支持跨线程队列连接只要你确保连接方式是Qt::QueuedConnection。第二类QCustomPlot在频繁调用replot()时崩溃。解决方法是设置setNotAntialiasedElements(QCP::aeAll)关闭抗锯齿能明显提升刷新速度再配合定时器合并重绘频率比如每50ms刷新一次避免每收到一帧数据就刷一次。第三类FFT时数组越界。kiss_fftr在点数不是2的整数次幂时会异常。我封装FFT函数时加了个断言Q_ASSERT((nfft (nfft - 1)) 0);不是2的幂就直接不给过。6.3 网络协议对接的常见报错NB-IoT模块上云对接时最容易遇到的报错是平台返回连接拒绝或者鉴权失败。排查要点如下连接拒绝检查APN设置是否正确。中国移动NB-IoT的APN一般是cmiot通过AT指令设置ATCGDCONT1,IP,cmiot鉴权失败检查产品ID、access_key是否填对。OneNET平台的产品ID可以在平台控制台查看鉴权信息是创建产品时生成的复制的时候注意别带上空格。如果上报数据到平台后平台侧看不到数据检查topic对不对。OneNET平台的数据流topic规则和设备的自动订阅策略有关要确保设备侧发布到正确的topic平台侧的数据流名称也要和上报JSON里的key一致否则数据会被平台丢弃。6.4 Qt HTTP请求的POST问题热词里还有“qt post请求 无法获取”和“qt request method post not supported”这个场景一般是用Qt做小程序后端或者数据管理系统时遇到的。排查方法经验如下先看服务端日志。如果是 “Request method POST not supported”基本是Spring MVC这类服务端没写对应的POST接口或路径不对返回了405。很多Qt新人以为是Qt的问题实际上是接口路径搞错了。再看请求头。用QNetworkAccessManager发POST请求必须设置Content-Type头。如果服务端接收的是JSON格式要设置application/jsonrequest.setHeader(QNetworkRequest::ContentTypeHeader, application/json);最后检查发送的数据。Qt的QNetworkAccessManager::post(request, data)的data可以是QByteArray或QHttpMultiPart。如果是JSON直接用QJsonDocument::toJson()转成字节数组发送即可不要再手动拼字符串转义。7. 技术选型复盘与后续扩展7.1 为什么Qt能撑起这个项目整套系统跑下来我对Qt在物联网数据采集领域的位置有了更清晰的认识。Qt不是万能的但在这种需要跨平台、界面交互丰富、还要集成绘图和网络通信的场景里它确实是最顺手的选项之一。C的性能保证FFT和大量波形数据刷新不卡Qt的信号槽让线程间通信清晰QCustomPlot的绘图性能也经住了现场长时间运行的考验。热词里有人问“qt creator vs vs code:2024年qt开发ide终极选择指南”我的建议很简单做Qt项目就用Qt Creator除非你有很特别的理由要待在VS Code的生态里。Qt Creator自带的Qt Designer可视化设计界面拖拽控件效率极高。VS Code写Qt也不是不行但配置编译环境、调试器、CMake工具链的过程会让你的耐心断送在第一步。7.2 项目后续可扩展的方向这个项目如果继续往下做有几个方向我很想尝试。一个是在终端侧加边缘计算能力。NB-IoT的带宽有限如果把原始波形都传到云端做FFT流量成本太高。更好的做法是终端MCU里直接集成一个轻量FFT函数库在本地算好频谱特征值只把特征值上报平台这样流量开销能降低一个数量级。KissFFT本身就支持很轻量的部署完全可以在STM32L4上跑。另一个方向是Qt上位机里加数据回放功能。当前实现是实时显示再加一个离线数据文件的回放模块就能对历史数据做二次分析。QCustomPlot支持导出数据把采集到的原始数据和频谱结果存成CSV或数据库回放时读取序号逐帧绘制即可。第三个方向是远程升级。NB-IoT模组支持FOTA升级协议MCU固件可以通过平台远程下发。Qt上位机端可以扩展一个固件管理界面操作者上传固件到平台再对指定设备发起升级。这个功能对设备部署在野外、不方便挨个去现场更新的场景非常关键。7.3 整体开发时间线复盘这个项目从需求分析到交付总共用了大概六个月。前两个月做需求分析和硬件平台搭建中间两个月做NB-IoT通信链路调试和平台对接后两个月集中做Qt上位机和整机联调。联调阶段是最耗精力的因为终端端和上位机端的错误经常互相掩盖最后是靠前面提到的“串口助手打底、分层排查”的方法把问题一个个剥出来的。时间线上最容易低估的是信创环境适配。我们前期花了两周时间在麒麟系统上调试Qt环境各种依赖问题比预想的多。如果项目启动一开始就并行安排一个环境适配小组会比最后集中突击好很多。说到最后一个经验做这样的软硬结合项目不要只闷头写代码一定要把串口协议文档和平台接入文档整理成一份团队共享的wiki。这个项目我们早期就因为协议字段说明模糊浪费了不少联调时间。后期把协议整理成表格字段名、字节偏移、取值范围都写清楚双方协作效率明显提升。项目收尾时把文档交付给运维团队后续维护也没有过高的沟通成本。这个采集终端项目后续又迭代了两版Qt上位机的界面和协议也一起演进。我自己最大的体会是技术栈不管怎么选稳定的通信链路、清晰的数据协议、好用的调试工具永远是这类项目的三条生命线。先保证这三点再谈界面的美观和功能的丰富项目的成功率就会高很多。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻