3. 低功耗设计基础

时钟,是 MCU 的心脏。没有它,芯片就是一块冰冷的硅片。GD32V 的时钟树,说实话,比我想象中要灵活不少。我第一次拿到参考手册时,看到那个时钟树图,密密麻麻的,心里也咯噔了一下。但别怕,拆开来看,其实就那么几大块。

3.1 系统时钟树解析

GD32V 的时钟源,一共有四个。我习惯把它们分成两类:内部的和外部的。

时钟源 缩写 典型频率 来源
高速外部晶振 HXTAL 8 MHz ~ 32 MHz 外部引脚 OSC_IN / OSC_OUT
低速外部晶振 LXTAL 32.768 kHz 外部引脚 OSC32_IN / OSC32_OUT
高速内部 RC IRC8M 8 MHz 芯片内部
低速内部 RC IRC40K 40 kHz 芯片内部

系统时钟(CK_SYS)最终供给 CPU、AHB 总线、APB 总线。它可以从 HXTAL、IRC8M 或者 PLL 输出中选择。PLL 的输入,又可以来自 HXTAL 或 IRC8M。这个选择路径,就是时钟树的核心。

我画了一张图,帮你把整个脉络理清楚。

GD32V 时钟树核心结构 HXTAL (8~32 MHz) LXTAL (32.768 kHz) IRC8M (8 MHz) IRC40K (40 kHz) PLL (倍频至 108 MHz) CK_SYS 选择器 CK_SYS → CPU / AHB / APB CK_LDO / RTC / WDT 💡 关键路径: HXTAL → PLL → CK_SYS 是高性能模式的标准路径。 IRC8M → CK_SYS 是上电复位后的默认路径,精度较低但无需外部晶振。

你看,从左边四个源头,经过选择、倍频,最终汇聚到右边的系统时钟。这个图,我建议你保存下来。调试的时候,对着它看寄存器,思路会清晰很多。

3.2 HXTAL / LXTAL / IRC 配置

配置时钟源,说白了就是操作几个控制位。但这里有个坑,我踩过。

3.2.1 HXTAL 配置

HXTAL 是外部高速晶振。上电后,它默认是关闭的。你需要手动开启,并等待它稳定。

/* 开启 HXTAL */
RCU->CTL |= RCU_CTL_HXTALEN;

/* 等待 HXTAL 稳定 */
while((RCU->CTL & RCU_CTL_HXTALSTB) == 0);

就这么两行。但要注意,等待稳定的时间,取决于晶振起振速度。我曾经在一个项目里,晶振质量不太好,起振很慢。结果 while 循环超时了,系统直接跑飞。后来我加了个超时判断,才解决。

⚠️ 注意: 不要无限等待 HXTAL 稳定标志。建议加一个超时计数,比如循环 10000 次还没稳定,就切换到 IRC8M 并报错。

3.2.2 LXTAL 配置

LXTAL 是低速外部晶振,专供 RTC 和看门狗。配置方法和 HXTAL 类似。

/* 开启 LXTAL */
RCU->BDCTL |= RCU_BDCTL_LXTALEN;

/* 等待 LXTAL 稳定 */
while((RCU->BDCTL & RCU_BDCTL_LXTALSTB) == 0);

LXTAL 的功耗极低,适合电池供电场景。但它的驱动能力也弱,PCB 走线要尽量短,远离高频信号。我见过有人把 LXTAL 的走线拉得很长,结果 RTC 时间总是不准。

3.2.3 IRC8M / IRC40K 配置

内部 RC 振荡器,上电默认就开启了。IRC8M 是 8 MHz,IRC40K 是 40 kHz。它们不需要外部元件,省成本。但精度一般,温漂比较大。

IRC8M 可以校准。GD32V 内部有个校准寄存器,可以微调频率。不过,除非你对频率精度有极高要求,否则默认值就够了。

💡 我的习惯: 开发阶段,我通常用 HXTAL + PLL,跑满速。调试方便。量产时,如果对功耗有要求,再考虑切到 IRC8M 并降频。

3.3 PLL 倍频设置

PLL 是性能的关键。GD32V 的 PLL 最高可以输出 108 MHz。它的计算公式很简单:

