2、PMIC关键寄存器解析:寄存器映射表、关键控制位、读写时序
好,咱们进入PMIC最核心的部分——寄存器操作。
说实话,很多工程师拿到PMIC datasheet,第一反应就是翻到寄存器列表,然后开始对着代码一个个配。这种做法我早年也干过,结果呢?板子点不亮,查了半天发现是某个控制位的默认值和我想的不一样。所以这一节,我带你系统地过一遍PMIC寄存器该怎么看、怎么用。
2.1 PMIC寄存器映射表——你得知道它长什么样
MTK PMIC的寄存器空间,通常是16位地址、8位数据。你想想看,一颗PMIC里可能有几百个寄存器,总不能全放在一个页面上吧?所以PMIC用了分页(Page)机制。
我习惯把PMIC寄存器映射表想象成一栋楼:
- Page 就是楼层号
- Register Address 就是房间号
- Bit Field 就是房间里的开关
以MT8676配套的PMIC为例,典型的寄存器映射表长这样:
| Page | Register Addr | Register Name | Reset Value | Description |
|---|---|---|---|---|
| 0x0 | 0x0120 | PMIC_RG_TOP_CFG | 0x00 | Top level configuration |
| 0x0 | 0x0124 | PMIC_RG_INT_EN | 0x00 | Interrupt enable register |
| 0x1 | 0x0010 | PMIC_RG_LDO_VOLT | 0x1F | LDO output voltage setting |
| 0x2 | 0x0008 | PMIC_RG_WAKEUP_CTRL | 0x00 | Wakeup source configuration |
2.2 关键控制位——这些位你绕不开
PMIC里寄存器很多,但真正在休眠唤醒场景下需要关注的,其实就那么几个。我列一下我项目中经常打交道的:
2.2.1 电源域使能位(Power Domain Enable)
每个LDO或DCDC都有一个使能位。比如:
// 假设 LDO1 的使能位在 Page0, Addr 0x0120, Bit[0]
#define PMIC_LDO1_EN_ADDR 0x0120
#define PMIC_LDO1_EN_BIT (1 << 0)
// 使能 LDO1
pmic_write_byte(PMIC_LDO1_EN_ADDR, PMIC_LDO1_EN_BIT);
// 关闭 LDO1
pmic_write_byte(PMIC_LDO1_EN_ADDR, 0x00);
2.2.2 休眠模式控制位(Sleep Mode Control)
这部分是座舱电源管理的核心。PMIC进入休眠后,哪些LDO保持供电,哪些关断,都由这些位控制。
举个例子:
// 寄存器:PMIC_RG_SLEEP_CTRL (Page0, Addr 0x0130)
// Bit[7:4] : LDO1~LDO4 在休眠时的行为
// 0x0: 跟随系统状态
// 0x1: 强制开启
// 0x2: 强制关闭
// 让 LDO1 在休眠时保持供电(给RTC用)
pmic_write_byte(0x0130, 0x10); // LDO1 强制开启
嗯,这里要注意:强制开启的LDO,在休眠时功耗会增大。我一般只给RTC、唤醒逻辑电路这类必须供电的模块用。
2.2.3 唤醒源配置位(Wakeup Source Config)
PMIC怎么知道系统该醒了?靠的就是唤醒源。常见的唤醒源有:
- GPIO唤醒: 比如ACC信号、车门解锁信号
- 定时器唤醒: 比如每隔10秒唤醒一次做CAN报文检测
- RTC闹钟唤醒: 比如每天早上6点唤醒车机
配置代码示例:
// 寄存器:PMIC_RG_WAKEUP_SRC (Page2, Addr 0x0008)
// Bit[0] : GPIO0 唤醒使能
// Bit[1] : GPIO1 唤醒使能
// Bit[2] : RTC 唤醒使能
// Bit[3] : 定时器唤醒使能
// 使能 GPIO0 和 RTC 唤醒
pmic_write_byte(0x0008, 0x05); // Bit0 + Bit2
2.3 读写时序——搞错了板子就废了
PMIC的通信接口通常是I2C或SPI。MTK平台用I2C居多。读写时序看起来简单,但细节决定成败。
2.3.1 I2C写时序
标准的I2C写操作:
START + SlaveAddr(W) + ACK + RegAddr + ACK + Data + ACK + STOP
但PMIC有个特殊要求——Page切换。因为PMIC内部有多个Page,读写前必须先切换到目标Page。
我见过有人这么写代码:
// 错误示范:没有切换Page
i2c_write(PMIC_ADDR, 0x0120, 0x01); // 直接写Page0的寄存器
// 如果当前在Page1,这行代码实际写的是Page1的0x0120寄存器,完全不是你想要的效果
正确的做法是:
// 正确示范:先切Page,再写寄存器
pmic_set_page(0x0); // 切换到Page0
i2c_write(PMIC_ADDR, 0x0120, 0x01); // 写Page0的寄存器
2.3.2 I2C读时序
读操作稍微复杂一点,需要先写寄存器地址,再重新发起读操作:
START + SlaveAddr(W) + ACK + RegAddr + ACK + STOP
START + SlaveAddr(R) + ACK + Data + NACK + STOP
代码实现:
uint8_t pmic_read_byte(uint16_t reg_addr)
{
uint8_t val = 0;
// 先写寄存器地址
i2c_write(PMIC_ADDR, reg_addr, 0); // 这里只发地址,不发数据
// 再读数据
i2c_read(PMIC_ADDR, &val, 1);
return val;
}
2.3.3 时序参数要求
PMIC对I2C时序有严格要求,尤其是休眠唤醒场景下:
| 参数 | 最小值 | 典型值 | 说明 |
|---|---|---|---|
| SCL频率 | - | 400kHz | 标准模式100kHz,快速模式400kHz |
| t_HD:STA | 0.6us | - | 起始条件保持时间 |
| t_SU:STO | 0.6us | - | 停止条件建立时间 |
| t_BUF | 1.3us | - | 停止和起始条件之间的总线空闲时间 |
2.4 实战经验总结
说了这么多,最后给你几个我实际项目中的经验:
- 读回验证: 写完寄存器后,一定要读回来确认。我遇到过I2C总线被拉死,写操作返回成功但实际没写进去的情况。
- 批量操作用Page Burst: 有些PMIC支持同一Page内的连续读写,效率高很多。比如配置多个LDO电压时,可以用burst write一次搞定。
- 保留位不要乱动: 寄存器里标记为Reserved的位,写0就对了。我曾经手贱写了个1,结果PMIC直接进入测试模式,功耗飙到200mA。
- 休眠前保存上下文: 如果你在休眠前修改了某些寄存器的值,记得在唤醒后恢复。我一般用一个结构体保存所有关键寄存器的值。
好了,PMIC寄存器这块就讲到这里。下一节我们聊聊具体的休眠唤醒流程,到时候你会看到这些寄存器是怎么串起来用的。