STM32F429入门(十三):RCC时钟树

RCC:reset clock control 复位和时钟控制器。我们用的比较多的是时钟控制器。

一、RCC的主要作用是时钟部分

设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少)、设置 APB2 分 频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各 个外设的分频因子;控制 AHB、APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。

打开汇编文件,找到此语句=SystemInit->void SystemInit(void)->SetSysClock();这个函数就是配置各种分频因子的地方。该函数的功能是利用 HSE 把时钟设置为:HCLK = SYSCLK=PLLCLK = 180M,PCLK1=HCLK/2 = 90M,PCLK1=HCLK/4 = 45M 。我们打开《STM32F4XX中文参考手册》,找到时钟树。

我们可以把时钟分为:系统时钟其他时钟

二、HSE 高速外部时钟信号

 

  • HSE : High Speed External Clock signal,即高速的外部时钟。

  • 来源:有源晶振(1-50M):当使用有源晶振时,时钟从 OSC_IN 引脚进入,OSC_OUT 引脚悬空。无源晶振(4-26M):当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT 进入,并且要配谐振电容。HSE 我们使用 25M 的无源晶振。

  • 控制:RCC_CR时钟控制寄存器的位16:HSEON控制。

当HSE故障时,我们会关闭HSE,此时高速的内部时钟信号HSI会作为备用的系统时钟,直到HSE恢复正常,HSI = 16M。

  • HSI:High Speed Internal Clock signal,即高速的内部时钟。

  • 来源:芯片内部,大小为16M,当HSE故障时,系统时钟会自动切换到HSI,直到HSE启动成功。

  • 控制:RCC_CR时钟控制寄存器的位0:HSION控制。

三、锁相环时钟PLL

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

 

  • 锁相环时钟.PLLCLK

  • 来源:HSI、HSE。由PLLSRC位配置。

  • HSE或HSI先经过一个分频因子M进行分频,然后再经过一个倍频因子N,然后再经过一个分频因子P,最后成为锁相环时钟:PLLCLK = (HSE/M)*N/P=25 / 25 / *360 / 2 = 180M。具体过程:

    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 这里做手脚。P

    LLCLK_OUTMAX = VCOCLK_OUTMAX/P_MIN = 432/2=216M,

    即 F429 最高可超频到 216M。而VCO的参数限制可以在手册中找到:

 

  • 控制:RCC_PLLCFGR:RCC PLL配置寄存器

  • 分频因子Q:PLL48CK:USB_FS、RANG、SDIO提供时钟。取值可以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。

  • 分频因子R:F429没有。

  • PLL的时钟配置经过,可以由如下公式表达:

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时要重新配置

四、系统时钟 SYSCLK

  • 最高为180M。

  • 来源:HSI、HSE、PLLCLK

  • 控制:RCC_CFGR时钟配置寄存器的SW位,常用的是使用锁相环作为系统时钟。如果系统时钟是由 HSE 经过 PLL 倍频之后的 PLLCLK 得到,当 HSE 出现故障的时候,系统时钟会切换为 HSI=16M,直到 HSE 恢复正常为止。

 

五、HCLK时钟

  • HCLK:AHB高速总线时钟,最高为180M。为AHB总线的外设提供时钟、为Cortex系统定时器提供时钟(SysTick)、为内核提供时钟(FCLK)=180M,也就是CPU时钟。

  • AHB为advance high-performance bus。

  • 来源:系统时钟分频得到。

  • 控制:RCC_CFGR时钟配置寄存器的HPRE位。

 

六、PCLK1时钟

  • PCLK1:APB低速总线时钟,最高为45M。为APB1总线的外设提供时钟。2倍频之后则为APB1总线的定时器提供时钟,最大为90M。

  • 来源:HCLK分频得到

  • 控制:RCC_CFGR时钟配置寄存器的PPRE1位。

七、PCLK2时钟

  • PCLK2:APB高速总线时钟,最高为90M。为APB2总线的外设提供时钟。2倍频之后则为APB2总线的定时器提供时钟,最大为180M。

  • 来源:HCLK分频得到。

  • 控制:RCC_CFGR时钟配置寄存器的PPRE2位。

八、其他时钟

(一)RTC时钟

  • RTC:为芯片内部的RTC提供时钟。

  • 来源:HSE_RTC(HSE分频得到)、LSE(外部32.768khz的晶提供)、LSI(32khz)。

  • 控制:RCC备份域控制寄存器RCC_BDCR:RTCSEL位控制。

  • 独立看门狗时钟:IWDGCLK,由LSI提供。

  • LSE 由外接的晶体谐振器产生,所配的谐振电容精度要求高,不然很容易不起震。

 (二)I2S时钟

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

