STM32F103标准库DMA驱动USART3收发实战:从阻塞到高效

简介:这份资源面向嵌入式开发初学者与中级工程师,聚焦STM32F103标准库下USART3的DMA收发实现,帮助解决串口通信中CPU占用高、实时性不足的问题。压缩包共171个文件,约3.41MB,以c源文件、h头文件、s汇编启动文件为主,另含o目标文件、crf编译中间文件、uvproj与uvopt工程配置、map映射文件及hex固件等,构成一套可直接编译的Keil工程模板。内容围绕DMA通道选择、传输方向与数据量配置、USART3时钟与波特率设置、DMA请求使能、传输完成与错误中断处理等关键环节展开,并给出初始化、启动传输与中断回调的软件流程。已有3312人学习下载,适合希望掌握标准库DMA串口收发、减少CPU干预并提升通信效率的开发者参考与移植。

1. 串口收数据还在死等中断?聊聊 STM32F103 上用 DMA 把 USART3 跑满的实战方案

如果你用 STM32F103 标准库做过串口通信,大概率经历过这样的场景:主循环里放一个while(USART_GetFlagStatus(USART3, USART_FLAG_RXNE) == RESET)死等,或者开个接收中断,每来一个字节就跳一次 ISR。波特率低的时候勉强能用,一旦拉到 115200 甚至更高,再加上系统里还有定时器、ADC、SPI 在抢 CPU,丢包就成了玄学问题——有时候跑一整天没事,有时候一上电就丢前几个字节。这套资源要解决的,就是 USART3 收发全走 DMA、把 CPU 从字节搬运里彻底解放出来的问题。它基于 STM32F103 标准库(StdPeriph_Lib),不依赖 HAL,适合还在维护老工程、或者想搞清楚 DMA 请求映射底层逻辑的嵌入式从业者。下面从通道选型、寄存器配置到收发分离的实现,一步步拆开讲。

2. DMA 请求映射与 USART3 通道选型:为什么你的 DMA 配置不生效

2.1 先搞清楚 USART3 的收发各挂在哪条 DMA 通道

STM32F103 的 DMA1 有 7 个通道,每个通道的请求来源是硬件固定映射的,不能随便改。USART3 的映射关系是:

方向DMA 控制器通道请求来源
USART3_TXDMA1Channel 2USART3_TX
USART3_RXDMA1Channel 3USART3_RX

很多人翻车就翻在这里:看到 USART1 用 Channel 4(TX)和 Channel 5(RX),想当然以为 USART3 也差不多,结果配了半天通道,DMA 死活不触发。记住一个口诀——USART3 发二收三,这是芯片参考手册里写死的,标准库的DMA_Init不会帮你检查通道对不对,配错了它照样返回成功,但数据就是不动。

选型上还有一个容易忽略的点:DMA1 和 DMA2 的分工。STM32F103 只有 DMA1 能挂 USART3,DMA2 一般留给 ADC、SPI1 这类外设。如果你的工程里 SPI1 也在用 DMA,注意别和 USART3 抢通道——SPI1 的 TX/RX 挂在 DMA1 的 Channel 3 和 Channel 2 上,和 USART3 正好冲突。常见做法是 SPI1 改用中断模式,或者把 SPI1 的 DMA 请求让给 USART3,具体看哪个外设的实时性要求更高。

2.2 标准库下 DMA 初始化的关键参数逐个拆

先看发送方向的 DMA 配置代码,这是整个方案的基础:

// USART3_TX 使用 DMA1 Channel2 void USART3_TX_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // DMA1 时钟必须开 DMA_DeInit(DMA1_Channel2); // 先复位,避免残留配置 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART3->DR; // 外设地址固定为数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART3_TX_Buf; // 内存缓冲区首地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存 -> 外设 DMA_InitStructure.DMA_BufferSize = 0; // 初始为 0,发送时再设 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 发送用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 外设请求,非内存到内存 DMA_Init(DMA1_Channel2, &DMA_InitStructure); DMA_Cmd(DMA1_Channel2, DISABLE); // 先不使能,等有数据要发时再开 }

