一、引言

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 00PLLN = 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进行时钟树配置,流程将大大简化,该部分内容放在后续文章中介绍

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