(三)PHY 以太网时钟

        429没有集成PHY,只能外接PHY芯片, 比如LAN8720,那PHY时钟就由外部的PHY芯片提供,大小为50M。使用 RMII 接口的好处是使用的 IO 减少了一半,速度还是跟 MII 接口一样。当 使用 RMII 接口时,PHY 芯片只需输出一路时钟给 MCU 即可,如果是 MII 接口,PHY 芯 片则需要提供两路时钟给 MCU。

(四)USB PHY时钟

  429的USB没有集成PHY,要想实现USB高 速传输,只能外接PHY芯片,比如USB33000。那USB PHY时 钟就由外部的PHY芯片提供。

(五)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 位选择。

 

那么了解了以上的时钟树,我们使用HSE来配置系统时钟吧。

我们将函数SetSysClock();拷贝至我们新建的文件中。之后删除不必要的东西。我们芯片采用的是无源晶振,所以我们将有源晶振部分去掉。

也就是#if defined(USE_HSE_BYPASS) 下面的那一块代码。

之后将其他芯片的配置删除掉,留下与F429相关的。最后再删除相应的宏,代码量将会减少很多。

最后的代码解析如下:

void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
/*-------------------------1-使能HSE,并等待HSE稳定---------------------------*/
  /* 使能HSE */
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待HSE初始化成功,当他初始化成功时,该引脚会变为1,如果超时则跳出*/
  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;	//启动不成功
  }

	//启动成功
  if (HSEStatus == (uint32_t)0x01)
  {
/*---------------------------------------------------------------------*/
    /* 选择电压调节器,选择输出模式1  */
		/* 使能电源接口时钟*/
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
		/*控制内部主调压器的输出电压,以便在器件末以最大频率工作时使能与功耗实现平衡*/
		/*模式1*/
    PWR->CR |= PWR_CR_VOS;
		
/*-----------------2-配置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;
		
/*-----------------3-配置PLL的各种预分频因子并使能PLL-------------------*/

    /* 配置主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)
    {
			
    }
		
/*---------------------------------------------------------------------*/
   
    /* 打开OVER-Drive模式,为的是达到更高的频率,硬件置一*/
    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;

/*----------------------4-选择系统时钟来源--------------------------------*/
    /* 选择主锁相环时钟为系统时钟 */
    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启动失败后所需的代码
  }

}

 实现了对库函数的理解,接下来我们要使用固件库来自己配置一个系统时钟,顺带试一下超频,可以自由的实现频率的大小。之后我们可以使用灯的闪烁情况来看频率对响应的影响。

//m:主PLL和音频PLL输入时钟的分频系数PLLM  2<=PLLM<=63
//n:适用于VCO的主PLL倍频系数 192<=PLLN<=432
//p:适用于主系统时钟的主PLL分频系数PLLP PLLP = 2\4\6\8
//q:主PLL分频系数,适用于USB OTG、SDIO和随机数发生器时钟 2<=PLLQ<=15
//SYSCLK = (HSE/m)*n/p = 25/25*432/2=261M    MaxHSE_SetSysClk(25,432,2,7)
void HSE_SetSysClk(uint32_t m,uint32_t n,uint32_t p,uint32_t q)
{
		__IO uint32_t HSEStartUpStatus = 0;
	
		//使能HSE,并等待启动完毕
		RCC_HSEConfig(RCC_HSE_ON);
	
		HSEStartUpStatus = RCC_WaitForHSEStartUp();
	
		if(HSEStartUpStatus==SUCCESS)
		{
		    /* 选择电压调节器,选择输出模式1  */
				/* 使能电源接口时钟*/
				RCC->APB1ENR |= RCC_APB1ENR_PWREN;
				/*控制内部主调压器的输出电压,以便在器件末以最大频率工作时使能与功耗实现平衡*/
				/*模式1*/
				PWR->CR |= PWR_CR_VOS;
			
				RCC_HCLKConfig(RCC_SYSCLK_Div1);
				RCC_PCLK2Config(RCC_HCLK_Div2);
				RCC_PCLK1Config(RCC_HCLK_Div4);
			
				RCC_PLLConfig(RCC_PLLSource_HSE,m, n, p, q);
			
				//打开主锁相环
				RCC_PLLCmd(ENABLE);
				
				//等待打开
				while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
				{
				}
				
				/* 配置Flash 预取指、指令缓存、数据缓存 和 等待周期 */
				FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
				//配置系统时钟
				RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
				while((RCC_GetSYSCLKSource())!=0X08)
				{
				}
				
		}
		else
		{
				//HSE启动失败,用户在这里添加纠错代码
		}
		
}

我们也可以调用MCO来监控我们的频率,可以使用示波器在外部接上杜邦线测:

// 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);
}

最后观察的函数如下,我们可以发现,灯的闪烁频率比180M的快一些:

void LED_GPIO_Config(void);

int main(void)
{
		HSE_SetSysClk(25,432,2,7);
	
		LED_GPIO_Config();
	  
    	MCO1_GPIO_Config(void);
	
    while(1)
		{
			LED1(ON);
			Delay(0x0FFFFF);
			LED1(OFF);
			Delay(0x0FFFFF);
		}
}

学会了HSE后,我们学学如何使用HSI,HSI与HSE十分相似,我们可以在HSE的代码的基础上修改。

HSI 为 16M,参数 m 我们一般也设置为 16,所以我们需要修改系统时钟的时候只需要 修改参数 n 和 p 即可,SYSCLK=PLLCLK=HSI/m*n/p

函数调用举例,使用 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_SetSysClk(uint32_t m,uint32_t n,uint32_t p,uint32_t q)
{
		__IO uint32_t HSItartUpStatus = 0;
	
		//把RCC外设初始化为复位状态
		RCC_DeInit();
	
		//使能HSI,并等待稳定
		RCC_HSICmd(ENABLE);
	
		HSItartUpStatus = RCC->CR & RCC_CR_HSIRDY;
	
		if(HSItartUpStatus==RCC_CR_HSIRDY)
		{
		    /* 选择电压调节器,选择输出模式1  */
				/* 使能电源接口时钟*/
				RCC->APB1ENR |= RCC_APB1ENR_PWREN;
				/*控制内部主调压器的输出电压,以便在器件末以最大频率工作时使能与功耗实现平衡*/
				/*模式1*/
				PWR->CR |= PWR_CR_VOS;
			
				RCC_HCLKConfig(RCC_SYSCLK_Div1);
				RCC_PCLK2Config(RCC_HCLK_Div2);
				RCC_PCLK1Config(RCC_HCLK_Div4);
			
				RCC_PLLConfig(RCC_PLLSource_HSI,m, n, p, q);
			
				//打开主锁相环
				RCC_PLLCmd(ENABLE);
				
				//等待打开
				while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
				{
				}
				
				/* 配置Flash 预取指、指令缓存、数据缓存 和 等待周期 */
				FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
				//配置系统时钟
				RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
				while((RCC_GetSYSCLKSource())!=0X08)
				{
				}
				
		}
		else
		{
				//HSE启动失败,用户在这里添加纠错代码
		}
		
}

 那么RCC就学到这里吧!明天开始学习新的东西吧!

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
stm32f407的GPIO配置可以通过以下代码实现: ```c #include "stm32f4xx.h" void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 开启GPIO时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE); // 配置GPIO引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_x; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_x; GPIO_InitStructure.GPIO_OType = GPIO_OType_x; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_x; GPIO_Init(GPIOx, &GPIO_InitStructure); } ``` 其中,`GPIOx`代表具体的GPIO端口,比如`GPIOA`、`GPIOB`等;`GPIO_Pin_x`代表具体的引脚号,比如`GPIO_Pin_0`、`GPIO_Pin_1`等;`GPIO_Mode_x`代表引脚的工作模式,比如输入模式`GPIO_Mode_IN`、输出模式`GPIO_Mode_OUT`等;`GPIO_Speed_x`代表引脚的速度,比如`GPIO_Speed_2MHz`、`GPIO_Speed_50MHz`等;`GPIO_OType_x`代表引脚的输出类型,比如推挽输出`GPIO_OType_PP`、开漏输出`GPIO_OType_OD`等;`GPIO_PuPd_x`代表引脚的上下拉电阻,比如上拉`GPIO_PuPd_UP`、下拉`GPIO_PuPd_DOWN`等。 通过调用`GPIO_Config`函数,可以实现对具体GPIO端口和引脚的配置。 #### 引用[.reference_title] - *1* *2* [STM32F407入门学习(1)---GPIO配置](https://blog.csdn.net/shadowfiend10086/article/details/53229724)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [STM32F407之GPIO](https://blog.csdn.net/minyuanxiani/article/details/21409463)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值