简介这份基于Qt框架的串口温度监测示例面向正在学习串口通信与桌面界面开发的初学者及嵌入式、物联网方向开发者重点解决温度数据实时接收、解析与曲线展示的入门实践问题。压缩包共9个文件约11KB包含C头文件与cpp源文件、ui界面文件、pro工程文件及用户配置分别承载串口逻辑、界面布局与工程构建配置体积轻量却保留了可编译运行的工程骨架。已有6220人学习下载说明其作为Qt串口入门案例具有较高参考价值。读者可从中了解QSerialPort的使用方式、信号与槽驱动界面刷新、QGraphicsView/QGraphicsScene绘制动态温度曲线以及原始串口数据到可读温度值的解析思路适合对照搭建自己的监测界面并扩展为更完整的物联网监控应用。1. 温度曲线画不出来先看串口那头到底吐了什么去年帮一个做恒温设备的朋友救火他桌上摆着一台烘箱、一个热电偶采集模块和一台笔记本模块每 500 ms 往串口吐一行$T,23.6*3F笔记本上那个基于 Qt 的串口温度显示界面是别人留下的能收到数但曲线一会儿一卡跑两小时内存飙到 1.2 G。这类“Qt 串口 实时数据 曲线显示”的小工具是工控、实验台、嵌入式调试里最常被临时攒出来的东西需求极短连上、收数、画线、看得清。这个项目就是干这件事的完整实现——QSerialPort 收数据、环形缓冲存历史、QPainter 自绘实时曲线带当前值、极值统计和超限提示。适合手上有点 Qt 基础、想要一个真能跑通再拿去改的开发者也适合第一次写上位机、不想在第三方图表库上折腾半天的人。2. 工程结构与选型为什么最后没用现成图表库拿到这类项目我第一件事不是看 mainwindow.cpp而是先看目录。目录里如果serial、chart、ui各占一块说明作者分得清“通信”和“显示”两条线后面加功能就不至于把业务逻辑塞进 paintEvent 里。这个小工具的结构不大但边界划得比较干净串口线程只管收字节和拆帧曲线控件只管把一串 double 画成折线主窗口做粘合。2.1 目录结构一个能编译的最小闭环tempmon/ ├── tempmon.pro # qmake 工程也可以用 CMakeLists.txt 替代 ├── main.cpp ├── mainwindow.h/.cpp/.ui # 窗口、按钮、状态栏、定时刷新 ├── serial/ │ ├── serialworker.h/.cpp # QSerialPort 封装打开/关闭/重连/收数 │ └── frameparser.h/.cpp # 拆帧与校验纯函数方便单测 ├── chart/ │ ├── curvewidget.h/.cpp # 自绘曲线控件 │ └── ringbuffer.h # 定长环形缓冲模板头文件实现 └── resources/ └── style.qss # 深色主题工控现场看着不刺眼这个划分有个好处frameparser不依赖 QSerialPort你拿一段录好的十六进制日志就能在单元测试里反复跑不用每次都插硬件。curvewidget也不关心数据从哪来它只暴露一个setBuffer(const RingBufferdouble,600*)和一个update()换成模拟数据、换成从 CSV 回放都不用动这一层。2.2 选型对比QChart、QCustomPlot、自绘三条路实时曲线在 Qt 里就三条常见路我一般按“刷新频率 × 部署难度”来选。方案上手成本打包体积2000 点 / 30fps适合场景Qt ChartsQChartView低多带一个 Charts 模块吃紧频繁 append 容易掉帧报表、静态图、点数少QCustomPlot中单 .h/.cpp 拖进工程好自带 setData 批量替换要缩放、游标、导出QPainter 自绘中偏高零额外依赖很好画多少点自己说了算界面固定、只要一条实时线这个项目选了自绘。原因很实在界面就一条温度曲线横轴固定最近 5 分钟纵轴自适应不需要缩放拖动用 QChart 反而要处理QChartView的 setRubberBand、动画开关、序列清空一大堆边角QCustomPlot 是好东西但一旦要出图给现场看自绘能把线宽、颜色、网格、字体全部按客户要求一次调死不留“这个能不能改一下”的扯皮空间。要加缩放再说那时候换 QCustomPlot 也只是把curvewidget换掉接口不变。2.3 .pro 里必须写对的那几行很多人卡在第一步不是代码错是QSerialPort根本没被链进来报一堆undefined reference to QSerialPort::...。QT core gui serialport greaterThan(QT_MAJOR_VERSION, 4): QT widgets CONFIG c11 TARGET tempmon TEMPLATE app SOURCES main.cpp mainwindow.cpp \ serial/serialworker.cpp serial/frameparser.cpp \ chart/curvewidget.cpp HEADERS mainwindow.h \ serial/serialworker.h serial/frameparser.h \ chart/curvewidget.h chart/ringbuffer.h用 CMake 的话对应两行find_package(Qt5 COMPONENTS Widgets SerialPort REQUIRED) target_link_libraries(tempmon PRIVATE Qt5::Widgets Qt5::SerialPort)顺便提醒一句模块版本Qt 5.x 里QSerialPort属于serialport模块Qt 6 里模块名没变但QSerialPort::error信号改成了errorOccurred两者不兼容。我一般会在serialworker.h里加一层#if QT_VERSION QT_VERSION_CHECK(6,0,0)的宏把信号名统一到一个槽上省得换版本时全工程搜改。3. 串口链路打通从 openPort 到 readyRead 的完整入口上位机的血泪经验出问题的地方九成不在画图在收数。收数这块又有三层——端口能不能开、字节流怎么切成一帧一帧、断了之后能不能自己接回来。这三层写扎实界面再丑都能用写不扎实界面再漂亮都是摆件。3.1 端口枚举与八条参数一次配全打开串口前先扫端口别让用户手敲COM3。QSerialPortInfo::availablePorts()能拿到端口名、描述和厂商用描述过滤掉一堆虚拟串口比如蓝牙模拟出来的现场就不会选错。// 枚举可用端口填进 QComboBox void MainWindow::refreshPorts() { ui-cbPort-clear(); const auto ports QSerialPortInfo::availablePorts(); for (const QSerialPortInfo info : ports) { // 名称 描述一起显示用户认的是描述不是 COM 号 ui-cbPort-addItem(QString(%1 (%2)).arg(info.portName(), info.description()), info.portName()); } ui-btnOpen-setEnabled(ui-cbPort-count() 0); }真正打开的时候八个参数一个都不能省。默认值看着能用实际现场只要有一个不对收到的就是一堆乱码或者干脆一个字都没有。参数本项目取值设错的典型表现PortNameCOM3/ttyUSB0打开失败PermissionErrorBaudRate9600 或 115200全是乱码或长度对但数值离谱DataBits8数据位错高位被吃ParityNoParity偶发单字节错看起来像干扰StopBitsOneStop边界多一个字节帧头对不上FlowControlNoFlowControl一发大数据就卡住不返回ReadBufferSize0交给驱动设太小会丢包打开模式ReadWrite只读模式下发不了配置指令bool SerialWorker::open(const QString portName, int baud) { if (m_port.isOpen()) { m_port.clear(QSerialPort::AllDirections); m_port.close(); // 先关再开避免句柄泄漏和重复连接 } m_port.setPortName(portName); m_port.setBaudRate(baud); m_port.setDataBits(QSerialPort::Data8); m_port.setParity(QSerialPort::NoParity); m_port.setStopBits(QSerialPort::OneStop); m_port.setFlowControl(QSerialPort::NoFlowControl); if (!m_port.open(QIODevice::ReadWrite)) { emit errorOccurred(m_port.errorString()); // 错误往上抛UI 弹状态栏 return false; } m_buf.clear(); // 上一次残留的半包必须清掉 m_aliveTimer-start(1000); // 顺带起心跳 return true; }m_buf.clear()这行别省。拔插过一次的设备缓冲区里往往会留着半个帧下次一连上就先把这半截当数据处理数值直接跳到一个不可能的值。3.2 readyRead 里最该防的两件事粘包和半包readyRead的触发时机跟你的帧边界没有任何关系。串口一次可能给你 1 个字节也可能一次给你三帧半。指望“一次 readyRead 就是一帧”项目上线第一天就会翻车。标准做法是全部readAll()追加到缓冲区然后循环找帧尾找到就切走切不完整就留着等下一批。void SerialWorker::onReadyRead() { m_buf.append(m_port.readAll()); // 兜底缓冲区异常增长说明帧尾永远没来砍掉旧数据防止吃满内存 if (m_buf.size() 8192) { m_buf m_buf.right(1024); emit errorOccurred(QStringLiteral(缓冲区溢出疑似帧尾丢失)); } int idx; while ((idx m_buf.indexOf(\n)) 0) { QByteArray frame m_buf.left(idx).trimmed(); // 顺手去掉 \r m_buf.remove(0, idx 1); if (!frame.isEmpty()) { handleFrame(frame); } } }indexOf(\n)是这套写法里最省事的一步只要协议保证以换行结尾就不用自己数长度、不用维护状态机。切出来的frame交给解析函数解析失败就直接丢绝不能让一条坏帧污染曲线。void SerialWorker::handleFrame(const QByteArray f) { // 帧格式$T,23.6*3F 校验 $ 与 * 之间所有字节异或 if (!f.startsWith($)) return; const int star f.indexOf(*); QByteArray body (star 0) ? f.mid(1, star - 1) : f.mid(1); if (star 0) { bool ok false; const quint8 want f.mid(star 1).toUInt(ok, 16); if (!ok || xorSum(body) ! want) { // 校验不过静默丢弃并计数 m_badFrames; return; } } const QListQByteArray parts body.split(,); if (parts.size() 2 || parts.at(0) ! T) return; // 类型不认识的帧直接跳过 bool ok false; const double t parts.at(1).toDouble(ok); if (!ok) return; // 物理量程兜底热电偶不可能报 300出现就是解析错位 if (t -55.0 || t 150.0) { m_badFrames; return; } emit temperatureArrived(t, QDateTime::currentMSecsSinceEpoch()); }几个参数值得说清楚xorSum(body)算的是$与*之间的异或值十六进制两位m_badFrames是个只增不减的计数器界面上显示成“错误帧 12”现场调试时这个数字比日志好用量程判断-55 ~ 150是按常见热电偶模块的上限拍的换成 PT100 就改成-200 ~ 600这一步看着多余实际是防错位最便宜的一招。3.3 断线检测与自动重连不能等用户来点按钮USB 转串口最烦人的地方就是热插拔后设备名会变COM3拔了再插可能变成COM7。用户不会知道这件事他们只会说“软件卡住了”。做法是加一个 1 秒心跳连续 N 秒没收到任何字节就判定掉线然后按“描述匹配”重新找端口找到了自动重开。void SerialWorker::onHeartbeat() { if (!m_port.isOpen()) return; if (m_lastByteMs 0 QDateTime::currentMSecsSinceEpoch() - m_lastByteMs 3000) { emit errorOccurred(QStringLiteral(超过 3 秒无数据尝试重连)); m_port.close(); // 按描述找回同一个设备端口号变了也不影响 for (const auto info : QSerialPortInfo::availablePorts()) { if (info.description().contains(m_deviceHint)) { open(info.portName(), m_port.baudRate()); return; } } } }m_deviceHint在第一次打开成功时存下来取QSerialPortInfo::description()是这套重连逻辑的关键。用端口名匹配基本没戏因为名字本来就会变用描述匹配只要现场不是同时插了五个同型号模块命中率很高。4. 实时曲线绘制环形缓冲加自绘折线30 帧稳住数据进得来之后才是显示。这块的坑不在“会不会画折线”在“画多久会卡”。新手最常见的写法是QListdouble m_data; m_data.append(t);然后paintEvent从头遍历到尾巴。跑十分钟没事跑两小时开始一秒一卡因为点数从 600 涨到了七万。4.1 数据容器定长环形缓冲别让容器无限长实时曲线的横轴只有“最近 N 个点”有意义历史数据要么落盘、要么扔掉绝不该留在绘制用的容器里。搞一个固定容量 600 的环形缓冲600 点 × 500 ms 5 分钟写满之后新数据覆盖最老的一条内存占用从启动到关机是一条平线。template typename T, int N class RingBuffer { public: void push(const T v) { m_data[m_head] v; m_head (m_head 1) % N; if (m_size N) m_size; } int size() const { return m_size; } bool full() const { return m_size N; } // i 0 取最老的一条i size()-1 取最新的一条 const T at(int i) const { return m_data[(m_head - m_size i 2 * N) % N]; } void clear() { m_head 0; m_size 0; } private: T m_data[N]; int m_head 0; // 下一个写入位置 int m_size 0; // 当前有效元素个数 }; 2 * N是为了处理m_head - m_size为负的情况C 的%对负数返回负值不加这层会越界读而且这种越界不一定会立刻崩属于典型的黑匣子问题。这个容器只有五行有效逻辑但它是整个曲线控件能不能长时间跑稳的地基。4.2 paintEvent坐标映射、网格和抗锯齿自绘曲线的核心就两个 lambdaxOf(i)把序号映射成像素横坐标yOf(v)把温度映射成像素纵坐标。纵轴量程不要用“当前最大最小”那样曲线会一直贴边抖看着像噪声做法是先留 1 ℃ 余量再保证最小跨度 5 ℃视觉上就稳了。void CurveWidget::paintEvent(QPaintEvent *) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿折线才不毛刺 p.fillRect(rect(), QColor(18, 18, 20)); const QRectF plot rect().adjusted(56, 16, -16, -36); // 留出刻度区和时间轴 double lo m_min - 1.0, hi m_max 1.0; if (hi - lo 5.0) { // 最小跨度防止曲线被放大成锯齿 const double mid (hi lo) / 2.0; lo mid - 2.5; hi mid 2.5; } auto xOf [](int i) { return plot.left() plot.width() * i / qMax(1, m_buf-size() - 1); }; auto yOf [](double v) { return plot.bottom() - plot.height() * (v - lo) / (hi - lo); }; // 横向网格 纵轴刻度 for (int k 0; k 5; k) { const double y plot.top() plot.height() * k / 5.0; p.setPen(QPen(QColor(45, 45, 48), 1)); p.drawLine(QPointF(plot.left(), y), QPointF(plot.right(), y)); p.setPen(QColor(150, 150, 150)); p.drawText(QRectF(0, y - 8, 50, 16), Qt::AlignRight | Qt::AlignVCenter, QString::number(hi - (hi - lo) * k / 5.0, f, 1)); } // 折线本体 QPainterPath path; for (int i 0; i m_buf-size(); i) { const QPointF pt(xOf(i), yOf(m_buf-at(i))); if (i 0) path.moveTo(pt); else path.lineTo(pt); } p.setPen(QPen(QColor(0, 220, 160), 2)); p.drawPath(path); // 最新点画个实心圆一眼能看出数据在不在动 if (m_buf-size() 0) { const int last m_buf-size() - 1; p.setPen(Qt::NoPen); p.setBrush(QColor(0, 220, 160)); p.drawEllipse(QPointF(xOf(last), yOf(m_buf-at(last))), 3, 3); } }几个细节setRenderHint(Antialiasing)只开一次放在函数开头网格线用QColor(45,45,48)这种接近背景的灰太亮会抢曲线qMax(1, size()-1)是防止只有一个点时除零。要用QPainterPath而不是逐段drawLine点多了以后性能差一个量级——drawLine是 600 次调用drawPath是一次。4.3 刷新节流数据到达频率不等于重绘频率串口 500 ms 来一条重绘也跟着 500 ms 一次看起来没问题。但一旦把波特率调高到 50 ms 一条update()就会被调用 20 次每秒而屏幕刷新只有 60 Hz中间那些重绘纯属浪费更糟的是如果每条数据都顺手更新一下 QLabel 文本界面线程会被排版计算吃满。正确做法是“数据到达只改模型定时器统一重绘”。数据槽只做三件事压缓冲、更新极值、置脏标记。真正碰控件的是 33 ms 的定时器。// 数据到达只改数据不碰界面 void MainWindow::onTemperature(double t, qint64) { m_buf.push(t); m_now t; m_max qMax(m_max, t); m_min qMin(m_min, t); m_dirty true; } // 33 ms 定时器统一刷新脏了才画 void MainWindow::onRefreshTick() { if (!m_dirty) return; m_dirty false; m_curve-update(); // 触发 paintEvent ui-lbNow-setText(QString::number(m_now, f, 2)); // 当前值 ui-lbMax-setText(QString::number(m_max, f, 2)); ui-lbMin-setText(QString::number(m_min, f, 2)); const bool over m_now m_limitHigh || m_now m_limitLow; ui-lbNow-setStyleSheet(over ? color:#ff5252; : color:#00dc9c;); }m_dirty这个布尔量值不了几个字节却能在低频数据时把 CPU 从“每秒 30 次空转绘制”压到“每秒 2 次”笔记本风扇都不转了。超限提示也不要弹 QMessageBox现场没人有空点确定把数字变红、状态栏写一行就够了。5. 避坑与排查串口上位机最容易翻车的六件事这个项目的代码量不大但把代码跑起来之后的问题比代码本身更费时间。下面六条是我在不同设备上反复遇到的每条按“看到什么、为什么、怎么改”写能省掉不少来回。第一条打开返回 true一个字节都收不到。现象是open()成功、isOpen()也是 true但bytesAvailable()永远是 0。原因通常有两个一是 USB 转串口的 TX/RX 接反了或者没共地电气层根本没通二是波特率对上了但数据位/校验位不匹配收到的字节被硬件判成帧错误直接丢弃驱动层不报错。解决顺序是先短接模块的 TX/RX 做回环测试能收到自己发的字节说明电脑侧没问题问题在设备和线序回环都收不到就换一根线、换一个 USB 口别在代码里找。第二条跑十分钟开始卡鼠标拖动窗口有明显延迟。原因是readyRead槽里直接调了update()甚至repaint()。repaint()是同步重绘会强制立刻走完整个 paintEvent数据来得快的时候界面线程直接被堵死。解决办法就是上面那套脏标记加定时器任何从 IO 回调里触发的重绘都走节流顺便把QTextEdit的日志也限行setMaximumBlockCount(500)一行搞定不然日志控件自己就能吃掉几百兆。第三条内存一路涨任务管理器里数字只增不减。九成是绘制容器用了QList::append没清理或者日志没有最大行数。判断方法很简单把曲线控件隐藏不绘制如果内存还在涨那就是数据容器或者日志如果隐藏后不涨了是绘制路径里分配了什么没释放比如每次 paintEvent 都 new 一个 QPainterPath 却没析构。环形缓冲的写法从根上掐掉第一种第二种靠QPainterPath用成员变量或者栈上对象解决。第四条数值偶尔跳到一个不可能的数比如 6553.5。这是典型的拆帧错位半个帧被当成完整帧解析toDouble()把一段乱码解释成了数字。原因往往是缓冲没清、或者协议里用了变长字段却没有长度校验。解决办法三层打开串口时清缓冲、帧尾用固定分隔符不要靠超时判断、解析完加物理量程兜底。加完量程判断之后这类脏数据基本进不到显示层最多让“错误帧”计数加一。第五条拔掉再插上软件就再也连不上了。现象是端口列表里明明有新端口点打开却报“设备不存在”或者一直失败。原因是上一次的QSerialPort对象没正确 close句柄还挂在系统里同时端口名从COM3变成了COM7界面上存的还是旧名字。解决是把 open 写成“先 close 再 open”并且重连逻辑按设备描述匹配而不是按端口名再稳一点就在aboutToClose里把缓冲区、心跳定时器一起停掉。第六条本机跑得好好的拷到现场机器双击就提示找不到串口模块。这是发布阶段最经典的坑QSerialPort属于插件式模块动态库名字是Qt5SerialPort.dll同时还需要qtbase/plugins/...下的串口相关插件目录。用windeployqt打包时它默认只带 Widgets 相关的东西漏掉 SerialPort 就会出现“界面能开、端口列表为空”。解决办法是在部署命令里显式指定模块或者手工确认Qt5SerialPort.dll和serialport插件目录都在。这一条我在现场翻过车后来每次发版都先在一台干净机器上双击一遍再交付。6. 二次开发改造从单路温度到多通道落盘的具体走法这个项目把一条链路跑通了真要用起来改动集中在三个方向多通道、落盘、报警策略。结构上不用大动因为frameparser和curvewidget都是可替换的零件。第一步扩多通道。协议里加通道号就行比如$T,1,23.6*3F解析函数多返回一个int chMainWindow里把单个RingBuffer换成QVectorRingBufferdouble,600CurveWidget的setBuffer改成接收整个 vectorpaintEvent 外面套一层按通道遍历、按通道取颜色的循环。改动量大概三十行难点只在配色别选太接近的。改造点原实现改法注意多通道单个环形缓冲QVectorRingBufferdouble,600各通道时间戳对齐别混用一个 x 轴落盘无QFileQTextStream按行写 CSV每 200 行 flush 一次别每条都 flush报警单一上下限每通道独立阈值 回差回差防抖否则边界上会疯狂闪烁回放无读 CSV 走同一套push接口复用curvedwidget别写第二套绘制第二步落盘。别在数据到达的槽里直接write加flush500 ms 一条无所谓50 ms 一条时磁盘 IO 会变成瓶颈。做法是先进一个QQueueQString用一个 1 秒的定时器批量写一次写几十行再一次 flush文件头在打开时先写一行timestamp,ch,temp。void DataRecorder::onTick() { if (m_queue.isEmpty()) return; QTextStream out(m_file); while (!m_queue.isEmpty()) { out m_queue.dequeue() \n; // 一行一条CSV 直接可读 } out.flush(); // 一秒一次够用且不伤盘 }第三步验证。改完别急着上设备先写一个FakeSource类用QTimer每 100 ms 造一个正弦加噪声的值走和真实串口完全相同的temperatureArrived信号。这样做的好处是曲线卡不卡、内存涨不涨、多通道配色能不能分清全都能在没有硬件的情况下压测半小时以上。我一般会把FakeSource留一个快捷键开关交付的时候不删下次现场出问题先切到模拟源能复现就是软件问题不能复现就去查线和设备排查路径一下短一半。那次内存飙到 1.2 G 的事故之后我给自己定了个硬规矩凡是带实时曲线的上位机绘制容器必须定长、重绘必须节流、部署必须在干净机器上双击一遍。这三条现在写进了我自己的项目模板里新建工程就带着省得每次靠记性。希望这些能帮到你少走一遍我踩过的路。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
PHP中gRPC核心机制与实战应用详解 1. 为什么我们需要深入理解PHP中的gRPC第一次在PHP项目中接触gRPC时,我被它复杂的配置流程和晦涩的概念搞得晕头转向。作为一个长期使用REST API的开发者,突然面对Protocol Buffers、HTTP/2传输和流式处理这些概念,确实需要一段适应期。但当我… · 2026/11/5 6:05:15
从GUI到NUI:下一代人机交互的技术革命与应用 1. 人机交互的现状与痛点:为什么我们需要一场革命?每天早晨,当我坐在电脑前开始一天的工作,总要先经历一套固定仪式:打开邮箱客户端、点击收件箱、滚动查找重要邮件、双击打开阅读...这套操作我已经重复了十几年。直到… · 2026/11/5 6:05:15
WPS字母上方加横杠和尖角:公式编辑器与域代码全解析 1. 这个需求到底在解决什么问题在文档编辑场景里,字母上方加横杠或尖角,是一个出现频率远比想象中高的需求。物理老师在课件里写“拉力F”,需要在F上方加一条横线表示矢量;数学老师在讲义里标注“线段AB”,需要在AB上方… · 2026/11/5 6:05:15
C# WinForms 弹出式进度条组件:跨线程进度上报与取消链路设计 简介:这份压缩包面向使用 C# WinForm 开发桌面应用的初中级开发者与自学者,围绕弹出式进度条这一常见交互需求,提供可运行的示例工程与配套代码,帮助理解进度显示、界面响应和任务反馈的实现方式。包内共 28 个文件,以… · 2026/11/5 6:56:33
离线人脸识别门禁系统:OpenCV+Dlib+SQLite全链路实现 简介:这是一套面向Python初学者与计算机视觉入门者的完整人脸识别系统实现,聚焦安全监控、身份验证等典型应用场景,提供从人脸采集、特征建库到实时识别的端到端解决方案。资源共118个文件,包含52个核心Python源码(含G… · 2026/11/5 6:56:33
XP老测试台VISA IO库配置与GPIB仪器控制排障指南 简介:Agilent IO Libraries Suite 15.5(Windows XP 版)压缩包面向仍需维护旧版安捷伦仪器与工控环境的实验室工程师、科研人员及测试开发人员,解决老旧 XP 工作站上仪器连接、驱动安装和统一通信调用的问题。压缩包共 43 个文件&a… · 2026/11/5 6:56:33
Unity+Go欢乐麻将源码:含胡牌算法、网络同步与防作弊框架 简介:这是一份面向游戏开发初学者与Cocos2D-X进阶学习者的手游麻将项目实战源码,聚焦于欢乐麻将核心逻辑实现与跨平台工程实践,助力开发者深入理解2D棋牌类游戏的架构设计与关键技术落地。资源共584个文件,涵盖126个PNG/UI素材、6… · 2026/11/5 6:56:33
嵌入式Linux驱动开发实战:从字符设备到平台总线源码解析 简介:这份资源是华清远见嵌入式培训的驱动源码合集,面向具备一定C语言与Linux基础、希望深入理解嵌入式驱动开发的学习者,可用于对照课程实验、复现板级驱动案例与梳理内核模块的编写流程。压缩包共286个文件,约4.97MB,… · 2026/11/5 6:56:33
ResNet18 CIFAR-10小图训练:结构适配、调参与避坑指南 简介:面向深度学习入门与图像识别实践者的 PyTorch 项目包,围绕 ResNet18 在 CIFAR10 十类彩色小图上的训练与评估展开,可用于理解残差块、短路连接、归一化与随机裁剪、水平翻转等数据增强,以及交叉熵损失、权重衰减和学习率调度… · 2026/11/5 6:55:33
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38