简介这是一份面向QNX嵌入式开发初学者与串口通信学习者的源码实例包围绕实时操作系统下的串口编程展开可用于理解设备节点访问、参数配置与数据收发流程适合需要快速搭建串口实验并对照源码排查配置问题的自学者。压缩包为rar格式共13个文件、约13KB包含C语言源码、makefile与mk构建脚本、工程配置文件、目标文件及可执行文件便于在QNX工程环境中查阅和编译实践。源码覆盖打开串口、通过termios与ioctl设置波特率、数据位、停止位和校验位、read/write收发以及关闭设备等关键环节并涉及返回值检查与错误处理思路。已有642人学习可作为嵌入式调试、设备控制及底层通信机制的入门参考也能帮助读者理解QNX串口程序的组织方式、工程结构与底层I/O机制。1. 一份能上板就跑的 QNX 串口程序省掉的到底是哪段功夫不少人在 QNX 上第一次写串口卡住的地方和算法无关而是open(/dev/ser1)之后那几百毫秒数据要么读不到要么读出来是乱的stty敲了一遍又一遍还是老样子。这份 QNX 串口程序源码包针对的就是这一段——把驱动启动参数、termios 原始模式、环形缓冲、帧状态机、多线程收发这几层按顺序摆好提供一份能编译、能上板、能对着排查的骨架。它适合已经拿到 BSP、手里有目标板、要在上面接传感器或上位机私有协议的嵌入式开发者。如果你只想弄明白串口在 QNX 里是怎么挂到文件系统上的看到第二章也就够了想直接拿去改协议、调实时性的后面几章的参数和坑都能直接用。2. 从 /dev/ser1 到 termios驱动、终端、应用三层参数怎么摆对QNX 上串口不通九成不是应用代码写错而是这三层里有一层没对上。驱动层负责把硬件寄存器变成设备节点终端层负责把字节流解释成「行」还是「原始字节」应用层负责搬数据。这三层任意一层用了默认值后面怎么调都是白费。2.1 驱动先起来devc-ser* 的启动参数与设备节点在 QNX Neutrino 里串口不是一个静态的/dev节点而是由devc-ser*这一族资源管理器进程注册出来的路径。进程没起/dev/ser1就不存在进程起了但参数不对节点在波特率却是错的。所以排查永远从「驱动进程」开始而不是从应用开始。典型的启动命令长这样放在启动脚本的[session]段或者 build 文件里# -b 指定驱动默认波特率-u 后面跟 单元号,端口基址,中断号x86 ISA 风格 # SoC 平台一般不用手工给端口驱动从 hwinfo 里拿具体参数用 -h 查 devc-ser8250 -b115200 -u1 3f8,4 # 起来之后先确认三件事 pidin | grep -i ser # 1) 驱动进程在不在、启动参数对不对 ls -l /dev/ser* # 2) 节点注册出来没有、权限够不够 stty /dev/ser1 # 3) 终端当前实际的波特率/数据位/流控-b只是驱动的默认值应用打开设备后一旦调tcsetattr以应用设的为准——这一点经常被误解成「驱动设了 115200 应用就不用管了」。-u的格式在不同驱动上不一样devc-ser8250是「单元号,端口,中断」SoC 上的devc-seromap之类往往只需要单元号或者干脆自动枚举拿不准就直接devc-serxxx -h看它自己的用法别照着别的平台抄。编译侧同样要对齐目标架构主机上编译出来跑不动是最常见的低级翻车# -V 选目标变体必须和板子上跑的系统一致aarch64le 与 armle 不能混 qcc -Vgcc_ntoaarch64le -O2 -Wall -Wextra -o ser_demo ser_open.c ser_io.c parser.c main.c提示/dev/ser1的权限默认常常只给 root应用以普通用户跑就会open失败返回EACCES。要么在启动脚本里chmod 666 /dev/ser1要么把应用放进有相应权限的账户下跑别在代码里加setuid那种歪招。2.2 termios把终端从「编辑模式」掰成原始模式这是 QNX 串口程序最容易翻车的一层。QNX 的终端默认是规范canonical文档里也叫 edited模式输入按行缓存、回显、0x03会被解释成中断字符、0x11/0x13会被当成软件流控吃掉。你的协议里只要出现这些字节数据就在驱动层被「加工」没了应用侧再怎么写都收不回来。要传二进制帧必须逐位清干净。下表是这份源码包里ser_open_raw()实际做的事termios 字段要清掉的位不清会怎样c_iflagIXON IXOFF IXANY ICRNL INLCR IGNCR ISTRIP INPCK BRKINT PARMRK负载里的0x11/0x13/0x0D被当控制字符吞掉或改写c_oflagOPOST发出去的0x0A被扩成0x0D 0x0A帧长直接错c_lflagICANON ECHO ECHOE ECHOK ISIG IEXTEN行缓冲导致read一直等换行符短帧永远读不出来c_cflagCSIZE PARENB CSTOPB数据位/校验位/停止位不是 8N1对端全乱码c_cflag置位CS8 CREAD CLOCAL不使能接收、被 modem 控制线挂住c_ccVMIN0、VTIME0配合 select 使用时行为不符合预期#include termios.h #include fcntl.h #include unistd.h #include errno.h #include stdio.h /* 以原始模式打开串口baud 用 B115200 这类常量不要传裸数字 */ int ser_open_raw(const char *dev, speed_t baud) { /* O_NOCTTY别让这个口变成控制终端否则收到控制字符会影响本进程 */ int fd open(dev, O_RDWR | O_NOCTTY); if (fd 0) { perror(open); return -1; } struct termios t; if (tcgetattr(fd, t) ! 0) { perror(tcgetattr); close(fd); return -1; } /* 输入关软件流控、关回车换行互转、关奇偶校验与 break 处理 */ t.c_iflag ~(IXON | IXOFF | IXANY | ICRNL | INLCR | IGNCR | ISTRIP | INPCK | BRKINT | PARMRK); /* 输出关掉 \n - \r\n 这类加工帧是二进制一个字节都不能动 */ t.c_oflag ~OPOST; /* 本地非规范模式不回显不产生 SIGINT/SIGQUIT */ t.c_lflag ~(ICANON | ECHO | ECHOE | ECHOK | ISIG | IEXTEN); /* 控制8N1使能接收忽略 modem 控制线 */ t.c_cflag ~(CSIZE | PARENB | CSTOPB); t.c_cflag | (CS8 | CREAD | CLOCAL); /* 需要 RTS/CTS 硬件流控时打开这两位QNX 用的是 IHFLOW/OHFLOW 不是别的系统里常见的 CRTSCTS抄错就编译不过 */ /* t.c_cflag | (IHFLOW | OHFLOW); */ cfsetispeed(t, baud); cfsetospeed(t, baud); /* VMIN0/VTIME0read 立即返回当前可用字节可能为 0节奏交给 select */ t.c_cc[VMIN] 0; t.c_cc[VTIME] 0; /* TCSADRAIN 等输出队列排空再改避免把正在发的半帧截断 刚打开、还没发过数据时用 TCSANOW 也可以 */ if (tcsetattr(fd, TCSADRAIN, t) ! 0) { perror(tcsetattr); close(fd); return -1; } return fd; }几个参数值得单独说清楚。VMIN/VTIME只在非规范模式下有意义四种组合里只有0/0是「有就读、没有立刻返回」这正是配合select想要的语义1/0是「至少等到 1 个字节」会让你的读线程在空闲时死等超时逻辑全部失效。TCSANOW、TCSADRAIN、TCSAFLUSH的区别也别忽略TCSAFLUSH会把已收到但还没读的数据丢掉如果应用是「先跑一会儿再切换参数」用它会莫名其妙丢帧。最后提醒一句HUPCL不要随手加。它的语义是关闭设备时拉低 DTR某些对接的单片机把 DTR 接到复位脚上你程序一退出对端就重启了现象是「每次重连设备都要初始化半天」。2.3 读写循环select 超时 短读短写处理原始模式配好只是让字节「原样进出」什么时候读、读多少、写不完整怎么办得靠读写循环兜住。这份程序里读侧用select等可读写侧用循环续写。#include sys/select.h /* 等可读最多等 timeout_ms返回 0 读到的字节数0 超时-1 出错 */ static int ser_read_wait(int fd, unsigned char *buf, size_t cap, int timeout_ms) { fd_set rfds; FD_ZERO(rfds); FD_SET(fd, rfds); struct timeval tv; tv.tv_sec timeout_ms / 1000; tv.tv_usec (timeout_ms % 1000) * 1000; int n select(fd 1, rfds, NULL, NULL, tv); if (n 0) return (errno EINTR) ? 0 : -1; /* 被信号打断按超时处理 */ if (n 0) return 0; /* 超时交给上层做帧超时判断 */ ssize_t r read(fd, buf, cap); if (r 0) return (errno EINTR) ? 0 : -1; return (int)r; /* 关键r 常常远小于 cap也可能为 0 */ } /* 写满 len 个字节处理短写与输出缓冲满 */ static int ser_write_all(int fd, const unsigned char *buf, size_t len) { size_t off 0; while (off len) { ssize_t w write(fd, buf off, len - off); if (w 0) { if (errno EINTR) continue; /* 重试即可 */ if (errno EAGAIN) { delay(1); continue; } /* 输出队列满让出 1ms */ return -1; } off (size_t)w; /* 短写只进去一部分必须续写 */ } return 0; }select的返回值有三种代码里必须分开处理0表示至少一个字节可读0是超时这里正是做「帧内超时重同步」的时机-1且errno EINTR说明被信号打断不能当错误直接退出否则程序跑着跑着就被一个无关信号干掉。read返回 0 也不要慌在VMIN0下它只表示「这次没拿到数据」不是对端关闭。真正需要担心的是短写write返回小于len是正常现象驱动输出队列满了就会这样不续写就会发出半帧对端校验全错现象看起来像「偶发丢包」实际是自己把帧截断了。3. 帧状态机与环形缓冲把「半截包」变成可校验的完整报文驱动 FIFO 的深度通常只有十几到几十字节select唤醒到read真正执行之间还有调度延迟。结果是一次read拿到 0.5 个帧、1.5 个帧、或者一个完整帧外加下一帧的帧头全都正常。指望「一次 read 一帧」的写法在实验室能跑上现场必翻车。3.1 为什么要在应用层再做一层缓冲因为读侧不能被解析拖慢。解析里可能有 CRC 计算、回调、日志耗时不确定一旦读线程在里面多待几毫秒驱动 FIFO 就溢出了而溢出丢的字节是找不回来的。常见做法是读线程只做「read 塞进环形缓冲」解析放到另一个执行体里两边用单生产者单消费者模型连锁都不用加。#define RB_SIZE 4096 /* 必须是 2 的幂取模用 而不是 % */ #define RB_MASK (RB_SIZE - 1) typedef struct { unsigned char buf[RB_SIZE]; volatile unsigned int head; /* 只由写者读线程修改 */ volatile unsigned int tail; /* 只由读者解析线程修改 */ } ring_t; /* 写者侧返回实际写入字节数满了就返回写了多少绝不阻塞 */ static size_t rb_put(ring_t *r, const unsigned char *d, size_t n) { unsigned int h r-head, t r-tail; size_t space RB_SIZE - (size_t)((h - t) RB_MASK) - 1; /* 留一个空位区分满/空 */ if (n space) n space; for (size_t i 0; i n; i) { r-buf[(h i) RB_MASK] d[i]; } r-head (h (unsigned int)n) RB_MASK; /* 最后一次性更新 head保证可见性顺序 */ return n; } /* 读者侧有多少取多少 */ static size_t rb_get(ring_t *r, unsigned char *out, size_t n) { unsigned int h r-head, t r-tail; size_t avail (size_t)((h - t) RB_MASK); if (n avail) n avail; for (size_t i 0; i n; i) { out[i] r-buf[(t i) RB_MASK]; } r-tail (t (unsigned int)n) RB_MASK; return n; }head和tail用无符号整数的自然溢出配合掩码省掉每次取模head必须在数据全部写完之后再更新否则读者可能看到「head 已经前进但数据还没落地」的窗口。缓冲大小 4096 是按「最高波特率下 100ms 的数据量」估的115200 下 100ms 约 1152 字节留四倍余量足够扛住一次调度抖动。如果你的波特率上到 921600这个值要跟着涨别照抄。3.2 状态机帧头、长度、负载、校验、超时重同步协议是0xAA 0x55 LEN BODY[LEN] SUM这种最常见的小端私有帧。解析用逐字节喂的状态机好处是断帧、粘帧、噪声插入全都能自愈不需要缓存整段再做memmem找帧头。typedef enum { S_H1, S_H2, S_LEN, S_BODY, S_SUM } pstate_t; #define FRAME_MAX 64 #define FRAME_GAP_MS 50 /* 帧内允许的最大字节间隔 */ typedef struct { pstate_t st; unsigned char len, idx, sum; unsigned char body[FRAME_MAX]; uint64_t last_ms; /* 上一个字节的到达时刻用于超时重同步 */ unsigned int good, bad_csum, bad_len, resync; } parser_t; /* now_ms() 用 CLOCK_MONOTONIC别用 CLOCK_REALTIME否则校时会让超时判断抽风 */ static void parser_feed(parser_t *p, const unsigned char *d, size_t n) { for (size_t i 0; i n; i) { unsigned char c d[i]; uint64_t now now_ms(); /* 半帧卡住超过 FRAME_GAP_MS 没后续字节直接丢弃回帧头状态 */ if (p-st ! S_H1 (now - p-last_ms) FRAME_GAP_MS) { p-st S_H1; p-resync; } p-last_ms now; switch (p-st) { case S_H1: if (c 0xAA) p-st S_H2; break; case S_H2: /* 收到 0xAA 0xAA 0x55 时保持在 S_H2不能直接回 S_H1 */ if (c 0x55) p-st S_LEN; else if (c ! 0xAA) p-st S_H1; break; case S_LEN: p-len c; p-idx 0; p-sum 0; if (c 0 || c FRAME_MAX) { p-bad_len; p-st S_H1; } else p-st S_BODY; break; case S_BODY: p-body[p-idx] c; p-sum (unsigned char)(p-sum c); if (p-idx p-len) p-st S_SUM; break; case S_SUM: if (c p-sum) { p-good; on_frame(p-body, p-len); } else p-bad_csum; p-st S_H1; /* 无论对错都回帧头绝不停在 SU M 等下一个字节 */ break; } } }S_H2那个分支是血泪经验最初的版本写成if (c 0x55) ... else p-st S_H1;结果两帧之间夹一个0xAA噪声时状态机把噪声当帧头、把真正的帧头0xAA当数据丢掉后面整段错位表现为「连续丢好几十帧然后自己恢复」。修法就是上面那句else if (c ! 0xAA)。FRAME_GAP_MS 50这个值不是拍脑袋在 115200 下传一个 64 字节的帧约 5.5ms帧内任何字节间隔超过 10 倍就基本可以判定是断帧。如果你的上位机是「攒够一批再发」的这个值要按它的最大内部间隔来调设太小会把正常的长帧切断设太大则断帧要等很久才自愈。3.3 校验、长度上限与错误计数校验用累加和还是 CRC16取决于对端不是你能选的但长度上限必须自己加。LEN字段是一个字节恶意或者错位的数据能给你一个 255body[64]直接越界这种内存踩踏在目标板上表现为「跑几个小时后莫名重启」查起来极痛苦。错误计数同理四个计数器good、bad_csum、bad_len、resync看着不起眼现场排障时全靠它们区分「是对端没发」「是线路干扰」还是「是我自己解析错位」。常见做法是每 10 秒往日志打一行比值能直接把问题定性bad_csum高而good也有多半是线路或波特率偏差resync高而bad_csum几乎为零大概率是自己状态机或超时参数的问题。4. 实时性调优收、解、发三个执行体的优先级和唤醒方式串口程序在实验室跑通和在现场跑稳中间隔的就是这一章。默认优先级下你的读线程会被系统里任何一个稍忙的进程挤开几十毫秒而驱动 FIFO 只能缓冲十几字节挤开一次就丢一次数据。4.1 收、解、发拆成三个执行体拆分的依据是「谁的时间约束最硬」。收线程直接对接驱动 FIFO延迟要求最高解析线程做协议处理可以慢一点但不能阻塞收发送线程负责把上层要发的报文排队发出顺带解决多线程同时write导致报文交错的问题。执行体调度策略建议优先级职责不能做的事收线程SCHED_RR较高如 30selectreadrb_put解析、打印日志、申请内存发送线程SCHED_RR中如 25从发送队列取报文ser_write_all无限等待某个事件解析线程SCHED_RR略低于收如 20rb_get 状态机 回调在回调里做耗时 IO主/监控SCHED_OTHER默认统计、日志、命令处理抢占上面三个优先级的具体数值要按系统里其他关键线程来排不能孤立地看。判断方法很朴素现场跑起来之后用pidin观察各线程的 CPU 占用和状态如果收线程长期处于READY而拿不到 CPU说明优先级排低了或者有更高优先级的线程在空转。4.2 优先级与调度策略怎么设QNX 里新建线程默认继承创建者的调度参数所以只调pthread_attr_setschedparam是不够的必须显式关掉继承否则你设的优先级根本不生效——这个坑几乎每个人都会踩一次。#include pthread.h #include sched.h /* prio 在 QNX 里范围是 1..255普通用户通常只能用到 63再高需要额外权限 */ static int spawn_rt(pthread_t *tid, void *(*fn)(void *), void *arg, int prio) { pthread_attr_t attr; pthread_attr_init(attr); /* 关键不关继承的话下面设的策略和优先级会被静默忽略 */ pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(attr, SCHED_RR); struct sched_param sp; sp.sched_priority prio; pthread_attr_setschedparam(attr, sp); pthread_attr_setstacksize(attr, 64 * 1024); /* 别用默认 8MB嵌入式内存紧张 */ int rc pthread_create(tid, attr, fn, arg); pthread_attr_destroy(attr); if (rc ! 0) { errno rc; perror(pthread_create); } return rc; }SCHED_RR与SCHED_FIFO的选择收线程用SCHED_RR因为它会在select上阻塞天然让出 CPU时间片机制只是兜底防止它死循环霸占如果某个线程里有很长的纯计算段用SCHED_FIFO更合适但必须自己保证它有阻塞点否则同级线程会被饿死。pthread_create返回的是错误码本身不是-1所以要errno rc再perror直接perror会打出Success白白浪费一次排查时间。三个线程共享解析器和发送队列时互斥锁要带优先级继承pthread_mutexattr_t ma; pthread_mutexattr_init(ma); pthread_mutexattr_setprotocol(ma, PTHREAD_PRIO_INHERIT); /* 防优先级反转 */ pthread_mutex_init(g_tx_lock, ma);不加PTHREAD_PRIO_INHERIT的后果很典型低优先级的日志线程拿着锁被中优先级线程抢占高优先级的收线程在锁上等等多久完全看运气表现出来就是「偶发几十毫秒的接收空洞」。4.3 异步通知什么时候不用 selectselect是主流做法简单、可控、超时语义明确。但如果你的收线程还要处理别的 fd比如一个管理通道或者希望连select那一层唤醒延迟都省掉可以用异步通知把 fd 设成O_ASYNC事件以信号形式投递用sigwait在专线程里等。/* 先屏蔽信号再交给 sigwait 同步等待顺序反了信号会被默认处理掉 */ sigset_t set; sigemptyset(set); sigaddset(set, SIGIO); pthread_sigmask(SIG_BLOCK, set, NULL); /* 把 fd 的异步事件投给本进程 */ fcntl(fd, F_SETOWN, getpid()); int fl fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, fl | O_ASYNC | O_NONBLOCK); /* 等待线程里 */ for (;;) { int sig; if (sigwait(set, sig) ! 0) continue; if (sig ! SIGIO) continue; /* 信号会合并来了 10 次可能只收到 1 次必须循环读到 EAGAIN 为止 */ unsigned char tmp[512]; for (;;) { ssize_t r read(fd, tmp, sizeof(tmp)); if (r 0) { rb_put(g_rb, tmp, (size_t)r); continue; } if (r 0 errno EINTR) continue; break; /* EAGAIN 或者出错退出内层循环 */ } }两个必须记住的点一是信号会合并SIGIO不能当作「有几个字节」的计数只能当作「可能还有数据」的提示二是O_ASYNC必须配O_NONBLOCK否则内层那个read会在读完最后一字节后永久阻塞线程再也回不到sigwait。这套写法比select少一层轮询但调试难度高一截我一般只在收线程确实被别的 fd 拖累时才切过去。5. 避坑清单五个让我在目标板上返工的串口问题下面五条都是现象、原因、解决三段式的实录症状描述尽量按你实际会看到的样子写方便对照。5.1 open 返回 ENOENT 或 EACCES现象程序启动就退出perror打出No such file or directory或者Permission denied但/dev下明明能看到节点。原因ENOENT基本是驱动进程根本没起来或者设备节点注册在别的路径下有些 BSP 会把第一个口映射成/dev/ser1也可能是别的编号取决于-u的单元号EACCES是节点权限问题应用以非 root 身份跑而节点是crw-rw---- root root。解决先pidin | grep -i ser确认驱动进程再ls -l /dev/ser*看节点和权限。驱动没起就去查启动脚本有没有被注释掉、-u参数和硬件对不对权限问题在启动脚本里chmod 666 /dev/ser1别在应用里动setuid的心思。这一步花十分钟比后面猜半小时值。5.2 改了波特率还是乱码现象代码里明明cfsetospeed(t, B115200)了收出来还是花屏或者固定几个错误字节。原因三种可能。一是只调了cfsetispeed/cfsetospeed却没调tcsetattr参数根本没下发到驱动二是tcsetattr的action用了TCSAFLUSH把配置过程中的数据丢了导致对端超时重发三是对端压根没跟着改或者它用的是自己的晶振分频实际波特率偏差超过 2%。解决改完立刻stty /dev/ser1看驱动侧的实际值和代码里的常量对一遍。波特率常量必须用B115200这类宏传裸数字 115200 进去在部分实现上会被当成无效值静默忽略。偏差问题用示波器或者让对方发0x55连续码看波形周期算实际速率。5.3 收到的数据被「吞」掉一部分或几帧粘在一起现象抓日志发现本该 8 字节的帧只到了 6 字节或者一次read返回了三帧拼在一起。原因前者九成是终端还处在规范模式0x0D、0x11、0x13、0x03这些字节被输入处理吃掉了后者是正常的字节流语义read从来不保证边界。解决把 2.2 那张表里的位逐个清干净清完用一段包含0x00到0xFF全字节的测试数据回环验证一遍——这是最快的自检方法全字节过一遍哪个字节被改写一眼就能看出来。粘包靠 3.2 的状态机解决不要试图用「按固定长度读」绕过去。5.4 跑几分钟后开始丢字节重启就好现象刚上电一切正常跑五到十分钟后开始零星丢帧resync计数缓慢上涨重启程序又能好一阵。原因读线程被抢占导致驱动 FIFO 溢出。默认调度策略下只要系统里有编译、日志刷盘、文件系统同步这类后台动作读线程就可能被挤开几十毫秒而 FIFO 只能存十几字节。解决按 4.2 把收线程提到SCHED_RR并显式关闭调度继承把解析和日志挪出收线程。改完用一个持续高频发送的对端压测半小时看resync是否还在涨。如果还涨就要查是不是中断被别的驱动共享拖慢了那是 BSP 层面的事。5.5 程序退出再启动串口打不开或者对端被复位现象kill掉进程后重新运行open卡住或者返回错误或者每次退出对接的单片机就重启一次。原因前一个进程的 fd 没关干净资源管理器那边还挂着对端复位则是HUPCL在关闭时拉低了 DTR而对方把 DTR 接到了复位电路。解决退出路径上一定有close(fd)并且SIGTERM的处理函数里也要走同一条清理路径别只写正常返回的分支。HUPCL按 2.2 的建议不要置位。如果确实遇到「上一个进程还占着」用pidin找残留进程别靠重启板子解决——重启会掩盖真正的资源泄漏。6. 回环验证与长稳压测本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
可自部署10分钟临时邮箱:SMTP收信、TTL清理与验证码提取 简介:面向开发者与Web学习者的10分钟临时邮箱源码包,围绕短时有效邮箱地址的生成、收信与到期失效流程,适合用于注册验证、下载试用等不愿暴露真实邮箱的场景,也可作为PHP邮件系统与Web服务设计的实践项目。压缩包共492个文件&… · 2026/11/5 2:41:02
DataGrip连接达梦数据库的完整配置指南 1. 项目概述最近在帮客户做数据迁移时遇到了一个典型场景:需要连接达梦数据库(DM)进行数据操作,但发现DataGrip这款主流数据库IDE并没有预置DM的驱动配置。这其实是个挺常见的痛点——很多国产数据库或小众数据库在主流工具中缺乏原生支持。经过一番摸索… · 2026/11/5 2:41:02
Proxmox VE 7 apt-get update 100错误排查 很多人在 Proxmox VE 7 上装完系统、打开网页面板、新建第一台虚拟机,回头在 shell 里敲一句apt-get update,结果被一屏红色输出糊了一脸,最后一行写着E: Some index files failed to download...或者干脆只留一句apt-get update failed: exi… · 2026/11/5 2:41:02
免费 GPU 马赛克修复:Jasna 从安装到出片完整指南 免费 GPU 马赛克修复:Jasna 从安装到出片完整指南 【免费下载链接】jasna Video restoration tool 项目地址: https://gitcode.com/GitHub_Trending/ja/jasna
想让视频去马赛克又不想花钱,就让 Jasna 把检测、修复、编码整条流水线全部跑在 GPU 上… · 2026/11/5 3:28:15
NocoDB 部署完全指南:15 分钟跑起你的可视化数据库平台 NocoDB 部署完全指南:15 分钟跑起你的可视化数据库平台 【免费下载链接】nocodb 🔥 🔥 🔥 A Free & Self-hostable Airtable Alternative 项目地址: https://gitcode.com/GitHub_Trending/no/nocodb
NocoDB 是一个可自… · 2026/11/5 3:28:15
MaaAssistantArknights:明日方舟助手一次配置,日常一键托管躺平 MaaAssistantArknights:明日方舟助手一次配置,日常一键托管躺平 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项… · 2026/11/5 3:28:15
PR封面设计(四类) /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 3:28:15
Umi-OCR 免费离线 OCR 指南:截图、批量、PDF 识别一次搞定 Umi-OCR 免费离线 OCR 指南:截图、批量、PDF 识别一次搞定 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。内置多国… · 2026/11/5 3:28:15
如何录制视频,用 OBS /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 3:27:15
双极步进电机控制实战: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