第三章 功耗测量方法论:板级功耗测量、芯片内部传感器与软件统计
功耗测量这件事,说白了就是回答三个问题:芯片到底吃了多少电?这些电都花在哪了?有没有办法省下来?
我做了这么多年功耗优化,发现很多工程师一上来就调参数、改代码,结果越调越乱。为什么?因为连功耗数据都不准,你调个啥?
这一章,咱们就把功耗测量的三种手段讲透。板级测量、芯片内部传感器、软件统计——这三板斧你都得会。
3.1 板级功耗测量:最直接的方法
板级测量,就是把芯片当成一个黑盒子,直接测它从电源上抽了多少电流。我个人习惯先用这个方法做基准,因为它最可靠。
3.1.1 Shunt电阻法
原理很简单:在电源路径上串一个小电阻,测它两端的压降,再用欧姆定律算出电流。
公式长这样:
I = V_shunt / R_shunt
P = I × V_core
嗯,这里要注意几个坑:
- 电阻值不能太大——我见过有人用1Ω的电阻,结果压降太大,芯片直接欠压复位了。一般选10mΩ到100mΩ比较合适。
- 电阻精度要够——至少1%,最好0.5%。我曾经因为用了5%的电阻,测出来的数据偏差20%,排查了三天才发现是电阻的问题。
- 走线要小心——Shunt电阻的焊盘要开Kelvin连接,不然焊锡电阻会引入误差。
3.1.2 功率监控芯片
如果你觉得Shunt电阻法太麻烦,可以用专用的功率监控芯片。比如TI的INA219、INA3221,或者Maxim的MAX34407。
这些芯片的好处是:
- 内置ADC,直接输出电流和功率值
- 支持I2C/SMBus接口,方便软件读取
- 精度高,一般能做到±1%以内
我在三星的时候,Exynos 2100的参考板上就用了INA3221,三通道同时监控CPU、GPU和内存的功耗。调试的时候特别方便,一个命令就能看到各路功耗。
代码示例(读取INA3221数据):
// 伪代码:读取INA3221通道1的电流和电压
uint16_t read_ina3221_channel(uint8_t ch) {
uint16_t shunt_voltage = i2c_read(INA3221_ADDR, SHUNT_VOLTAGE_REG + ch*2);
uint16_t bus_voltage = i2c_read(INA3221_ADDR, BUS_VOLTAGE_REG + ch*2);
// 计算电流和功率
float current_mA = shunt_voltage * 0.005; // 取决于Shunt电阻值
float power_mW = current_mA * bus_voltage * 0.001;
return power_mW;
}
3.2 芯片内部功耗传感器
板级测量只能看到芯片整体的功耗,但内部各个模块(CPU核、GPU、NPU、DSP)到底吃了多少电?这时候就得靠芯片内部的传感器了。
3.2.1 Thermal Diode(热敏二极管)
Thermal Diode其实不是直接测功耗的,它是测温度的。但温度变化和功耗有强相关性,所以可以用来估算功耗。
原理:二极管的正向压降随温度变化,大约是-2mV/°C。通过测量压降,反推温度。
在Exynos芯片里,每个主要模块都配了Thermal Diode。我记得Exynos 990有十几个温度传感器,分布在CPU、GPU、NPU、Modem等区域。
用法:
- 先校准:在已知温度下记录电压值
- 运行时读取电压,换算成温度
- 根据温度变化率,估算功耗
3.2.2 Power Monitor(片上功耗监控器)
这个才是真正的功耗传感器。三星从Exynos 9820开始,在芯片内部集成了Power Monitor模块。
工作原理:在每个电源域(Power Domain)的供电路径上,集成一个小的电流检测电路。它不像Shunt电阻那样需要外部元件,而是用片上MOSFET做电流镜,把电流复制一份出来,然后通过ADC量化。
优点:
- 精度高:一般能做到±5%以内
- 响应快:微秒级,能捕捉到瞬态功耗变化
- 粒度细:每个电源域独立监控
我在Exynos 2100的项目中,就用Power Monitor抓到了GPU的一个功耗异常。当时跑一个游戏场景,GPU功耗突然飙到8W,但帧率没提升。后来发现是某个shader单元没正确进入低功耗模式。如果没有Power Monitor,这种问题根本发现不了。
读取Power Monitor的寄存器示例:
// 读取Exynos GPU Power Monitor寄存器
uint32_t gpu_power = readl(EXYNOS_PMU_BASE + GPU_POWER_MONITOR_REG);
// 寄存器格式:高16位是电流值,低16位是电压值
uint16_t current_raw = (gpu_power >> 16) & 0xFFFF;
uint16_t voltage_raw = gpu_power & 0xFFFF;
// 换算成实际值
float current_A = current_raw * 0.001; // 单位:A
float voltage_V = voltage_raw * 0.001; // 单位:V
float power_W = current_A * voltage_V;
3.3 软件功耗统计
板级测量和芯片传感器都是硬件手段,但很多时候我们没法接示波器,也没法读寄存器。比如用户手里的手机,你怎么测?这时候就得靠软件了。
3.3.1 BatteryStats
Android系统里的BatteryStats,说白了就是记录每个应用、每个硬件模块用了多少电。它基于一个简单的模型:
- CPU:根据运行时间和频率,查表得到功耗
- 屏幕:根据亮度和时间,查表得到功耗
- WiFi/蜂窝:根据收发数据量和时间,查表得到功耗
嗯,这里要泼一盆冷水:BatteryStats的精度其实不高。我做过对比测试,同一个场景,BatteryStats报的功耗和板级实测值能差30%以上。
为什么?因为它的功耗模型是固定的,但实际芯片的功耗受温度、制程偏差、具体 workload 的影响很大。你想想看,同一个CPU频率,跑整数运算和浮点运算的功耗能差一倍,但BatteryStats只按频率查表,怎么可能准?
不过,BatteryStats有一个好处:它能告诉你「相对」的功耗分布。比如某个App占了50%的功耗,那它大概率就是耗电大户。用来做问题定位,还是很有价值的。
3.3.2 PowerProfile
PowerProfile是Android系统里的功耗配置文件,存在/system/framework/framework-res.apk里。它定义了各个硬件模块在不同状态下的功耗值。
举个例子:
<?xml version="1.0" encoding="utf-8"?>
<device name="Android">
<item name="screen.on">200</item>
<item name="screen.full">800</item>
<item name="wifi.active">100</item>
<item name="wifi.scan">200</item>
<item name="cpu.speeds">[200000, 500000, 1000000, 1500000]</item>
<item name="cpu.active">[50, 100, 200, 400]</item>
</device>
这个文件里的数值,单位是mA(毫安)。系统根据这些值,结合硬件使用时间,算出总功耗。
我在三星的时候,负责过Exynos 990的PowerProfile校准。说实话,这是个苦差事。你得在几十种场景下实测功耗,然后反推每个模块的系数。而且不同批次芯片的功耗有差异,还得取平均值。
3.4 三种方法的对比
好了,三种方法都讲完了。我做个总结对比:
| 方法 | 精度 | 响应速度 | 粒度 | 适用场景 |
|---|---|---|---|---|
| Shunt电阻法 | ±1%~5% | 纳秒级(示波器) | 板级 | 实验室基准测量、瞬态分析 |
| 功率监控芯片 | ±1%~3% | 毫秒级 | 板级/模块级 | 长时间监控、自动化测试 |
| Thermal Diode | ±10%~20% | 毫秒级 | 模块级 | 温度保护、功耗趋势分析 |
| Power Monitor | ±5% | 微秒级 | 电源域级 | 芯片内部功耗分析、调优 |
| BatteryStats | ±20%~30% | 秒级 | 应用级 | 用户场景功耗分析 |
我个人建议的实践流程是:
- 先用Shunt电阻法或功率监控芯片,拿到板级基准功耗
- 再用Power Monitor,定位到具体哪个模块耗电
- 最后用BatteryStats,验证优化效果在用户场景下的表现
这三步走下来,功耗问题基本无处遁形。下一章,咱们就讲讲怎么用这些工具,去抓Exynos芯片里的「电老虎」。