2. GD32 时钟系统与低功耗模式:时钟树配置、睡眠模式、深度睡眠模式、待机模式、唤醒源配置

做低功耗 FOC 控制,时钟系统是第一个绕不开的坎。你想想看,电机控制要算电流环、速度环,算得慢了扭矩就抖;但算得快了功耗又上去了。GD32 的时钟树给了我们很多选择,怎么在性能和功耗之间找到平衡点,这就是本章要聊的核心。

2.1 时钟树概览——先看清全局

GD32 的时钟源有好几个:内部高速振荡器(IRC8M,8MHz)、外部高速晶振(HXTAL,4~32MHz)、内部低速振荡器(IRC40K,40kHz)、外部低速晶振(LXTAL,32.768kHz),还有锁相环(PLL)可以倍频。我个人习惯把时钟树想象成一个“水源分配系统”——源头是振荡器,中间经过 PLL 增压,最后分给各个外设总线。

关键点: FOC 算法对计算精度要求高,建议主频至少跑到 72MHz 以上。但低功耗模式下,主频必须降下来甚至关掉。

下面这张图是我自己整理的 GD32 时钟树结构,画出来方便你对照着看。

GD32 时钟树结构图 IRC8M (8MHz) HXTAL (4~32MHz) IRC40K (40kHz) LXTAL (32.768kHz) PLL (x2~x16) SYSCLK RTC / WDG AHB (HCLK) APB1 (PCLK1) APB2 (PCLK2) GPIO / DMA TIM / USART ADC / SPI 说明: • 系统时钟 SYSCLK 可选择来自 IRC8M、HXTAL 或 PLL。 • AHB 总线时钟 HCLK = SYSCLK / AHB 预分频器。 • APB1 最大 36MHz,APB2 最大 72MHz(不同型号有差异)。 • 低功耗模式下,PLL 和 HXTAL 会被关闭,仅保留 IRC40K 或 LXTAL。

2.2 时钟树配置实战

配置时钟树,说白了就是告诉芯片:你用什么源、倍频多少、分频多少。我一般在项目初始化第一步就把时钟配好,后面所有外设的时序都依赖它。

下面这段代码是我常用的配置模板,跑 72MHz 主频,外部 8MHz 晶振输入,PLL 倍频到 72MHz:

void system_clock_config(void)
{
    /* 使能 HXTAL */
    RCU_CTL |= RCU_CTL_HXTALEN;
    while(!(RCU_CTL & RCU_CTL_HXTALSTB));

    /* 配置 PLL:8MHz * 9 = 72MHz */
    RCU_CFG0 &= ~RCU_CFG0_PLLSEL;
    RCU_CFG0 |= RCU_CFG0_PLLSEL_HXTAL;
    RCU_CFG0 &= ~RCU_CFG0_PLLMF_MASK;
    RCU_CFG0 |= RCU_CFG0_PLLMF_9;

    /* 使能 PLL */
    RCU_CTL |= RCU_CTL_PLLEN;
    while(!(RCU_CTL & RCU_CTL_PLLSTB));

    /* 切换系统时钟到 PLL */
    RCU_CFG0 &= ~RCU_CFG0_SCS_MASK;
    RCU_CFG0 |= RCU_CFG0_SCS_PLL;
    while((RCU_CFG0 & RCU_CFG0_SCSS_MASK) != RCU_CFG0_SCSS_PLL);

    /* AHB 不分频,APB1 2分频(最大36MHz),APB2 不分频 */
    RCU_CFG0 &= ~RCU_CFG0_AHBPSC_MASK;
    RCU_CFG0 |= RCU_CFG0_AHBPSC_DIV1;
    RCU_CFG1 &= ~RCU_CFG1_APB1PSC_MASK;
    RCU_CFG1 |= RCU_CFG1_APB1PSC_DIV2;
    RCU_CFG1 &= ~RCU_CFG1_APB2PSC_MASK;
    RCU_CFG1 |= RCU_CFG1_APB2PSC_DIV1;
}
小技巧: 配置时钟时,每切换一个时钟源都要等待对应的稳定标志位。我曾经遇到过因为没等 HXTAL 稳定就直接切 PLL,结果系统跑飞的情况。别省这一步。

2.3 低功耗模式——省电的四种姿势

GD32 的低功耗模式分三种:睡眠模式、深度睡眠模式、待机模式。嗯,这里要注意,不同系列叫法可能略有差异,但核心逻辑是一样的。我按功耗从低到高给你排个序:

模式 CPU 状态 SRAM 保持 外设时钟 唤醒源 典型电流
睡眠模式 暂停 是 由配置决定 任何中断 ~mA 级
深度睡眠模式 暂停 是 仅低速时钟 RTC、外部中断、WKUP ~µA 级
待机模式 掉电 否 全部关闭 WKUP 引脚、RTC 闹钟、NRST ~nA 级

你想想看,FOC 控制通常需要周期性执行电流采样和计算。如果控制周期是 100µs,那剩下的时间完全可以睡一觉。我个人习惯用深度睡眠模式,既能保持 SRAM 数据,又能把功耗降到 µA 级。

2.4 睡眠模式——最轻量的休眠

睡眠模式最简单,CPU 暂停执行,但所有外设时钟照常运行。任何中断都能把它唤醒。适合那种“忙一会儿、闲一会儿”的场景。

进入睡眠模式就一行代码:

__WFI();  /* Wait For Interrupt */

唤醒后,CPU 从下一条指令继续执行。嗯,这里有个坑——如果你用 __WFI() 进入睡眠,中断服务函数执行完后会回到 __WFI() 的下一条指令。但如果你用 __WFE()(Wait For Event),唤醒条件会不一样。我建议统一用 __WFI(),逻辑更清晰。

注意: 睡眠模式下,如果 FOC 的 PWM 定时器还在运行,它会持续产生中断,导致芯片刚睡下就被唤醒。所以进入睡眠前,记得关掉不需要的中断源。

2.5 深度睡眠模式——性能和功耗的平衡点

深度睡眠模式是我在低功耗 FOC 项目中最常用的模式。它会把 PLL 和 HXTAL 都关掉,只保留 IRC40K 或 LXTAL。SRAM 内容保持,寄存器状态也保持,唤醒后可以快速恢复计算。

配置深度睡眠模式需要两步:

  1. 设置睡眠模式类型: 在系统控制寄存器中把睡眠模式设为深度睡眠。
  2. 选择唤醒时钟: 唤醒后是用 IRC8M 还是 HXTAL,需要提前配好。

代码示例:

void enter_deepsleep_mode(void)
{
    /* 设置深度睡眠模式 */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    /* 选择唤醒后的时钟源:IRC8M */
    RCU_CTL &= ~RCU_CTL_SLEEPMODE;
    RCU_CTL |= RCU_CTL_SLEEPMODE_IRC8M;

    /* 进入睡眠 */
    __WFI();
}

唤醒后,系统时钟会自动切到 IRC8M。如果你需要恢复 72MHz,记得在唤醒中断服务函数里重新配置 PLL。我曾经踩过一个坑:唤醒后直接操作外设,结果因为时钟还没切回高速,定时器频率不对,导致 FOC 电流环失控。所以我的习惯是——唤醒后第一件事就是恢复时钟配置。

2.6 待机模式——极致省电

待机模式是最省电的模式,但代价也最大。SRAM 内容全部丢失,寄存器大部分复位,只有 RTC 和备份域的数据能保留。唤醒后相当于一次复位,程序从头开始执行。

进入待机模式的步骤:

  1. 清除 WKUP 标志位。
  2. 使能电源管理接口时钟。
  3. 设置待机模式并进入。
void enter_standby_mode(void)
{
    /* 清除 WKUP 标志 */
    PMU_CTL0 |= PMU_CTL0_WURST;

    /* 使能 PMU 时钟 */
    RCU_APB1EN |= RCU_APB1EN_PMUEN;

    /* 设置待机模式 */
    PMU_CTL0 &= ~PMU_CTL0_LOWVDD;
    PMU_CTL0 |= PMU_CTL0_STBMOD;

    /* 进入 */
    __WFI();
}
警告: 待机模式唤醒后,所有外设寄存器都是默认值。如果你在 FOC 控制中用了待机模式,必须重新初始化 PWM 定时器、ADC、编码器接口等所有外设。我个人只在电池供电且长时间不工作的场景下用待机模式。

2.7 唤醒源配置——怎么叫醒芯片

不同的低功耗模式,支持的唤醒源不一样。我整理了一张表,方便你对照:

唤醒源 睡眠模式 深度睡眠模式 待机模式
任何中断 ✓ ✗ ✗
外部中断 (EXTI) ✓ ✓ ✗
RTC 闹钟 ✓ ✓ ✓
WKUP 引脚 ✓ ✓ ✓
NRST 复位 ✓ ✓ ✓

在 FOC 应用中,我通常用 RTC 闹钟作为唤醒源。比如控制周期是 1ms,RTC 每 1ms 产生一次闹钟中断,把芯片从深度睡眠中唤醒。这样 CPU 只在需要算电流环的时候工作,其余时间都在睡觉。

配置 RTC 闹钟唤醒的步骤:

  1. 使能 RTC 时钟(用 LXTAL 或 IRC40K)。
  2. 配置 RTC 闹钟值。
  3. 使能 RTC 闹钟中断,并连接到 EXTI 17(RTC 闹钟事件)。
  4. 进入深度睡眠模式。
经验之谈: 唤醒后不要立即进行高精度计算。因为 IRC8M 刚启动时频率可能有 ±1% 的偏差,等几个毫秒稳定后再切到 PLL 倍频。我在一个项目中没注意这个细节,导致 FOC 启动瞬间电流波形有毛刺,排查了半天才发现是时钟稳定性的问题。

2.8 低功耗 FOC 的时钟策略建议

最后,我结合自己的项目经验,给出一套实用的时钟策略:

  • 正常运行: 72MHz,PLL 输出,AHB 不分频,APB1 2分频,APB2 不分频。FOC 电流环和速度环都在这个频率下跑。
  • 空闲等待: 进入深度睡眠模式,RTC 闹钟每 100µs~1ms 唤醒一次。唤醒后先用 IRC8M 快速处理数据,处理完继续睡。
  • 长时间待机: 进入待机模式,RTC 闹钟每几秒甚至几分钟唤醒一次,做一次状态检测或数据上报。

这套策略我在一个手持 FOC 驱动设备上验证过,电池续航从原来的 4 小时延长到了 12 小时以上。说白了,低功耗不是让芯片一直睡,而是让它“该睡就睡,该醒就醒”。