3. G4时钟系统:HSI/HSE/PLL配置、时钟树分析、SysTick定时器

时钟,说白了就是MCU的心脏。没有它,芯片就是一坨硅。我刚开始做数字电源那会儿,总觉得时钟配置嘛,无非就是改几个寄存器,能有多难?直到有一次,我做的图腾柱PFC在满载测试时突然炸管,查了三天才发现是时钟抖动导致PWM相位错位。嗯,从那以后,我再也不敢小看时钟树了。

今天咱们就好好捋一捋STM32G4的时钟系统。G4系列在数字电源领域这么火,很大程度上得益于它那颗灵活又精准的“心脏”。

3.1 时钟源概览:你有哪些选择?

G4的时钟源,说白了就四种:

  • HSI16:内部16MHz高速振荡器。上电默认就用它,精度一般,胜在即开即用。
  • HSE:外部高速晶振,通常接4-26MHz的晶振。精度高,适合对频率敏感的应用。
  • PLL:锁相环,可以把低频时钟倍频到高频。G4的PLL最高能跑到170MHz。
  • LSI/LSE:低速时钟,主要给RTC和独立看门狗用,咱们做电源主控一般不太关心。
我的习惯: 做数字电源原型时,我通常先用HSI16跑起来,等基本功能调通了,再切到HSE+PLL。这样能避免一开始就被晶振起振问题卡住。

3.2 时钟树分析:信号是怎么流到你手里的?

时钟树,你可以想象成城市的水管网络。水源(时钟源)经过泵站(PLL)增压,再通过阀门(分频器)分配到各个水龙头(外设)。

G4的时钟树有几个关键节点:

  • SYSCLK:系统时钟,CPU和AHB总线的主时钟。最高170MHz。
  • HCLK:AHB总线时钟,由SYSCLK分频得到。给Flash、DMA、GPIO这些高速外设用。
  • PCLK1/PCLK2:APB外设时钟。PCLK1最高170MHz,PCLK2最高170MHz。注意,定时器的时钟可能比PCLK更高,因为有个倍频器。
  • ADC时钟:G4的ADC很特殊,它有自己的独立时钟源,最高60MHz。这一点在做电源采样时特别重要。

为什么会强调这个?我在做一款LLC谐振变换器时,发现ADC采样值总是跳变。查到最后,发现是ADC时钟配置过高,超过了60MHz的限制。你想想看,采样值不准,环路控制能稳吗?

3.3 PLL配置:把频率“拧”上去

PLL的配置公式其实很简单:

VCO频率 = 输入时钟 / M * N
系统时钟 = VCO频率 / P / R

其中M、N、P、R都是分频/倍频系数。G4的PLL有多个输出分支,可以同时产生不同频率的时钟。

我常用的配置套路是这样的:

  1. 先用HSE 8MHz作为输入源
  2. M设为2,得到4MHz的PLL输入
  3. N设为85,VCO频率 = 4MHz * 85 = 340MHz
  4. P设为2,得到170MHz的系统时钟
  5. R设为2,给ADC提供170MHz/2 = 85MHz?不对,ADC最高60MHz,所以R要再分频。
我曾经踩过的坑: 有一次我直接把PLL的N设到了最大值,VCO频率飙到了500MHz以上。结果芯片发热严重,程序跑飞。后来查手册才发现,VCO频率范围是64-344MHz。超出这个范围,PLL输出就不稳定了。

3.4 SysTick定时器:系统的心跳

SysTick,说白了就是一个24位的递减计数器。它挂在系统时钟上,每来一个时钟脉冲就减1,减到0就触发中断。

在数字电源里,SysTick的用途很广:

  • 任务调度:比如每1ms执行一次慢速环路,每100us执行一次快速保护。
  • 软件延时:虽然我不推荐在电源控制中用软件延时,但调试时偶尔用用还行。
  • 时间基准:给RTOS提供心跳,或者给状态机提供计时。

配置SysTick的代码很简单:

// 配置SysTick为1ms中断
// 假设系统时钟为170MHz
// 重装载值 = 170000000 / 1000 = 170000
SysTick_Config(170000);

注意,SysTick的时钟源可以选择系统时钟或者系统时钟的8分频。我建议用系统时钟直接驱动,这样精度更高。但如果你需要低功耗模式,可以考虑用8分频。

3.5 实战配置:从HSI到PLL的完整切换

下面我给出一段完整的时钟初始化代码。这段代码我在多个项目中验证过,你可以直接拿来用:

void SystemClock_Config(void)
{
    // 1. 开启HSE
    RCC->CR |= RCC_CR_HSEON;
    while(!(RCC->CR & RCC_CR_HSERDY));
    
    // 2. 配置PLL
    RCC->PLLCFGR = 0;
    RCC->PLLCFGR |= (2 << RCC_PLLCFGR_PLLM_Pos);   // M=2
    RCC->PLLCFGR |= (85 << RCC_PLLCFGR_PLLN_Pos);  // N=85
    RCC->PLLCFGR |= (1 << RCC_PLLCFGR_PLLP_Pos);   // P=2
    RCC->PLLCFGR |= (2 << RCC_PLLCFGR_PLLR_Pos);   // R=2
    RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE;         // 时钟源为HSE
    
    // 3. 开启PLL
    RCC->CR |= RCC_CR_PLLON;
    while(!(RCC->CR & RCC_CR_PLLRDY));
    
    // 4. 配置Flash等待周期
    // 170MHz需要5个等待周期
    FLASH->ACR = FLASH_ACR_LATENCY_5WS;
    
    // 5. 切换系统时钟到PLL
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
    
    // 6. 配置AHB/APB分频
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;   // HCLK = SYSCLK
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;  // PCLK1 = HCLK
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;  // PCLK2 = HCLK
    
    // 7. 配置SysTick
    SysTick_Config(SystemCoreClock / 1000);
}
关键点: 切换时钟源之前,一定要先确保目标时钟源已经稳定就绪。我见过有人直接写SW位,结果PLL还没准备好,系统直接卡死。

3.6 避坑指南:时钟配置的常见问题

做数字电源这么多年,我总结了几条时钟相关的血泪教训:

  • Flash等待周期:系统频率越高,Flash读取越慢。170MHz下必须配5个等待周期,否则程序会随机崩溃。
  • ADC时钟独立:G4的ADC有自己的时钟树,别跟系统时钟混为一谈。ADC时钟超过60MHz,采样精度会急剧下降。
  • PLL锁定时间:PLL从开启到稳定需要几十微秒。如果你在中断里切换时钟源,一定要考虑这个延时。
  • 时钟输出监测:G4的MCO引脚可以把内部时钟输出到示波器。我调试时经常用这个功能来确认时钟频率是否准确。

嗯,时钟系统这块儿就先聊到这儿。下一章咱们会深入PWM定时器的配置,那才是数字电源控制的核心。到时候你会发现,时钟配置的每一个细节,都会直接影响PWM的精度和稳定性。