STM32F4的系统时钟

1、 RCC 主要作用

  设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少) 、设置 APB1 分频因子(决定 PCLK1 等于多少)、 设置 APB2 分频因子(决定 PCLK2 等于多少)、设置各个外设的分频因子;

  控制 AHB、 APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。

  对于 SYSCLK、 HCLK、 PCLK1、 PCLK2 这四个时钟的配置一般是:HCLK =SYSCLK= PLLCLK = 168M, PCLK2=HCLK/2 = 84M, PCLK1=HCLK/4 = 42M(STM32F429对应的则是HCLK =SYSCLK= PLLCLK = 180M, PCLK2=HCLK/2 = 90M, PCLK1=HCLK/4 = 45M)。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。

2、RCC 框图剖析—时钟树

  时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容易,而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数:SetSysClock(); 以这个函数的编写流程来讲解时钟树,这个函数也是我们用库的时候默认的系统时钟设置函数。该函数的功能是利用 HSE 把时钟设置为: HCLK = SYSCLK=PLLCLK= 168M, PCLK2=HCLK/2 = 84M, PCLK1=HCLK/4 = 42M (STM32F429对应的则是HCLK =SYSCLK= PLLCLK = 180M, PCLK2=HCLK/2 = 90M, PCLK1=HCLK/4 = 45M)下面我们就以这个代码的流程为主线, 来分析时钟树,对应的是图中的黄色部分,代码流程在时钟树中以数字的大小顺序标识。

 

  在STM32中,有五个时钟源,为HSE、HSI、LSI、LSE、PLL。

  ①、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz。
  ②、HSI是高速内部时钟,RC振荡器,频率为16MHz。
  ③、LSI是低速内部时钟,RC振荡器,频率为32kHz。
  ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
  ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSE、HSE/2、HSI/2。倍频可选择为2~16倍,但是其输出频率最大不得超过168MHz。

  (1)HSE 高速外部时钟信号

  HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4-26MHZ不等。当使用有源晶振时,时钟从 OSC_IN 引脚进入, OSC_OUT 引脚悬空当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT 进入,并且要配谐振电容

  HSE 我们使用 25M 的无源晶振。 如果我们使用 HSE 或者 HSE 经过 PLL 倍频之后的时钟作为系统时钟 SYSCLK,当 HSE 故障时候,不仅 HSE 会被关闭, PLL 也会被关闭,此时高速的内部时钟时钟信号HSI 会作为备用的系统时钟,直到 HSE 恢复正常, HSI=16M。

  (2)锁相环 PLL

  PLL 的主要作用是对时钟进行倍频,然后把时钟输出到各个功能部件。 PLL 有两个,一个是主 PLL,另外一个是专用的 PLLI2S,他们均由 HSE 或者 HSI 提供时钟输入信号。

  主 PLL 有两路的时钟输出,第一个输出时钟 PLLCLK 用于系统时钟,  F407 里面最高是 168M,F429 里面最高是 180M第二个输出用于 USB OTG FS 的时钟( 48M)、 RNG 和 SDIO 时钟( <=48M)

  专用的 PLLI2S 用于生成精确时钟,给 I2S 提供时钟。

  HSE 或者 HSI 经过 PLL 时钟输入分频因子 M( 2~63)分频后,成为 VCO 的时钟输入,VCO 的时钟必须在 1~2M 之间, 我们选择 HSE=25M 作为 PLL 的时钟输入, M 设置为 25那么 VCO 输入时钟就等于 1M。VCO 输入时钟经过 VCO 倍频因子 N 倍频之后,成为 VCO 时钟输出, VCO 时钟必须在 192~432M 之间。我们配置 N  F407为336,F429为360,则 VCO 的输出时钟等于 F407为336M,F429为360M。如果要把系统时钟超频,就得在 VCO 倍频系数 N 这里做手脚。 PLLCLK_OUTMAX =VCOCLK_OUTMAX/P_MIN = 432/2=216M,即 F407和F429 最高可超频到 216M。

  VCO 输出时钟之后有三个分频因子: PLLCLK 分频因子 P, USB OTG FS/RNG/SDIO时钟分频因子 Q,分频因子 R( F446 才有, F407和F429 没有)。P可以取值 2、 4、 6、 8,我们配置为 2,则得到 F407的PLLCLK=168M,F429的PLLCLK=180M。 Q 可以取值 4~15,但是 USB OTG FS 必须使用 48M,F407的Q=VCO 输出时钟 336/48=7,F429的Q=VCO 输出时钟 360/48=7.5,出现了小数这明显是错误,权衡之策是是重新配置 VCO 的倍频因子 N=336,VCOCLK=1M*336=336M, PLLCLK=VCOCLK/2=168M, USBCLK=336/7=48M,细心的读者应该发现了,在使用 USB 的时候, F429的PLLCLK 被降低到了 168M,不能使用 180M,这实乃 ST 的一个奇葩设计。有关 PLL 的配置有一个专门的RCC PLL 配置寄存器 RCC_PLLCFGR,具体描述看手册即可。

  PLL 的时钟配置经过,稍微整理下可由如下公式表达:

    F407:

    VCOCLK_IN = PLLCLK_IN / M = HSE / 25 = 1M
    VCOCLK_OUT = VCOCLK_IN * N = 1M * 336 = 336M 
    PLLCLK_OUT=VCOCLK_OUT/P=338/2=168M
    USBCLK = VCOCLK_OUT/Q=336/7=48。

    F429:

    VCOCLK_IN = PLLCLK_IN / M = HSE / 25 = 1M
    VCOCLK_OUT = VCOCLK_IN * N = 1M * 360 = 360M   
    PLLCLK_OUT=VCOCLK_OUT/P=360/2=180M
    USBCLK = VCOCLK_OUT/Q=360/7=51.7。

  暂时这样配置,到真正使用 USB 的时候会重新配置。

  (3)系统时钟 SYSCLK

  系统时钟来源可以是: HSI、 PLLCLK、 HSE,具体的由时钟配置寄存器 RCC_CFGR的 SW 位配置。我们这里设置系统时钟:F407的SYSCLK = PLLCLK = 168M,F429的SYSCLK = PLLCLK = 180M。 如果系统时钟是由HSE 经过 PLL 倍频之后的 PLLCLK 得到,当 HSE 出现故障的时候,系统时钟会切换为HSI=16M,直到 HSE 恢复正常为止。

  (4)AHB 总线时钟 HCLK

  系统时钟 SYSCLK 经过 AHB 预分频器分频之后得到时钟叫 AHB 总线时钟,即 HCLK,分频因子可以是:[1,  2,  4, 8, 16, 64, 128, 256, 512],具体的由时钟配置寄存器RCC_CFGR 的 HPRE 位设置。片上大部分外设的时钟都是经过 HCLK 分频得到,至于 AHB总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB 的时钟即可。 我们这里设置为 1 分频,即 HCLK=SYSCLK=180M。

  (5)APB2 总线时钟 HCLK2

  APB2 总线时钟 PCLK2 由 HCLK 经过高速 APB2 预分频器得到,分频因子可以是:[1,2,4, 8, 16],具体由时钟配置寄存器 RCC_CFGR 的 PPRE2 位设置。 HCLK2 属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的 GPIO、 USART1、 SPI1等。至于 APB2 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB2 的时钟即可。我们这里设置为 2 分频,即 F407的PCLK2 =HCLK /2= 84M,F429的PCLK2 =HCLK /2= 90M。

  (6)APB1 总线时钟 HCLK1

  APB1 总线时钟 PCLK1 由 HCLK 经过低速 APB 预分频器得到,分频因子可以是:[1,2,4,8, 16],具体由时钟配置寄存器 RCC_CFGR 的 PPRE1 位设置。HCLK1 属于低速的总线时钟,最高为 45M,片上低速的外设就挂载到这条总线上,比如USART2/3/4/5、 SPI2/3, I2C1/2 等。至于 APB1 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB1 的时钟即可。我们这里设置为 4 分频,即F407的 PCLK1 = HCLK/4 = 42M,F429的 PCLK1 = HCLK/4 = 45M。

 3、设置系统时钟库函数

   上面的 6 个步骤对应的设置系统时钟库函数具体见代码清单 16-1,为了方便阅读,已经把跟 407 不相关的代码删掉,把英文注释翻译成了中文,并把代码标上了序号,总共 6个步骤。该函数是直接操作寄存器的,有关寄存器部分请参考数据手册的 RCC 的寄存器描述部分。

