秉火429笔记之八 RCC时钟

目录

1. RCC 作用概述

2. RCC框图剖析—时钟树

3. 编程要点

4. 源码实例


1. RCC 作用概述

RCC :reset clock control 复位和时钟控制器。

设置系统时钟SYSCLK、设置AHB分频因子(决定HCLK等于多少)、设置APB2分频因子(决定PCLK2等于多少)、设置APB1分频因子(决定PCLK1等于多少)、设置各个外设的分频因子;控制AHB、APB2和APB1这三条总线时钟的开启、控制每个外设的时钟的开启。对于SYSCLK、HCLK、PCLK2、PCLK1这四个时钟的配置一般是:HCLK = SYSCLK=PLLCLK = 180M,PCLK1=HCLK/2 = 90M,PCLK1=HCLK/4 = 45M。如果需要使用USB,HCLK=168M为宜。

2. RCC框图剖析—时钟树

秉火429笔记之八 RCC时钟

                                                          数据手册F429 时钟树

  • HSE 高速外部时钟信号

外部时钟信号,可由有源晶振或无源晶振提供,频率范围 4-26MHZ。若使用HSE作为时钟源,当HSE故障时,将会切换到HSI,直到HSE恢复正常,HSI=16MHZ.

  • 锁相环PLL

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

主PLL有两路的时钟输出,第一个输出时钟PLLCLK用于系统时钟,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为360,则VCO的输出时钟等于360M。如果要把系统时钟超频,就得在VCO倍频系数N这里做手脚。PLLCLK_OUTMAX = VCOCLK_OUTMAX/P_MIN = 432/2=216M,即F429最高可超频到216M。

VCO输出时钟之后有三个分频因子:PLLCLK分频因子p,USB OTG FS/RNG/SDIO时钟分频因子Q,分频因子R(F446才有,F429没有)。p可以取值2、4、6、8,我们配置为2,则得到PLLCLK=180M。Q可以取值4~15,但是USB OTG FS必须使用48M,Q=VCO输出时钟360/48=7.5,出现了小数这明显是错误,权衡之策是是重新配置VCO的倍频因子N=336,VCOCLK=1M*336=336M,PLLCLK=VCOCLK/2=168M,USBCLK=336/7=48M,细心的读者应该发现了,在使用USB的时候,PLLCLK被降低到了168M,不能使用180M,这实乃ST的一个奇葩设计。因此,通常对时钟无高速需求的情况下,配置为168M为宜。

  • 系统时钟SYSCLK

系统时钟来源可以是:HSI、PLLCLK、HSE,具体的由时钟配置寄存器RCC_CFGR的SW位配置.

  • AHB总线时钟HCLK

系统时钟SYSCLK经过AHB预分频器分频之后得到时钟叫APB总线时钟,即HCLK,分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器RCC_CFGR的HPRE位设置。

通常情况下,设置为1分频,即HCLK=SYSCLK。

  • APB2总线时钟HCLK2

APB2总线时钟PCLK2由HCLK经过高速APB2预分频器得到,分频因子可以是:[1,2,4,8,16],具体由时钟配置寄存器RCC_CFGR的PPRE2位设置。。HCLK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上.

通常情况下,设置为2分频,即PCLK2 = HCLK /2。

  • APB1总线时钟HCLK1

APB1总线时钟PCLK1由HCLK经过低速APB预分频器得到,分频因子可以是:[1,2,4,8,16],具体由时钟配置寄存器RCC_CFGR的PPRE1位设置。HCLK1属于低速的总线时钟,最高为45M,片上低速的外设就挂载到这条总线上.

通常情况下,设置为4分频,即PCLK1 = HCLK/4。

  • 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由外接的晶体谐振器产生,所配的谐振电容精度要求高,不然很容易不起震。

  • 独立看门狗时钟

独立看门狗时钟由内部的低速时钟LSI提供,大小为32KHZ

  • I2S时钟

I2S时钟可由外部的时钟引脚I2S_CKIN输入,也可由专用的PLLI2SCLK提供,具体的由RCC 时钟配置寄存器 (RCC_CFGR)的I2SSCR位配置。我们在使用I2S外设驱动W8978的时候,使用的时钟是PLLI2SCLK,这样就可以省掉一个有源晶振。

  • ETH PHY以太网时钟

