3、固件功耗模型:固件执行路径与功耗的映射关系,指令级与任务级功耗模型

好,咱们进入第三章。这一章,我想跟你聊聊固件功耗模型。

说白了,就是搞清楚一件事:你的代码到底是怎么把电吃掉的?

我刚开始做功耗优化那会儿,总觉得功耗是硬件的事。软件嘛,跑得快就行。结果有一次,一个低功耗项目死活过不了测试,电池续航差了一大截。我拿着示波器一量,发现芯片在某个状态下电流就是降不下来。查了三天,最后发现是一个轮询任务没关,CPU一直在跑空循环。嗯,从那以后,我再也不敢说「功耗跟软件没关系」了。

3.1 固件执行路径与功耗的映射关系

先讲个基本概念。你写的每一行C代码,最终都会变成一串指令。这些指令在CPU里跑,每一步都会消耗能量。

功耗不是均匀分布的。你想想看,CPU执行一条加法指令,跟执行一条乘法指令,功耗能一样吗?CPU进入睡眠模式,跟全速运行,功耗能一样吗?

所以,我们需要建立一种映射关系:固件执行路径 → 硬件活动 → 功耗

核心观点: 功耗是「执行路径」的函数。不同的路径,功耗天差地别。

我在项目中遇到过这样一个案例:一个传感器采集任务,正常路径是「唤醒→采集→处理→睡眠」,功耗很低。但有一次,因为一个中断处理不当,导致CPU频繁唤醒,每次只处理一点点数据就又睡了。结果平均功耗飙升了5倍。这就是执行路径变了,功耗也跟着变了。

怎么建立这个映射?我个人习惯分三步走:

  1. 抓取执行路径:用Trace工具(比如SEGGER SystemView、ARM ETM)记录固件实际跑了哪些函数、哪些中断。
  2. 标注功耗状态:把CPU的各个功耗模式(Run、Sleep、Deep Sleep)标注到时间轴上。
  3. 计算路径功耗:每个路径的功耗 = 该路径上各状态的时间 × 对应状态的功耗。

举个例子,一个典型的MCU,Run模式功耗可能是10mA,Sleep模式是1mA,Deep Sleep是10μA。如果你的固件在Run模式跑了10ms,Sleep模式睡了90ms,那平均功耗就是 (10×10 + 1×90) / 100 = 1.9mA。但如果你的固件频繁唤醒,Run模式跑了50ms,那平均功耗就变成了 (10×50 + 1×50) / 100 = 5.5mA。你看,路径变了,功耗翻倍。

小技巧: 我建议你在Trace工具里,把功耗状态直接叠加到执行路径图上。一眼就能看出哪个函数在「吃电」。

3.2 指令级功耗模型

指令级功耗模型,是更底层的视角。它关注的是:每一条指令,到底消耗了多少能量?

为什么会这样?因为不同的指令,会触发CPU内部不同的硬件单元。比如:

  • 算术指令:只用到ALU,功耗较低。
  • 访存指令:要访问Cache或内存,功耗较高。
  • 分支指令:可能引起流水线冲刷,功耗波动大。
  • 乘除法指令:硬件乘法器/除法器功耗大,比加法高一个数量级。

我记得有一次,我在优化一个音频解码算法。算法里有个循环,里面用到了大量的除法运算。我一看,这不行。除法指令在ARM Cortex-M4上要执行2-12个周期,而且功耗比加法高得多。我把它改成了查表法,用乘法代替除法。结果算法执行时间缩短了30%,功耗降低了15%。这就是指令级优化的威力。

指令级功耗模型,通常用下面的公式表示:

E_instruction = Σ (C_i × V^2 × f)

其中:

  • C_i 是第i条指令的等效电容(跟指令类型相关)
  • V 是工作电压
  • f 是工作频率

不过说实话,在实际项目中,我们很少去精确计算每条指令的功耗。太麻烦了,而且芯片厂商也不会给你那么细的数据。我更常用的方法是:用性能计数器来估算

比如,ARM Cortex-M系列有DWT(Data Watchpoint and Trace)单元,可以统计指令执行数量、时钟周期数、睡眠周期数等。通过这些数据,结合芯片手册里的功耗参数,就能估算出指令级的功耗分布。