/**
  * 使用 HSE 时,设置系统时钟的步骤
  * 1、开启 HSE ,并等待 HSE 稳定
  * 2、设置 AHB、 APB2、 APB1 的预分频因子
  * 3、设置 PLL 的时钟来源
  *    设置 VCO 输入时钟 分频因子 m
  *    设置 VCO 输出时钟 倍频因子 n
  *    设置 PLLCLK 时钟分频因子 p
  *    设置 OTG FS,SDIO,RNG 时钟分频因子 q
  * 4、开启 PLL,并等待 PLL 稳定
  * 5、把 PLLCK 切换为系统时钟 SYSCLK
  * 6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
  */

#define PLL_M 25
#define PLL_N 336
#define PLL_P 2
#define PLL_Q 7

// 要超频的话,修改 PLL_N 这个宏即可,取值范围为: 192~432。
void SetSysClock(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // ①使能 HSE
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    // 等待 HSE 启动稳定
    do 
    {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } 
    while ((HSEStatus==0)&&(StartUpCounter!=HSE_STARTUP_TIMEOUT));

    if ((RCC->CR & RCC_CR_HSERDY) != RESET) 
    {
        HSEStatus = (uint32_t)0x01;
    } 
    else 
    {
        HSEStatus = (uint32_t)0x00;
    }

    // HSE 启动成功
    if (HSEStatus == (uint32_t)0x01) 
    {
        // 调压器电压输出级别配置为 1,以便在器件为最大频率
        // 工作时使性能和功耗实现平衡
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;

        // ②设置 AHB/APB2/APB1 的分频因子
        // HCLK = SYSCLK / 1
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
        // PCLK2 = HCLK / 2
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
        // PCLK1 = HCLK / 4
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;

        // ③配置主 PLL 的时钟来源,设置 M,N,P,Q
        // Configure the main PLL
        RCC->PLLCFGR = PLL_M|(PLL_N<<6) | (((PLL_P >> 1) -1) << 16) |(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

        // ④使能主 PLL
        RCC->CR |= RCC_CR_PLLON;

        // 等待 PLL 稳定
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) 
        {
            
        }
        /*----------------------------------------------------*/
        // 配置 FLASH 预取指,指令缓存,数据缓存和等待状态
        FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
        /*---------------------------------------------------*/

        // ⑤选择主 PLLCLK 作为系统时钟源
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;

        // ⑥读取时钟切换状态位,确保 PLLCLK 选为系统时钟
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
        {
            
        }
    } 
    else 
    {
        // HSE 启动出错处理
    }
}

 

