STM32笔记(四)---配置系统时钟实验
2022-03-17 来源:eefocus
配置系统时钟实验
1 使用 HSE
一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。
通常的配置是: HSE=8M, PLL 的倍频因子为: 9,系统时钟就设置成:SYSCLK = 8M * 9 = 72M。使用 HSE,系统时钟 SYSCLK 最高是128M(16倍频)。
当程序来到 main 函数之前,启动文件: statup_stm32f10x_hd.s 已经调用 SystemInit()函数把系统时钟初始化成 72MHZ, SystemInit()在库文件: system_stm32f10x.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。
2 使用HSI
当 HSE 故障的时候, 如果 PLL 的时钟来源是 HSE, 那么当 HSE 故障的时候,不仅HSE 不能使用,连 PLL 也会被关闭,这个时候系统会自动切换 HSI 作为系统时钟,此时SYSCLK=HSI=8M,如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。
如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在CSS 中断里面采取补救措施,使用 HSI,并把系统时钟设置为更高的频率,最高是 64M,64M 的频率足够一般的外设使用,如: ADC 、 SPI、 I2C 等。但是这里就又有一个问题了,原来 SYSCLK=72M,现在因为故障改成 64M,那么那些外设的时钟肯定被改变了,那么外设工作就会被打乱,那我们是不是在设置 HSI 时钟的时候,也重新调整外设总线的分频因子,即 AHB, APB2 和 APB1 的分频因子,使外设的时钟达到跟 HSE 没有故障之前一样。但是这个也不是最保障的办法,毕竟不能一直使用 HSI,所以当 HSE 故障时还是要采取报警措施。
编程要点对应着时钟树图中的序号:1、开启 HSE/HSI , 并等待 HSE/HSI 稳定2、设置 AHB、 APB2、 APB1 的预分频因子3、设置 PLL 的时钟来源,和 PLL 的倍频因子,设置各种频率主要就是在这里设置4、开启 PLL,并等待 PLL 稳定5、把 PLLCK 切换为系统时钟 SYSCLK6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
3 HSE库函数版配置
void HSE_SetSysClock(uint32_t pllmul)
{
__IO uint32_t StartUpCounter = 0, HSEStartUpStatus = 0;
// 把 RCC 外设初始化成复位状态
RCC_DeInit();
//使能 HSE,开启外部晶振--8M
RCC_HSEConfig(RCC_HSE_ON);
// 等待 HSE 启动稳定
HSEStartUpStatus = RCC_WaitForHSEStartUp();
// 只有 HSE 稳定之后则继续往下执行
if (HSEStartUpStatus == SUCCESS) {
//-----------------------------------------------------------------//
// 这两句是操作 FLASH 闪存用到的,如果不操作 FLASH,这两个注释掉也没影响
// 使能 FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
// 0: 0 < SYSCLK <= 24M
// 1: 24< SYSCLK <= 48M
// 2: 48< SYSCLK <= 72M
FLASH_SetLatency(FLASH_Latency_2);
//-----------------------------------------------------------------//
// AHB 预分频因子设置为 1 分频, HCLK = SYSCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// APB2 预分频因子设置为 1 分频, PCLK2 = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
// APB1 预分频因子设置为 2 分频, PCLK1 = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
//-----------------设置各种频率主要就是在这里设置-------------------//
// 设置 PLL 时钟来源为 HSE,设置 PLL 倍频因子
// PLLCLK = 8MHz * pllmul
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);
//-------------------------------------------------------------//
// 开启 PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL 稳定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
// 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
while (RCC_GetSYSCLKSource() != 0x08) {
}
}
else
{
// 如果 HSE 开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
// 当 HSE 开启失败或者故障的时候,单片机会自动把 HSI 设置为系统时钟,
// HSI 是内部的高速时钟, 8MHZ
while (1) {
}
}
}
4 HSI库函数版配置
void HSI_SetSysClock(uint32_t pllmul)
{
__IO uint32_t HSIStartUpStatus = 0;
// 把 RCC 外设初始化成复位状态
RCC_DeInit();
//使能 HSI
RCC_HSICmd(ENABLE);
// 等待 HSI 就绪
HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
// 只有 HSI 就绪之后则继续往下执行
if (HSIStartUpStatus == RCC_CR_HSIRDY) {
//-------------------------------------------------------------//
// 这两句是操作 FLASH 闪存用到的,如果不操作 FLASH,这两个注释掉也没影响
// 使能 FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
// 0: 0 < SYSCLK <= 24M
// 1: 24< SYSCLK <= 48M
// 2: 48< SYSCLK <= 72M
FLASH_SetLatency(FLASH_Latency_2);
//------------------------------------------------------------//
// AHB 预分频因子设置为 1 分频, HCLK = SYSCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// APB2 预分频因子设置为 1 分频, PCLK2 = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
// APB1 预分频因子设置为 1 分频, PCLK1 = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
//-----------设置各种频率主要就是在这里设置-------------------//
// 设置 PLL 时钟来源为 HSE,设置 PLL 倍频因子
// PLLCLK = 4MHz * pllmul
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);
//-- -----------------------------------------------------//
// 开启 PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL 稳定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
// 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
while (RCC_GetSYSCLKSource() != 0x08) {
}
}
else
{
// 如果 HSI 开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
// 当 HSE 开启失败或者故障的时候,单片机会自动把 HSI 设置为系统时钟,
// HSI 是内部的高速时钟, 8MHZ
while (1) {
}
}
}
5 MCO 输出
在 STM32F103 系列中, PA8 可以复用为 MCO 引脚,对外提供时钟输出,我们也可以用示波器监控该引脚的输出来判断我们的系统时钟是否设置正确。
MCO GPIO 初始化函数:
/*
* 初始化 MCO 引脚 PA8
* 在 F103 系列中 MCO 引脚只有一个,即 PA8,在 F4 系列中, MCO 引脚有两个
*/
void MCO_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启 GPIOA 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 选择 GPIO8 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
//设置为复用功能推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//设置 IO 的翻转速率为 50M
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化 GPIOA8
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
MCO 输出时钟选择:
// 设置 MCO 引脚输出时钟,用示波器即可在 PA8 测量到输出的时钟信号,
// 我们可以把 PLLCLK/2 作为 MCO 引脚的时钟来检测系统时钟是否配置准确
// MCO 引脚输出可以是 HSE,HSI,PLLCLK/2,SYSCLK
//RCC_MCOConfig(RCC_MCO_HSE);
//RCC_MCOConfig(RCC_MCO_HSI);
//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);
RCC_MCOConfig(RCC_MCO_SYSCLK);
6 主函数
int main(void)
{
// 程序来到 main 函数之前,启动文件: statup_stm32f10x_hd.s 已经调用
// SystemInit()函数把系统时钟初始化成 72MHZ
// SystemInit()在 system_stm32f10x.c 中定义
// 如果用户想修改系统时钟,可自行编写程序修改
// 重新设置系统时钟,这时候可以选择使用 HSE 还是 HSI
// 使用 HSE 时, SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16],最高是 128M
HSE_SetSysClock(RCC_PLLMul_9);
// 使用 HSI 时, SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,...16],最高是 64MH
//HSI_SetSysClock(RCC_PLLMul_16);
// MCO 引脚初始化
MCO_GPIO_Config();
// 设置 MCO 引脚输出时钟,用示波器即可在 PA8 测量到输出的时钟信号,
// 我们可以把 PLLCLK/2 作为 MCO 引脚的时钟来检测系统时钟是否配置准确
// MCO 引脚输出可以是 HSE,HSI,PLLCLK/2,SYSCLK
//RCC_MCOConfig(RCC_MCO_HSE);
//RCC_MCOConfig(RCC_MCO_HSI);
//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);
RCC_MCOConfig(RCC_MCO_SYSCLK);
// LED 端口初始化
LED_GPIO_Config();
while (1)
{
LED1( ON ); // 亮
Delay(0x0FFFFF);
LED1( OFF ); // 灭
Delay(0x0FFFFF);
}
}
在主函数中,可以调用 HSE_SetSysClock()或者 HSI_SetSysClock()这两个函数把系统时钟设置成各种常用的时钟,然后通过 MCO 引脚监控,或者通过 LED 闪烁的快慢体验不同的系统时钟对同一个软件延时函数的影响。