STM32笔记之系统时钟
写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
在单片机中时钟就相当于人的心脏,以提供心跳(后面我们再去了解滴答时钟的实现)
本篇来分析一下利用官方提供的 SystemInit()函数设置时钟配置,这也算是我们要执行的第一个函数
一、时钟树分析
1、根据上图,我们可以发现跟时钟有关联的有 5个引脚:
- OSC_IN、OSC_OUT:这一对输入输出时钟管脚是接外部时钟源的,为芯片提供高速外部时钟
- OSC32_IN、OSC32_OUT:这一对输入输出时钟管脚也是接外部时钟源的,为芯片提供低速外部时钟
- MCO:主时钟输出引脚,只要配置一下就可以在该引脚输出跟主时钟一样频率的方波
2、在STM32中,管理时钟的主要有 5个时钟源
- 高速内部时钟(HSI):以内部 RC振荡器产生,频率为 8Mhz,但相较于外部时钟不稳定。
- 高速内部时钟(HSE):以外部晶振作为时钟源,晶振频率可取范围为 4~16Mhz,一般采用 8Mhz的晶振。
- 低速外部时钟(LSE): 以外部晶振作为时钟源,主要是提供给实时时钟模块,所以一般选用 32.768khz,该频率下定时器方便取整。
- 低速内部时钟(LSI): 从内部 RC振荡器产生,频率为 40khz,也是主要提供给实时时钟模块。
- 锁相环倍频输出(PLL):其时钟输入源可选择为 HSI/2、HSE或者 HSE/2;倍频可选择为 2~16倍,但是其输出频率最大不得超过 72MHz。
3、几个与我们开发密切相关的时钟
根据上图的流程配置下来,经历了一系列的分频和倍频,得到图中右边显示的时钟(也就是我们开发所需要的时钟)
- SYSCLK:系统时钟,是STM32大部分器件的时钟来源,主要由AHB预分频器分配到各个部件。
- HCLK:由AHB预分频器直接输出得到,它是高速总线AHB的时钟信号,提供给存储器,DMA及Cortex内核,是Cortex内核运行的时钟,CPU主频就是这个信号。
- PCLK1:外设时钟,由APB1分频得到,最大可为72Mhz,提供给APB1总线上的外设使用。
- PCLK2:外设时钟,由APB2预分频输出得到,最大为72Mhz,提供给APB2总线上的外设。
- FCLK:也是由AHB输出得到,是内核的“*运行时钟”。“*”表现在它不来自时钟HCLK。因此在HCLK停止时FCLK也可以继续运行。也就是说,即使CPU休眠了,也能够采样到外部中断和跟踪休眠事件。(低功耗模式下使用)
4、硬件时钟管脚处理
在 STM32上如果不使用外部晶振,请按照下面方法处理:
- 对于 100脚或 144脚的产品,OSC_IN应接地,OSC_OUT应悬空。
- 对于少于 100脚的产品,有 2种接法:
- OSC_IN和 OSC_OUT分别通过 10K电阻接地。此方法可提高 EMC性能;
- 分别重映射 OSC_IN和 OSC_OUT至 PD0和 PD1,再配置 PD0和 PD1为推挽输出并输出 '0'。此方法可以减小功耗并(相对上面)节省 2个外部电阻。
二、SystemInit() 函数执行分析
SystemInit() 函数封装在官方提供的 system_stm32f10x.c 文件中
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
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
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
根据上一篇我们预定义了宏 “ STM32F10X_HD ” ,所以上面的 code根据宏的处理来执行,但是无论是定义了哪个宏,从上面的 code中都可以执行到 SetSysClock() 这个函数,根据注释可以得知该函数是用来配置上面所说的与我们开发密切相关的时钟(系统时钟频率、HCLK、PCLK2和PCLK1预分频器)
进入 SetSysClock() 这个函数参看
/**
* @brief Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
* @param None
* @retval None
*/
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
从代码中可以看出,这个函数是按照所定义的时钟频率的宏来执行的,而官方默认定义的宏是 SYSCLK_FREQ_72MHz;看下面的代码,这个可以在文件顶部看到(如果你想选择其他的系统时钟时间,就只需去注释对应的宏定义就好了,记得不用的要注释掉,只留一个)
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
所以相对的执行了 SetSysClockTo72() 函数,当我们跳入函数里面查看
/**
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2
* and PCLK1 prescalers.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
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; // 就绪后赋值标志位 HSEStatus
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01) // 判断就绪
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; // cpu的速度比芯片速度快的多 设置FLASH等待:两个等待状态
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK / 2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef STM32F10X_CL
/* Configure PLLs ------------------------------------------------------*/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
/* PREDIV1 configuration: 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);
/* Enable PLL2 */
RCC->CR |= RCC_CR_PLL2ON;
/* Wait till PLL2 is ready */
while((RCC->CR & RCC_CR_PLL2RDY) == 0)
{
}
/* PLL configuration: 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 configuration: 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 */
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
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 */
}
}
#endif
在上面的 code中我们可以发现,配置 HCLK、PCLK2和PCLK1预分频器主要是以下这串代码
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; // 不分频
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; // 不分频
/* PCLK1 = HCLK / 2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; // 二分频
所以如果想要配置不同的外设时钟就直接更改对应的分频系数就行了,其中官方宏定义的 PREDIV2_division_factor 参数有如下几个(其他分频系数对应的宏也一一定义在 stm32f10x.h文件中)
#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000) /*!< HCLK not divided */
#define RCC_CFGR_PPRE2_DIV2 ((uint32_t)0x00002000) /*!< HCLK divided by 2 */
#define RCC_CFGR_PPRE2_DIV4 ((uint32_t)0x00002800) /*!< HCLK divided by 4 */
#define RCC_CFGR_PPRE2_DIV8 ((uint32_t)0x00003000) /*!< HCLK divided by 8 */
#define RCC_CFGR_PPRE2_DIV16 ((uint32_t)0x00003800) /*!< HCLK divided by 16 */
三、高速外部时钟输入频率更改(依旧是做成 72MHz输出)
在上面我们分析了官方提供的默认的 72MHz(利用了锁相环倍频输出(PLL))的执行过程;在这里,值得注意的是宏 HSE_VALUE默认是 8MHz的,但很多时候我们用的高速外部时钟(HSE)不一定是 8MHz,那么就要更改一下配置
从上图看到,也就是说,如果你外部接入的高速晶振不是 8MHz的,那么你就要更改一下这个参数,位置在 stm32f10x.h 文件中
同样的,想要更改 HCLK、PCLK2和PCLK1预分频器,调整上图所示最上面的那一个框,但不能超过他们的最大输出频率;还有值得注意的是,因为我们输入的时钟频率变了,那么就要修改锁相环的分频系数,并把系统时钟做成 72MHz,看上图最下面框选的地方,选择更改宏 RCC_CFGR_PLLMULL9;例如:我们接入的时钟晶振是 12MHz,先把 HSE_VALUE的参数改成 12000000,接着一定要把宏 RCC_CFGR_PLLMULL9换成 RCC_CFGR_PLLMULL6,不然就超频了;官方所提供的 PLL分频宏参数有:
#define RCC_CFGR_PLLMULL2 ((uint32_t)0x00000000) /*!< PLL input clock*2 */
#define RCC_CFGR_PLLMULL3 ((uint32_t)0x00040000) /*!< PLL input clock*3 */
#define RCC_CFGR_PLLMULL4 ((uint32_t)0x00080000) /*!< PLL input clock*4 */
#define RCC_CFGR_PLLMULL5 ((uint32_t)0x000C0000) /*!< PLL input clock*5 */
#define RCC_CFGR_PLLMULL6 ((uint32_t)0x00100000) /*!< PLL input clock*6 */
#define RCC_CFGR_PLLMULL7 ((uint32_t)0x00140000) /*!< PLL input clock*7 */
#define RCC_CFGR_PLLMULL8 ((uint32_t)0x00180000) /*!< PLL input clock*8 */
#define RCC_CFGR_PLLMULL9 ((uint32_t)0x001C0000) /*!< PLL input clock*9 */
#define RCC_CFGR_PLLMULL10 ((uint32_t)0x00200000) /*!< PLL input clock10 */
#define RCC_CFGR_PLLMULL11 ((uint32_t)0x00240000) /*!< PLL input clock*11 */
#define RCC_CFGR_PLLMULL12 ((uint32_t)0x00280000) /*!< PLL input clock*12 */
#define RCC_CFGR_PLLMULL13 ((uint32_t)0x002C0000) /*!< PLL input clock*13 */
#define RCC_CFGR_PLLMULL14 ((uint32_t)0x00300000) /*!< PLL input clock*14 */
#define RCC_CFGR_PLLMULL15 ((uint32_t)0x00340000) /*!< PLL input clock*15 */
#define RCC_CFGR_PLLMULL16 ((uint32_t)0x00380000) /*!< PLL input clock*16 */
四、时钟频率确认
当配置好时钟后,我们需调用一下 SystemInit() 函数来执行我们配置好的参数
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdio.h>
/************************************************
函数名称 : main
功 能 : 主函数入口
参 数 : 无
返 回 值 : 无
*************************************************/
int main(void)
{
RCC_ClocksTypeDef Rcc_clock;
/* Initial Configuration */
SystemInit();
/* -------- End -------- */
RCC_GetClocksFreq(&Rcc_clock); // 获取系统时钟配置
/* Infinite loop */
while (1)
{
}
}
在上面的代码中,可以发现,利用 RCC_ClocksTypeDef定义了一个名为 Rcc_clock的结构体,这是为了在硬件调试中检验一下我们配置的时钟是否跟理想中的参数一致;好了,先来了解一下 RCC_ClocksTypeDef的结构
typedef struct
{
uint32_t SYSCLK_Frequency; /*!< returns SYSCLK clock frequency expressed in Hz */
uint32_t HCLK_Frequency; /*!< returns HCLK clock frequency expressed in Hz */
uint32_t PCLK1_Frequency; /*!< returns PCLK1 clock frequency expressed in Hz */
uint32_t PCLK2_Frequency; /*!< returns PCLK2 clock frequency expressed in Hz */
uint32_t ADCCLK_Frequency; /*!< returns ADCCLK clock frequency expressed in Hz */
}RCC_ClocksTypeDef;
该结构体在 stm32f10x_rcc.h 文件中,而 RCC_GetClocksFreq() 函数,通过查看他的注释,是获取不同芯片系统时钟配置的频率
最后,我们通过仿真看一下时钟频率
根据上图可以看出:SYSCLK == 72MHz、HCLK == 72MHz、PCLK1 == 36MHz、PCLK2 == 72MHz、ADCCLK == 9MHz,前四项都对上了我们之前配置的参数
五、结语
至此,时钟的笔记就记录完成了,等待下一篇利用硬件提供的时钟制作滴答时钟(基时钟,也是跑 RTOS的滴答心跳)