逐条说下为什么这么设。DMA_PeripheralBaseAddr必须指向USART3->DR的地址,因为 DMA 搬运的本质就是往数据寄存器里写字节,硬件自动触发发送移位。DMA_DIR_PeripheralDST表示目的地是外设,也就是内存往外设搬。DMA_PeripheralInc_Disable很关键——外设地址不能自增,否则第二个字节就写到 DR 后面的寄存器去了,直接跑飞。DMA_Mode_Normal表示搬完指定数量就停,适合发送这种一次性任务;如果设成DMA_Mode_Circular,发完一轮会自动从头再来,串口会不停重复发同一段数据,除非你就是要做循环发送。

接收方向的配置和发送有三处不同,这是最容易搞混的地方:

// USART3_RX 使用 DMA1 Channel3 void USART3_RX_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART3->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART3_RX_Buf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设 -> 内存 DMA_InitStructure.DMA_BufferSize = USART3_RX_BUF_SIZE; // 接收缓冲区大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 接收用循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 接收优先级高于发送 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStructure); DMA_Cmd(DMA1_Channel3, ENABLE); // 接收通道初始化后立即开启 }

接收用DMA_Mode_Circular是常见做法,因为串口数据什么时候来不确定,循环模式让 DMA 搬完一圈自动回到缓冲区头部继续搬,不会因为缓冲区满了就停止接收。DMA_Priority_High给接收通道更高优先级,避免发送大量数据时把接收请求挤掉导致丢包。DMA_BufferSize设成缓冲区大小,比如 256 或 512,具体看你的协议一帧最长多少字节。

2.3 USART3 本身的初始化不能省

DMA 配好了,USART3 这边也得把对应的使能位打开,否则 DMA 请求根本发不出来:

void USART3_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // USART3 挂在 APB1 // PB10 -> TX,复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // PB11 -> RX,浮空输入或上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART3, &USART_InitStructure); USART_DMACmd(USART3, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // 关键:使能 DMA 请求 USART_Cmd(USART3, ENABLE); }

USART_DMACmd这一行是很多人漏掉的,没有它,USART3 不会向 DMA 控制器发出请求,DMA 配置再对也没用。另外注意 USART3 的时钟在 APB1 上,不是 APB2,开错总线时钟会导致寄存器写不进去。

3. 发送与接收的代码实现:从阻塞式到 DMA 搬运的完整改造

3.1 发送端:把数据丢给 DMA 就返回

发送的核心思路是——应用层把数据准备好,调用一个函数启动 DMA,然后该干嘛干嘛,DMA 搬完了通过中断通知你。先看发送函数:

#define USART3_TX_BUF_SIZE 256 uint8_t USART3_TX_Buf[USART3_TX_BUF_SIZE]; // 返回 0 表示启动成功,1 表示上一次发送还没完成 uint8_t USART3_SendBytes(uint8_t *data, uint16_t len) { if (len == 0 || len > USART3_TX_BUF_SIZE) return 1; if (DMA_GetCmdStatus(DMA1_Channel2) != DISABLE) return 1; // 通道还在忙 memcpy(USART3_TX_Buf, data, len); // 拷贝到 DMA 源缓冲区 DMA_Cmd(DMA1_Channel2, DISABLE); // 改配置前先关通道 DMA_SetCurrDataCounter(DMA1_Channel2, len); // 设置本次搬运长度 DMA_ClearFlag(DMA1_FLAG_TC2 | DMA1_FLAG_GL2); // 清发送完成标志 DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE); // 开传输完成中断 DMA_Cmd(DMA1_Channel2, ENABLE); // 启动 return 0; }

这里有几个细节值得说。DMA_GetCmdStatus判断通道是否还在使能状态,如果上一次发送没完成就返回忙,避免覆盖还没搬完的数据。DMA_SetCurrDataCounter必须在通道关闭时调用,运行中改这个寄存器行为不确定。DMA_ClearFlag清标志要在使能之前做,否则可能一开通道就误触发中断。发送完成中断服务函数里做两件事——清标志、关通道,然后可以置一个软件标志告诉应用层“发完了”。

void DMA1_Channel2_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC2) != RESET) { DMA_ClearITPendingBit(DMA1_IT_TC2); DMA_Cmd(DMA1_Channel2, DISABLE); // 发送完成,关通道 g_USART3_TX_Done = 1; // 通知应用层 } }

别忘了在NVIC里使能DMA1_Channel2_IRQn,否则中断进不来。

3.2 接收端:循环 DMA + 空闲中断的组合拳

接收比发送麻烦一点,因为数据什么时候来、来多少都不确定。纯 DMA 循环模式虽然能自动搬,但应用层不知道一帧数据什么时候结束。常见做法是 DMA 循环接收 + USART 空闲中断(IDLE)配合——DMA 负责把字节搬进缓冲区,空闲中断负责在总线安静下来时告诉应用层“这一帧收完了”。

先看空闲中断的配置:

void USART3_IDLE_Init(void) { NVIC_InitTypeDef NVIC_InitStructure; // 使能 USART3 空闲中断 USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }

中断服务函数里要算清楚这一帧收了多少字节:

#define USART3_RX_BUF_SIZE 256 uint8_t USART3_RX_Buf[USART3_RX_BUF_SIZE]; volatile uint16_t g_RxLen = 0; volatile uint8_t g_RxFlag = 0; void USART3_IRQHandler(void) { if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) { USART_ReceiveData(USART3); // 读 DR 清空闲标志(必须做) // 计算已接收字节数 = 缓冲区大小 - DMA 剩余计数器 g_RxLen = USART3_RX_BUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel3); g_RxFlag = 1; // 通知应用层处理 // 重新校准 DMA 计数器,准备接收下一帧 DMA_Cmd(DMA1_Channel3, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel3, USART3_RX_BUF_SIZE); DMA_Cmd(DMA1_Channel3, ENABLE); } }

USART_ReceiveData(USART3)这一句不能省,空闲中断的标志需要读一次 DR 才能清除,不读的话中断会反复触发。DMA_GetCurrDataCounter返回的是还没搬的字节数,用缓冲区总大小减去它就是已经收到的字节数。重新校准 DMA 计数器是为了让下一帧从缓冲区头部开始写,避免上一帧的残留数据干扰。

应用层主循环里这样用:

while (1) { if (g_RxFlag) { g_RxFlag = 0; // 此时 USART3_RX_Buf 里有 g_RxLen 个字节,可以解析协议了 ProcessFrame(USART3_RX_Buf, g_RxLen); } // 其他任务 }

3.3 收发同时跑时的优先级与缓冲区规划

发送和接收同时工作时,DMA1 的 Channel2 和 Channel3 会竞争总线。STM32F103 的 DMA 控制器在同一时刻只能服务一个通道,硬件按优先级仲裁。接收通道设DMA_Priority_High、发送设DMA_Priority_Medium,是为了保证接收不丢字节——发送慢一点没关系,接收丢了就找不回来了。

缓冲区规划上,发送缓冲区建议和接收缓冲区一样大或者略小,因为发送是主动行为,应用层可以控制节奏。接收缓冲区如果协议帧比较长,比如 Modbus RTU 一帧最多 256 字节,那就设 256 或 512。注意 STM32F103 的 RAM 只有 20KB 或 64KB(看具体型号),别把缓冲区开太大挤占其他变量空间。

提示:如果接收数据量很大且帧率很高,空闲中断可能来不及处理就被下一帧覆盖。这种情况可以考虑用双缓冲(DMA 的 Memory Target 切换),但标准库对双缓冲支持有限,需要手动操作寄存器,复杂度较高。一般工业场景 115200 波特率下单缓冲够用。

4. 避坑与排查:DMA 串口调试中最容易翻车的五个点

4.1 现象:DMA 配置全对但一个字节都收不到

原因通常是USART_DMACmd没调用,或者调用时参数写错了。标准库里这个函数的第二个参数是USART_DMAReq_Tx和USART_DMAReq_Rx,不是USART_DMAReq_TX(大小写不同)。另外检查RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)有没有执行——DMA1 时钟在 AHB 上,不在 APB1/APB2,开错了时钟 DMA 寄存器写不进去。

解决:在初始化完成后,用调试器读一下DMA1_Channel3->CCR寄存器的 EN 位是否为 1,再读USART3->CR3的 DMAT 和 DMAR 位是否为 1。两个都对了,再查 GPIO 和波特率。

4.2 现象:发送能发但数据错位,第一个字节变成 0x00

原因一般是DMA_SetCurrDataCounter在通道使能状态下被调用,或者DMA_ClearFlag没做导致传输完成标志残留。还有一种可能是发送缓冲区是局部变量,函数返回后栈被回收,DMA 搬的是已经被覆盖的内存。

解决:发送缓冲区用全局数组或静态数组,确保生命周期覆盖整个 DMA 传输过程。DMA_SetCurrDataCounter前先DMA_Cmd(DISABLE),设完再DMA_Cmd(ENABLE)。

4.3 现象:接收偶尔丢最后一两个字节

原因是空闲中断触发时,DMA 可能还有一个字节正在搬运中,DMA_GetCurrDataCounter读到的值比实际少 1。这是 DMA 和中断之间的竞争窗口,属于硬件层面的时序问题。

解决:在空闲中断里读计数器之前加一个极短的延时(几个 NOP),或者连续读两次计数器取较小值。更稳妥的做法是在协议层做超时重传,不依赖单次接收的完整性。

4.4 现象:系统跑一段时间后 DMA 通道卡死,不再触发中断

原因通常是中断标志没有正确清除,导致中断反复触发或者挂起。DMA_ClearITPendingBit和DMA_ClearFlag要配对使用,只清一个可能不够。另外如果中断服务函数里做了耗时操作(比如解析协议、打印日志),可能阻塞后续 DMA 中断。

解决:中断服务函数里只做标志清除和数据长度记录,协议解析放到主循环。检查DMA1->ISR寄存器的 TCIF、HTIF、TEIF 等标志位,确认没有残留。

4.5 现象:和 SPI1 同时用时,SPI 数据出错

原因前面提过——SPI1 的 TX 用 DMA1 Channel3,和 USART3_RX 冲突。两个外设同时请求同一个通道,DMA 控制器只响应优先级高的那个,另一个的数据就丢了。

解决:把 SPI1 改成中断模式,或者把 USART3 的接收改用查询方式(不推荐,失去 DMA 意义)。如果必须都用 DMA,考虑用 DMA2 挂 SPI1(STM32F103 的 DMA2 只有部分型号有),或者换用有更多 DMA 通道的型号。

5. 进阶技巧:用 DMA 半传输中断做流式数据的分包处理

前面讲的空闲中断方案适合“一帧数据之间有明确间隔”的场景,比如 Modbus RTU 的 3.5 字符间隔。但如果数据是连续流式的,帧与帧之间没有空闲,空闲中断就不会触发。这时候可以用 DMA 的半传输中断(HT)和传输完成中断(TC)配合,把缓冲区切成两半,一半在收的时候另一半在处理。

配置上只需要在接收 DMA 初始化后加一行:

DMA_ITConfig(DMA1_Channel3, DMA_IT_HT | DMA_IT_TC, ENABLE);

然后在DMA1_Channel3_IRQHandler里判断是半传输还是全传输:

void DMA1_Channel3_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_HT3) != RESET) { DMA_ClearITPendingBit(DMA1_IT_HT3); // 前半缓冲 [0, SIZE/2) 已满,可以处理 ProcessHalfBuffer(USART3_RX_Buf, USART3_RX_BUF_SIZE / 2); } if (DMA_GetITStatus(DMA1_IT_TC3) != RESET) { DMA_ClearITPendingBit(DMA1_IT_TC3); // 后半缓冲 [SIZE/2, SIZE) 已满,可以处理 ProcessHalfBuffer(USART3_RX_Buf + USART3_RX_BUF_SIZE / 2, USART3_RX_BUF_SIZE / 2); } }

这个方案的好处是处理延迟固定——每收满半个缓冲区就处理一次,不会因为等空闲而积压。代价是应用层要自己处理帧边界,如果一帧数据跨了半缓冲区,需要做拼接。我一般会在协议层加一个帧头帧尾校验,跨缓冲区的帧先缓存起来,等下一半数据到了再拼。

验证 DMA 是否真的在搬运,最直接的方法是用调试器看DMA1_Channel3->CNDTR寄存器的值。每收一个字节这个值减 1,收到 0 后循环模式会自动重载为DMA_BufferSize。如果这个值一直不变,说明 DMA 请求没触发,回去查USART_DMACmd和通道使能位。另一个方法是拿示波器看 TX 引脚,DMA 发送时波形应该是连续的,如果中间有断点,说明 DMA 搬运速度跟不上波特率,需要检查 DMA 优先级或者总线是否被其他高优先级外设占用。

从那以后我每次配 DMA 串口,都强制走一遍“时钟 → 通道映射 → 外设 DMA 使能 → 中断标志清除”这四步检查,少一步都不下载。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

STM32F103标准库USART3 DMA收发实战:彻底解放CPU

STM32F103标准库USART3 DMA收发实战:彻底解放CPU

简介:这份资源面向嵌入式开发初学者与中级工程师,聚焦STM32F103标准库下USART3的DMA收发实现,帮助解决串口通信中CPU占用高、实时性不足的问题。包内共171个文件,以c源码、h头文件、s汇编启动文件为主,辅以o目标文件、…

2026/10/20 11:47:44 阅读更多 →
STM32驱动VL53L1X激光测距:从I2C通信到稳定读数

STM32驱动VL53L1X激光测距:从I2C通信到稳定读数

简介:一份基于STM32F103VET6微控制器与VL53L1X激光测距传感器的完整工程包,面向需要实现非接触式距离测量的嵌入式开发者。主控芯片采用ARM Cortex-M3内核,通过I2C总线与VL53L1X通信,PA2/PA3分别配置为SDA/SCL,PA4接XS…

2026/10/20 11:46:43 阅读更多 →
无线传感器网络覆盖优化:MATLAB虚拟力算法与PSO/GA代码实战

无线传感器网络覆盖优化:MATLAB虚拟力算法与PSO/GA代码实战

简介:这份MATLAB代码合集面向无线传感器网络(WSN)覆盖优化方向的学习者与研究者,适合具备一定MATLAB基础、希望快速复现并对比多种智能优化算法效果的高年级本科生、研究生及科研人员。资源围绕WSN节点部署与覆盖增强问题&#xf…

2026/10/20 11:46:43 阅读更多 →

最新新闻

Spring Boot社区在线问诊系统实战:从需求到部署的完整技术拆解

Spring Boot社区在线问诊系统实战:从需求到部署的完整技术拆解

最近后台经常有人问社区家庭医生在线问诊系统怎么做,正好我前阵子完整跟过一个类似项目,从需求梳理到部署上线走了不少弯路。我干脆把这套系统的技术拆解和踩坑记录整理成一篇长文,一方面给自己做个复盘,另一方面给正在做毕业设计…

2026/10/20 13:09:01 阅读更多 →
Git克隆Demo代码仓库实战:从地址到跑通全流程

Git克隆Demo代码仓库实战:从地址到跑通全流程

1. 拿到仓库地址后,先别急着敲命令很多同学第一次接触代码仓库时,最容易犯的毛病就是:复制了地址,打开终端,直接输入git clone,回车,然后开始疯狂刷新浏览器看着进度条发呆。如果仓库小&#xf…

2026/10/20 13:09:01 阅读更多 →
OpenHarmony设备上Flutter淡出过渡动画移植实战与性能优化

OpenHarmony设备上Flutter淡出过渡动画移植实战与性能优化

上个月我把一个原本跑在标准Flutter环境里的页面过渡动画方案移植到OpenHarmony设备上,遇到的第一个问题倒不是动画本身,而是animations 这个官方维护的三方动画库直接编译不过。项目里本来打算用它实现页面切换时的淡出过渡效果,想着省一笔自…

2026/10/20 13:09:01 阅读更多 →
轻量级技能管理方案:从零搭建可量化可组合的技能表

轻量级技能管理方案:从零搭建可量化可组合的技能表

1. 从“skills”这个词说起:它到底指什么“skills”这个词看起来简单,但放在不同语境里,指向完全不同。做招聘的人看到它,想到的是岗位能力模型;做在线教育的人看到它,想到的是课程标签体系;做开…

2026/10/20 13:09:01 阅读更多 →
企业级RAG问答系统实战:知识库、检索链路与大模型应用的完整落地指南

企业级RAG问答系统实战:知识库、检索链路与大模型应用的完整落地指南

做了将近半年的基于大模型的智能问答系统,上线之后我最大的感受是:**决定这套系统好不好用的,往往不是大模型本身,而是知识库和检索链路。**模型答得再聪明,检索不到对的资料,它也只能一本正经地编。我们做…

2026/10/20 13:09:01 阅读更多 →
GraphRAG工程实践:知识图谱如何补足向量检索的推理短板

GraphRAG工程实践:知识图谱如何补足向量检索的推理短板

向量检索失效的那一刻,我才决定认真搞GraphRAG年初做过一个企业内部制度问答系统,刚开始用的是经典的向量库RAG方案:文档切块、embedding、top-k召回、拼prompt喂给大模型。原型阶段跑得很顺,演示的时候领导问了一个问题——"…

2026/10/20 13:08:00 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →