一、时钟系统结构
1–简述
复位和时钟控制器为STM32F429微控制器提供了内核和片上外设需要的工作时钟,RCC具有高度的时钟选择和配置的灵活性。STM32F429单片机内部时钟系统以时钟树的形式存在。(下图可能不太清楚,树可查看STM32F4xx中文参考手册低107页) 根据上面的图可以知道,用户在运行内核和外设时可选择使用外部晶振、内部振荡器或PLL(锁相环),也可为以太网、USB OTG FS,以及HS、I2S和SDIO等需要特定时钟的外设提供合适的时钟源。 看图这是我说一下,就是跟着箭头走,有节点的说明这两条线是连接的。可以走,大致意思就是这。
1.1 总线时钟
RCC可通过多个预分频器配置AHB、高速APB(APB2)和低速APB(APB1)。 AHB的最大允许频率为108M,APB2为90MHz,APB1为45MHz。
2时钟源
3–HSE时钟
HSE有以下两种输入方式: 1、外部晶振/陶瓷谐振器(无源电路)OSC_IN和OSC_OUT引脚上外接的晶振电路和内部谐振电路产生谐振输出信号,作为HSE供系统使用。 如图: 谐振器和负载电容必须尽可能地靠近振荡器的引脚,以尽量减小输出失真和起振稳定时间。负载电容值必须根据所选振荡器的不同做适当调整。负载电容一般选择22~33pF。 HSE电路工作需要先被使能,可通过RCC时钟控制寄存器(RCC_CR)中的HSEON位使能或禁止。 2、外部时钟(有源电路)。 在此模式下,可以旁路内部谐振电路,直接将外部时钟信号输入芯片内部作为HSE使用。通过将RCC时钟控制寄存器中的HSEBYP和HSEON位置1选择此模式。必须使用占空比约为50%的外部时钟信号(方波、正弦波或三角波)来驱动OSCIN引脚,同时OSCOUT引脚应保持为高阻态,一般悬空。HSE时钟输入电路图如图所示。
4-- PLL配置
STM32F4系列微控制器的器件具有两个PLL,PLL内部结构图如图所示。
1.主PLL:
主PLL由HSE振荡器或HSI振荡器提供时钟信号,并具有两个不同的 输出时钟, (1)第一个输出用于生成高速系统时钟(180MHz)。 (2)第二个输出用于生成USBOTG FS时钟(48MHz)、随机数发生器时钟(48MHz)和 SDIO时钟(48MHz)。
2.专用PLL(PLLI2S)
专用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。
4—LSE时钟
低速外部时钟信号(L.SE)有以下2种电路连接方式。
1.外部晶振/陶瓷谐振器(无源电路)
LSE晶振是32.768kHz低速外部晶振或陶瓷谐振器,可作为实时时钟外设(RTC)的时钟源来提供时钟/日历或其他定时功能,LSE晶振具有功耗低且精度高的优点。 LSE晶振通过RCC备份域控制寄存器(RCC BDCR)中的LSEON位打开和关闭。通过检测RCC备份域控制寄存器中的LSERDY标志,可以知道L.SE晶振是否稳定。例如,在RCC时钟中断寄存器(RCC_CIR)中使能中断,LSE就可以产生中断请求。
2、外部时钟(有源电路)
此时内部LSE振荡电路被旁路,必须提供外部时钟源,最高频率不超过lMHz。此模式通过将RCC备份域控制寄存器中的LSEBYP和LSEON位置l进行选择。外部时钟必须使用占空比约为50%的外部时钟信号(方波、正弦波或三角波)来驱动OSC32_IN引脚, 同时OSC32_OUT引脚应保持为高阻态。 总结:
5–LSI时钟
LSIRC振荡器可作为低功耗时钟源在停机和待机模式下保持运行,供独立看门狗IWDG) 和自动唤醒单元(AWU)使用。LSI时钟频率在32kHz左右。 LSIRC振荡器可通过RCC时钟控制和状态寄存器(RCC_CSR)中的LSION位打开或关闭。 RCC时钟控制和状态寄存器中的LSIRDY标志指示低速内部振荡器是否稳定。在启动时,硬件将此位置1后,LSI时钟才可以使用。若在RCC时钟中断寄存器中使能中断,则LSI就绪后可产生中断。
6–时钟输出功能
RCC共有两个微控制器时钟输出(MCO)引脚,
1.MCO1
用户可通过可配置的预分频器(1~5)向MCO1引脚(PA8)输出4个不同的时钟源:HSI 时钟、LSE时钟、HSE时钟、PLL时钟。用户所需的时钟源通过RCC时钟配置寄存器中的MCO1PRE[2:0]和MCO1[1:0]位选择。
2.MCO2
用户可通过可配置的预分频器(1~5)向MCO2引脚(PC9)输出4个不同的时钟源:HSE 时钟、PLL时钟、系统时钟、PLLI2S时钟。 用户所需的时钟源通过RCC时钟配置寄存器中的MCO2PRE[2:0]和MCO2位选择。对于不同的MCO引脚,必须将相应的GPIO端口在复用功能模式下进行设置。MCO输出时钟频率不得超过100MHz。 总结:
PLL时钟系统配置步骤及常用库函数
在微控制器中的使用过程中,一般使用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)]。
2–常用库函数
与RCC相关的函数和宏都被定义在以下两个文件中。 头文件:stm32f4xx_rcc.h。 源文件:stm32f4xx_rcc.c。
1.时钟使能配置函数
控制片上外设时钟的使能或禁止,只有工作时钟被使能,片上外设才能工作。对于片上外设来讲,其初始化的第一步是时钟使能。片上外设的时钟信号大都来自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)
后面还有不少,可以去查看数据手册。函数就不一一说明了,上面这部分理解了,剩下的接没啥问题了。 总结:
实例
主函数main
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"
void Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
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);
}
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;
}
|