双串口收发实战:STM32F103双串口中断与DMA方案详解
简介一份基于STM32F103的双串口收发工程源码包面向嵌入式入门开发者以及需要同时使用多串口完成数据交互与监控调试的工程师。资源以结构体方式封装串口资源覆盖时钟配置、波特率设置、数据位/停止位/校验位、收发模式以及NVIC中断使能等关键环节并提供完整收发示例与串口回环验证逻辑。压缩包内含143个文件包含31个C源文件、34个H头文件、2个汇编启动文件、1个HEX烧录文件以及uvprojx工程配置、sct链接脚本、map映射表等整体大小2.21MB可直接用Keil打开工程对照学习。已有2692人学习下载适合在设备控制、多机通信、串口日志监控等场景中作为基础模板能帮助读者理解USART初始化流程、结构体驱动的封装思路以及双串口协同工作的实现方法快速搭建可调试的收发功能。1. 双串口收发为什么单串口能跑通双串口就翻车很多开发者在 stm32f103 上调单串口收发时都挺顺利配置一个 USART、开一个接收中断、把数据塞进缓冲收发就通了。但把第二个串口加上去问题立刻冒出来——两个串口单独发都正常一旦同时收发数据就丢、错位甚至整个程序卡死在某个中断里。“调试通过”这四个字指的不只是两个串口各自能发能收而是两套中断、两组缓冲、两种波特率放在一起还能稳定协作。这篇笔记把实际调过的方案拆开从时钟配置、中断收发框架到联调方法和踩坑记录给出一套能在 stm32f103 上直接照抄的双串口收发实现。2. 双串口底层准备时钟、GPIO 复用与两个 USART 的初始化2.1 时钟差异决定了两个串口的初始化顺序stm32f103 的两个常用串口挂在不同的总线时钟上。USART1 挂在 APB2 上典型时钟 72MHzUSART2 和 USART3 挂在 APB1 上典型时钟 36MHz。这个差异直接决定了两件事使能时钟时要用不同的函数USART1 用RCC_APB2PeriphClockCmdUSART2 用RCC_APB1PeriphClockCmd。同样配置 115200 波特率两个外设内部的分频系数完全不同。库函数会自动算但如果外部晶振不准两个串口的实际波特率误差方向不一样联调时容易发现一个串口正常、另一个串口间歇性丢字节。初始化顺序我习惯固定成先使能 GPIO 和 AFIO 时钟再使能串口时钟然后配 GPIO 复用最后配 USART。如果先开串口再配 GPIO上电瞬间引脚状态不稳定偶尔会把第一帧数据收乱。/* GPIOA 与 AFIO 时钟USART1 和 USART2 都用 PA 口所以一并打开 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); /* USART1 在 APB2 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); /* USART2 在 APB1 */这段代码有两个容易遗漏的坑。第一个是RCC_APB2Periph_AFIO很多人不写当你不做重映射、用默认引脚时确实不报错一旦后续代码里用了重映射函数系统会卡死在断言上。第二个是 USART1 和 USART2 的时钟函数不能搞混把 USART2 的时钟误写成RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART2, ENABLE)在编译期不会报错但运行时串口完全不工作排查起来很费劲。2.2 GPIO 复用六根引脚先认清楚双串口最省事的引脚分配是USART1 用 PA9(TX)、PA10(RX)USART2 用 PA2(TX)、PA3(RX)。六根引脚互不冲突不需要打开重映射接线也方便。如果要考虑板子布局USART1 还可以重映射到 PB6/PB7但默认引脚方案在绝大多数场景下够用。TX 引脚要配置成复用推挽输出RX 引脚配置成浮空输入或上拉输入。如果外部电路已经做了上下拉就配成浮空输入如果是直接和模块对接没有额外电阻我一般配成上拉输入避免空闲时电平悬空产生乱码。GPIO_InitTypeDef gpio; /* USART1_TX: PA9 */ gpio.GPIO_Pin GPIO_Pin_9; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); /* USART1_RX: PA10 */ gpio.GPIO_Pin GPIO_Pin_10; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); /* USART2_TX: PA2 */ gpio.GPIO_Pin GPIO_Pin_2; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); /* USART2_RX: PA3 */ gpio.GPIO_Pin GPIO_Pin_3; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio);这段配置里最常见的错误是把 PA9 和 PA2 都写成同一个结构体然后只改 Pin 不改 Mode结果两个 TX 引脚配置不一致。实际调试时不会报错但示波器抓波形会发现其中一个 TX 输出的电平摆幅不对。另一个值得注意的点是 GPIO_Speed 对串口这种低速外设没有实质影响统一设 50MHz 即可不要在这里纠结速度档位。2.3 USART 参数初始化同一个结构体别复用两个串口的初始化逻辑完全一致只是外设基地址不同。需要注意数据位、停止位、校验位要保持一致硬件流控一般关闭省下 CTS/RTS 两根引脚三线接法就能和串口助手通信。USART_InitTypeDef usart1_cfg; usart1_cfg.USART_BaudRate 115200; usart1_cfg.USART_WordLength USART_WordLength_8b; usart1_cfg.USART_StopBits USART_StopBits_1; usart1_cfg.USART_Parity USART_Parity_No; usart1_cfg.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart1_cfg.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, usart1_cfg); USART_InitTypeDef usart2_cfg; usart2_cfg.USART_BaudRate 115200; usart2_cfg.USART_WordLength USART_WordLength_8b; usart2_cfg.USART_StopBits USART_StopBits_1; usart2_cfg.USART_Parity USART_Parity_No; usart2_cfg.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart2_cfg.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, usart2_cfg); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); USART_Cmd(USART2, ENABLE);波特率参数说明两个串口可以不同比如一个 9600 对接老设备一个 115200 对接传感器模块。库函数会自动根据 PCLK 计算分频不需要手算。两个串口都用 115200 时保持参数一致最省心波特率不同时高波特率的那个串口中断更密集在后面的 NVIC 配置里要给更高优先级。硬件流控字段按上面代码保持None如果误开了 RTS/CTS对方设备不支持时会直接收不到数据。提示USART_Init 内部有断言检查参数是否合法若在初始化后修改某个外设配置例如把 USART1 改成 8 位数据位必须重新调用一次 USART_Init不能只改结构体字段。3. 中断收发框架环形队列与双串口中断优先级设计3.1 为什么选择中断接收加查询发送双串口收发的方案有几种常见组合轮询收发、中断接收加查询发送、中断收发、DMA 接收。轮询收发在裸机系统里最不推荐两个串口同时来数据时主循环只能一个个轮询高波特率下很容易丢。中断接收加查询发送是 stm32f103 上最平衡的方案接收靠硬件中断保证不丢字节发送的数据量一般不大查询发送足够。查询发送的代价是发送过程中 CPU 会被占用但发送一个字节在 115200 波特率下大约耗时 87us发几十字节也就几毫秒对大多数控制类应用完全可接受。真正有压力的是接收方向尤其两个串口同时涌入数据时任何一个中断处理慢了另一个串口的数据寄存器就会被覆盖。所以核心设计原则是中断服务函数里只做一件事——把收到的字节放进属于自己的环形队列然后立刻返回。所有协议解析、数据转发、逻辑处理全部放到主循环。这个原则在双串口场景里比单串口更严格因为两个中断之间会互相抢占。3.2 环形队列写代码前先定好容量双串口需要两个独立的队列每个串口一个读指针、一个写指针。我不会用同一个结构体变量初始化两次而是定义两个全局实例避免中断函数里把数据塞错队列。typedef struct { uint8_t data[256]; /* 队列存储区 */ volatile uint16_t head; /* 写指针中断中更新 */ volatile uint16_t tail; /* 读指针主循环中更新 */ } RingBuf; RingBuf uart1_rx { .head 0, .tail 0 }; RingBuf uart2_rx { .head 0, .tail 0 }; int RingBuf_Write(RingBuf *rb, uint8_t byte) { uint16_t next (rb-head 1) % sizeof(rb-data); if (next rb-tail) { return -1; /* 队列满丢弃该字节 */ } rb-data[rb-head] byte; rb-head next; return 0; } int RingBuf_Read(RingBuf *rb, uint8_t *byte) { if (rb-head rb-tail) { return -1; /* 队列空 */ } *byte rb-data[rb-tail]; rb-tail (rb-tail 1) % sizeof(rb-data); return 0; }设计要点有三个。第一head和tail必须用volatile uint16_t。head在中断里写、主循环里读tail在主循环里写、中断里读没有 volatile 修饰时编译器可能优化掉一次读取出现极其诡异的偶发丢数据。第二判断队列满的条件是(head 1) % size tail所以 256 字节的数组实际可用 255 字节。第三返回 -1 表示满或空调用方要处理这个返回值。中断函数里队列满时直接丢弃该字节并返回这是最简单可靠的策略后续如果担心丢数据可以在这里加一个溢出计数器。这个环形队列没有加锁。它在单写者单读者的模型下是安全的写者只有对应串口的中断读者只有主循环。双串口各自有独立的队列也不会互相竞争。如果用到了 RTOS 并且把读操作放在多个任务里就必须加临界区保护了。3.3 两个中断服务函数只进队列不做解析void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); RingBuf_Write(uart1_rx, ch); } } void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART2); RingBuf_Write(uart2_rx, ch); } }两个中断函数结构完全一样。USART_ReceiveData 会读取数据寄存器读完之后 RXNE 位自动清零。这里的隐患是如果两个串口同时触发中断优先级较低的那个中断会被挂起只要中断函数足够短挂起的延迟只有几个指令周期不会丢数据。反过来如果在中断里做协议解析、字符串拼接、延时等待另一个串口的 RXNE 位在等待期间再次置位数据寄存器被新数据覆盖老数据就丢了。主循环侧的处理方式uint8_t ch; while (1) { if (RingBuf_Read(uart1_rx, ch) 0) { /* 处理 USART1 收到的数据 */ } if (RingBuf_Read(uart2_rx, ch) 0) { /* 处理 USART2 收到的数据 */ } }两个队列轮流读取优先级靠 NVIC 配置决定而不是靠主循环里的轮询顺序。主循环读到了哪一帧数据就处理哪一帧处理过程中新来的字节会继续被中断塞进队列不会丢失。3.4 NVIC 优先级双串口稳定工作的关键NVIC 配置是整个双串口方案里最容易翻车、也最常被忽视的部分。优先级分组的设置必须在 main 开头做一次推荐分组 2也就是 2 位抢占优先级、2 位子优先级每个优先级数值范围 0 到 3。NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef nvic; nvic.NVIC_IRQChannel USART1_IRQn; nvic.NVIC_IRQChannelPreemptionPriority 0; nvic.NVIC_IRQChannelSubPriority 0; nvic.NVIC_IRQChannelCmd ENABLE; NVIC_Init(nvic); nvic.NVIC_IRQChannel USART2_IRQn; nvic.NVIC_IRQChannelPreemptionPriority 1; nvic.NVIC_IRQChannelSubPriority 0; nvic.NVIC_IRQChannelCmd ENABLE; NVIC_Init(nvic);这里给 USART1 抢占优先级 0USART2 抢占优先级 1。含义是两个串口同时来中断时USART1 优先进USART2 中断执行过程中 USART1 来了能打断它反过来不行。这样配置的理由是 USART1 挂在 APB2 上数据吞吐能力更高在多数项目里做主串口用。优先级怎么选如果两个串口波特率一样可以把抢占优先级设为相同让硬件按照中断号仲裁如果波特率不同高波特率的那个串口一定要抢占优先级低否则它的中断服务频率高但优先级低和低频串口有重叠时高频串口的队列会连续溢出。波特率差距大时这个影响非常明显。注意NVIC_PriorityGroupConfig 只能调用一次。第二次调用会把整个系统所有中断的优先级分组强制修改如果之前其他外设已经初始化了中断这会引发意料之外的中断行为。4. 双串口联调回环、交叉回环与波形验证4.1 回环测试一根杜邦线先验证单路把 USART1 的 TX 和 RX 用杜邦线短接串口助手向 USART1 发一个字节程序把它原样发回去看是否收到同样的字节。这个测试用来验证单个串口从物理通路、中断到环形队列的整条链路。uint8_t ch; while (1) { if (RingBuf_Read(uart1_rx, ch) 0) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, ch); } }这里发送前等待USART_FLAG_TC不是USART_FLAG_TXE。TXE 表示数据已经从数据寄存器移入移位寄存器但最后一个字节的停止位可能还在发送中TC 表示整个字节包括停止位已经发送完成。回环测试里如果用 TXE串口助手显示的回显会少最后一位或乱码这个细节在后续避坑章节还会展开。USART2 用同样的方式单独测一遍确认两路都通。4.2 交叉回环验证两个串口的中断队列没有互相污染单路回环通过后把 USART1 的 TX 接到 USART2 的 RXUSART2 的 TX 接到 USART1 的 RX形成交叉连接。然后写一段转发逻辑uint8_t ch; while (1) { /* USART1 收到的数据从 USART2 发出去 */ if (RingBuf_Read(uart1_rx, ch) 0) { while (USART_GetFlagStatus(USART2, USART_FLAG_TC) RESET); USART_SendData(USART2, ch); } /* USART2 收到的数据从 USART1 发出去 */ if (RingBuf_Read(uart2_rx, ch) 0) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, ch); } }外部设备接在 USART1 上发数据数据会从 USART2 出来接在 USART2 上发数据从 USART1 出来。两边同时发长数据观察是否出现丢字节、错位。这个测试专门验证两个中断服务函数是否拿错了队列。如果代码里服务函数写反了单路回环测不出来交叉回环立刻暴露。交叉回环通过后我还有一个小习惯给两个串口的数据加不同的帧头比如 USART1 发[U1]开头USART2 发[U2]开头。联调时如果分不清哪路收发的数据串了看帧头一眼就知道。4.3 逻辑分析仪抓波形验证波特率误差在可接受范围回环测试只证明数据能通不能证明波特率准确。用逻辑分析仪的串口解码功能接在 TX 引脚上抓一段数据它会自动识别波特率。识别出的值和配置值偏差在 2% 以内是安全的超过 2%长时间收发时会出现间歇性乱码。误差来源八成不在 USART 配置里而在系统时钟。stm32f103 使用外部 8MHz 晶振配 PLL 到 72MHz 时精度最好使用内部 RC 时钟时误差可能到 1% 到 2%两个串口的实际波特率都会偏移。# 逻辑分析仪抓取后常见结论 # 配置 115200实测 114980偏差 0.19%安全 # 配置 115200实测 118900偏差 3.2%需要检查晶振与 PLL 配置如果实测偏差偏大先检查SystemInit是否正确执行、外部晶振是否起振再用示波器测 MCO 引脚输出的系统时钟来做最终确认。这一步能省下后面大量排查时间不要跳过。5. 双串口调试避坑5 个翻车现象与排查路径5.1 第二个串口完全收不到数据现象USART1 收发正常USART2 在串口助手里什么也收不到发送也没反应。原因最常见的是时钟使能漏了。USART2 挂在 APB1 上有些人写完代码只检查了 GPIO 时钟漏掉RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)。另一个原因是引脚配置错误PA3 被配成了推挽输出RX 引脚不能正常读取电平。解决逐个查三处——RCC 时钟是否使能、GPIO 模式是否为输入、USART_Cmd 是否执行。我通常用调试器在USART_Init前后检查USART2-CR1寄存器如果初始化后 CR1 的 UE 位没有置 1说明初始化没有真正执行一般是时钟或结构体参数问题。这个问题在单串口项目里不会出现是双串口项目特有的第一步坑。5.2 单路正常双路同时收发丢首字节现象单路回环测试都通过两路同时收发时其中一路收到的每一段数据第一个字节丢失后面内容完好。原因优先级配置不合理。低优先级的串口中断正在执行时高优先级串口中断插进来低优先级串口的 RXNE 中断被延迟如果延迟时间超过了一个字节的传输时间数据寄存器被新字节覆盖产生溢出错误 ORE后续 RXNE 中断被阻断表现为丢首字节。解决把频繁收发的一路优先级配成抢占优先级 0另一路配 1。也可以在中断服务函数里主动检查溢出标志并清掉if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) ! RESET) { USART_ReceiveData(USART2); /* 读 DR 同时清掉 ORE 与 RXNE */ }这段代码要放在 RXNE 分支之前。ORE 发生时数据寄存器可能已经有新字节读一次 DR 能恢复中断接收能力代价是最多丢一个字节。对控制类协议来说丢一个字节附加上下文里的帧头校验就能发现。如果一帧数据丢字节频繁优先调优先级而不是在这里补。5.3 用 TXE 判断发送最后一个字节总丢现象发送一串数据串口助手收到的内容末尾少一个字节。发送速度越快丢的概率越高。原因USART_FLAG_TXE置位只表示数据已进入发送移位寄存器停止位是否发完未知。如果发完最后一字节后立刻进入低功耗模式、关闭串口时钟或者切换波特率最后一位停止位没送完外部设备就不认为这字节完整。解决最后一个字节的发送要等待USART_FLAG_TC。批量发送时有两种做法简单的是每个字节都等 TC可靠但稍慢更高效的是等 TXE 发送最后一个字节再专门等 TC。裸机小数据量场景我直接用 TC代码简单且行为一致。void UART1_SendString(const char *s) { while (*s) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, (uint8_t)*s); } }5.4 一个串口进不了中断另一个正常现象USART2 的中断收发正常USART1 的接收中断从不触发把主循环里的逻辑绕过中断直接读USART1-DR又有数据。原因NVIC 配置问题。常见是两个串口复用了同一个 NVIC_InitTypeDef 结构体只改了 PreemptionPriority 没改 IRQChannel导致两次初始化写的是同一个中断通道也有的是中断优先级分组在初始化函数里又调用了一次NVIC_PriorityGroupConfig改变了整个系统仲裁方式。解决每个串口用独立的 NVIC_InitTypeDef 变量IRQChannel 明确写上USART1_IRQn和USART2_IRQn。确保NVIC_PriorityGroupConfig只在 main 开头执行一次。在调试器里检查NVIC-ISER寄存器的对应位能确认中断有没有真的被使能到。5.5 环形队列收满 256 字节后整个串口数据错乱现象短报文收发一切正常连续发送几百字节后后续数据全部错位已经读出来的数据也出现乱码。原因head 或 tail 用了uint8_t类型256 字节回绕时直接溢出变成 0队列的判空判满逻辑全部失效。另一个原因是在 Keli 或 GCC 下开了char有符号优化比较负数时逻辑反转。解决把 head 和 tail 统一为volatile uint16_t。队列大小保持 256 时可以用位与运算替代取模效率稍高#define RING_BUF_SIZE 256 uint16_t next (rb-head 1) (RING_BUF_SIZE - 1);注意这个写法要求队列大小是 2 的幂改成 512、1024 时同时要改掩码。这个坑最隐蔽的地方在于单串口收发时即使 head 溢出也可能因为数据量小而不暴露双串口联调时数据量一上去就原形毕露。6. 双串口进阶DMA 接收与帧边界处理6.1 高波特率下把中断接收换成 DMA 空闲中断收帧中断逐字节接收在 115200 波特率下没有压力但到 460800 或 921600 时每字节中断间隔只有 10us 左右中断开销开始吃掉主循环时间。这个阶段我一般会切到 DMA 接收加空闲中断。DMA 把 USART 收到的数据直接搬进内存缓冲区不进中断只有检测到一帧传输结束总线空闲时才产生一次空闲中断。DMA_InitTypeDef dma; dma.DMA_PeripheralBaseAddr (uint32_t)USART2-DR; dma.DMA_MemoryBaseAddr (uint32_t)uart2_dma_buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize 128; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; dma.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; dma.DMA_Mode DMA_Mode_Circular; dma.DMA_Priority DMA_Priority_High; DMA_Init(DMA1_Channel6, dma); /* USART2_RX 对应 DMA1_Channel6 */ DMA_Cmd(DMA1_Channel6, ENABLE); USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);USART1_RX 对应的通道是 DMA1_Channel5USART1_TX 是 DMA1_Channel4USART2_TX 是 DMA1_Channel7这几个通道映射要记牢写错之后数据不报错但收不到。DMA 接收开启后原来的 RXNE 中断要关掉否则会重复处理同一批数据。空闲中断 IDLE 用USART_GetITStatus(USART2, USART_IT_IDLE)读取读完之后要连读一次 SR 和 DR 才能清掉标志。6.2 粘包不是 DMA 的问题是帧边界没定义好DMA 循环模式收长数据时缓冲区满了指针回绕读侧要自己维护读位置本质上还是环形队列的模型。真正容易翻车的是帧边界处理。如果协议本身没有固定帧头加长度字段DMA 收进来的是连续的字节流主循环根本不知道哪一帧结束。我一般会在协议上做三层约束固定帧头、包含长度字段、带累加和校验。空闲中断只能说明线上停止出现了一定时长的状态可以作为分包参考但不能完全依赖它。如果两个串口的数据都是同一套协议那就准备两套解析状态机分别在各自的数据缓冲上运行解析完一个完整帧再触发应用层回调。这套思路比逐字节中断解析省太多 CPU协议升级时只需要改解析状态机不用动收发底层。我在第一次把串口接收从中断切到 DMA 时就是因为主循环里多了几个浮点运算逐字节中断在 460800 波特率下开始感觉到卡顿。DMA 搬完数据后主循环每次只处理一帧整个系统的节奏一下就清晰了。如果你现在还在 115200 波特率下跑中断加环形队列完全够用如果哪天把波特率提上去记得先画一张 DMA 通道映射表再动手。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

