STM32时钟系统的配置寄存器和源码分析
一、时钟系统
概述
时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令,时钟系统就是CPU的脉搏,决定cpu速率。 STM32有多个时钟来源的选择,为什么 STM32 要有多个时钟源呢?因为首先 STM32 本身非常复杂,外设非常的多,而使用任何外设都需要时钟才能启动,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。如图所示:
STM32F10x时钟系统图
从图中蓝色部分可以看出STM32有5个时钟源:HSI、HSE、LSE、LSI、PLL。
- HSI时钟:高速内部时钟,RC振荡器,频率约为8MHz,精度不高。直接作为 8MHz 的系统时钟或者用作 4MHz 的PLL时钟输入。
- HSE时钟:高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为3MHz~25MHz。(一般是8MHZ),外部振荡器可为系统提供非常精确的主时钟。
- LSI时钟:低速内部时钟,RC振荡器,频率为32kHz,提供低功耗时钟。主要供独立看门狗和自动唤醒单元使用。
- LSE时钟:低速外部时钟,接频率为32.768kHz的石英晶体,它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
- PLL时钟:产生倍频的输出
系统时钟(SYSCLK)选择
从时钟系统图中可以看出,系统复位后,HSI振荡器被选为系统时钟。此时系统时钟的来源有三种选择,可以选择HSI、PLL、HSE/2,而PLL又有两种时钟源可以选择HSE、HSI/2。
注意:切换时钟源时需要等待新的时钟源就绪,否则系统时钟源不会切换。在时空控制寄存器(RCC_CR)里的状态指示可以看到已经准备好的时钟已经被当前系统使用的时钟。时钟安全系统(CSS)
从时钟系统图中可以看出,当HSE振荡器被直接或间接地作为系统时钟时(直接指的是系统时钟源为HSE/2,间接指的是HSE通过PLL产生的倍频时钟作为系统时钟源),时钟故障将导致系统时钟自动切换到HSI振荡器,同时外部HSE振荡器被关闭。在时钟失效时,如果HSE振荡器时钟是作为PLL的输入时钟,PLL也将被关闭。
注意:一旦CSS被激活,并且HSE时钟出现故障,CSS中断就产生,并且NMI也自动产生。NMI将被不断执行,直到CSS中断挂起位被清除。因此,在NMI的处理程序中必须通过设置时钟中断寄存器(RCC_CIR)里的CSSC位来清除CSS中断
# 如果需要72MHz的系统时钟,我们可以选择8MHz的外部时钟(HSE),PLL的倍频设置为9
SYSCLK = 8(HSE) * 9(PLL) = 72MHz
RTC时钟(RTCCLK)
通过设置 备份域控制寄存器(RCC_BDCR)里的RTCSEL[1:0]位,RTCCLK时钟源可以由HSE/128、LSE或LSI时钟提供。看门狗时钟
如果独立看门狗已经由硬件选项或软件启动,LSI振荡器将被强制在打开状态,并且不能被关
闭。在LSI振荡器稳定后,时钟供应给IWDG。时钟输出
微控制器允许输出时钟信号到外部MCO引脚。通过MCO引脚输出时钟时,GPIO端口寄存器必须被配置为相应功能,时钟信号有四种来源SYSCLK、HSI、HSE、PLL/2。
注意:在MCO上输出的时钟必须小于50MHz(这是I/O端口的最大速度),时钟的选择由时钟配置寄存器(RCC_CFGR)中的MCO[3:0]位控制。
二、寄存器
时钟控制寄存器(RCC_CR)
时钟配置寄存器(RCC_CFGR)
时钟中断寄存器(RCC_CIR)
APB2 外设复位寄存器 (RCC_APB2RSTR)
APB1 外设复位寄存器 (RCC_APB1RSTR)
AHB外设时钟使能寄存器 (RCC_AHBENR)
APB2 外设时钟使能寄存器(RCC_APB2ENR)
APB1 外设时钟使能寄存器(RCC_APB1ENR)
备份域控制寄存器 (RCC_BDCR)
控制/状态寄存器 (RCC_CSR)
RCC寄存器地址映像
三、程序分析
从上面可以看出配置系统时钟有10个寄存器,分别的作用就不用过多介绍了,文档已经很详细。接下来看STM32提供的库函数是怎么配置的。
- RCC相关寄存器的结构体
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;
#ifdef STM32F10X_CL
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
uint32_t RESERVED0;
__IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
} RCC_TypeDef;
系统时钟的初始化函数
现在应知道,系统时钟是CPU的心脏,所以在运行程序之前,首先需要进行时钟的配置,也就是说在执行main函数之前已经执行了系统时钟的配置。系统时钟的初始函数SystemInit(),当然这个函数名是可以更改的,所以需要了解STM32运行时,是在哪里调用SystemInit()这个函数进行初始化的。
SystemInit()函数
void SystemInit (void)
{
/* 将RCC时钟配置重置为默认重置状态(用于调试目的) */
RCC->CR |= (uint32_t)0x00000001; // 打开HSION位(内部高速时钟使能)
/* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
RCC->CFGR &= (uint32_t)0xF8FF0000;
/* 复位 HSEON, CSSON 和 PLLON 位 */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* 复位 HSEBYP 位 */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* 复位 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位 */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
/* 禁用所有中断并清除挂起位 */
RCC->CIR = 0x009F0000;
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl(); // 设置的是STM32F10X_HD,所以不执行此函数
#endif /* DATA_IN_ExtSRAM */
#endif
/* 配置系统时钟频率、HCLK、PCLK2和PCLK1预分频器 */
/* 配置闪存延迟周期并启用预取缓冲区 */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 内部SRAM中的向量表重定位. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 内部FLASH中的向量表重定位. */
#endif
}
- SetSysClock()函数
配置系统时钟频率、HCLK、PCLK2和PCLK1预分频器。这里配置的系统时钟频率是72MHz
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()函数
将系统时钟频率设置为72MHz并配置HCLK、PCLK2和PCLK1预分频器。
/**
* @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;
}
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 */
}
}
从这里看来系统时钟的配置就比较简单了,只需要花一些时间对一下相应的寄存器,希望感兴趣的小伙伴自己按照上面的寄存器表格与程序进行对比一下,很容易就能看懂了。当然也可以跟着上面的思路自己写一个SystemInit()函数,如果不考虑各种芯片的适配,那么程序相对就比较简单,程序量也比较少。
参考文献
STM32时钟系统以及配置及源码分析:https://blog.csdn.net/qq_36243942/article/details/83655339
【STM32】系统时钟RCC详解(超详细,超全面):https://blog.csdn.net/as480133937/article/details/98845509
《STM32中文参考手册》
STM32时钟系统的配置寄存器和源码分析的更多相关文章
- STM32入门系列-STM32时钟系统,STM32时钟树
时钟对于单片机来说是非常重要的,它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行.时钟系统犹如人的心脏,一旦有问题整个系统就崩溃.我们知道STM32属于高级单片机,其内部有很多的外设,但不是 ...
- Android Debuggerd 简要介绍和源码分析(转载)
转载: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E ...
- Quartz学习--二 Hello Quartz! 和源码分析
Quartz学习--二 Hello Quartz! 和源码分析 三. Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...
- Kubernetes Job Controller 原理和源码分析(一)
概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...
- Kubernetes Job Controller 原理和源码分析(二)
概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...
- Kubernetes Job Controller 原理和源码分析(三)
概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...
- Apollo配置中心源码分析
Apollo配置中心源码分析 1. apollo的核心代码分享 SpringApplication启动的关键步骤 在SpringApplication中,会加载所有实现了Init方法的类 protec ...
- Unity时钟定时器插件——Vision Timer源码分析之二
Unity时钟定时器插件——Vision Timer源码分析之二 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面的已经介绍了vp_T ...
- Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...
随机推荐
- feign中开启熔断的书写步骤
/** 1.在pom.xml中引入依赖 2.在application.yaml中开启hystrix 3.在方法上配置熔断类 4.书写接口的实现类 **/ //1.在pom.xml中引 ...
- 【C/C++】拔河比赛/分组/招商银行
题目:小Z组织训练营同学进行一次拔河比赛,要从n(2≤n≤60,000)个同学中选出两组同学参加(两组人数可能不同).对每组同学而言,如果人数超过1人,那么要求该组内的任意两个同学的体重之差的绝对值不 ...
- 深入.NET框架与面向对象的回顾
.NET DOTNET DNET 点NET(.NET框架支持跨语言开发.如C#,VB .NET ,C++.NET,F# ,lronRuby,Others) 任何人,在任何地方,使用任何终端设备,都能访 ...
- Redis5.0.8 Cluster集群部署
目录 一.Redis Cluster简介 二.部署 三.创建主库 一.Redis Cluster简介 Redis Cluster集群是一种去中心化的高可用服务,其内置的sentinel功能可以提供高可 ...
- 【python】青果教务系统模拟登陆
使用 python 的 selenium + chrome 来模拟登陆学校教务系统 完整代码传至 github,增加了一个自动识别验证码的功能,不过是用的别人的轮子,识别度也不高 这是需要手动输入验证 ...
- 突破结构限制的“数据透视表”(Excel技巧集团)
出个题:根据A2:C16生成E2:G18的汇总结果.这里的汇总是求和. 遇到这种情况,首选肯定是函数公式,虽然数据源表是个很规范的一维表,可以用数据透视表,可是想建构到上图那么奇葩,数据透视表无此异能 ...
- .NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案
背景 在这篇文章中,我们实现了基于自定义Attribute的审计日志数据对象属性过滤,但是在实际项目的应用中遇到了一点麻烦.需要进行审计的对象属性中会包含其他类对象,而我们之前的实现是没办法处理这种类 ...
- 模仿写了一个摸鱼APP解决原作者的问题
前几天见到微博里有人提到摸鱼APP,发现需要在windows store下载才可以使用,文件约100多M左右的样子,自已没有登录微软Store的习惯.想想只有一个介面,没有必要这么大,于是,自已动手写 ...
- linux 下查看文件修改时间
linux 下查看文件修改时间 等 http://blog.sina.com.cn/s/blog_6285b04e0100f4xr.html 查看文件时间戳命令:stat awk.txtFile: ` ...
- 平衡二叉树判定方法(c++)实现
!!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!! 作者:mohist -- 欢迎指正-- 平衡二叉树特点: 任意一个结点的平衡因子(左子树高度 - 右子树高度)的 ...