429要想实现以太网功能,除了有本身内置的MAC之外,还需要外接一个PHY芯片,常见的PHY芯片有DP83848和LAN8720,其中DP83848支持MII和RMII接口,LAN8720只支持RMII接口。秉火F429开发板用的是RMII接口,选择的PHY芯片是LAB8720。使用RMII接口的好处是使用的IO减少了一半,速度还是跟MII接口一样。当使用RMII接口时,PHY芯片只需输出一路时钟给MCU即可,如果是MII接口,PHY芯片则需要提供两路时钟给MCU。

  • USB PHY时钟

F429的USB没有集成PHY,要想实现USB高速传输的话,必须外置USB PHY芯片,常用的芯片是USB3300。当外接USB PHY芯片时,PHY芯片需要给MCU提供一个时钟。外扩USB3300会占用非常多的IO,跟SDRAM和RGB888的IO会复用的很厉害。

  • MCO时钟输出

MCO是microcontroller clock output的缩写,是微控制器时钟输出引脚,主要作用是可以对外提供时钟,相当于一个有源晶振。F429中有两个MCO,由PA8/PC9复用所得。MCO1所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO1PRE[2:0] 和 MCO1[1:0]位选择。MCO2所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO2PRE[2:0] 和 MCO2位选择。

秉火429笔记之八 RCC时钟

3. 编程要点

  • 开启HSE/HSI ,并等待 HSE/HSI 稳定
  • 设置 AHB、APB2、APB1的预分频因子
  • 设置PLL的时钟来源,设置VCO输入时钟 分频因子PLL_M,设置VCO输出时钟
  • 倍频因子PLL_N,设置PLLCLK时钟分频因子PLL_P,设置OTG FS,SDIO,RNG
  • 时钟分频因子 PLL_Q
  • 开启PLL,并等待PLL稳定
  • 把PLLCK切换为系统时钟SYSCLK
  • 读取时钟切换状态位,确保PLLCLK被选为系统时钟
  • 官方有快捷配置工具

4. 源码实例

#include "./rcc/bsp_clkconfig.h"
#include "stm32f4xx_rcc.h"

/*
 * 使用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被选为系统时钟
 */

/*
 * m: VCO输入时钟 分频因子,取值2~63
 * n: VCO输出时钟 倍频因子,取值192~432
 * p: PLLCLK时钟分频因子  ,取值2,4,6,8
 * q: OTG FS,SDIO,RNG时钟分频因子,取值4~15
 * 函数调用举例,使用HSE设置时钟
 * SYSCLK=HCLK=180M,PCLK2=HCLK/2=90M,PCLK1=HCLK/4=45M
 * HSE_SetSysClock(25, 360, 2, 7);
 * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法
 
 * 系统时钟超频到216M爽一下
 * HSE_SetSysClock(25, 432, 2, 9);
 */
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)	
{
  __IO uint32_t HSEStartUpStatus = 0;
  
  // 使能HSE,开启外部晶振,秉火F429使用 HSE=25M
  RCC_HSEConfig(RCC_HSE_ON);
	
  // 等待HSE启动稳定
	HSEStartUpStatus = RCC_WaitForHSEStartUp();

  if (HSEStartUpStatus == SUCCESS)
  {
    // 调压器电压输出级别配置为1,以便在器件为最大频率
		// 工作时使性能和功耗实现平衡
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;
		
		// HCLK = SYSCLK / 1
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		
		// PCLK2 = HCLK / 2
		RCC_PCLK2Config(RCC_HCLK_Div2);
		
		// PCLK1 = HCLK / 4
		RCC_PCLK1Config(RCC_HCLK_Div4);
		
    // 如果要超频就得在这里下手啦
		// 设置PLL来源时钟,设置VCO分频因子m,设置VCO倍频因子n,
		//  设置系统时钟分频因子p,设置OTG FS,SDIO,RNG分频因子q
		RCC_PLLConfig(RCC_PLLSource_HSE, m, n, p, q);
		
		// 使能PLL
		RCC_PLLCmd(ENABLE);
  
	  // 等待 PLL稳定
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
    {
    }   

/*-----------------------------------------------------*/
    //开启 OVER-RIDE模式,以能达到更高频率
    PWR->CR |= PWR_CR_ODEN;
    while((PWR->CSR & PWR_CSR_ODRDY) == 0)
    {
    }
    PWR->CR |= PWR_CR_ODSWEN;
    while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
    {
    }      
    // 配置FLASH预取指,指令缓存,数据缓存和等待状态
    FLASH->ACR = FLASH_ACR_PRFTEN 
		            | FLASH_ACR_ICEN 
		            | FLASH_ACR_DCEN 
		            | FLASH_ACR_LATENCY_5WS;
/*-----------------------------------------------------*/
		
		// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
    while (RCC_GetSYSCLKSource() != 0x08)
    {
    }
  }
  else
  { // HSE启动出错处理

    while (1)
    {
    }
  }
}

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