C# Socket局域网直连实战:UDP广播发现与TCP通信

C# Socket局域网直连实战:UDP广播发现与TCP通信

简介:C#网络编程学习者可借助这份Socket通信示例工程,掌握面向连接套接字下服务端与多客户端交互,以及客户端之间直接通信的实现方式。项目围绕实验功能展开,服务端能响应多客户端连接、支持单发与群发,通信双方均包含…

2026/10/15 15:40:40 阅读更多 →
从单一自身抗体到多抗体组合:类风湿关节炎标志物研究的新方向

从单一自身抗体到多抗体组合:类风湿关节炎标志物研究的新方向

更新于2026年10月14日类风湿关节炎(Rheumatoid Arthritis,RA)具有显著的临床与免疫学异质性,不同患者在自身抗体水平、炎症活动度、关节损伤及治疗应答方面均可能存在差异。长期以来,类风湿因子(RF&#xf…

2026/10/15 15:40:40 阅读更多 →
开放式rTMS系统:可编程刺激控制与多模态实验同步实践

开放式rTMS系统:可编程刺激控制与多模态实验同步实践

简介:openrTMS是一套面向神经科学研究与临床应用的开放式经颅磁刺激(TMS)软件平台,采用C开发,适合具备一定编程基础的研究人员、工程师及临床医生使用,用于搭建刺激方案设定、参数监控与数据分析的完整工作…

2026/10/15 15:39:39 阅读更多 →

最新新闻

MATLAB常用信号处理函数清单:从FFT到滤波器实战指南

MATLAB常用信号处理函数清单:从FFT到滤波器实战指南

1. 项目概述:为什么要单独整理一份信号处理函数清单做信号处理这几年,我最大的感受就是:MATLAB的功能强大到让人眼花缭乱,但真正高频使用的函数翻来覆去就那么几十个。很多初学者最容易犯的毛病,是一上来就抱着几百页的…

2026/10/15 17:18:28 阅读更多 →
拓扑优化入门:99行与88行程序对比及OC与MMA算法解析

拓扑优化入门:99行与88行程序对比及OC与MMA算法解析

简介:这份资源面向拓扑优化初学者与结构优化方向的学生、研究人员,提供一套可直接运行的MATLAB入门程序,涵盖优化准则法(OC)与移动渐近线法(MMA)两种主流求解思路,帮助读者理解如何在…

2026/10/15 17:18:28 阅读更多 →
Agent Skills 标准实战:用 SKILL.md 让 AI Agent 真正学会干活

Agent Skills 标准实战:用 SKILL.md 让 AI Agent 真正学会干活

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

2026/10/15 17:18:28 阅读更多 →
开题报告写到崩溃?这份“保姆级”通关攻略请收好

开题报告写到崩溃?这份“保姆级”通关攻略请收好

每到毕业季,无数本科生的第一道坎往往不是毕业论文答辩,而是开题报告。选题不知道从哪下手,研究思路理不清,格式改了又改还是被导师打回重写,不少同学一提到“开题报告”四个字就头皮发麻。其实,过来人都清…

2026/10/15 17:18:28 阅读更多 →
CANopen协议栈源码拆解:从对象字典到PDO映射的嵌入式实现

CANopen协议栈源码拆解:从对象字典到PDO映射的嵌入式实现

简介:CANopen完整源码是一套面向嵌入式控制开发者的开源协议栈实现,基于CAN总线构建,适合从事工业自动化、运动控制、汽车电子等领域的工程师与学习者研究和使用。资源包共77个文件,以C语言源文件与头文件为主体(28个.…

2026/10/15 17:18:28 阅读更多 →
STM32驱动MAX6675测温:SPI时序解析与避坑实战

STM32驱动MAX6675测温:SPI时序解析与避坑实战

简介:这份资源是面向嵌入式初学者与STM32开发者的MAX6675测温驱动测试例程,基于STM32F103与K型热电偶专用芯片MAX6675搭建,重点解决SPI通信配置、冷端补偿读取与温度数据解析等实际问题,适合用于温度采集类课程设计、实验验证或工…

2026/10/15 17:17:27 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →