2. GD32 时钟系统与低功耗模式:时钟树配置、睡眠模式、深度睡眠模式、待机模式、唤醒源配置
做低功耗 FOC 控制,时钟系统是第一个绕不开的坎。你想想看,电机控制要算电流环、速度环,算得慢了扭矩就抖;但算得快了功耗又上去了。GD32 的时钟树给了我们很多选择,怎么在性能和功耗之间找到平衡点,这就是本章要聊的核心。
2.1 时钟树概览——先看清全局
GD32 的时钟源有好几个:内部高速振荡器(IRC8M,8MHz)、外部高速晶振(HXTAL,4~32MHz)、内部低速振荡器(IRC40K,40kHz)、外部低速晶振(LXTAL,32.768kHz),还有锁相环(PLL)可以倍频。我个人习惯把时钟树想象成一个“水源分配系统”——源头是振荡器,中间经过 PLL 增压,最后分给各个外设总线。
下面这张图是我自己整理的 GD32 时钟树结构,画出来方便你对照着看。
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;
}
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(),逻辑更清晰。
2.5 深度睡眠模式——性能和功耗的平衡点
深度睡眠模式是我在低功耗 FOC 项目中最常用的模式。它会把 PLL 和 HXTAL 都关掉,只保留 IRC40K 或 LXTAL。SRAM 内容保持,寄存器状态也保持,唤醒后可以快速恢复计算。
配置深度睡眠模式需要两步:
- 设置睡眠模式类型: 在系统控制寄存器中把睡眠模式设为深度睡眠。
- 选择唤醒时钟: 唤醒后是用 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 和备份域的数据能保留。唤醒后相当于一次复位,程序从头开始执行。
进入待机模式的步骤:
- 清除 WKUP 标志位。
- 使能电源管理接口时钟。
- 设置待机模式并进入。
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();
}
2.7 唤醒源配置——怎么叫醒芯片
不同的低功耗模式,支持的唤醒源不一样。我整理了一张表,方便你对照:
| 唤醒源 | 睡眠模式 | 深度睡眠模式 | 待机模式 |
|---|---|---|---|
| 任何中断 | ✓ | ✗ | ✗ |
| 外部中断 (EXTI) | ✓ | ✓ | ✗ |
| RTC 闹钟 | ✓ | ✓ | ✓ |
| WKUP 引脚 | ✓ | ✓ | ✓ |
| NRST 复位 | ✓ | ✓ | ✓ |
在 FOC 应用中,我通常用 RTC 闹钟作为唤醒源。比如控制周期是 1ms,RTC 每 1ms 产生一次闹钟中断,把芯片从深度睡眠中唤醒。这样 CPU 只在需要算电流环的时候工作,其余时间都在睡觉。
配置 RTC 闹钟唤醒的步骤:
- 使能 RTC 时钟(用 LXTAL 或 IRC40K)。
- 配置 RTC 闹钟值。
- 使能 RTC 闹钟中断,并连接到 EXTI 17(RTC 闹钟事件)。
- 进入深度睡眠模式。
2.8 低功耗 FOC 的时钟策略建议
最后,我结合自己的项目经验,给出一套实用的时钟策略:
- 正常运行: 72MHz,PLL 输出,AHB 不分频,APB1 2分频,APB2 不分频。FOC 电流环和速度环都在这个频率下跑。
- 空闲等待: 进入深度睡眠模式,RTC 闹钟每 100µs~1ms 唤醒一次。唤醒后先用 IRC8M 快速处理数据,处理完继续睡。
- 长时间待机: 进入待机模式,RTC 闹钟每几秒甚至几分钟唤醒一次,做一次状态检测或数据上报。
这套策略我在一个手持 FOC 驱动设备上验证过,电池续航从原来的 4 小时延长到了 12 小时以上。说白了,低功耗不是让芯片一直睡,而是让它“该睡就睡,该醒就醒”。