ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32 DMA 进阶实战:把 CPU 从搬运数据里彻底解放出来

STM32 DMA 进阶实战:把 CPU 从搬运数据里彻底解放出来 你有没有遇到过这种情况ADC 在连续采样UART 要不断往外发SPI 还在往屏幕刷像素。主循环里for循环一遍遍搬数组CPU 占用率跑到 90%稍微加点业务逻辑就卡顿丢数。问题出在「数据搬运」这件苦力活全压在 CPU 身上。STM32 带 DMA直接内存访问它能在不惊动 CPU 的情况下自己把外设的数据搬进内存、把内存的数据搬出去甚至内存搬内存。把这件事用对了CPU 占用率能直接从 90% 掉到个位数。这篇文章讲 DMA 最常用的三种进阶玩法双缓冲、循环与链表模式、内存到内存拷贝以及那些一踩就丢数据的坑。DMA 到底在干什么一句话DMA 是挂在总线上的另一个「master」它和 CPU 平等地访问内存和外设寄存器。配置好源地址、目的地址、数据量DMA 控制器就按节拍自己搬搬完产生一个中断通知 CPU「活干完了来处理吧」。关键点在于 CPU 只在「开始搬」和「搬完了」这两个时刻参与中间那段数据流过路CPU 可以去跑你的算法。三种典型场景外设到内存ADC 采样值直接进数组UART 收的数据直接进缓冲。内存到外设把要发的报文丢给 UART 的发送寄存器DMA 一个个字节推出去。内存到内存把一块缓冲区整体拷贝到另一块比如字库从 Flash 搬到 RAM。双缓冲让「处理」和「接收」并行普通 DMA 是「搬完一块才通知你」。如果你处理这块数据的时间比下一块到来还长新的数据就没地方放了只能丢。双缓冲有的系列叫 MultiBuffer解决这个问题DMA 同时盯着两块缓冲区。它先填缓冲区 AA 满了自动切到缓冲区 B 接着填同时产生一个「半传输完成」中断告诉你「A 满了赶紧处理」。等你处理完 AB 也满了又产生「传输完成」中断让你处理 B。这样处理和接收在时间上错开互不耽误。#defineBUF_LEN1024uint16_tadc_buf[2][BUF_LEN];// 两块缓冲// 启动双缓冲HAL_ADC_Start_DMA_MultiMode(hadc,(uint32_t*)adc_buf[0],(uint32_t*)adc_buf[1],BUF_LEN);// 第一块满了处理 buf[0]voidHAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef*hadc){process(adc_buf[0],BUF_LEN);}// 第二块满了处理 buf[1]voidHAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc){process(adc_buf[1],BUF_LEN);}这里两个回调必须分清HalfCplt 对应前一块Cplt 对应后一块。很多人只在 Cplt 里处理结果前一块的数据在切换瞬间被新数据覆盖采到的永远是错位的值。循环模式与内存到内存拷贝循环模式Circular适合「源源不断」的数据流比如音频采集。DMA 搬完一圈自动回到起点再来不中断、不停止直到你手动关掉。DAC 播波形、定时器触发 ADC 扫描都爱用这个模式。内存到内存拷贝用起来就像开了个加速器搬数组HAL_DMA_Start(hdma_memtomem,(uint32_t)src,(uint32_t)dst,len);// len 是「传输次数」HAL_DMA_PollForTransfer(hdma_memtomem,HAL_DMA_FULL_TRANSFER,100);注意len是按「单次传输的数据宽度」算的次数不是字节数。如果数据宽度是 32 位len100实际搬 400 字节。算错这一步目标缓冲区就溢出或者少搬。上图说明数据宽度对不上为什么错位外设每次吐 16 位内存按 8 位接第二次来的高字节直接顶掉第一次的低字节整列数据全串味。十个坑DMA 翻车基本都在这数据宽度对不上。外设是 16 位ADC 12位存在半字里内存用uint8_t数组接DMA 按字节搬结果高低字节全错位。PeriphDataAlign 和 MemDataAlign 必须和你的数组类型一致。地址自增没开。PeriphInc 或 MemInc 设成 DisableDMA 会一直往同一个地址写数据全叠在一个格子后面的全丢。外设寄存器通常不自增内存缓冲要自增。内存没对齐到数据宽度。32 位 DMA 访问一个非 4 字节对齐的地址部分系列直接 HardFault。缓冲数组用uint32_t或显式对齐别用栈上随便一个指针。HalfCplt 和 Cplt 回调没分开处理。两块缓冲当一块用切换瞬间新数据盖掉正在处理的旧数据采出来全是错位值。回调里处理太久。双缓冲的意义是「处理」和「接收」并行你要是在回调里跑一个 200 毫秒的算法下一块早溢出了。回调里只做搬运和打标重活丢给主循环或任务。缓冲区没加 volatile 或放在栈上被回收。DMA 在后台改内存编译器不知道可能把你的缓冲优化掉或者当成死变量。大缓冲用全局/静态数组别在函数里开局部数组传给 DMA。内存到内存 DMA 抢总线。Mem2Mem 走 AHB 总线带宽拉满时其他 DMA 流和外设访问被拖慢表现像 SPI 掉帧、ADC 采样间隔不均。大块拷贝分段做或者挑总线空闲时搬。带 Cache 的内核M7没做一致性。CPU 改了内存Cache 没刷DMA 读到的是旧值DMA 写了内存Cache 没失效CPU 读到的是旧值。搬运前后要SCB_CleanDCache/SCB_InvalidateDCache。DMA 中断优先级压过关键任务。DMA 完成中断设太高RTOS 里高优先级任务被频繁打断时序乱套。DMA 中断只发个信号量真正处理留给任务。Circular 模式忘了停。调试时重复启动同一个 DMA 流却不先Abort状态机卡死后续启动全失败。重新配置前先HAL_DMA_Abort干净。把这件事收一下DMA 不是「开了就快」的开关它是把 CPU 从重复劳动里替换出来的机制。双缓冲解决「边收边处理」循环模式解决「源源不断」内存到内存解决「大块拷贝」。真正卡人的从来不是配置那几行而是数据宽度、地址对齐、缓冲生命周期这三件事没想清楚。CPU 该算算法不该当搬运工。DMA 接管数据流你才腾得出手做真正值钱的事宽度对齐、缓冲常驻少一样都白搭。
返回列表