STM32 时钟树(4-玩转STM32之STM32RCC时钟树)
STM32 时钟树(4-玩转STM32之STM32RCC时钟树)低速外部时钟信号(L.SE)有以下2种电路连接方式。专用PLL(PLLI2S)用于生成精确时钟,在I2S接口可实现高品质音频性能(参考STM32F4手册)由于在PLL使能后主PLL配置参数便不可更改,因此建议先对PLL进行配置,然后使能(选择IHIS振荡器时钟或HSE振荡器时钟作为PLL时钟源,并配置预分频系数M、N、P和Q)。PLLI2S使用与主PLL相同的输入时钟(PLLM[5:O]和PLLSRC位为两个PLL所共用)。但是,PLLI2S具有专门的使能/禁止和预分频系数(N和R)配置位。在PLLI2S使能后,配置参数便不能更改。在进入停机和待机模式后,两个PLL将由硬件禁止,如将HSE振荡器时钟或PLLCLK(由HSE振荡器提供时钟信号)用作系统时钟,则在HSE振荡器发生故障时,两个PLL也将由硬件禁止。RCC PLL配置寄存器(RCC PLLCFGR)和RCC时钟配置寄存器可分别用于
一、时钟系统结构1--简述复位和时钟控制器为stm32f429微控制器提供了内核和片上外设需要的工作时钟,RCC具有高度的时钟选择和配置的灵活性。STM32F429单片机内部时钟系统以时钟树的形式存在。(下图可能不太清楚,树可查看STM32F4xx中文参考手册低107页)
根据上面的图可以知道,用户在运行内核和外设时可选择使用外部晶振、内部振荡器或PLL(锁相环),也可为以太网、USB OTG FS,以及HS、I2S和SDIO等需要特定时钟的外设提供合适的时钟源。
看图这是我说一下,就是跟着箭头走,有节点的说明这两条线是连接的。可以走,大致意思就是这。
RCC可通过多个预分频器配置AHB、高速APB(APB2)和低速APB(APB1)。
AHB的最大允许频率为108M,APB2为90MHz,APB1为45MHz。
HSE有以下两种输入方式:
1、外部晶振/陶瓷谐振器(无源电路)OSC_IN和OSC_OUT引脚上外接的晶振电路和内部谐振电路产生谐振输出信号,作为HSE供系统使用。
如图:
谐振器和负载电容必须尽可能地靠近振荡器的引脚,以尽量减小输出失真和起振稳定时间。负载电容值必须根据所选振荡器的不同做适当调整。负载电容一般选择22~33pF。
HSE电路工作需要先被使能,可通过RCC时钟控制寄存器(RCC_CR)中的HSEON位使能或禁止。
2、外部时钟(有源电路)。
在此模式下,可以旁路内部谐振电路,直接将外部时钟信号输入芯片内部作为HSE使用。通过将RCC时钟控制寄存器中的HSEBYP和HSEON位置1选择此模式。必须使用占空比约为50%的外部时钟信号(方波、正弦波或三角波)来驱动OSCIN引脚,同时OSCOUT引脚应保持为高阻态,一般悬空。HSE时钟输入电路图如图所示。
STM32F4系列微控制器的器件具有两个PLL,PLL内部结构图如图所示。
主PLL由HSE振荡器或HSI振荡器提供时钟信号,并具有两个不同的
输出时钟,
(1)第一个输出用于生成高速系统时钟(180MHz)。
(2)第二个输出用于生成USBOTG FS时钟(48MHz)、随机数发生器时钟(48MHz)和 SDIO时钟(48MHz)。
专用PLL(PLLI2S)用于生成精确时钟,在I2S接口可实现高品质音频性能(参考STM32F4手册)
由于在PLL使能后主PLL配置参数便不可更改,因此建议先对PLL进行配置,然后使能(选择IHIS振荡器时钟或HSE振荡器时钟作为PLL时钟源,并配置预分频系数M、N、P和Q)。
PLLI2S使用与主PLL相同的输入时钟(PLLM[5:O]和PLLSRC位为两个PLL所共用)。但
是,PLLI2S具有专门的使能/禁止和预分频系数(N和R)配置位。在PLLI2S使能后,配置参数便不能更改。
在进入停机和待机模式后,两个PLL将由硬件禁止,如将HSE振荡器时钟或PLLCLK(由
HSE振荡器提供时钟信号)用作系统时钟,则在HSE振荡器发生故障时,两个PLL也将由硬件禁止。RCC PLL配置寄存器(RCC PLLCFGR)和RCC时钟配置寄存器可分别用于配置主PLL和 PLLI2S.
选择HSE振荡器作为主PLL的输入源,PLLCLR-(HSE/AM)×N/P。
低速外部时钟信号(L.SE)有以下2种电路连接方式。
1.外部晶振/陶瓷谐振器(无源电路)LSE晶振是32.768kHz低速外部晶振或陶瓷谐振器,可作为实时时钟外设(RTC)的时钟源来提供时钟/日历或其他定时功能,LSE晶振具有功耗低且精度高的优点。
LSE晶振通过RCC备份域控制寄存器(RCC BDCR)中的LSEON位打开和关闭。通过检测RCC备份域控制寄存器中的LSERDY标志,可以知道L.SE晶振是否稳定。例如,在RCC时钟中断寄存器(RCC_CIR)中使能中断,LSE就可以产生中断请求。
此时内部LSE振荡电路被旁路,必须提供外部时钟源,最高频率不超过lMHz。此模式通过将RCC备份域控制寄存器中的LSEBYP和LSEON位置l进行选择。外部时钟必须使用占空比约为50%的外部时钟信号(方波、正弦波或三角波)来驱动OSC32_IN引脚,
同时OSC32_OUT引脚应保持为高阻态。
总结:
LSIRC振荡器可作为低功耗时钟源在停机和待机模式下保持运行,供独立看门狗IWDG)
和自动唤醒单元(AWU)使用。LSI时钟频率在32kHz左右。
LSIRC振荡器可通过RCC时钟控制和状态寄存器(RCC_CSR)中的LSION位打开或关闭。
RCC时钟控制和状态寄存器中的LSIRDY标志指示低速内部振荡器是否稳定。在启动时,硬件将此位置1后,LSI时钟才可以使用。若在RCC时钟中断寄存器中使能中断,则LSI就绪后可产生中断。
RCC共有两个微控制器时钟输出(MCO)引脚,
1.MCO1用户可通过可配置的预分频器(1~5)向MCO1引脚(PA8)输出4个不同的时钟源:HSI
时钟、LSE时钟、HSE时钟、PLL时钟。用户所需的时钟源通过RCC时钟配置寄存器中的MCO1PRE[2:0]和MCO1[1:0]位选择。
用户可通过可配置的预分频器(1~5)向MCO2引脚(PC9)输出4个不同的时钟源:HSE
时钟、PLL时钟、系统时钟、PLLI2S时钟。
用户所需的时钟源通过RCC时钟配置寄存器中的MCO2PRE[2:0]和MCO2位选择。对于不同的MCO引脚,必须将相应的GPIO端口在复用功能模式下进行设置。MCO输出时钟频率不得超过100MHz。
总结:
在微控制器中的使用过程中,一般使用PLLCLK作为系统时钟,并选择HSE振荡器时钟作为PLL的输入参考时钟。
1--PLL时钟系统配置步骤以使用HSE振荡器时钟作为PLL时钟源为例,PLL时钟系统的配置步骤如下。
(1)开启HSE振荡器,并等待HSE振荡器稳定。
如果当前微控制器正在使用PLLCLK作为系统时钟,则需要将系统时钟源切换到HSE振荡器时钟(或其他),然后关闭PLL;否则,在PLL运行期间,PLL不能配置成功。
(2)设置AHB、APB2、APB1的预分频系数。
(3)设置PLL的参数。
设置VCO输入时钟预分频系数M。
设置VCO输出时钟倍频系数N。
设置PLLCLK时钟预分频系数P。
设置OTG FS、SDIO、RNG时钟预分频系数Q。
PLLCLK输出频率计算公式:SYSCLK=(HSE×NMD/P。
(4)开启PLL,并等待PLL稳定。
(5)将PLLCLK切换为系统时钟。
(6)读取时钟切换状态位,确保PLLCLK被选为系统时钟。
在此过程涉及的寄存器有:RCC时钟控制寄存器[步骤(1)、(4)]、RCC PLL配置寄存器
[步骤(3)]、RCC时钟配置寄存器[步骤(2)、(5)、(6)]。
与RCC相关的函数和宏都被定义在以下两个文件中。
头文件:stm32f4xx_rcc.h。
源文件:stm32f4xx_rcc.c。
控制片上外设时钟的使能或禁止,只有工作时钟被使能,片上外设才能工作。对于片上外设来讲,其初始化的第一步是时钟使能。片上外设的时钟信号大都来自AHB1、AHB2、APB1和APB2四条总线的时钟。涉及以下函数:
(1)AHB1总线片上外设时钟使能。
RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph FunctionalState NewState);
参数1:uint32_t RCC_AHB1Periph,时钟使能对象,以宏定义形式定义在stm32f4xx_rcc.h文件中。
参数1:uint32.t RCC_AHB2Periph,时钟使能对象,以宏定义形式定义在 stm32f4xx rc.h文件中。
#define RCC_AHB2Periph_DCMI ((uint32_t)0x00000001)
#define RCC_AHB2Periph_CRYP ((uint32_t)0x00000010)
#define RCC_AHB2Periph_HASH ((uint32_t0)0x00000020)
#define RCC_AHB2Periph_RNG ((uint32_t)0x00000040)
#define RCC_AHB2Periph _OTG FS ((uint32_t)0x00000080)
操作的是RCC AHB2外设时钟使能寄存器(RCCAHB2ENR),功能同函数 RCC_AHBIPeriphClockCmd。
参数2:FunctionalState NewState,使能(ENABLE)或禁止(DISABLE)时钟。
例如,通过将RCC AHB2ENR的位6.置位,使能RNG的时钟。
RCC_ AHBIPeriphClockCmd (RCC_ AHB2Periph_RNG ENABLE);
RCC_APB1 PeriphClockCmd(uint32_t RCC_APB1Periph FunctionalState NewState);参数1:uint32 t RCC APB1Periph,时钟使能对象,以宏定义形式定义在stm32f4xx rcc.h文件中。
#define RCC_APB1Periph_TIM2 ((uint32_t)0x00000001)
#define RCC_APBIPeriph_TIM3 ((uint32_t)0x00000002)
#define RCC_APB1Periph_UART8 ((uint32_t)0x80000000)
操作的是RCC APB1外设时钟使能寄存器(RCC_APB1ENR),功能同函数RCC_AHB1PeriphClockCmd。
参数2:FunctionalState NewState,使能(ENABLE)或禁止(DISABLE)时钟。
例如,通过将RCC_APB1ENR的位0置位,使能TIM2的时钟。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 ENABLE);
(4)APB2总线片上外设时钟使能。
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph FunctionalState NewState);
参数1:uint32_t RCC_ APB2Periph,时钟使能对象,以宏定义形式定义在stm32f4xx rcc.h文件中。
#define RCC_APB2Periph_ TIM1 ((uint32_t)0x00000001)
#define RCC_APB2Periph_ TIM8 ((uint32_t)0x00000002)
#define RCC_APB2Periph_LTDC ((uint32_t)0x04000000)
操作的是RCCAPB2外设时钟使能寄存器(RCC_APB2ENR),功能同函数
RCC_AHB1PeriphClockCmd。
参数2:FunctionalState NewState,使能(ENABLE)或禁止(DISABLE)时钟。
例如,通过将RCC_APB2ENR的位0置位,使能TIM1的时钟。
RCC_APB2PeriphClockCmd(RCC APB2Periph_ TIM1 ENABLE);
2.配置系统时钟源函数
void RCC_SYSCLKConfig(uint32 t RCC_SYSCLKSource)
参数:uint32_t RCC_SYSCLKSource, 时钟源, 定义在 stm32f4xx_rcc.h 文件中。
#define RCC_SYSCLKSource_HSI ((uint32_t)0x00000000)
#define RCC_SYSCLKSource_HSE ((uint32_t)0x00000001)
#define RCC_SYSCLKSource_PLLCLK ((uint32_t)0x00000002)
后面还有不少,可以去查看数据手册。函数就不一一说明了,上面这部分理解了,剩下的接没啥问题了。
总结:
int main(void)
{
u8 flag=0;
delay_init(); //初始化延时函数
// LED 端口初始化
LED_Config();
/*初始化按键*/
Key_Config();
while (1)
{
if( Key_Scan(GPIOA GPIO_Pin_0) == KEY_ON )
{
flag=~flag;
if(flag)
HSE_SetSysClock(25 180 2 9);//系统时钟切换到到90M,最高是216M
else
HSE_SetSysClock(25 360 2 9);//系统时钟切换到到180M,最高是216M
}
LED_ON ; // LED等亮
delay_ms(500);
LED_OFF; // LED等灭
delay_ms(500);
}
}
RCC_CLCK配置代码
#include "bsp_clkconfig.h"
#include "stm32f4xx_rcc.h"
void SetSysClock_HSE(uint32_t m uint32_t n uint32_t p uint32_t q)
{
__IO uint32_t HSEStartUpStatus = 0;
/*-------------------第1步--------------------*/
RCC_HSEConfig(RCC_HSE_ON); // 使能HSE,开启外部晶振
HSEStartUpStatus = RCC_WaitForHSEStartUp();// 等待HSE启动稳定
if (HSEStartUpStatus == SUCCESS)//判断HSE是否启动成功,不成功的话,出错处理
{
/*在程序运行中更改系统时钟的话需要先将时钟源切换到其他,并关闭PLL,再进行PLL配置*/
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); // 将系统时钟切换到HSE
while (RCC_GetSYSCLKSource() != 0x04) // 判断HSE是否被选为系统时钟
{
}
RCC_PLLCmd(DISABLE);//禁止PLL
/*-------------------第2步--------------------*/
RCC_HCLKConfig(RCC_SYSCLK_Div1); // HCLK = SYSCLK / 1
RCC_PCLK2Config(RCC_HCLK_Div2); // PCLK2 = HCLK / 2
RCC_PCLK1Config(RCC_HCLK_Div4); // PCLK1 = HCLK / 4
/*-------------------第3步--------------------*/
RCC_PLLConfig(RCC_PLLSource_HSE m n p q); // 配置PLL
/*-------------------第4步--------------------*/
RCC_PLLCmd(ENABLE); // 使能PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) // 等待 PLL稳定
{
}
/*--------开启 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;
/*-------------------第5步--------------------*/
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //把PLL时钟切换为系统时钟
/*-------------------第6步--------------------*/
while (RCC_GetSYSCLKSource() != 0x08) //判断PLLCLK是否被选为系统时钟
{
}
}
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);
}
按键
#include "bsp_key.h"
#include "delay.h"
/**
* @brief 配置按键用到的I/O口
* @param 无
* @retval 无
*/
void Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启按键GPIO口的时钟*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ENABLE);
/*选择按键的引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
/*设置引脚为输入模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
/*设置引脚不上拉也不下拉*/
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
/*使用上面的结构体初始化按键*/
GPIO_Init(GPIOA &GPIO_InitStructure);
}
/**
* @brief 检测是否有按键按下
* @param1 GPIO_TypeDef* GPIOx: 具体的端口 x可以是(A...K)
* @param2 uint16_t GPIO_Pin : 具体的端口位, 可以是GPIO_PIN_x(x可以是0...15)
* @param3 uint8_t Key_Lvl : 按键的有效电平,‘0’表示低电平有效,‘1’表示高电平有效
* @retval 按键的状态
* @arg KEY_ON:按键按下
* @arg KEY_OFF:按键没按下
*/
uint8_t Key_Scan(GPIO_TypeDef* GPIOx uint16_t GPIO_Pin uint8_t Key_Lvl)
{
/*检测是否有按键按下 */
if(GPIO_ReadInputDataBit(GPIOx GPIO_Pin) == Key_Lvl ) //第一次检测有效电平
{
delay_ms(10);//去抖动
if(GPIO_ReadInputDataBit(GPIOx GPIO_Pin) == Key_Lvl) //第二次检测有效电平
return KEY_ON;//确认有效按键动作返回
else
return KEY_OFF;//无有效按键动作返回
}
else
return KEY_OFF; //无有效按键动作返回
}
/*********************************************END OF FILE**********************/