100ask七天物联网训练营学习笔记 - RCC
今天回顾了直播课程又自己阅读芯片手册系统学习了stm32f103的RCC模块,针对学习的内容做一些梳理和总结。
1. 什么是时钟
计算机硬件中时钟不是我们日常理解的时钟,其实是一个固定频率的方波发生器,可以理解成队列在行进时喊得“1、2、1”口令。时钟系统就是CPU
的脉搏,决定CPU
速率。
2. 为什么要有时钟
时钟是为了同步CPU
中的各种门电路。有了时钟信号,就可以通过每一个信号来驱动CPU进行工作,当然还包括其他需要同步时钟才能工作的外设电路。CPU
以及不同的外设电路需要的时钟频率各不相同,为了满足大家的需求,通常系统中有多个时钟源组成时钟总线,优势在于可以满足不同速率需求的外设,同时也方便硬件的管理,关闭不需要工作的外设的时钟,从而优化能耗。
3. STM32的时钟
3.1 时钟源
STM32F103
中的4种可选时钟源:
高速外部时钟
(HSE): 以外部晶振作时钟源,晶振频率可取范围为 4~16 MHz,常用 8MHz 晶振,开发板上的 8MHz 时钟就是指的这个。高速内部时钟
(HSI): 由内部 RC 振荡器产生,频率为8MHz, 无须外接晶振,但精确性比外部时钟差。低速外部时钟
(LSE): 以外部晶振作时钟源,可以提供时钟信号给实时时钟模块,一般采用 32.768KHz 晶振,较为少用。低速内部时钟
(LSI): 由内部 RC 振荡器产生,也主要提供信号给实时时钟模块,频率在 30-60KHz 间浮动,较为少用。
单片机启动时默认使用高速内部时钟 (HSI),启动之后可以通过 RCC
时钟控制寄存器器改用其他时钟源。
3.2 系统时钟(SYSCLK)
以STM32F103
为例,系统时钟 SYSCLK 最大频率为 72MHz,它是供 STM32
中绝大部分部件工作的时钟源。系统时钟可由 PLL(锁相环)、HSI 或者 HSE 提供输入,并且通过 AHB(高速总线) 分频器分频后输出送给各模块使用。
3.3 锁相环(PLL)
如果打算使单片机运行在最高频率 (72 MHz),我们还需要倍频高速时钟源的时钟信号。锁相环能够将输出频率锁定在输入频率的正整数倍。STM32F103
的锁相环提供了 2 到 16 倍的倍频系数。假设我们使用高速内部时钟源 (8 MHz) ,想要使 STM32F103
达到最高主频 (72 MHz),那么我们就要要启用锁相环,设置为 9 倍倍频,并将SYSCLK
时钟源设置为 PLL
。
3.4 时钟总线
STM32F103
中有 4 条时钟总线:
-
AHB
高速总线,时钟为HCLK
,最大频率为 72 MHz,时钟信号提供给存储器,DMA
及Cortex
内核,是内核运行的时钟,也就是主频,它的大小与STM32
运算速度,数据存取速度密切相关。 -
APB1
低速外设总线,时钟为PCLK1
,最大频率为 36 MHz,提供给挂载在APB1总线上的外设, 如USART2
, -
APB2
高速外设总线,时钟为PCLK2
,最大频率为 72 MHz,提供给挂载在APB2总线上的外设,如GPIO
,USART1
。 -
FCLK
自由运行时钟,独立于内核运行,一般设置为与HCLK
同频率,常用于采样中断和调试模块供时。
3.5 时钟系统框图
4. 时钟的配置(以STM32F103为例)
4.1 时钟初始化流程
要想通过寄存器进行时钟配置,需要先搞清楚时钟初始化流程。
经过查阅芯片手册得知,RCC
时钟初始化过程可以分为以下几个步骤:
- 启用外部晶振 (可选)
- 等待外部晶振稳定
- 设置各总线分频系数(AHB、APB1、APB2、PLL)
- 设置
FLASH
等待系数与预读取 - 启动
PLL
锁相环 - 等待
PLL
锁相环锁定 - 将
SYSCLK
切换到锁相环信号输入 - 等待信号切换完成
4.1 通过CubeMX配置时钟
由于STM32
芯片上电时默认使用HSI作为时钟源,HSI
集成在芯片内部,从上图可以看出其频率为8MHz,若让HSI
时钟作为PLL
信号源则必须要先进行2分频,导致SYSCLK
最高为36MHz,所以我们通常会使用外部晶振。
4.2 通过寄存器配置时钟
// main.c
#include <stdint.h>
// 定义RCC的基地址
#define RCC_BASE 0x40021000U
// RCC_CR寄存器地址
#define RCC_CR (RCC_BASE + 0x00U)
// RCC_CFGR寄存器地址
#define RCC_CFGR (RCC_BASE + 0x04U)
// 定义FLASH的基地址
#define FLASH_BASE 0x40022000U
// FLASH_ACR寄存器地址
#define FLASH_ACR (FLASH_BASE + 0x00U)
int main(void)
{
volatile uint32_t *rcc_cr = (uint32_t *)RCC_CR;
volatile uint32_t *rcc_cfgr = (uint32_t *)RCC_CFGR;
volatile uint32_t *flash_acr = (uint32_t *)FLASH_ACR;
uint32_t tmp = 0;
// 1. 使能外部晶振
*rcc_cr |= (1 << 16);
// 2. 等待外部晶振稳定
while (!(*rcc_cr & (1U << 17)));
// 3. 分别设置AHB总线时钟不分频、APB1总线时钟2分频,APB2总线时钟不分频
*rcc_cfgr |= (0 << 4) | (4 << 8) | (0 << 11);
// 3. 设置PLL倍频系数为9
*rcc_cfgr |= (7 << 18);
// 3. 设置HSE不分频信号作为PLL信号源
*rcc_cfgr |= (1 << 16);
// 4. 设置FLASH等待系数与预读取,其实我也不知道这个起什么作用,手册说需要设置
*flash_acr |= (2 << 0) | (1 << 3) | (1 << 4);
// 5. 启动PLL
*rcc_cr |= (1 << 24);
// 6. 等待PLL锁定
while (!(*rcc_cr & (1U << 25)));
// 7. 把PLL切换为SYSCLK的时钟源
*rcc_cfgr |= (2 << 0);
// 8. 等待SYSCLK信号切换完成
do {
tmp = *rcc_cfgr & (3 << 2);
tmp >>= 2;
} while (tmp != 2);
// 此时,系统时钟频率为72M
while (1)
{
}
}
至此,完全通过寄存器完成了RCC的配置。
5. 总结
至此,完全通过寄存器完成了RCC的配置。我们发现RCC的配置过程还是相对繁琐的,我们日常使用时完全可以通过CubeMX来进行配置,但是还是要熟悉时钟初始化的流程,才能更好的理解时钟系统。