第一章 课程导论与硬件基础

各位同学好,我是你们这门课的主讲。做了十几年嵌入式Linux,从早期的AT91RM9200一路折腾到现在的Cortex-A72,说实话,功耗这件事儿,以前真没多少人当回事。但到了今天,谁要是还不管功耗,产品基本就别想上市了。

这一章,我们先搭个全景图,看看低功耗优化到底要管哪些层面。然后拿一块我比较熟的芯片——i.MX8M Plus——来拆解一下SoC内部的功耗架构。最后,我会教你怎么用工具把功耗数据“抓”出来。

1.1 嵌入式Linux低功耗优化全景图

很多人一提到低功耗,第一反应就是“调调CPU频率,关几个外设”。嗯,这没错,但太浅了。真正的低功耗优化,是一个从硬件到软件、从芯片到系统的系统工程。

我个人习惯把整个优化体系分成四个层次,你想想看,是不是这么回事:

层次 核心手段 典型收益
芯片级 DVFS、时钟门控、电源域开关 30%~50%
内核级 cpuidle、CPUFreq、PM QoS 15%~30%
驱动级 runtime PM、设备suspend/resume 10%~20%
应用级 事件驱动、批处理、唤醒源管理 5%~15%

这四个层次不是孤立的。我见过不少团队,应用层拼命做优化,结果发现内核的cpuidle根本没配好,CPU根本进不了深度睡眠——白忙一场。

核心观点:低功耗优化必须从芯片手册开始,到内核配置,再到驱动和应用,形成一条完整的链路。跳过任何一层,效果都会大打折扣。

下面这张图,是我自己画的全景框架,你可以把它当作整门课的地图:

嵌入式Linux低功耗优化全景框架 应用层 事件驱动架构 · 批处理合并 · 唤醒源管理 · 定时器对齐 内核层 cpuidle · CPUFreq · PM QoS · 时钟事件 · 中断管理 驱动层 runtime PM · 设备suspend/resume · 时钟门控 · 电源域控制 芯片层 DVFS · 多电源域 · 深睡眠模式 · LDO/DC-DC切换 优化收益递减 芯片级: 30%~50% 内核级: 15%~30% 驱动级: 10%~20% 应用级: 5%~15%

1.2 主流SoC功耗架构解析——以i.MX8M Plus为例

讲理论太空了,我们拿一块具体的芯片来解剖。i.MX8M Plus是NXP近几年很火的一款,四核Cortex-A53加一个Cortex-M7协处理器。我去年帮一个客户做工业平板,用的就是这颗芯片,对它的功耗特性还算熟悉。

1.2.1 电源域划分

这颗SoC内部有四个主要的电源域,每个域可以独立开关:

  • A53域:四个Cortex-A53核心,L2缓存,SCU。这是功耗大头,跑起来能到2W以上。
  • M7域:Cortex-M7实时核,适合做低功耗后台任务。
  • GPU/VPU域:图形和视频处理,不用时可以完全断电。
  • Always-on域:始终供电,包括RTC、唤醒控制器、一部分GPIO。

实战技巧:我在项目里把M7域当作“低功耗管家”。主系统休眠时,M7跑一个极简的轮询任务,检测到按键或传感器事件后,再唤醒A53。这样A53大部分时间都处于断电状态,整机功耗从1.8W降到了0.3W。

1.2.2 运行模式与功耗数据

i.MX8M Plus定义了四种运行模式,我整理了一张表,方便你对照:

模式 A53状态 M7状态 典型功耗 唤醒时间
Run 全速运行 运行 2.5W~4.0W —
Idle WFI状态 运行 0.8W~1.2W ~10μs
Suspend 断电 运行/断电 0.2W~0.5W ~5ms
Deep Sleep 断电 断电 0.05W~0.1W ~50ms

这里有个坑,我必须要提醒你:

避坑指南:我曾经在Suspend模式下,发现功耗比手册标称高了0.15W。查了两天才找到原因——有一个GPIO没配置成内部下拉,导致漏电流。所以,硬件设计时,所有未使用的引脚一定要处理好上拉/下拉状态,否则软件再怎么优化也白搭。

1.3 功耗测量工具与基准测试方法

没有测量,就没有优化。你改了一个参数,功耗降了还是升了?必须用数据说话。

1.3.1 硬件测量工具

我常用的工具有两种,各有各的适用场景:

  • 功率分析仪(如Keysight N6705C):精度高,采样率快,能抓到微秒级的电流瞬态。适合做深度分析,但价格贵,一台好几万。
  • INA219/I2C电流监测模块:便宜,几块钱一片,通过I2C读取。精度够用,适合做长期监控。我在开发板上经常焊一个INA219上去。

INA219的用法很简单,我贴一段读取代码,你可以在自己的板子上试试:

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    int fd = open("/dev/i2c-2", O_RDWR);
    ioctl(fd, I2C_SLAVE, 0x40);  // INA219默认地址
    
    // 读取电流寄存器 (0x04)
    unsigned char reg = 0x04;
    write(fd, &reg, 1);
    unsigned char buf[2];
    read(fd, buf, 2);
    
    int raw = (buf[0] << 8) | buf[1];
    float current_mA = raw * 0.1;  // 取决于校准寄存器设置
    printf("Current: %.2f mA\n", current_mA);
    
    close(fd);
    return 0;
}

1.3.2 基准测试方法

测功耗不能光测一个静态值。我一般会跑一套标准的测试序列,这样才能对比不同配置的效果:

  1. 空闲功耗:系统启动后,不跑任何应用,只保留基础服务。记录5分钟的平均功耗。
  2. 满负载功耗:用stress工具压满所有CPU核心,同时跑GPU负载。记录峰值功耗。
  3. 休眠功耗:执行 echo mem > /sys/power/state,进入Suspend模式。测量维持电流。
  4. 唤醒功耗:用RTC或GPIO唤醒,记录从唤醒到系统恢复的功耗曲线。

我的习惯:每次改完内核配置或驱动代码,都跑一遍这四步测试。数据记到表格里,方便对比。别靠感觉,感觉会骗人,数据不会。