/*
 * m: VCO输入时钟 分频因子,取值2~63
 * n: VCO输出时钟 倍频因子,取值192~432
 * p: PLLCLK时钟分频因子  ,取值2,4,6,8
 * q: OTG FS,SDIO,RNG时钟分频因子,取值4~15
 * 函数调用举例,使用HSI设置时钟
 * SYSCLK=HCLK=180M,PCLK2=HCLK/2=90M,PCLK1=HCLK/4=45M
 * HSI_SetSysClock(16, 360, 2, 7);
 * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法
 
 * 系统时钟超频到216M爽一下
 * HSI_SetSysClock(16, 432, 2, 9);
 */

void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)	
{
  __IO uint32_t HSIStartUpStatus = 0;
	
	// 把RCC外设初始化成复位状态
  RCC_DeInit();
  
  //使能HSI, HSI=16M
	RCC_HSICmd(ENABLE);
	
  // 等待 HSI 就绪
	HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
	
	// 只有 HSI就绪之后则继续往下执行
  if (HSIStartUpStatus == RCC_CR_HSIRDY)
  {
    // 调压器电压输出级别配置为1,以便在器件为最大频率
		// 工作时使性能和功耗实现平衡
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;
		
		// HCLK = SYSCLK / 1
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		
		// PCLK2 = HCLK / 2
		RCC_PCLK2Config(RCC_HCLK_Div2);
		
		// PCLK1 = HCLK / 4
		RCC_PCLK1Config(RCC_HCLK_Div4);
		
    // 如果要超频就得在这里下手啦
		// 设置PLL来源时钟,设置VCO分频因子m,设置VCO倍频因子n,
		//  设置系统时钟分频因子p,设置OTG FS,SDIO,RNG分频因子q
		RCC_PLLConfig(RCC_PLLSource_HSI, m, n, p, q);
		
		// 使能PLL
		RCC_PLLCmd(ENABLE);
  
	  // 等待 PLL稳定
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
    {
    }   

/*-----------------------------------------------------*/
    //开启 OVER-RIDE模式,以能达到更高频率
    PWR->CR |= PWR_CR_ODEN;
    while((PWR->CSR & PWR_CSR_ODRDY) == 0)
    {
    }
    PWR->CR |= PWR_CR_ODSWEN;
    while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
    {
    }      
    // 配置FLASH预取指,指令缓存,数据缓存和等待状态
    FLASH->ACR = FLASH_ACR_PRFTEN 
		            | FLASH_ACR_ICEN 
		            |FLASH_ACR_DCEN 
		            |FLASH_ACR_LATENCY_5WS;
/*-----------------------------------------------------*/
		
		// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
    while (RCC_GetSYSCLKSource() != 0x08)
    {
    }
  }
  else
  { // HSI启动出错处理
    while (1)
    {
    }
  }
}


// MCO1 PA8 GPIO 初始化
void MCO1_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  
  // MCO1 GPIO 配置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure); 
}

// MCO2 PC9 GPIO 初始化
void MCO2_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  
  // MCO2 GPIO 配置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}