3. GD32时钟系统与低功耗模式:时钟树解析与模式配置

做电机控制,时钟就是心脏。心跳乱了,电机就抖。GD32的时钟树其实不复杂,但很多人一上来就被一堆缩写吓住了——HXTAL、LXTAL、PLL、IRC... 别急,我们一个一个捋清楚。

3.1 时钟树整体架构

GD32的时钟源主要有这么几个:

  • HXTAL:外部高速晶振,通常接8MHz或16MHz
  • LXTAL:外部低速晶振,32.768kHz,RTC专用
  • IRC8M:内部8MHz RC振荡器,上电默认就用它
  • IRC40K:内部40kHz RC振荡器,省电场景用
  • PLL:锁相环,把低频倍频到高频

我个人习惯,做FOC控制时主频拉到108MHz或120MHz(看具体型号),所以PLL是必选的。但低功耗场景下,我会切回IRC8M甚至IRC40K。

核心原则:高性能跑PLL,低功耗跑IRC。切换时钟源时,一定要等目标时钟稳定后再切,否则直接死机。

GD32时钟树结构图 HXTAL (8-16MHz) LXTAL (32.768kHz) IRC8M (8MHz) IRC40K (40kHz) PLL (倍频至108/120MHz) CK_SYS 系统主时钟 AHB / APB1 / APB2 注意 切换时钟源前 必须等待 目标时钟稳定

3.2 时钟配置实战

上电后GD32默认跑IRC8M,也就是8MHz。做FOC肯定不够,我们要切到PLL。下面这段代码是我项目里一直在用的模板:

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

    /* 配置PLL:HXTAL 8MHz -> 108MHz */
    RCU_CFG0 &= ~RCU_CFG0_PLLSEL;          // 选择HXTAL作为PLL源
    RCU_CFG0 |= (108 << 18);               // PLL倍频因子

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

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

我的经验:PLL倍频因子不要超过芯片手册上限。我曾经为了跑更高频率把倍频设到极限,结果芯片发热严重,电机控制反而更差。稳定比极限重要。

3.3 低功耗模式详解

做电池供电的电机控制,低功耗是躲不开的。GD32有三种低功耗模式:睡眠、停止、待机。区别在哪?我直接给你列个表:

模式 唤醒方式 内核状态 外设状态 唤醒时间
睡眠模式 任何中断/事件 暂停 全部保持 ~5μs
停止模式 外部中断/RTC 掉电 可配置保持 ~10μs
待机模式 WKUP引脚/RTC复位 完全掉电 全部关闭 ~50μs

你看,睡眠模式最轻量,外设全活着,适合短暂等待。待机模式最彻底,但唤醒也最慢。我一般这么用:

  • 电机运行时:不进入低功耗,全速跑FOC
  • 电机停止等待指令:睡眠模式,有指令就唤醒
  • 长时间待机:停止模式,RTC定时唤醒检测位置
  • 关机状态:待机模式,按键唤醒

3.4 睡眠模式配置

睡眠模式最简单,执行WFI或WFE指令就行。但要注意,进入前要处理好中断优先级,别被无关中断唤醒。

void enter_sleep_mode(void)
{
    /* 关闭不用的外设时钟,减少唤醒后的处理 */
    RCU_APB2EN &= ~RCU_APB2EN_USART0EN;
    RCU_APB1EN &= ~RCU_APB1EN_TIMER1EN;

    /* 进入睡眠模式 */
    __WFI();  // 等待中断唤醒

    /* 唤醒后恢复外设时钟 */
    RCU_APB2EN |= RCU_APB2EN_USART0EN;
    RCU_APB1EN |= RCU_APB1EN_TIMER1EN;
}

踩坑提醒:我曾经在睡眠前没关ADC,结果ADC的连续转换中断一直触发,芯片根本睡不沉。进入低功耗前,一定要检查所有外设的中断使能状态。

3.5 停止模式与待机模式

停止模式比睡眠深一层。内核停了,但SRAM和寄存器内容还在。配置时要注意电压调节器的设置:

void enter_stop_mode(void)
{
    /* 设置电压调节器为低功耗模式 */
    PMU_CTL |= PMU_CTL_LDOLP;

    /* 选择停止模式 */
    PMU_CTL |= PMU_CTL_STBMOD;
    PMU_CTL &= ~PMU_CTL_STBMOD;

    /* 执行WFI进入 */
    __WFI();
}

待机模式就更彻底了。进去后SRAM内容全丢,唤醒相当于复位。所以进待机前,一定要把关键数据存到备份寄存器里:

void enter_standby_mode(void)
{
    /* 保存关键数据到备份寄存器 */
    PMU_BKP_DATA0 = motor_position;
    PMU_BKP_DATA1 = target_speed;

    /* 清除待机标志 */
    PMU_CTL |= PMU_CTL_CLRSTBF;

    /* 使能待机模式 */
    PMU_CTL |= PMU_CTL_STBMOD;

    /* 执行WFI进入 */
    __WFI();
}

3.6 低功耗模式下外设工作状态

这个很多人搞混。我直接给你列清楚:

外设 睡眠模式 停止模式 待机模式
CPU内核 暂停 掉电 掉电
SRAM 保持 保持 丢失
GPIO 保持状态 保持状态 高阻
定时器 运行 停止 停止
ADC 运行 停止 停止
RTC 运行 运行 运行
DMA 运行 停止 停止

关键点:停止模式下,RTC和部分GPIO还能工作。这意味着你可以用RTC定时唤醒,或者用外部按键唤醒。待机模式下只有WKUP引脚和RTC能唤醒。

3.7 低功耗与FOC的配合策略

做低功耗FOC,核心思路是:该跑的时候全力跑,该停的时候彻底停。

我常用的策略是这样的:

  1. 电机匀速运行时:全速108MHz,PLL输出,FOC 20kHz控制频率
  2. 电机减速停止后:切到IRC8M,进入睡眠模式,等待下一个指令
  3. 长时间无操作:切到IRC40K,进入停止模式,RTC每100ms唤醒一次检测位置变化
  4. 电池电量低:保存位置到备份寄存器,进入待机模式

我的习惯:每次切换模式前,我都会先关PWM输出,防止电机在模式切换瞬间产生异常力矩。等模式稳定后再重新使能PWM。

嗯,时钟系统和低功耗模式就这些。记住一句话:时钟是基础,功耗是约束,FOC是目标。三者平衡好了,你的电机控制才能既快又省电。