内容纲要
2讲 RCC 时钟树讲解、系统时钟配置函数、使用HSE配置系统时钟
AHB 高速总线
APB 低速外设总线
AHB----168M
APB1----42M 四分频
做项目的时候通常使用HSE时钟提供频率
使用有源晶振 HSI精度不高
(PPT错误,Low应该是High)
Q:为什么不在外部只用高频率的晶振?而是用锁相环来修改晶振频率?
S:外部晶振频率越高对器件工艺要求会更高,锁相环经过分频因子M将频率分成1MHZ 然后倍频因子N 倍频为336Mhz,流向AHB经过分频为2 修改时钟为168Mhz
- 图上经过/M分频得到1Mhz 倍频xN 倍,得到VCO时钟 然后分频P 得到168Mhz
- 经过/Q 的时候是将336分成48MHz 用处如下
系统时钟
来源:1. HSI 最高16M 2. HSE最高4-26M 3. 来源于PLLCLK 最高168Mhz ( 对于该芯片来说 )
- HCLK时钟
-
PCLK1时钟
-
PCLK2时钟
-
RTC时钟
一般使用LSE 外部时钟,只要是STM32芯片,LSE外部频率始终是32.768KHZ。LSI不稳定所以用的少。
-
MCO时钟
-
I2C时钟
3讲 时钟配置SetSysClock() 自带的时钟函数学习
- 先理清流程。
#include "bsp_clkconfig.h"
#include "stm32f4xx.h"
#define PLL_M 25
/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
#define PLL_Q 7
#define PLL_N 336
/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P 2
//////////////////////////这个函数是直接从复制的system_stm32f4xx.c文件的 void SystemInit(void); 函数/////////////////////////////
/**
* @brief Configures the System clock source, PLL Multiplier and Divider factors,
* AHB/APBx prescalers and Flash settings
* @Note This function should be called only once the RCC clock configuration
* is reset to the default reset state (done in SystemInit() function).
* @param None
* @retval None
*/
void UserSetSysClock(void)
{
RCC_DeInit(); //这里调用的是stm32f4xx_rcc.c中的函数进行复位
/******************************************************************************/
/* PLL (clocked by HSE) used as System clock source */
/******************************************************************************/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Enable HSE */
/* 使能HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
/* 等待HSE(高速外部时钟) 启动稳定,如果超时则退出 */
do
{
/* 1字节=8Byte */
//也就是Linux32位下int 大小4字节占用 32bit 16进制每2位代表8bit(1字节)
StartUpCounter++;
//下方代码, RCC_CR_HSERDY 这个标志位是只读标志位 RCC->CR 代表调用这一块寄存器,将RCC_CR_HSERDY开启,
HSEStatus = RCC->CR & RCC_CR_HSERDY; //#define RCC_CR_HSERDY ((uint32_t)0x00020000) 10 0000 0000 0000 0000
//
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); //当 启动失败 或者 定时器没有超时就继续向上循环。
/* 反过来启动成功 或者 定时器超时 就跳出循环) */
//RESET的定义 typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
if ((RCC->CR & RCC_CR_HSERDY) != RESET) //这部分代码就是等待HSE 启动稳定,如果超时就退出
{
HSEStatus = (uint32_t)0x01; //单纯将状态记录下来,在下方代码会有体现对状态的修改。
}
else
{
HSEStatus = (uint32_t)0x00;
}
// 如果HSE 启动稳定,
if (HSEStatus == (uint32_t)0x01)
{
/************************* 与电源有关的外设 ************************/
/* Select regulator voltage output Scale 1 mode */
/* 选择电压调节器的模式为1 */
RCC->APB1ENR |= RCC_APB1ENR_PWREN; //将外设时钟寄使能寄存器置1
PWR->CR |= PWR_CR_VOS;
/* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; //配置 AHB预分频器 (AHB prescaler ) 让寄存器知道它分频了多少 单词prescaler(预分频器)
/*!< RCC clock configuration register, */
/* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //divice 分,除法。
/* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; //配置 APB1 低速预分频器 (APB Low Speed prescaler)
/* Configure the main PLL */
/****** 配置锁相环PLL 的 PQR M 分频因子,配置 N倍频因子 *****/
//这里的晶振采用的 25MHz,如果采用别的晶振,这里的PLL_M 可以改为别的,比如16MHz 就改为16
//分频因子M必须得到1Mhz 的时钟
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
/****** 配置锁相环PLL 的 PQR M 分频因子,配置 N倍频因子 *****/
/* Enable the main PLL */
/*使能主PLL*/
RCC->CR |= RCC_CR_PLLON;
/* Wait till the main PLL is ready */
/* 等待主PLL稳定**/
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
/* 配置FLSH预取址,指令缓存,数据缓存,等待周期 */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
/* Select the main PLL as system clock source */
/* 选择主PLL时钟作为系统时钟 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* Wait till the main PLL is used as system clock source */
/* 确保主PLL 选为系统时钟 */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
/* HSE启动失败,则在这里添加启动失败的处理代码 */
}
}
Summary:
在系统默认初始化RCC 时钟之后,如果你想使用自己的时钟,你需要复位时钟,然后再调用你的时钟。
4讲 使用HSE配置系统时钟并使用MCO输出监控系统时钟
- 使用HSE配置系统时钟
/********************************* HSE操作固件库进行编程 ***********************************************/
void HSE_SetSysClock(uint32_t PLLM, uint32_t PLLN, uint32_t PLLP, uint32_t PLLQ)
{
ErrorStatus HSE_ConfigStatus = ERROR;
RCC_DeInit();
/* Enable HSE */
/* 使能HSE */
//RCC->CR |= ((uint32_t)RCC_CR_HSEON);
//1.调用函数库实现HSE使能
RCC_HSEConfig(RCC_HSE_ON);
//2.等待时钟初始化完毕
HSE_ConfigStatus = RCC_WaitForHSEStartUp(); //这个函数执行的时候会自动等待
if( HSE_ConfigStatus == SUCCESS)
{
//3.开启APB1 的电源使能 //ask? 为什么不开启AHB1的呢? 不过AHB1在函数调用的时候会开。
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS; //VOS[1:0] bits (Regulator voltage scaling output selection)
//4.配置预分频器
RCC_HCLKConfig(RCC_SYSCLK_Div1); //AHB高速总线直接全频
RCC_PCLK1Config(RCC_HCLK_Div4); //APB1四分频
RCC_PCLK2Config(RCC_HCLK_Div2); //APB2二分频
//5.配置锁相环
RCC_PLLConfig(RCC_PLLSource_HSE,PLLM,PLLN, PLLP,PLLQ);
//6.使能主PLL
RCC_PLLCmd(ENABLE);
//7.等待主PLL 稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
// /* 配置FLSH预取址,指令缓存,数据缓存,等待周期 */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
//8.配置PLL 为系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//8.验证时钟是否为系统时钟。
while( RCC_GetSYSCLKSource() != 0x08 )
{
/* 循环体里面表示函数没配置成功 */
}
}else
{
//如果HSE启动失败,
}
}
/********************************* HSE操作固件库进行编程 END ***********************************************/
- 用MCO输出监控系统时钟
/********************************- 用MCO输出监控系统时钟 ****************************************************/
void OutPutMCO1()
{
//假定使用的是HSE 时钟来提供频率
//1.配置GPIOA 的pin8引脚, MCO1引脚 PA8 MCO 输出时钟不超过100MHz 最大I/O速度
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitTypeDef initGPIO;
initGPIO.GPIO_Pin = GPIO_Pin_8;
initGPIO.GPIO_Mode = GPIO_Mode_AF; //这里选择的是复用模式。
initGPIO.GPIO_Speed = GPIO_High_Speed;
initGPIO.GPIO_OType = GPIO_OType_PP;
initGPIO.GPIO_PuPd = GPIO_PuPd_NOPULL; //输出端口既不上拉也不下拉
GPIO_TypeDef typeGPIOA;
typeGPIOA.ODR = GPIO_Pin_8;
GPIO_Init(GPIOA, &initGPIO);
//配置MCO 输出 选择MCO 输出的源端口信号。
RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_1);
}
void OutPutMCO2()
{
//假定使用的是HSE 时钟来提供频率
//1.配置GPIOA 的pin9引脚, MCO2引脚 PC9 MCO 输出时钟不超过100MHz 最大I/O速度
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitTypeDef initGPIO;
initGPIO.GPIO_Pin = GPIO_Pin_9;
initGPIO.GPIO_Mode = GPIO_Mode_AF; //这里选择的是复用模式。
initGPIO.GPIO_Speed = GPIO_High_Speed;
initGPIO.GPIO_OType = GPIO_OType_PP;
initGPIO.GPIO_PuPd = GPIO_PuPd_NOPULL; //输出端口既不上拉也不下拉
GPIO_TypeDef typeGPIOC;
typeGPIOC.ODR = GPIO_Pin_9;
GPIO_Init(GPIOC, &initGPIO);
//配置MCO 输出 选择MCO 输出的源端口信号。
RCC_MCO2Config(RCC_MCO2Source_SYSCLK, RCC_MCO2Div_1); //MCO2与MCO1有一个区别在于这里可以输出系统时钟,而MCO1没有这个选项
}
/********************************- 用MCO输出监控系统时钟 END ****************************************************/
5讲 使用HSE配置系统时钟
void HSI_SetSysClock(void)
{
volatile ErrorStatus HSI_ConfigStatus = ERROR;
RCC_DeInit();
//uint32_t timeOutCount = 0;
//1.调用函数库实现HSI使能
//RCC_HSIConfig(RCC_HSI_ON);
RCC_HSICmd(ENABLE);
//2.等待时钟初始化完毕
/*由于HSI 没有等待函数,所以需要自己写一个*/
//timeOutCount++;
HSI_ConfigStatus = RCC->CR & 0x02; //RCC->CR 和0x02相与,如果CR的 HSIRDY(默认0) 位为1就代表HSI启动成功
if( HSI_ConfigStatus != ERROR)
{
//3.开启APB1 的电源使能 //ask? 为什么不开启AHB1的呢? 不过AHB1在函数调用的时候会开。
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS; //VOS[1:0] bits (Regulator voltage scaling output selection)
//4.配置预分频器
RCC_HCLKConfig(RCC_SYSCLK_Div1); //AHB高速总线直接全频
RCC_PCLK1Config(RCC_HCLK_Div4); //APB1四分频
RCC_PCLK2Config(RCC_HCLK_Div2); //APB2二分频
//5.配置锁相环
RCC_PLLConfig(RCC_PLLSource_HSI,16,336, 2,7);
//6.使能主PLL
RCC_PLLCmd(ENABLE);
//7.等待主PLL 稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
// /* 配置FLSH预取址,指令缓存,数据缓存,等待周期 */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
//8.配置PLL 为系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//8.验证时钟是否为系统时钟。
while( RCC_GetSYSCLKSource() != 0x08 )
{
/* 循环体里面表示函数没配置成功 */
}
}else
{
//如果HSI启动失败,
}
}