STM32学习5 时钟系统
一、STM32系统时钟概述
系统时钟是STM32微控制器中最重要的部分之一,它负责提供时序信号以驱动处理器核心、外设和其他系统模块的运行。
系统时钟通常由多个时钟源、时钟分频器和时钟树组成,这些组件共同构成了系统时钟的组成和层级结构。
二、STM32时钟源
时钟源用来产生系统时钟信号,STM32有以下内部时钟源和外部时钟源:
1. HSE
HSE(High-Speed External)是指高速外部时钟,使用外部晶振,常见频率有 8MHz、12MHz、16MHz等。
在STM32微控制器中,HSE通常被配置为系统的主时钟源,用于产生系统的主时钟信号。通过将HSE连接到微控制器的时钟输入引脚(通常是HSE引脚),可以将外部晶体振荡器的时钟信号输入到微控制器中。
2. HSI
HSI(High-Speed Internal)是指高速内部时钟,它由微控制器内部的8MHz RC振荡器提供,用于产生系统的主时钟信号。
HSI可直接作为系统时钟或2分频后作为PLL输入。
3. LSE
LSE(Low-Speed External)是指低速外部时钟,频率32.768kHz。它为实时时钟或其它定时功能提供一个低功耗且精确的时钟源。
4. LSI
LSI(Low-Speed Internal)低速内部时钟,频率大约是 40kHz(30kHz~60kHz之间),它可以在停机或待机模式下运行,可供看门狗使用或自动唤醒使用。
3. 时钟分频器
时钟分频器是用于将系统时钟频率分频为所需频率的模块。在STM32微控制器中,时钟分频器可以用于调整各个外设的时钟频率,以满足不同外设的工作要求。
时钟分频器有以下几种类型:
(1)AHB分频器
用于将系统主时钟(AHB 总线时钟)分频为用于驱动存储器和某些外设的时钟信号。
AHB是高性能系统总线,主AHB总线用于DMA、中断控制器等;子AHB总线连接到主AHB总线上,并连接到次要外设,如GPIO、SPI、I2C等。
(2)APB分频器
用于将 AHB 总线时钟分频为用于驱动低速外设的时钟信号。在STM32中,有两个APB总线,即APB1和APB2,它们分别驱动不同类型的外设。APB 分频器可以将 AHB 总线时钟分频为适合驱动这些外设的频率。
(3)锁相环PLL(Phase-Locked Loop)分频器
用于调整 PLL 时钟源的频率,以产生所需的系统时钟频率。通过调整 PLL 分频器的分频系数,可以实现将外部高频振荡器(HSE)的频率倍增到系统所需的主时钟频率。
三、时钟树
1. 时钟树
时钟树是指微控制器中各种时钟信号之间的关系和组织结构。
从《STM32F1xx中文参考手册》可以查询时钟树的图示:
2. 主时钟输出MCO(Main Clock Output)
MCO 是 STM32微控制器中的主时钟输出功能。通过MCO功能,可以将系统的主时钟信号输出到特定的引脚,以供外部设备使用。
STM32微控制器通常提供一个或多个MCO引脚,用于输出主时钟信号。MCO引脚通常可配置为多种功能,如系统时钟(SYSCLK)、高速外部时钟(HSE)、PLL时钟等。
3. USB预分频器
USB预分频器的主要作用是根据输入时钟信号的频率,将其分频以生成USB所需的时钟信号。USB通信中的时钟信号要求精确,因此需要通过预分频器来保证时钟信号的稳定和准确性。
如果要在应用中使用USB接口,PLL必须被设置为输出48或72MHz时钟,用于提供48MHz的USBCLK时钟。
4. SYSCLK
SYSCLK是整个系统的主时钟信号,用于驱动CPU核心和大多数外设的时钟信号。
SYSCLK的源头通常来自于STM32微控制器中的晶体振荡器(HSE)或内部RC振荡器(HSI)。
SYSCLK 最大9倍频 * 8MHz 可以由PLLCLK提供72MHz频率。
5. PLLCLK
PLLCLK是指锁相环(PLL)产生的时钟信号,PLLCLK的频率是通过HSE或HSI的频率进行倍频得到的。
PLLCLK 可以通过相应的寄存器配置来设置PLL的工作参数,如倍频系数(PLLMUL)、分频系数(PLLDIV),以及时钟源的选择(HSE或HSI)。通过合适的配置,可以得到所需的PLLCLK频率。
6. RTC时钟
RTC时钟用于提供准确的实时时钟和日历功能,通常与外部32.768kHz晶体振荡器配合使用。
四、RCC寄存器
RCC(Reset and Clock Control)主要负责系统时钟的控制和管理,它提供了一系列的寄存器,用于配置和控制处理器的时钟源、时钟频率和时钟分频器,以及外设的时钟使能和配置。
在 《STM32F1xx 中文参考手册》中有RCC寄存器描述,本文不再缀述。
五、SystemInit()初始化时钟分析
系统复位后,HSI被选为系统时钟。
在STM32的启动代码中,通常会包含一个名为SystemInit()的函数,该函数用于初始化系统的时钟和其他重要的系统配置。
系统时钟初始化后频率:
- SYSCLK=72MHz
- AHB 总线时钟(HCLK=SYSCLK)=72MHz
- APB1 总线时钟(PCLK1=SYSCLK/2)= 36MHz
- APB2 总线时钟(PCLK2=SYSCLK/1)=72MHz
- PLL 主时钟 = 72MHz
在本文对应的开源仓库里,可以查看 SystemInit()函数内容:
这里对部分代码结合RCC寄存器进行分析:
1. SystemInit()
void SystemInit (void)
{
/* 将RCC时钟配置重置为默认的复位状态(仅用于调试目的) */
/* 设置HSI位 */
RCC->CR |= (uint32_t)0x00000001;
/* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* 复位HSEON、CSSON和PLLON位 */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* 复位HSEBYP位 */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* 复位PLLSRC、PLLMUL和USBPRE/OTGFSPRE位 */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* 复位PLL2ON和PLL3ON位 */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* 禁用所有中断并清除挂起位 */
RCC->CIR = 0x00FF0000;
/* 复位CFGR2寄存器 */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* 禁用所有中断并清除挂起位 */
RCC->CIR = 0x009F0000;
/* 复位CFGR2寄存器 */
RCC->CFGR2 = 0x00000000;
#else
/* 禁用所有中断并清除挂起位 */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* 配置系统时钟频率、HCLK、PCLK2和PCLK1分频器 */
/* 配置Flash延迟周期并使能预取缓冲区 */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 将向量表重定位到内部SRAM。*/
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 将向量表重定位到内部Flash。*/
#endif
}
函数中调用 了 SetSysClock() 来设置系统时钟,跟踪这个函数:
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}
跳转到 SetSysClockTo72()。
2. SetSysClockTo72()
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK、HCLK、PCLK2和PCLK1配置 ---------------------------*/
/* 使能HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* 等待HSE就绪,如果超时则退出 */
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)
{
/* 使能预取缓冲区 */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2等待状态 */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef STM32F10X_CL
/* 配置PLL ------------------------------------------------------*/
/* PLL2配置:PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1配置:PREDIV1CLK = PLL2 / 5 = 8 MHz */
RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
/* 使能PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* 等待PLL2就绪 */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}
/* PLL配置:PLLCLK = PREDIV1 * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
#else
/* PLL配置:PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* 使能PLL */
RCC->CR |= RCC_CR_PLLON;
/* 等待PLL就绪 */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* 选择PLL作为系统时钟源 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* 等待PLL作为系统时钟源 */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{
/* 如果HSE启动失败,应用程序将具有错误的时钟配置。
用户可以在这里添加一些代码来处理此错误 */
}
}
六、STM32 时钟函数
STM32 时钟函数定义在 stm32f10x_rcc.c 文件里。
1. RCC 配置函数
(1)RCC_HSEConfig
示例:
RCC_HSEConfig(ENABLE); // 启用外部高速时钟(HSE)
RCC_HSEConfig(DISABLE); // 禁用外部高速时钟(HSE)
(2)RCC_LSEConfig
(3)RCC_PLLConfig
用于配置 PLL 的输入时钟分频系数和倍频系数。其中参数 div 表示 PLL 的输入时钟分频系数,参数 pllm 表示 PLL 的倍频系数。
RCC_PLLSource参数:
- RCC_PLLSource_HSI_Div2: PLL 输入时钟为 HSI / 2。
- RCC_PLLSource_HSE_Div1: PLL 输入时钟为 HSE。
- RCC_PLLSource_HSE_Div2: PLL 输入时钟为 HSE / 2。
- RCC_PLLSource_HSE_Prediv: PLL 输入时钟为 HSE/PREDIV。
pllm 参数
pllm 的取值范围为 2 到 16。通过调整 pllm 参数,可以在一定范围内调整 PLL 的输出时钟频率。
例如,当 pllm 取值为 2 时,输出时钟频率将是输入时钟频率的 2 倍;
当 pllm 取值为 16 时,输出时钟频率将是输入时钟频率的 16 倍。
(())RCC_MCOConfig
(5)RCC_SYSCLKConfig
示例:
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); // 将HSI配置为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); // 将HSE配置为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 将PLL输出配置为系统时钟源
(6)RCC_HCLKConfig
配置 AHB 总线时钟的分频系数,从而设置 HCLK(AHB 总线时钟)的频率。
示例:
RCC_HCLKConfig(RCC_SYSCLK_Div2); // 设置 HCLK 为 SYSCLK 的一半
- RCC_PCLK1Config
- RCC_PCLK2Config
- RCC_RTCCLKConfig
- RCC_ADCCLKConfig
- RCC_USBCLKConfig
2. PLL配置函数
用于配置 PLL 的输入源和倍频系数。
void RCC_PLLConfig(uint32_t PLLSource, uint32_t PLLMul);
3. 时钟使能函数
(1)RCC_HSICmd
用于使能或禁用内部高速时钟(HSI)。
(2)RCC_LSICmd
用于使能或禁用内部低速时钟(LSI)。
(3)RCC_PLLCmd
用于使能或禁用PLL(Phase-Locked Loop)。
(4)RCC_RTCCLKCmd
用于使能或禁用实时时钟(RTC)时钟源。
示例:
RCC_RTCCLKCmd(ENABLE); // 启用RTC时钟源
RCC_RTCCLKCmd(DISABLE); // 禁用RTC时钟源
(5)RCC_AHBPeriphClockCmd
使能或禁用指定的AHB总线上的外设时钟。
示例:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // 启用GPIOA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, DISABLE); // 禁用GPIOA时钟
(6)RCC_APBxPeriphClockCmd
使能或禁用指定的APB1或APB2总线上的外设时钟。
示例:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 启用USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE); // 禁用USART1时钟
七、系统时钟设置步骤
1. 步骤说明
-
选择时钟源:
首先需要确定系统时钟源,通常可以是内部时钟源(HSI 或 HSE)或外部时钟源(例如外部晶体振荡器)。 -
配置时钟源:
根据需要,使能并配置所选的时钟源。对于外部时钟源,需要配置外部晶体振荡器(HSE)或外部时钟输入(例如 HSE 或者 PLL)。对于内部时钟源,例如 HSI,可以直接使能。 -
配置 PLL(可选):
如果需要更高的系统时钟频率,则可以配置 PLL 来实现时钟倍频。在这一步中,需要设置 PLL 的输入时钟源、倍频系数以及输出时钟频率。 -
配置时钟分频器:
配置 AHB、APB1 和 APB2 的分频系数,以确定这些总线的时钟频率。这些分频系数可以根据系统需求进行调整,以满足外设的时钟要求。 -
等待时钟稳定:
对于外部时钟源(如 HSE),需要等待其稳定后再切换到该时钟源。 -
切换系统时钟:
最后,将系统时钟切换到所选的时钟源。确保切换后,系统时钟以正确的频率运行,并且各个外设和总线的时钟也被正确配置。 -
配置 Flash 读取延迟:
根据新的系统时钟频率,配置 Flash 存储器的读取延迟以确保稳定的 Flash 访问速度。这一步对于高频率下的系统运行至关重要。
2. 自定义系统时钟实现
/**
* @brief 配置外部高速时钟(HSE)和 PLL
* @param div: PLL输入时钟分频系数
* pllm: PLL倍频系数
* @retval None
*/
void RCC_HSE_Config(uint32_t div, uint32_t pllm) {
RCC_DeInit(); // 复位 RCC 寄存器到默认值
// 使能外部高速时钟(HSE)
RCC_HSEConfig(RCC_HSE_ON);
// 等待外部时钟稳定
if (RCC_WaitForHSEStartUp() == SUCCESS) {
// 配置 AHB 时钟分频系数
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB 不分频
// 配置 APB1 和 APB2 时钟分频系数
RCC_PCLK1Config(RCC_HCLK_Div2); // 低速 APB1 分频为 HCLK/2 36M
RCC_PCLK2Config(RCC_HCLK_Div1); // 高速 APB2 不分频 72M
// 配置 PLL 输入时钟分频系数和倍频系数
RCC_PLLConfig(div, pllm);
// 使能 PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL 就绪
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 将 PLL 作为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟源稳定
while (RCC_GetSYSCLKSource() != 0x08);
}
}
完整代码参考开源仓库:
https://gitee.com/xundh/stm32_arm_learn/tree/master/lesson5
原文地址:https://blog.csdn.net/xundh/article/details/136336241
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!