当前位置:网站首页>STM32的时钟系统
STM32的时钟系统
2022-07-07 06:26:00 【一只大喵咪1201】
时钟系统
描述
时钟系统是为单片机工作提供能量的系统,时钟系统通过晶振振荡或者是RC振荡电路会提供一定频率的节拍,在这个节拍下,单片机的各个功能单位进行工作。这个节拍就像是我们生活中按一定频率拍手一样,时钟系统的作用就类似于每拍一次手,单片机内部就执行一条指令,这样它们的工作就有了一定的章程,不会混乱,当然,时钟系统的振荡次数(节拍数)和指令或者外设的工作进行有严格的数量关系。
时钟系统框图及说明
时钟源
图中有5个红色的矩形框,这就是STM32的5个时钟源,也就是给STM32提供时钟的能量来源。
- HSI是高速内部时钟信号。是由RC 振荡器产生的,频率约为 8MHz(因为RC振荡器产生的信号不稳定)。
- PLL为锁相环倍频输出,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为2~16 倍,但是其输出频率最大不得超过 72MHz
- HSE是高速外部时钟,可接石英/陶瓷谐振器等外部晶振,或者接外部时钟源,频率范围为4MHz~16MHz。本喵使用的STM32F10ZET6开发板接的是 8M 的晶振。
- LSE是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC (系统实时时钟)的时钟源。
- LSI 是低速内部时钟,RC 振荡器,频率约为 40kHz(因为RC振荡器产生的信号不稳定)。独立看门狗的时钟源只能是 LSI,同时LSI还可以作为 RTC 的时钟源。
对着5个时钟源进行分类:
- 按照高速和低速来分:可以分为高速时钟源(包括HSI,HSE,PLL三个时钟源),和低俗时钟源(包括LSI,LSE两个时钟源)。
- 按照内部和外部来分:可以分为外部时源(包括HSE,LSE两个时钟源),和内部时钟源(包括HSI,LSI,PLL三个时钟源)。
时钟输出
图中蓝色的框框是5个选择器,它可以选择时钟源。绿色的框框是时钟源提供的时钟的去向。本喵从A到E的顺序来介绍:
A:MCO 是 STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
B:这里是 RTC(系统实时时钟) 时钟源,从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 的 128 分频。
C:从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。STM32 中有一个全速功能的 USB 模块,其串行接口需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。
D:D 处就是 STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者 HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
E:这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。
这些模块包括:
- AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
- 通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。
- 直接送给 Cortex 的空闲运行时钟 FCLK。
- 送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz),另一路送给定时器(Timer)2、3、4 倍频器使用。
- 送给 APB2 分频器。APB2 分频器分频输出一路供 APB2 外设使(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用,还有一路送给ADC分频使用(ADCCLK,最大频率是14MHZ)
- 经2分频后送给SDIO的AHB接口使用。
APB1和APB2的区别:APB1上挂载的都是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2上挂载的都是高速外设,包括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。
**注意:**所有的时钟输出时都需要使能,也就是上面的6个模块,当使用到某个模块的时候,要对相应的时钟输出进行使能。通俗理解就是使用到哪个模块就需要将哪个时钟打开。具体的使用后面会说到。
RCC寄存器
RCC寄存器就是用来配置时钟系统的,它还包括很多寄存器。
时钟控制寄存器(RCC_CR)
这是它32个位的分布以情况。
这是它的具体控制情况。
使用较多的就是就是时钟源的使能,以及读取时钟源就绪标志位。
时钟源开启以后需要一定的时间来稳定,当稳定以后,就序标志位就会由硬件置1,当读取到该为是1以后才能进行后续的操作。
时钟配置寄存器(RCC_CFGR)
这是它32个位的部分情况。
这是它的具体控制情况。
使用较多的就是系统时钟来源的选择,可供选择的有HSI,HSEMPLL三个时钟源。
还有时钟输出时分频或者倍频的倍数选择,调整SYSCLK的频率。
AHB外设时钟使能寄存器 (RCC_AHBENR)
它每一位的分布情况。
这是它的具体控制情况。
它主要是对AHB总线上挂靠的外设进行使能。
APB2 外设时钟使能寄存器(RCC_APB2ENR)
这是在前面实验中使用的寄存器。详细请参考前面的跑马灯实验。
这些寄存器在使用的时候需要对照着手册一位一位的设置。
RCC存器中还有很多的寄存器,本喵只介绍了一些重要的,和目前使用到的,其他寄存器在后面使用到的时候会详细介绍。
APB1 外设时钟使能寄存器(RCC_APB1ENR)
这是它的分布情况。
这是它的具体控制情况。
RCC中的寄存器经常使用到的就是这5个,至于其他的,等使用到的时候本喵会进行详细的介绍。
时钟系统的配置
库函数简介
STM官方提供的标准库中对时钟系统进行了配置,实质就是对上面提到的寄存器的每一位进行了配置。我们每次在使用的时候直接调用库函数就可以,不需要一步一步进行配置。
这些函数就是对RCC进行配置的,在官方提供的库函数stm32f10x_rcc.h中可以看到。
主要分为7类库函数:
- 时钟使能配置:
RCC_LSEConfig() 、RCC_HSEConfig()、RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() ……
这些函数是用来使能时钟源的,在使用哪个时钟源之前,需要对其使能,相当于打开该时钟源。
- 时钟源相关配置:
RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、RCC_RTCCLKConfig() …
这些函数是用来配置时钟源的一些参数的。
- 分频系数选择配置:
RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()…
这些函数是用来配置分频系数的。
- 外设时钟使能:
RCC_APB1PeriphClockCmd(): //APB1线上外设时钟使能
RCC_APB2PeriphClockCmd(); //APB2线上外设时钟使能
RCC_AHBPeriphClockCmd(); //AHB线上外设时钟使能
这些函数是用来对外设进行使能的,注意对外设的使能和对时钟源的使能是有区别的。
- 其他外设时钟配置:
RCC_ADCCLKConfig () 、 RCC_RTCCLKConfig()...
这些函数是用来配置其他外设的,比如ADC,RTC等。
- 状态参数获取参数:
RCC_GetClocksFreq() 、 RCC_GetSYSCLKSource() 、 RCC_GetFlagStatus()...
这些函数是用来获于与时钟相关的数据的。
7. RCC中断相关函数 :
RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()…
这些函数是用来实现与RCC有关的中断的。
以上仅列举了一部分库函数,大致分类介绍了一下它们的作用。
时钟初始化函数分析
在stm32f10x.h中创建了一个结构体:
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
} RCC_TypeDef;
将RCC中的各个寄存器放在这个结构体用来进行寄存器地址名的映射,这几个寄存器中就有上面提到的常用的五个寄存器(包括与使能相关的三个寄存器,以及与控制和状态有关的两个寄存器)。关于寄存器地址名的映射,本喵在前面专门有篇文章讲解。
开始讲解函数:
extern void SystemInit(void);
这就是时钟初始化函数,在system_stm32f10x.h中声明,在system_stm32f10x.c中进行了定义。
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
}
这是它的全部代码,接下来本喵拆开来分析一下。
1.
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
该语句将时钟控制寄存器(RCC_CR)中的第0位置1,将HSI时钟源打开。
2.
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
本喵的开发板是高容量的,所以不执行第一条语句,直接执行第二条语句,该语句的作用是将时钟配置寄存器(RCC_CFGR)中的 SW, HPRE, PPRE1, PPRE2, ADCPRE 和 MCO 相关的位全部清0。
3.
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
该语句的作用是将钟控制寄存器(RCC_CR)中的第16位,19位,24位清0,
将HSE关闭,将安全时钟关闭,将PLL关闭。
4.
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
该语句的作用是将钟控制寄存器(RCC_CR)中的第18位清0,选择没有外部高速时钟旁路。
5.
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
该语句是将时钟配置寄存器(RCC_CFGR)中的PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE清0。
6.
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
这是将与时钟相关的中断全部关闭。
以上是RCC寄存器开始时的通用配置,不是重点关注的地方。
接下来调用了一个一个函数:
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) */
}
这里有一个判断,是根据系统的频率进入到不同的函数。
#define SYSCLK_FREQ_72MHz 72000000
这里是系统的频率我用户自己定义的,本喵使用的是72MHZ的频率,如果不用这个频率,只需要修改define定义的值即可。
根据72MHZ进入到SetSysClockTo72()函数:
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;
}
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;
/* 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
/* 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 */
}
}
这是它整个函数的代码。
1.
#define RCC_CR_HSEON ((uint32_t)0x00010000)
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
该语句的作用是将时钟控制寄存器(RCC_CR) 的第16位置1
将HSE时钟源使能。
2.
#define RCC_CR_HSERDY ((uint32_t)0x00020000)
#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
这里是在读取时钟控制寄存器(RCC_CR) 的第17位。
当这一位是0的时候便会一直循环,等待时钟源稳定,当时钟源稳定后,这一位便会被置1,然后开始继续往下执行程序。
3.
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
这里是判断时钟源是否稳定,当稳定后就给HSEStatus变量赋值1
4.
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;
这里是在闪存FLASH的相关操作。因为CPU执行的速度比较快,所以在闪存中需要设置使CPU进行一定的等待时间。
与闪存相关的内容在后面涉及到的时候再进行详细介绍,这里仅需要知道这部分代码是什么作用即可。
5.
#define RCC_CFGR_HPRE_DIV1 ((uint32_t)0x00000000)
#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000)
#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0x00000400)
/* 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;
这里是将时钟配置寄存器(RCC_CFGR) 中的AHB和APB2设置为不分频,也就是72MHZ,将APB1设置为2分频,也就是36MHZ。
6.
#define RCC_CFGR_PLLSRC ((uint32_t)0x00010000)
#define RCC_CFGR_PLLXTPRE ((uint32_t)0x00020000)
#define RCC_CFGR_PLLMULL ((uint32_t)0x003C0000)
/* 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);
这里的代码是将PLL设置为9倍频输出,将HSE作为PLL的输入时钟,将HSE输入PLL时设置为2分频
此时PLL输出的频率就是8*9=72MHZ。
7.
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
该语句是将时钟控制寄存器(RCC_CR)的第24为置1
将PLL时钟源使能,也就是打开该时钟。
8.
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
这里是等待PLL时钟源稳定,待稳定后继续执行下面的程序。
9.
#define RCC_CFGR_SW ((uint32_t)0x00000003)
#define RCC_CFGR_SW_PLL ((uint32_t)0x00000002)
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
这里是将时钟配置寄存器(RCC_CFGR)中的位0和1先清零,再置为10
将HSE作为系统时钟。
10.
#define RCC_CFGR_SWS ((uint32_t)0x0000000C)
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
该语句是在等待硬件做出指示,当检测到的结果是0x08的时候,说明此时PLL作为系统时钟。
初始化过程(图示)
整个时钟初始化的过程是按照上图中红色框框标号的顺序进行的。
总结
时钟的初始化是一个很复杂的过程,如果每次使用STM32的时候都挨个设置寄存器会很耗时间,而这个过程STM官方将其封装成了库函数,且不需要我们自己去调用,它在启动文件中自动的进行调用。如果在使用的时候,系统的频率或者设置有自己特殊的需求,可以对时钟初始化函数进行修改。
边栏推荐
猜你喜欢
随机推荐
NCS Chengdu New Electric interview Experience
Count the number of words in the string c language
MySQL主从延迟的解决方案
[Nanjing University] - [software analysis] course learning notes (I) -introduction
2022-06-30 Unity核心8——模型导入
cmake命令行使用
【Istio Network CRD VirtualService、Envoyfilter】
Skills that testers must know: Selenium's three waiting ways are interpreted clearly
9c09730c0eea36d495c3ff6efe3708d8
Cmake command line use
RuntimeError: Calculated padded input size per channel: (1 x 1). Kernel size: (5 x 5). Kernel size c
使用Typora编辑markdown上传CSDN时图片大小调整麻烦问题
JS operation
Interpretation of MySQL optimization principle
Markdown editor Use of MD plug-in
Greenplum6.x-版本变化记录-常用手册
硬核分享:硬件工程师常用工具包
Esp32-ulp coprocessor low power mode RTC GPIO interrupt wake up
NCS Chengdu Xindian interview experience
为不同类型设备构建应用的三大更新 | 2022 I/O 重点回顾