注意: 指令级功耗模型,更适合用在「热点函数」的微优化上。不要一上来就搞指令级分析,那是杀鸡用牛刀。先看任务级,再往下钻。

3.3 任务级功耗模型

任务级功耗模型,才是我们日常优化中最常用的。它关注的是:每个任务(或函数),消耗了多少能量?

说白了,就是把功耗「分摊」到各个任务头上。这样你就能知道,哪个任务是「电老虎」,哪个任务很省电。

任务级功耗模型,通常这样建立:

  1. 划分任务:把固件拆成若干个独立的任务(比如:传感器采集、数据处理、通信、显示等)。
  2. 测量任务时间:用Trace工具记录每个任务的执行时间、调用次数。
  3. 测量系统功耗:用功耗分析仪(如Joulescope、Otii)记录整个系统的电流曲线。
  4. 关联功耗与任务:把电流曲线跟Trace时间轴对齐,算出每个任务的平均功耗。

我给大家看一个我实际项目中的例子。一个IoT设备,有四个任务:

任务名称 执行时间 (ms) 调用频率 (次/秒) 平均电流 (mA) 单次能耗 (mJ) 总能耗占比
传感器采集 5 10 12 0.6 15%
数据处理 20 1 15 3.0 7.5%
无线通信 50 0.1 25 12.5 3.1%
空闲任务 975 1 0.5 0.4875 74.4%

你看,无线通信任务虽然单次能耗最高,但因为调用频率低,总能耗占比反而很小。而空闲任务占了74.4%的能耗。这意味着什么?优化空闲任务的功耗,比优化无线通信任务更有效。

我曾经在一个项目中,花了大量精力优化通信协议,把通信功耗降了30%。结果一看总功耗,只降了1%。后来我把精力转向了空闲任务,优化了睡眠模式,把空闲电流从0.5mA降到了0.1mA,总功耗直接降了60%。这就是任务级功耗模型的价值——帮你找到真正的优化方向。

我的建议: 做功耗优化,先建任务级模型。找到占比最大的任务,再决定要不要往下钻到指令级。别一上来就抠指令,那是舍本逐末。

3.4 从任务级到指令级的联动分析

任务级模型告诉你「哪个任务耗电」,指令级模型告诉你「为什么这个任务耗电」。两者结合,才是完整的固件功耗分析。

我常用的方法是:

  1. 先用Trace工具跑一遍,拿到任务级的时间分布。
  2. 找到耗电最多的任务,比如「数据处理」。
  3. 对这个任务做指令级分析,看它内部哪些指令占用了最多的周期。
  4. 优化那些「周期多、功耗高」的指令。

举个例子。我在一个项目中,发现「数据处理」任务占了30%的功耗。用指令级分析一看,里面有个循环,循环体里有个除法指令,占了这个任务60%的周期。我把除法改成了移位和加法,任务执行时间缩短了40%,总功耗降了12%。

嗯,这里要注意一点:指令级优化,不要过度。 我曾经为了优化一条指令,把代码改得面目全非,结果编译器优化一开,我改的东西全被优化掉了。白忙活一场。所以,我建议你先用编译器的高优化等级(比如-O2、-O3)跑一遍,再去看哪些地方还有优化空间。

避坑指南: 我曾经在ARM Cortex-M0上做指令级优化,发现用32位乘法比8位乘法快。但后来发现,M0没有硬件乘法器,32位乘法是用软件模拟的,反而更慢。所以,一定要查芯片手册,搞清楚硬件支持哪些指令。

3.5 小结

这一章,我们聊了固件功耗模型的两个层次:

  • 指令级功耗模型:关注每条指令的能耗,适合微优化。
  • 任务级功耗模型:关注每个任务的能耗,适合宏观优化。

我个人认为,任务级模型是基础,指令级模型是进阶。先把任务级模型建好,找到「电老虎」,再决定要不要用指令级模型去「解剖」它。

下一章,我们会讲具体的Trace工具怎么用。到时候,我会拿一个实际项目,手把手带你走一遍完整的功耗分析流程。敬请期待。