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 ...
随机推荐
- SpringBoot(1):初始SpringBoot
一. SpringBoot 简介 1. SpringBoot介绍 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程.该框架使用了特 ...
- Linux上用Jexus部署Asp.Net网站:常规部署与Docker部署
(一)常规部署 一.把 jexus压缩包下载到linux临时文件夹中. cd /tmp wget linuxdot.net/down/jexus-6.2.x-arm64.tar.gz (不同的操作系统 ...
- SQL Server中修改“用户自定义表类型”问题的分析与方法
前言 SQL Server开发过程中,为了传入数据集类型的变量(比如接受C#中的DataTable类型变量),需要定义"用户自定义表类型",通过"用户自定义表类型&quo ...
- matplotlib 坐标轴刻度能见度问题
matplotlib 画图时如果图像太大就会挡住了坐标轴,刻度就看不到了.可以利用zorder设置level,level越小越先绘制. 例子: 1 import matplotlib.pyplot a ...
- 20 个 .NET 6 新增的 API
DateOnly & TimeOnly .NET 6 引入了两种期待已久的类型 - DateOnly 和 TimeOnly, 它们分别代表DateTime的日期和时间部分. DateOnly ...
- JavaMoney规范(JSR 354)与对应实现解读
一.概述 1.1 当前现状 当前JDK中用来表达货币的类为java.util.Currency,这个类仅仅能够表示按照**[ISO-4217]**描述的货币类型.它没有与之关联的数值,也不能描述规范外 ...
- 【.NET 与树莓派】控制彩色灯带(WS28XX)
彩色灯带,相信不用老周多说,大家都知道,没准你家里的灯墙里面就有.老周的茅屋是早期建造的,所以没有预留的灯槽,明灯的话是不好看的,因此老周家里没使用灯带.不过,像柜子后面,显示器后面,书桌边沿这些地方 ...
- Tableau如何绘制双柱折线组合图
一.数据准备如下所示 二.将日期拖拽至列,销售额拖拽至行,结果如下所示 三.右键日期排序-选择手动排序 四.将指标拖拽至标记卡上 五.创建计算字段增长率 SUM(IF YEAR(日期)=2017 th ...
- [BUUCTF]PWN——bjdctf_2020_babyrop2
bjdctf_2020_babyrop2 附件 步骤: 例行检查,64位程序,开启了NX和canary保护 2. 试运行一下程序,看看大概的情况 提示我们去泄露libc 3. 64位ida载入,从ma ...
- PMP变更流程
变更流程: