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 ...
随机推荐
- 【Linux】【Services】【SaaS】Docker+kubernetes(9. 安装consul实现服务注册发现)
1. 简介 1.1. 官方网站: https://www.consul.io 1.2. Consul的功能: 服务发现:通过DNS或HTTP接口使得消费者发现服务,应用程序可以轻松找到所依赖的服务. ...
- java 9+版本中,接口的内容总结
java 9+版本中,接口的内容可以有: 1.成员变量其实是常量,格式: [public] [static] [final] 数据类型 常量名称=数据值: 注意: 常量必须进行赋值,而且一旦赋值不 ...
- 【C/C++】拔河比赛/分组/招商银行
题目:小Z组织训练营同学进行一次拔河比赛,要从n(2≤n≤60,000)个同学中选出两组同学参加(两组人数可能不同).对每组同学而言,如果人数超过1人,那么要求该组内的任意两个同学的体重之差的绝对值不 ...
- windows下安装linux虚拟机(wsl2),并安装docker。
一.windows terminal(重要工具,但也可以不装) 这是微软官方推荐的终端工具,类似mac的iterm2,可同时开启多个终端,最开始默认有power shall,cmd,可下载gsudo集 ...
- [OpenGL ES 02]OpenGL ES渲染管线与着色器
[OpenGL ES 02]OpenGL ES渲染管线与着色器 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循"署名-非商业用途-保持一致"创 ...
- secret_file
拿到题目例行检查,进入main函数 这个逆向有些复杂,程序首先让我们像dest输入256个字符,我们可以看到关键的strcmp(v15,v17),若相等则执行poppen poppen这个函数有额外的 ...
- [BUUCTF]REVERSE——[WUSTCTF2020]level1
[WUSTCTF2020]level1 附件 步骤: 下载下来的附件有两个,output.txt里是一堆数字 64位ida打开第一个附件,检索字符串,发现了flag字样 双击跟进,ctrl+x交叉引用 ...
- 十年后回到百年前?(Excel技巧集团)
在单元格里输入日期,有时可以偷懒,比如明年的日期可以输入至少一位的年+横杠(或斜杠)+至少一位的月+横杠(或斜杠)+至少一位的日,也就是"21-1-1",单元格里就会自动显示&qu ...
- CF1132B Discounts 题解
Content 有一个长度为 \(n\) 的数组 \(a_1,a_2,a_3,...,a_n\).有 \(q\) 次询问,每次询问给定一个数 \(x\).对于每次询问,求出数组中去掉一个第 \(x\) ...
- CF78B Easter Eggs 题解
Content 有一个有 \(n\) 个点的环,你可以将其染成一种颜色.一共有 \(7\) 种颜色(R,O,Y,G,B,I,V)可以选择.你的染色方案应该满足下面的要求: 每一个点都要被染色,且 \( ...