一、日志采集的痛点:传统方式为什么拖垮了你的CPU?

做嵌入式开发的朋友,肯定都跟日志打过交道。调试阶段还好说,一旦到了产品阶段,日志采集就成了一个让人头疼的问题。

我个人习惯在项目初期就把日志方案定下来,不然后面改起来,那叫一个痛苦。你想想看,一个跑着实时控制算法的系统,突然因为打印日志卡顿了那么几毫秒,后果可能很严重。

1.1 传统日志采集的“三板斧”

咱们先看看最常见的几种做法:

  • printf 大法:简单粗暴,哪里需要打哪里。但 printf 是阻塞的,一个字符没发完,CPU 就得等着。
  • 串口轮询发送:在 main 循环里不断查串口状态,有数据就发。这玩意儿就像你一边写代码一边时不时看手机,效率能高吗?
  • 中断发送:比轮询好一点,但每个字节都触发一次中断,频繁的上下文切换照样吃掉大量 CPU 时间。

说白了,这些方法的核心问题都一样:CPU 亲自下场干苦力。每次发送一个字节,CPU 都要参与搬运、状态检查、中断处理。对于动辄几十 KB 的日志数据,这个开销是惊人的。

核心矛盾:日志采集需要占用 CPU 时间,而实时任务也需要 CPU 时间。两者争抢同一个资源,必然导致冲突。

1.2 一个真实的“血泪史”

我曾经接手过一个电机控制项目,前任工程师用 printf 打日志,波特率设到了 115200。结果呢?电机在低速运行时一切正常,一旦加速到 3000 转,系统就频繁丢步。查了三天,最后发现是 printf 占用了太多 CPU 时间,导致 PWM 更新不及时。

嗯,这里要注意:日志采集不是“顺便”做的事,它是有代价的。代价就是 CPU 的宝贵周期。

1.3 量化分析:传统方式到底有多“贵”?

咱们来算一笔账。假设串口波特率 115200,发送一个字节需要大约 87 微秒。如果采用阻塞式发送,CPU 在这 87 微秒里什么都干不了。

日志方式 每字节 CPU 占用 发送 1KB 日志耗时 对实时任务影响
printf(阻塞) 87 μs(100%) 89 ms 严重,完全阻塞
串口轮询 60 μs(70%) 61 ms 中等,占用主循环
中断发送 15 μs(17%) 15 ms 较轻,但中断频繁
DMA 方式 2 μs(2%) 2 ms 几乎无影响

看到差距了吧?DMA 方式下,CPU 只需要把数据地址告诉 DMA 控制器,然后就可以去干别的事了。剩下的搬运工作,DMA 全包了。

我的建议:如果你的系统里有一个周期性的实时任务(比如 1ms 的控制循环),那么日志采集的 CPU 占用率最好控制在 5% 以内。超过这个数,你就该考虑 DMA 方案了。

1.4 为什么 DMA 能解决这个问题?

DMA 的全称是 Direct Memory Access,直接存储器访问。它的核心思想很简单:让硬件干硬件的活,让 CPU 干 CPU 的活

具体来说,DMA 控制器是一个独立于 CPU 的硬件模块。它可以自己从内存里读数据,然后直接送到外设(比如串口)的发送寄存器里。整个过程不需要 CPU 参与,CPU 只需要在开始的时候告诉 DMA:“嘿,帮我把这 100 个字节发出去”,然后就可以转身去处理实时任务了。

为什么会这样?因为 DMA 和 CPU 是并行工作的。CPU 在跑控制算法的时候,DMA 在背后默默地把日志数据搬走。两者互不干扰。

下面这张图可以帮你理解 DMA 的工作流程:

DMA 日志采集工作流程 CPU 执行实时任务 DMA 控制器 搬运日志数据 内存 日志缓冲区 [0x20000100] "temp=25.3" [0x20000110] "speed=1200" [0x20000120] "error=0x01" ... 串口外设 发送寄存器 ① 配置 DMA 传输 ② 读取日志数据 ③ 写入发送寄存器 CPU 与 DMA 并行工作 互不干扰,各司其职

从这张图可以看出,整个流程分三步:

  1. CPU 配置 DMA:告诉 DMA 要搬多少数据、从哪里搬、搬到哪里去。这一步很快,只需要写几个寄存器。
  2. DMA 读取内存:DMA 自己从日志缓冲区里把数据读出来。此时 CPU 可以去做别的事。
  3. DMA 写入串口:DMA 把数据写到串口的发送寄存器里,串口自动把数据发出去。

注意:DMA 虽然好,但不是万能的。它需要占用一条总线带宽。如果你的系统里已经有多个 DMA 通道在跑(比如 ADC 采集、显示刷新),要注意总线冲突的问题。我曾在项目中遇到过 DMA 通道优先级设置不当,导致日志数据被其他 DMA 传输打断的情况。

1.5 什么时候该用 DMA 日志?

不是所有场景都需要上 DMA。我个人总结了几条经验:

  • 日志数据量大:每秒超过 1KB 的日志输出,建议用 DMA。
  • 有硬实时任务:控制周期在 1ms 以内,且对抖动敏感的系统,必须用 DMA。
  • 多任务环境:RTOS 下多个任务同时打日志,用 DMA 可以避免任务切换带来的延迟。
  • 低功耗场景:DMA 可以在 CPU 休眠时继续工作,适合电池供电的设备。

反过来说,如果你的项目只是简单打印几个调试信息,波特率 9600,一天也打不了几行,那用 printf 也没啥问题。别为了用 DMA 而用 DMA,工具要选对场景。

1.6 小结

传统日志采集的痛点,说白了就是 CPU 被当成了搬运工。而 DMA 方案的核心思路,就是把搬运工作交给专门的硬件去做,让 CPU 回归它的本职工作——处理实时任务。

这一章我们分析了问题的根源和 DMA 的基本原理。下一章,我会带你看看具体的硬件配置和代码实现,包括 DMA 通道的选择、缓冲区的设计、以及如何避免常见的坑。

一句话总结:日志采集不该成为实时系统的瓶颈。用 DMA,把 CPU 的时间还给真正需要它的地方。

专注资料整理