Linux下Qt与USB-CANII集成:多线程CAN总线通信实战
1. 项目缘起与整体设计思路1.1 为什么要在Linux下折腾USB-CANII嵌入式开发和汽车电子测试领域里CAN总线调试几乎是绕不开的日常。市面上常见的CAN分析仪里周立功USB-CANII算是性价比和稳定性都比较能打的一款Windows下的驱动和上位机生态也很成熟。但问题来了——很多实际项目跑在Linux环境里比如车载域控制器的台架测试、工控网关的联调、机器人主控的现场调试这些场景下你不可能为了用CAN分析仪专门装一台Windows机器。我最初接触这个需求是在一个工控网关项目上主控跑的是Ubuntu需要通过CAN总线采集底层设备的状态数据同时在上位机界面实时显示。硬件用的是USB-CANII软件框架选的是Qt。看起来是个很常规的组合但真正动手才发现坑不少官方Linux驱动只提供了基础的字符设备接口Qt的事件循环和CAN的阻塞读取天然冲突多线程下的数据同步稍不注意就出问题。这篇文章就是把这套组合拳从头到尾捋一遍包括驱动编译、Qt线程模型设计、数据收发实现、以及我在实际调试中踩过的那些坑。适合有一定Linux基础和Qt开发经验、正在做CAN总线相关项目的朋友参考。如果你只是想在Windows下用官方上位机点几下那这篇内容可能不太对你的胃口。1.2 整体架构怎么搭才不别扭先说结论不要把CAN读写放在Qt的主线程里。这是整个项目设计的第一原则也是后面所有架构决策的出发点。原因很直接。USB-CANII的Linux驱动通过字符设备暴露接口读取操作默认是阻塞的。如果你在主线程里直接read界面会直接卡死用户体验就是点了按钮之后窗口白屏。有人会说可以用非阻塞模式加定时器轮询但轮询间隔设大了丢帧设小了CPU占用飙升而且USB设备的读取延迟本身就不稳定轮询方案在实际项目里很难调到一个舒服的平衡点。所以我的方案是独立线程负责CAN数据收发通过Qt的信号槽机制与主线程通信。具体分工是这样的CAN接收线程阻塞读取字符设备拿到一帧数据就通过信号发出去CAN发送线程从发送队列取数据写入字符设备主线程负责界面刷新、业务逻辑处理、发送指令的下发这个架构的好处是职责清晰接收线程只管收发送线程只管发主线程只管显示和逻辑。线程之间通过Qt的信号槽和线程安全队列通信避免了直接共享内存带来的竞态问题。注意Qt的信号槽在跨线程时默认是队列连接QueuedConnection这意味着信号发出后不会立即执行槽函数而是把事件投递到目标线程的事件循环里。这个特性在这里正好为我们所用天然实现了线程间的解耦。1.3 驱动层和应用层的边界在哪里很多初学者容易把驱动层和应用层的职责搞混。我的经验是驱动层只负责最原始的字节流读写所有协议解析、帧格式转换、错误处理都放在应用层。周立功USB-CANII的Linux驱动加载后通常会在/dev目录下生成对应的字符设备节点。应用层通过open、read、write、ioctl这些标准系统调用来操作。驱动本身不理解CAN帧的业务含义它只负责把USB数据包转换成字节流或者反过来。这意味着什么呢意味着你在应用层需要自己处理CAN帧的组装和解析。标准CAN帧是13字节包含帧信息、帧ID、数据长度、8字节数据扩展帧也是13字节但帧ID占4字节。这些细节都需要在代码里体现。把边界划清楚的好处是将来如果换用其他型号的CAN分析仪只需要替换驱动适配层上层的业务逻辑和界面代码基本不用动。我在第二个项目里换了一个国产的CAN盒子就是因为当初分层做得好只改了一个适配类就搞定了。2. 核心细节解析与实操要点2.1 驱动编译与设备节点确认周立功官方提供的Linux驱动源码通常是一个压缩包里面包含Makefile和几个C文件。编译之前有几件事必须先确认内核头文件是否齐全。Ubuntu下需要安装linux-headers-$(uname -r)否则编译会报找不到头文件的错误。这个坑我踩过不止一次尤其是刚装好的系统默认是不带头文件的。内核版本兼容性。官方驱动不一定支持最新的内核如果编译报错大概率是内核API变了。常见的是file_operations结构体的成员变化或者copy_from_user的签名调整。遇到这种情况要么降内核版本要么手动改驱动源码适配。编译命令很标准make sudo make install sudo modprobe usbcan2加载成功后用dmesg | tail看内核日志应该能看到驱动注册成功的提示同时会告诉你设备节点名通常是/dev/usbcan2或者类似的。如果没有自动创建设备节点可能需要手动mknod。实操心得设备节点的权限默认是root可读写普通用户访问会报Permission denied。临时方案是sudo chmod 666 /dev/usbcan2但重启后就失效了。正规做法是写一条udev规则根据设备的vendor id和product id自动设置权限和属主。udev规则文件放在/etc/udev/rules.d/下文件名以.rules结尾内容大概是SUBSYSTEMusb, ATTR{idVendor}xxxx, MODE0666这样的格式。确认设备节点存在后可以用cat /dev/usbcan2测试一下如果CAN总线上有数据应该能看到乱码输出因为终端把二进制数据当文本显示了。这个测试虽然粗糙但能快速验证驱动是否正常工作。2.2 CAN帧格式与字节序处理这是整个项目里最容易出错的地方没有之一。USB-CANII的驱动读写的是原始字节流每一帧固定13字节。这13字节的布局是这样的字节偏移含义说明0帧信息包含帧类型、数据长度等1-4帧ID标准帧用2字节扩展帧用4字节5-12数据最多8字节有效数据帧信息字节的位定义需要仔细看手册。通常bit7表示帧格式0标准帧1扩展帧bit6表示帧类型0数据帧1远程帧低4位表示数据长度。这个位定义不同版本的固件可能有细微差异一定要以你手上设备的手册为准。字节序问题更隐蔽。CAN帧ID在总线上传输时是大端序但x86和ARM处理器是小端序。驱动在转换时是否做了字节序处理取决于驱动的实现。我的经验是不要假设直接实测。发一帧已知ID的数据用示波器或者另一个CAN分析仪抓包确认比看代码猜要靠谱得多。// 假设驱动返回的13字节原始数据在buf中 unsigned char frame_info buf[0]; unsigned int can_id 0; if (frame_info 0x80) { // 扩展帧4字节ID can_id (buf[1] 24) | (buf[2] 16) | (buf[3] 8) | buf[4]; } else { // 标准帧2字节ID can_id (buf[1] 8) | buf[2]; } unsigned char data_len frame_info 0x0F;这段代码看起来简单但字节序搞反了的话收到的ID会完全对不上。我在一个项目里就因为这个问题排查了大半天最后发现是驱动做了字节序转换而我在应用层又转了一次等于转回去了。2.3 Qt线程模型的选择与取舍Qt提供了多种多线程方案选哪种取决于你的具体需求。我把常见的几种方案列出来对比一下方案优点缺点适用场景QThread子类化直观容易理解只有run函数在新线程执行简单的后台任务moveToThread灵活对象所有槽函数都在新线程需要理解事件循环需要信号槽交互的场景QtConcurrent代码简洁不适合长期运行的任务一次性计算任务线程池资源复用任务粒度不好控制大量短任务对于CAN收发这种长期运行、需要持续交互的场景我推荐moveToThread方案。具体做法是定义一个Worker类把CAN读写的逻辑封装成槽函数然后创建一个QThread对象把Worker对象move到那个线程里。class CanWorker : public QObject { Q_OBJECT public: explicit CanWorker(QObject *parent nullptr); public slots: void startReceive(); void sendFrame(unsigned int id, const QByteArray data); void stop(); signals: void frameReceived(unsigned int id, const QByteArray data); void errorOccurred(const QString msg); private: int m_fd; std::atomicbool m_running; };这种写法的好处是Worker对象的所有槽函数都会在子线程的事件循环里执行你不需要手动加锁保护成员变量前提是这些变量只在子线程里访问。主线程通过信号触发子线程的槽函数子线程通过信号把数据传回主线程整个交互过程干净利落。注意moveToThread之后Worker对象的构造函数仍然在原来的线程执行只有槽函数在新线程执行。所以不要在构造函数里做任何依赖线程的操作比如打开设备文件。正确的做法是定义一个init槽函数通过信号触发它在新线程里执行。2.4 阻塞读取与线程退出的优雅处理接收线程的核心是一个阻塞的read调用。问题在于当你想退出程序时read还在阻塞着线程无法正常结束。如果直接terminate可能导致资源泄漏甚至程序崩溃。解决方案有几种我试过之后觉得最靠谱的是用select或poll做超时等待。具体做法是在read之前先用select检查设备是否可读设置一个合理的超时时间比如100毫秒。如果超时了检查一下退出标志如果标志为true就退出循环如果设备可读再执行read。void CanWorker::startReceive() { m_running true; fd_set readSet; struct timeval timeout; while (m_running) { FD_ZERO(readSet); FD_SET(m_fd, readSet); timeout.tv_sec 0; timeout.tv_usec 100000; // 100ms int ret select(m_fd 1, readSet, nullptr, nullptr, timeout); if (ret 0) { emit errorOccurred(select error); break; } else if (ret 0) { continue; // 超时检查退出标志 } unsigned char buf[13]; int n read(m_fd, buf, sizeof(buf)); if (n 13) { // 解析并发送信号 parseAndEmit(buf); } } }这样退出时只需要把m_running设为false最多等100毫秒线程就会自然结束。比起用pthread_cancel或者QThread::terminate这种方式安全得多。3. 实操过程与核心环节实现3.1 环境准备与依赖安装先说一下我的测试环境Ubuntu 20.04 LTS内核版本5.15Qt 5.15.2编译器GCC 9.4.0。这个组合比较稳定推荐大家用类似的版本。需要安装的依赖包sudo apt update sudo apt install build-essential linux-headers-$(uname -r) sudo apt install qtbase5-dev qt5-qmake sudo apt install libqt5serialport5-dev # 如果后续要扩展串口功能Qt Creator的话可以直接从官网下载安装包也可以用apt安装qtcreator。我个人习惯用命令行qmake加Makefile的方式轻量且可控。驱动源码的编译前面已经说过了这里补充一点如果编译过程中报错说找不到asm/io.h之类的头文件通常是内核头文件路径不对。可以用make -C /lib/modules/$(uname -r)/build M$(pwd) modules这种方式指定内核源码路径。3.2 设备打开与参数配置打开设备节点的代码很标准但有几个细节需要注意int CanWorker::openDevice(const QString devPath) { m_fd open(devPath.toLocal8Bit().constData(), O_RDWR); if (m_fd 0) { emit errorOccurred(QString(open device failed: %1).arg(strerror(errno))); return -1; } return 0; }参数配置是通过ioctl完成的主要是设置波特率。USB-CANII支持的波特率通常是固定的几档125k、250k、500k、1M等。具体的ioctl命令码需要查驱动源码或者手册不同版本的驱动可能不一样。// 设置波特率为500k具体命令码以驱动为准 int baudrate 500000; if (ioctl(m_fd, USBCAN2_SET_BAUDRATE, baudrate) 0) { emit errorOccurred(set baudrate failed); return -1; }实操心得波特率设置错误是新手最常见的坑。如果发送正常但接收不到数据或者接收到的全是错误帧第一件事就是检查波特率和总线上其他设备是否一致。CAN总线对波特率非常敏感差一点都不行。另外总线的两端必须各有一个120欧姆的终端电阻缺一个都可能导致通信不稳定。3.3 接收线程的完整实现把前面的片段串起来接收线程的完整逻辑是这样的void CanWorker::startReceive() { m_running true; fd_set readSet; struct timeval timeout; unsigned char buf[13]; while (m_running) { FD_ZERO(readSet); FD_SET(m_fd, readSet); timeout.tv_sec 0; timeout.tv_usec 100000; int ret select(m_fd 1, readSet, nullptr, nullptr, timeout); if (ret 0) { if (errno EINTR) continue; emit errorOccurred(QString(select: %1).arg(strerror(errno))); break; } if (ret 0) continue; int n read(m_fd, buf, sizeof(buf)); if (n 0) { if (errno EINTR) continue; emit errorOccurred(QString(read: %1).arg(strerror(errno))); break; } if (n ! 13) continue; unsigned char info buf[0]; unsigned int id; if (info 0x80) { id (buf[1] 24) | (buf[2] 16) | (buf[3] 8) | buf[4]; } else { id (buf[1] 8) | buf[2]; } int len info 0x0F; if (len 8) len 8; QByteArray data(reinterpret_castchar*(buf 5), len); emit frameReceived(id, data); } emit receiveStopped(); }这段代码里我加了EINTR的处理因为select和read都可能被信号中断不处理的话会误报错误。另外数据长度做了上限保护防止驱动返回异常数据导致越界。3.4 发送队列与线程安全发送这边我用了一个QQueue加QMutex的方案。主线程调用sendFrame时把数据压入队列然后通过信号通知发送线程。发送线程从队列取数据写入设备。void CanWorker::sendFrame(unsigned int id, const QByteArray data) { QMutexLocker locker(m_sendMutex); CanFrame frame; frame.id id; frame.data data; m_sendQueue.enqueue(frame); // 唤醒发送线程如果用的是条件变量 }其实更简单的做法是直接用Qt的信号槽主线程emit一个sendRequested信号发送线程的槽函数直接执行write。因为信号槽跨线程是队列连接天然就是线程安全的。但这种方式在高频发送时可能会有性能问题因为每个信号都要走一遍事件循环。如果发送频率不高比如每秒几十帧直接用信号槽就够了如果频率很高还是用队列加条件变量的方案更稳妥。3.5 主线程的界面联动主线程这边主要做两件事接收子线程的frameReceived信号并更新界面以及把用户的发送操作转发给子线程。// 在主窗口类里 connect(m_canWorker, CanWorker::frameReceived, this, MainWindow::onFrameReceived); void MainWindow::onFrameReceived(unsigned int id, const QByteArray data) { QString str QString(ID: 0x%1 Data: %2) .arg(id, 8, 16, QChar(0)) .arg(QString(data.toHex( ).toUpper())); ui-textEdit-append(str); // 如果数据量很大需要限制显示行数防止内存暴涨 if (ui-textEdit-document()-blockCount() 1000) { ui-textEdit-clear(); } }这里有个细节如果CAN总线上数据量很大界面刷新会成为瓶颈。我的做法是加一个缓冲每100毫秒批量刷新一次界面而不是每收到一帧就刷新。这样既保证了实时性又不会让界面卡顿。4. 常见问题与排查技巧实录4.1 设备打开失败的那些原因设备打开失败是最常见的问题报错信息通常是Permission denied或者No such file or directory。排查思路如下现象可能原因解决方法No such file驱动未加载modprobe加载驱动检查dmesgPermission denied权限不足chmod或配置udev规则Device busy设备已被占用检查是否有其他进程在用Open success但read失败设备未正确初始化检查ioctl配置是否成功还有一种隐蔽的情况设备节点存在权限也对但open返回成功却读不到数据。这通常是USB连接问题换一个USB口试试或者检查USB线是否接触良好。USB-CANII对USB供电比较敏感有些前置USB口供电不足会导致设备工作不稳定。4.2 收不到数据的排查流程收不到数据比打不开设备更让人头疼因为没有任何报错。我的排查流程是这样的确认硬件连接CAN_H接CAN_HCAN_L接CAN_L终端电阻是否接好确认波特率用示波器或者另一个分析仪确认总线波特率确认驱动配置ioctl设置波特率是否成功返回值检查了吗确认过滤设置有些驱动支持验收滤波器如果设了过滤规则不匹配的帧会被丢弃确认发送端用另一个设备发一帧已知数据看能否收到我遇到过一次诡异的情况设备能发不能收。排查了半天发现是驱动里的接收缓冲区满了因为应用层读取速度跟不上总线数据速率。解决办法是加大驱动的接收缓冲区或者提高应用层的读取优先级。4.3 线程退出的正确姿势程序退出时线程没清理干净会导致下次启动时设备被占用。正确的退出流程是MainWindow::~MainWindow() { // 1. 通知工作线程停止 emit stopReceive(); // 2. 等待线程结束 m_canThread-quit(); m_canThread-wait(3000); // 3. 关闭设备 // 在Worker的stop槽函数里close(m_fd) }顺序很重要先停线程再关设备。如果反过来线程还在read的时候设备被关了read会返回错误虽然不会崩溃但日志里会有一堆错误信息。实操心得QThread::wait的参数是毫秒如果超时了线程还没结束说明线程里有死循环或者阻塞操作没处理好。这时候不要用terminate而是回头检查代码找到阻塞点。terminate是最后的手段用了之后资源状态是不确定的。4.4 高频数据下的性能优化当总线负载率超过50%时性能问题就会显现出来。我做过一个测试500k波特率下满负载发送每秒大约4000帧。这个速率下如果每帧都走一遍信号槽加界面刷新CPU占用会很高。优化手段有几个批量读取一次read读取多帧数据减少系统调用次数批量刷新界面每100毫秒刷新一次而不是每帧刷新数据分流不是所有帧都需要显示可以按ID过滤只显示关心的帧零拷贝如果数据量特别大可以考虑共享内存方案避免数据在信号槽里拷贝我在一个项目里用了批量读取加批量刷新的组合CPU占用从40%降到了8%左右效果非常明显。4.5 常见问题速查表问题排查方向快速验证方法打不开设备驱动、权限、占用ls -l /dev/usbcan*收不到数据波特率、接线、过滤用回环模式自发自收数据错乱字节序、帧格式发已知ID验证界面卡顿刷新频率、数据量加计数器统计帧率线程退不出阻塞点、退出标志加日志定位阻塞位置发送失败总线仲裁、缓冲区满检查write返回值这张表是我在实际项目中总结出来的基本上覆盖了90%以上的问题。遇到新问题时先对照这张表排查能省不少时间。5. 一些个人体会和扩展思路这套方案我在三个项目里用过从工控网关到车载测试台架整体稳定性是经得起考验的。最关键的经验就是前面反复强调的那条线程边界要清晰驱动层和应用层要解耦。这两条做到了后面的问题都好解决。扩展方面如果项目需要支持多路CAN可以创建多个Worker对象每个对应一个设备节点各自跑在独立的线程里。Qt的信号槽机制天然支持这种一对多的关系主线程只需要连接不同的信号到不同的槽函数就行。另外如果后续要接入CANopen或者J1939这些上层协议建议在Worker和界面之间再加一层协议解析层。Worker只负责收发原始帧协议解析层负责把原始帧转换成有业务含义的数据结构界面只跟协议层打交道。这样分层之后换协议或者加协议都不会影响底层通信代码。最后说一个调试小技巧在开发阶段可以写一个简单的回环测试程序把发送的数据直接读回来验证驱动和线程逻辑是否正确。这个测试不需要真实的CAN总线只需要把设备配置成回环模式就行。等回环测试通过了再接到真实总线上调试能排除掉很多干扰因素。

相关新闻

MindSpeed-LLM 分布式评估实战:基准、指令微调与 LoRA 权重评估指南

MindSpeed-LLM 分布式评估实战:基准、指令微调与 LoRA 权重评估指南

人工智能大模型分布式训练预训练微调模型推理服务强化学习Ascend 【免费下载链接】MindSpeed-LLM 昇腾LLM分布式训练框架 项目地址: https://gitcode.com/Ascend/MindSpeed-LLM 点击查看 免费下载 MindSpeed-LLM 在昇腾硬件上提供开箱即用的大模型分布式评估能力&a…

2026/10/14 3:10:33 阅读更多 →
crossbeam-utils 演进全史:从版本变更日志看 Rust 并发原语的打磨之路

crossbeam-utils 演进全史:从版本变更日志看 Rust 并发原语的打磨之路

并发编程内存管理 【免费下载链接】crossbeam Tools for concurrent programming in Rust 项目地址: https://gitcode.com/gh_mirrors/cr/crossbeam 点击查看 免费下载 导读 本文以 crossbeam-utils 的 CHANGELOG.md 为核心骨架,从 0.1.0 到 0.8.23 的…

2026/10/14 3:10:33 阅读更多 →
cjhead 开发指南:仓颉头文件(cj.d)生成工具的导出规则、架构与编译构建

cjhead 开发指南:仓颉头文件(cj.d)生成工具的导出规则、架构与编译构建

开发工具CLI代码质量 【免费下载链接】cangjie_tools 仓颉编程语言命令行工具,包括仓颉包管理工具、仓颉格式化工具、仓颉多语言桥接工具及仓颉语言服务。 项目地址: https://gitcode.com/Cangjie/cangjie_tools 点击查看 免费下载 cjhead(C…

2026/10/14 3:10:33 阅读更多 →

最新新闻

控制中心UI手册阅读指南:从界面布局到权限矩阵的落地实践

控制中心UI手册阅读指南:从界面布局到权限矩阵的落地实践

简介:这份Desigo CC用户界面手册由西门子(中国)有限公司提供,是楼宇自动化系统Desigo CC中文培训资料,面向需要掌握系统操作与界面配置的工程师、运维人员和集成商。手册聚焦用户如何借助摘要栏、系统浏览器、主窗格与…

2026/10/14 3:52:40 阅读更多 →
测试工程师KPI数据整理指南:从指标拆解到绩效汇报

测试工程师KPI数据整理指南:从指标拆解到绩效汇报

看到测试工程师 KPI 这几个字,估计不少同行心里都会咯噔一下。平时埋头写用例、提缺陷、跑回归,一到月底要填绩效表了,才发现自己干的活很难用一两句话讲清楚。量化吧,怕领导觉得数字虚;讲贡献吧,又怕说不出…

2026/10/14 3:52:40 阅读更多 →
HOL形式化验证:列表与集合在操作系统内核建模中的核心应用

HOL形式化验证:列表与集合在操作系统内核建模中的核心应用

在形式化验证这块,很多人一上来就被各种抽象符号劝退。但实际做操作系统内核的验证时你会发现,真正每天都在打交道的,其实是最朴素的列表和集合。上一篇文章我们把证明环境跑通,梳理了HOL里的函数定义和基本推理规则,这…

2026/10/14 3:52:40 阅读更多 →
初中生课堂看小说检讨书写作指南与技巧

初中生课堂看小说检讨书写作指南与技巧

1. 初中生课堂看小说检讨书写作指南作为一名曾经也犯过类似错误的老学长,我完全理解你现在的心情。上课看小说被老师抓包,既尴尬又懊悔,还得写检讨书,这种滋味确实不好受。但别担心,这份检讨书其实是个很好的反思机会&…

2026/10/14 3:52:40 阅读更多 →
OpenClaw本地提权实战:从改配置到改源码,解锁游戏引擎高级权限

OpenClaw本地提权实战:从改配置到改源码,解锁游戏引擎高级权限

玩怀旧游戏这么多年,我一直有个执念:手里有经典但已经“过时”的作品,一定要想办法在现在的机器上跑起来,而且还要跑得比原版更爽。OpenClaw这个开源重制引擎,就是我被朋友拉着入坑的第一个项目,它把当年那…

2026/10/14 3:52:40 阅读更多 →
基于Spring Boot的诊所管理系统:从需求到落地的完整实战

基于Spring Boot的诊所管理系统:从需求到落地的完整实战

做这个诊所管理系统之前,我在好几家小诊所蹲过点,也跟几个开诊所的朋友聊过他们的日常。一个很扎心的事实是:大多数诊所的数字化还停留在"Excel 纸质病历"的水平,收费靠手工计算,药品库存靠月底盘点&#x…

2026/10/14 3:51:40 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →