基于STM32的嵌入式软件开发(3)——STM32F407ZGT6芯片时钟系统介绍
一、引言
MCU最小系统:供电,时钟,复位,下载,启动模式,后备电池,其中,时钟系统是最为复杂的,每个芯片的时钟树几乎都不相同。
关于STM32的时钟系统,我自己来来回回学了三四遍才稍微理解一些,同学们刚接触时候会比较摸不着头脑,多看几遍就理清楚啦
二、STM32F407ZGT6时钟树

1. 时钟源
从上图可以看出:有 5 个时钟源,为 HSI、 HSE、 LSI、 LSE、 PLL
(1) LSI是低速内部时钟,RC振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用;
(2) LSE是低速外部时钟,接频率为32.768kHz的石英晶体。这个主要是 RTC 的时钟源;
(3) HSI是高速内部时钟,RC 振荡器,频率为16MHz。可以直接作为系统时钟或者用作 PLL输入
(4) HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz ~ 26MHz。我的开发板接的是 8M 的晶振。 HSE 也可以直接做为系统时钟或者 PLL 输入;
(5) PLL 为锁相环倍频输出。STM32F4有两个 PLL:
1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz)第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO时钟。
我的开发板外部晶振为 8MHz。同时我们设置相应的分频器 M=8, 倍频器倍频系数 N=336,分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为:
PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz
2)专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。
2. 标准固件库中时钟的初始化
只有标准固件库系统有SystemInit()函数,在系统运行main()函数之前会先运行SystemInit(),用来配置时钟树
常用的时钟配置相关寄存器有:
RCC 时钟控制寄存器 (RCC_CR)

RCC 时钟配置寄存器 (RCC_CFGR)

RCC PLL 配置寄存器 (RCC_PLLCFGR)

RCC 时钟中断寄存器 (RCC_CIR)

相关寄存器的bit位含义请参考:STM32F4XX参考手册6.3小节
看一下SystemInit()函数,该函数一般位于system_stm32f4xx.c文件中,主要看其对于寄存器的控制:
(1) /* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;//打开HSI
(2) /* Reset CFGR register */
RCC->CFGR = 0x00000000;//复位CFGR寄存器
(3) /* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;// 0xFEF6FFFF的bit16,bit19,bit24为0
由于是与运算,即将RCC->CR的相应比特位清零,分别是:
Bit16: 关闭HSE

Bit19: 关闭HSE时钟检测器

Bit24:关闭锁相环

//锁相环所有配置均需要关闭锁相环之后才能配置,所以上文中要与上bit24
(4) /* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;// bit4/bit12/bit13/bit26/bit29置1,其余均为0, 0x24003010为该寄存器复位值

(5) /* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;//将RCC->CR bit18清零

(6) /* Disable all interrupts */
RCC->CIR = 0x00000000;//关闭所有时钟相关中断
而后进入SetSysClock()函数继续配置
在前文新建Keil工程时,定义了STM32F40_41xxx宏开关
(7) /* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
//(其中,#define RCC_CR_HSEON ((uint32_t)0x00010000))
// RCC->CR bit16置1,即打开HSE
(8) /* 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));
//其中:
//#define RCC_CR_HSERDY ((uint32_t)0x00020000)
//#define HSE_STARTUP_TIMEOUT ((uint16_t)0x05000) /*!< Time out for HSE start up */

判断RCC->CR bit17在超时时间内是否为1
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
判断结果
先看失败,即!=1时,说明时钟出错,需要手动编写错误代码
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 */
}
如果HSE准备就绪:
if (HSEStatus == (uint32_t)0x01):
继续执行以下步骤
(9) /* Select regulator voltage output Scale 1 mode */
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 其中,#define RCC_APB1ENR_PWREN ((uint32_t)0x10000000),把RCC->APB1ENR寄存器的bit28置1:

PWR->CR |= PWR_CR_VOS;
//其中,#define PWR_CR_VOS ((uint32_t)0x0000C000),把PWR->CR寄存器bit14、bit15置1,此为电源寄存器
(10) /* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
/*其中,RCC_CFGR_HPRE_DIV1 = 0x00000000,此处或上0,对于RCC->CFGR寄存器未进行任何操作,上面自reset该寄存器后,也未对其进行赋值*/
此时,RCC->CFGR寄存器的值应全为0,HCLK,也即是AHB总线时钟为系统时钟不分频,但是,此时系统时钟SYSCLK使用的还是HSI的时钟
(11) /* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
//其中,#define RCC_CFGR_PPRE2_DIV2 ((uint32_t)0x00008000),将RCC->CFGR bit15置1,表示APB2时钟由AHB时钟2分频而来

(12) /* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
//#其中,define RCC_CFGR_PPRE1_DIV4 ((uint32_t)0x00001400)将RCC->CFGR bit10、bit12置1,表示表示APB1时钟由AHB时钟4分频而来

(13) /* Configure the main PLL */
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
//其中,#define PLL_M 8(1000b,bit3置1),
#define PLL_N 336(0001 0101 0000b,左移6位,bit10、bit12、bit14置1),
#define PLL_P 2,(右移一位为1,减1等于0,左移16位,此步骤未对于计算结果起作用)
#define RCC_PLLCFGR_PLLSRC_HSE ((uint32_t)0x00400000),bit22置1
#define PLL_Q 7(bit24 bit25 bit26置1)
所以,RCC->PLLCFGR = 0010 0111 0100 0000 0101 0100 0000 1000
Bit5-bit0: 00 1000, PLLM = 8

Bit14-bit6: 101 0100 00,PLLN = 336

Bit17-bit16: 00, PLLP = 2

Bit22:1, 选择HSE作为PLL的时钟源

Bit27-bit24: 0111, PLLQ = 7

其余位均为reserved,必须保持复位值
此时,PLL主时钟PLLP=HSE* PLLN / (PLLM* PLLP) = 8MHz*336/(8*2)= 168MHz,可用于高速系统时钟
外部时钟源频率配置在stm32f4xx.h文件中的宏定义HSE_VALUE

(14) /* Enable the main PLL */
RCC->CR |= RCC_CR_PLLON; //
/* Wait till the main PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{ }
//打开PLL(RCC->CR bit24置1),并等待PLL ready(RCC->CR bit24 == 0)
(15) /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS; //配置Flash相关寄存器
(16) /* Select the main PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
// 其中,#define RCC_CFGR_SW ((uint32_t)0x00000003)
#define RCC_CFGR_SW_PLL ((uint32_t)0x00000002)
这两步,先将RCC->CFGR的bit1\bit0清零,而后将bit1置1,
表示选择PLLP作为SYSCLK时钟

(17) /* Wait till the main PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
{ }
其中,#define RCC_CFGR_SWS ((uint32_t)0x0000000C)
#define RCC_CFGR_SWS_PLL ((uint32_t)0x00000008)
判断RCC->CFGR bit2、bit3,判断是否为10,若为10,则表示成功设置PLLP作为SYSCLK时钟

3. 整个系统时钟频率
此时,在本系统中,SYSCLK = PLLP = 168MHz
HCLK(AHB) = SYSCLK= 168MHz
APB1总线时钟PCLK1 = HCLK/4 = 42MHz
APB2总线时钟PCLK2 = HCLK/2 = 84MHz
三、结语
STM32F407时钟系统初始化过程非常复杂,基于固件库的更是分析一遍下来让人头大,搞不懂也没关系,大部分工程均只用外部晶振+锁相环倍频的模式,只要能够在基础模板上学会如何设置外部晶振数值,如何修改锁相环参数宏定义,便能得到想要的时钟树配置。
相比之下,若基于HAL库进行开发,若使用CubeMx进行时钟树配置,流程将大大简化,该部分内容放在后续文章中介绍。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)