PLL 输出频率 = PLL 输入频率 × PLL 倍频系数

PLL 输入可以是 HXTAL 或 IRC8M。倍频系数通过寄存器 RCU->CFG0 的 PLLMF 位域设置。

举个例子,假设你用了 8 MHz 的 HXTAL,想要跑到 108 MHz:

/* 108 MHz = 8 MHz × 13.5 */
/* 但 PLLMF 是整数倍频,所以需要组合 */
/* 实际配置:先倍频到 108 MHz,需要 8 × 13.5,但 PLL 不支持小数 */
/* 所以改用 12 MHz 晶振:12 × 9 = 108 MHz */
/* 或者用 8 MHz 晶振,倍频到 104 MHz:8 × 13 = 104 MHz */

嗯,这里要注意。PLL 的倍频系数是整数。你不能直接配出 13.5 倍。所以,选择晶振频率时,要算好。我一般用 8 MHz 晶振,配到 104 MHz 或者 96 MHz。够用,而且稳定。

配置代码示例:

/* 配置 PLL 时钟源为 HXTAL,倍频系数为 13 */
RCU->CFG0 &= ~RCU_CFG0_PLLSEL;
RCU->CFG0 |= RCU_CFG0_PLLSEL_HXTAL;

RCU->CFG0 &= ~RCU_CFG0_PLLMF;
RCU->CFG0 |= RCU_CFG0_PLLMF_13;

/* 开启 PLL */
RCU->CTL |= RCU_CTL_PLLEN;

/* 等待 PLL 稳定 */
while((RCU->CTL & RCU_CTL_PLLSTB) == 0);

/* 切换系统时钟到 PLL */
RCU->CFG0 &= ~RCU_CFG0_SCS;
RCU->CFG0 |= RCU_CFG0_SCS_PLL;
⚠️ 注意: 切换系统时钟前,确保目标时钟已经稳定。否则,芯片会立即死掉。另外,PLL 输出频率不要超过芯片规格书的最大值(108 MHz)。超频虽然可能跑起来,但长期可靠性无法保证。

3.4 时钟输出与测量

调试时钟,最直接的方法就是把它引出来,用示波器看。GD32V 有一个 CKOUT 功能,可以把内部时钟输出到某个 GPIO 引脚。

3.4.1 配置 CKOUT

CKOUT 的配置,主要涉及两个寄存器:RCU->CFG1 和 AFIO 的端口重映射。

/* 使能 CKOUT 时钟输出,选择输出源为 PLL 的 2 分频 */
RCU->CFG1 &= ~RCU_CFG1_CKOUTSEL;
RCU->CFG1 |= RCU_CFG1_CKOUTSEL_PLL_DIV2;

/* 配置 CKOUT 分频系数,比如不分频 */
RCU->CFG1 &= ~RCU_CFG1_CKOUTDIV;
RCU->CFG1 |= RCU_CFG1_CKOUTDIV_0;

/* 配置 GPIO 为复用功能,输出 CKOUT 信号 */
/* 具体引脚请参考数据手册,通常是 PA8 */

3.4.2 用示波器测量

把示波器探头接到对应的 GPIO 引脚上,就能看到时钟波形了。我一般会测三个东西:

  • 频率: 是不是你配置的值?比如 54 MHz?
  • 占空比: 是不是接近 50%?如果偏差太大,说明 PLL 配置可能有问题。
  • 抖动: 波形边缘是不是干净?如果有毛刺,可能是电源噪声或者 PCB 布局问题。
💡 避坑指南: 我曾经在测量时,发现 CKOUT 输出的频率只有配置值的一半。查了半天,发现是 GPIO 的 slew rate 设置太慢,导致波形上升沿变缓,示波器触发了错误的边沿。把 GPIO 速度调到最高,问题就解决了。

另外,CKOUT 输出会消耗额外的功耗。在低功耗产品中,调试完成后,记得关闭 CKOUT 功能,或者把 GPIO 切回普通模式。

好了,时钟系统这部分,核心就是这些。记住:先理解时钟树,再配置时钟源,然后设置 PLL,最后用 CKOUT 验证。按这个流程走,基本不会出大问题。