3. 电源管理单元(PMU)配置:PMU寄存器详解、电压调节器模式与Vcore动态调节

电源管理单元,简称PMU,是MCU低功耗设计的核心。说白了,它就是芯片的“供电调度中心”。你想想看,一个系统里外设那么多,CPU核心、内存、模拟模块,各自对电压和功耗的要求都不一样。PMU就是负责把这些需求理顺,在性能和功耗之间找到平衡点。

我个人习惯,拿到一款新MCU,第一件事就是翻开PMU章节。为什么?因为很多低功耗的坑,其实都埋在这里。CMS32的PMU设计得比较灵活,但配置不当,轻则功耗下不来,重则系统跑飞。咱们今天就把这块啃透。

3.1 PMU寄存器详解

CMS32的PMU寄存器不算多,但个个关键。我挑几个最常用的,给大家拆开揉碎了讲。

寄存器名称 偏移地址 核心功能 我关注的点
PMU_CTRL 0x4000_0000 主控制寄存器,使能PMU模块,选择调节器模式 上电后默认值,别乱改
PMU_VCR 0x4000_0004 电压调节器配置寄存器,设定Normal/Low Power模式 切换时注意稳定时间
PMU_VCORE 0x4000_0008 核心电压调节寄存器,动态调整Vcore输出 步进和跳变限制,容易忽略
PMU_STATUS 0x4000_000C 状态寄存器,指示当前调节器模式、电压稳定标志 切换后必须轮询此寄存器
PMU_WKCFG 0x4000_0010 唤醒配置寄存器,设定唤醒源和唤醒延迟 延迟设太短容易误唤醒

关键提醒:PMU_CTRL寄存器的bit0是PMU使能位。芯片复位后,PMU默认是使能的,但调节器模式默认是Normal。如果你直接切到Low Power模式,却不改外设时钟,系统可能会因为供电不足而异常。我踩过这个坑,后来养成了“先降频,再降压”的习惯。

3.2 电压调节器模式:Normal vs Low Power

CMS32的电压调节器有两种工作模式:Normal模式和Low Power模式。这两种模式,说白了就是“大力出奇迹”和“细水长流”的区别。

  • Normal模式:调节器全速工作,输出纹波小,负载响应快。适合CPU全速运行、外设大量工作的场景。代价是调节器自身功耗较高,大约在几十微安级别。
  • Low Power模式:调节器进入低静态电流状态,输出能力下降,纹波会大一些。适合CPU休眠、仅保留少数外设(如RTC、LPTIM)工作的场景。调节器自身功耗可以降到几微安。

切换模式时,有个细节很多人会忽略:切换不是瞬间完成的。调节器内部有模拟电路,需要时间稳定输出。我曾经在项目里,切完模式立刻去读ADC,结果采样值跳得厉害。后来查了手册,才发现需要等待PMU_STATUS寄存器的VCO_STABLE位变为1。

我的经验:切换模式后,建议插入一个5~10微秒的软件延时,或者直接轮询VCO_STABLE位。别用硬延时,因为此时系统时钟可能已经变了,硬延时不准。用轮询最可靠。

3.3 核心电压(Vcore)动态调节

动态电压调节,简称DVS,是低功耗设计里的一把利器。CMS32支持在1.0V到1.3V之间,以50mV为步进,动态调整Vcore输出。

为什么要调Vcore?因为CPU的功耗和电压的平方成正比。电压降一点,功耗降一大截。但电压降了,CPU能跑的最高频率也会降。所以DVS的核心思路是:根据负载需求,动态匹配电压和频率。

CMS32的Vcore调节流程,我总结为三步:

  1. 降频:先把CPU频率降到目标电压对应的安全频率以下。
  2. 降压:通过PMU_VCORE寄存器,逐级降低Vcore。注意,不要一次跳太多,建议每次降50mV,等待稳定后再降下一步。
  3. 验证:读取PMU_STATUS,确认电压稳定,且系统工作正常。

升压流程反过来:先升压,再升频。

警告:千万不要在高压下直接降频,或者在低压下直接升频。前者可能导致逻辑电路时序违规,后者可能导致CPU因供电不足而死机。我见过一个同事,为了省电,在1.0V下把CPU频率设到了48MHz,结果系统跑几分钟就随机死机。查了两天才找到原因。

下面这张图,是我自己总结的DVS状态机,大家写代码时可以照着这个逻辑来。

CMS32 动态电压调节(DVS)状态机 CMS32 动态电压调节(DVS)状态机 IDLE(空闲) 降频(Set Divider) 降压(Write VCR) 等待稳定(Poll Status) 升压(Write VCR) 升频(Set Divider) 需要降功耗 需要提性能 稳定且未到目标 未稳定 升压后等待 稳定后升频 完成 说明: 降电压路径:IDLE → 降频 → 降压 → 等待稳定 → IDLE(循环直到目标电压) 升电压路径:IDLE → 升压 → 等待稳定 → 升频 → IDLE

嗯,这里要注意,DVS不是随便调的。CMS32的PMU_VCORE寄存器,写一次只能改变50mV。如果你想从1.3V降到1.0V,需要写6次,每次都要等稳定。为什么这么设计?因为模拟调节器需要时间响应,跳变太快会导致输出电压过冲或下冲,严重时可能损坏芯片。

实战代码片段:下面是我在一个IoT项目里用的Vcore降压函数,大家可以参考。

/**
 * @brief  逐步降低Vcore电压
 * @param  target_mv  目标电压,单位mV,范围1000~1300,步进50
 * @retval 0: 成功, -1: 参数错误, -2: 超时
 */
int pmu_vcore_decrease(uint16_t target_mv) {
    uint16_t current_mv;
    int retry;
    
    // 1. 参数检查
    if (target_mv < 1000 || target_mv > 1300 || (target_mv % 50) != 0) {
        return -1;
    }
    
    // 2. 读取当前电压
    current_mv = 1000 + ((PMU->VCORE & 0x3F) * 50);
    
    // 3. 逐级降压
    while (current_mv > target_mv) {
        current_mv -= 50;
        PMU->VCORE = (current_mv - 1000) / 50;  // 写入新值
        
        // 4. 等待稳定,超时10ms
        retry = 10000;
        while (!(PMU->STATUS & PMU_STATUS_VCO_STABLE_MASK)) {
            if (--retry == 0) {
                return -2;  // 超时
            }
            // 注意:这里不能调用HAL_Delay,因为时钟可能已变
            __NOP();
        }
    }
    
    return 0;
}

避坑指南:我曾经在DVS过程中,忘了关中断。结果降压到一半,一个高优先级中断进来,ISR里试图访问一个需要高压才能工作的外设,直接导致总线错误。所以,DVS操作建议在临界区里做,或者至少保证中断服务程序里不访问电压敏感的外设。

最后,说一个很多人问的问题:DVS到底能省多少电?我实测过一个项目,CPU跑48MHz时,Vcore 1.3V,系统总电流约8mA。降到1.0V后,频率降到24MHz,电流降到3.5mA。省了一半还多。当然,代价是性能降了一半。所以,DVS适合那些对实时性要求不高的场景,比如传感器数据采集、周期性上报等。