/**
  * 使用HSE时,设置系统时钟的步骤
  * 1、开启HSE ,并等待 HSE 稳定
  * 2、设置 AHB、APB2、APB1的预分频因子
  * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
  * 4、开启PLL,并等待PLL稳定
  * 5、把PLLCK切换为系统时钟SYSCLK
  * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
  */

/** m:VCO输入时钟分频因子,取值2~63,VCO 的时钟必须在1~2M之间,我们选择HSE=25M作为PLL的时钟输入,m一般设置为25
  * n:VCO输入时钟倍频因子,取值192~432,VCO输入时钟经过VCO倍频因子N倍频之后,成为VCO时钟输出,VCO 时钟必须在192~432M之间
  * p:SYSCLK时钟分屏分频因子,取值2、4、6、8,一般设置为2,即clk/m*n/2
  * q:OTG  FS、SDIO、RNG、分频因子q,取值2~15, Q 可以取值 4~15,但是USB OTG FS必须使用48M,即vco输出时钟为48*7=336
  * 举例:SYSCLK = HCLK = 180M, PCLK2=HCLK/2=90M, PCLK1=HCLK/4=45M     
  *       HSE_SetSysClock(25,360,2,7);   SYSCLK = (HSE/m) * n/p = 25/25 * 360/2 = 180M   
          HSE_SetSysClock(25,336,2,7);   SYSCLK = (HSE/m) * n/p = 25/25 * 336/2 = 168M     
  *       HSE_SetSysClock(25,432,2,9);   SYSCLK = (HSE/m) * n/p = 25/25 * 432/2 = 216M 
  * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法
  */
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)    
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct;
    RCC_OscInitTypeDef RCC_OscInitStruct;

    /* 使能电源控制时钟 */
    __HAL_RCC_PWR_CLK_ENABLE();

    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /* 启用HSE振荡器并激活以HSE为源的锁相环 */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;//时钟源选择HSE
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = m;
    RCC_OscInitStruct.PLL.PLLN = n;
    RCC_OscInitStruct.PLL.PLLP = p;
    RCC_OscInitStruct.PLL.PLLQ = q;
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        while(1) {};
    }

    /* 选择PLL作为系统时钟源,配置HCLK、PCLK1和pclk2时钟分频器*/
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;//系统时钟选择PLLCLK
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;//AHB选择1分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;  
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
        while(1) 
        {
        
        };
    }

    /* STM32F405x/407x/415x/417x Revision Z devices: prefetch is supported  */
    if (HAL_GetREVID() == 0x1001)
    {
        /* Enable the Flash prefetch */
        __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
    }
}

 

 

4、其他时钟
  A、 RTC 时钟
  RTCCLK 时钟源可以是 HSE 1 MHz( HSE 由一个可编程的预分频器分频)、 LSE 或者 LSI 时钟。选择方式是编程 RCC 备份域控制寄存器 (RCC_BDCR) 中的 RTCSEL[1:0] 位和 RCC 时钟配置寄存器 (RCC_CFGR) 中的 RTCPRE[4:0] 位。所做的选择只能通过复位备份域的方式修改。 我们通常的做法是由 LSE 给 RTC 提供时钟,大小为 32.768KHZ。 LSE由外接的晶体谐振器产生,所配的谐振电容精度要求高,不然很容易不起震。

   B、独立看门狗时钟
  独立看门狗时钟由内部的低速时钟 LSI 提供,大小为 32KHZ。
  C、 I2S 时钟
  I2S 时钟可由外部的时钟引脚 I2S_CKIN 输入,也可由专用的 PLLI2SCLK 提供,具体的由 RCC 时钟配置寄存器 (RCC_CFGR)的 I2SSCR 位配置。我们在使用 I2S 外设驱动W8978 的时候,使用的时钟是 PLLI2SCLK,这样就可以省掉一个有源晶振。
  D、 PHY 以太网时钟
  F407 要想实现以太网功能,除了有本身内置的 MAC 之外,还需要外接一个 PHY 芯片,常见的 PHY 芯片有 DP83848 和 LAN8720,其中 DP83848 支持 MII 和 RMII 接口,LAN8720 只支持 RMII 接口。 野火 F407 开发板用的是 RMII 接口,选择的 PHY 芯片是LAB8720。使用 RMII 接口的好处是使用的 IO 减少了一半,速度还是跟 MII 接口一样。当使用 RMII 接口时, PHY 芯片只需输出一路时钟给 MCU 即可,如果是 MII 接口, PHY 芯片则需要提供两路时钟给 MCU。
  E、 USB PHY 时钟
  F407 的 USB 没有集成 PHY,要想实现 USB 高速传输的话,必须外置 USB PHY 芯片,常用的芯片是 USB3300。当外接 USB PHY 芯片时, PHY 芯片需要给 MCU 提供一个时钟。外扩 USB3300 会占用非常多的 IO,跟 SDRAM 和 RGB888 的 IO 会复用的很厉害,鉴于 USB 高速传输用的比较少, 野火 F407 霸天虎就没有外扩这个芯片。
  F、 MCO 时钟输出
  MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,主要作用是可以对外提供时钟, 相当于一个有源晶振。 F407 中有两个 MCO,由 PA8/PC9 复用所得。MCO1 所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO1PRE[2:0] 和MCO1[1:0]位选择。 MCO2 所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的MCO2PRE[2:0] 和 MCO2 位选择。有关 MCO 的 IO、时钟选择和输出速率的具体信息如下表所示:

5、 配置系统时钟实验
  (1)使用 HSE
  一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。 F407系统时钟最高为 168M,这个是官方推荐的最高的稳定时钟,如果你想铤而走险,也可以超频,超频最高能到 216M。
  如果我们使用库函数编程,当程序来到 main 函数之前,启动文件:startup_stm32f40xxx.s 已经调用 SystemInit()函数把系统时钟初始化成 168MHZ, SystemInit()在库文件: system_stm32f4xx.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。
  (2)使用 HSI
  当 HSE 直接或者间接(HSE 经过 PLL 倍频)的作为系统时钟的时候,如果 HSE 发生故障,不仅 HSE 会被关闭,连 PLL 也会被关闭, 这个时候系统会自动切换 HSI 作为系统时钟,此时 SYSCLK=HSI=16M,如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。
  如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施,使用 HSI, 重新设置系统频率为 168M,让系统恢复正常使用。但这只是权宜之计,并非万全之策,最好的方法还是要采取相应的补救措施并报警,然后修复 HSE。临时使用 HSI 只是为了把损失降低到最小,毕竟 HSI 较于 HSE 精度还是要低点。
  F103 系列中,使用 HSI 最大只能把系统设置为 64M,并不能跟使用 HSE 一样把系统时钟设置为 72M,究其原因是 HSI 在进入 PLL 倍频的时候必须 2 分频,导致 PLL 倍频因子调到最大也只能到 64M,而 HSE 进入 PLL 倍频的时候则不用 2 分频。在 F407 中,无论是使用 HSI 还是 HSE 都可以把系统时钟设置为 168M,因为 HSE 或者 HSI 在进入 PLL 倍频的时候都会被分频为 1M 之后再倍频。还有一种情况是,有些用户不想用 HSE,想用 HSI,但是又不知道怎么用 HSI 来设置系统时钟,因为调用库函数都是使用 HSE, 下面我们给出个使用 HSI 配置系统时钟例子,起个抛砖引玉的作用。

 

 

 

 

 

 

 

 

 

posted @ 2022-06-20 22:50  孤情剑客  阅读(2271)  评论(0)    收藏  举报